Useful utilities for debugging JavaScript event handlers

When working on a large web application including numerous libraries and frameworks (say knockout.js, jQuery UI, and Bootstrap), it can be difficult to figure out what originally caused a function to be bound to various events (be they browser native or user-defined). These 3 remarkable little utilities all help to find out exactly that:

jQuery._data(document, “events”)
This is a piece of internal jQuery API (so shouldn’t be relied on in production) that will list all events attached to the element provided as the first argument:jQuery eventsNote that the first argument should be an object conforming to the Element or Document DOM interfaces, rather than a jQuery object or selector (although the raw DOM object can be retrieved using a jQuery selector and dereferencing the first item of the jQuery “array”: $(“p:first”)[0])

getEventListeners()
Specific to Safari and Chrome, getEventListeners() provides much the same functionality as the jQuery ._data() call above, but without the dependency on jQuery:
getEventListeners

monitorEvents()
Again specific to Safari and Chrome, monitorEvents() takes either one or two arguments, the first of which is the Element or Document DOM object to monitor, and the second (optional) argument is the type of event to listen for, presently limited to one of four strings: “mouse”, “key”, “touch”, or “control”. On executing monitorEvents(document) in the console, all future events applying to that element are logged, optionally filtered by event type:
monitorEvents
Calling unmonitorEvents() on the same DOM object will stop logging to the console.

Angular.js tabs directive with dynamic loading of partial templates and controllers

The Angular.js framework strongly encourages modular application design, particularly when it comes to separating application logic from DOM manipulation. One of the greatest “it-just-works” components of Angular.js has got to be the ngRoute module, which allows extremely straightforward mapping of URLs to partial templates that load into a region of the main application template marked up with an ngView directive. For instance, a couple of simple routings might be defined as follows:

app.config(["$routeProvider",
	function($routeProvider) {
		$routeProvider.
			when("/home", {
				controller: "Home",
				templateUrl: "partials/home.html"
			}).
			when("/products", {
				controller: "Products",
				templateUrl: "partials/products.html"
			}).otherwise({
				redirectTo:"/home"
			})
	}
])

This would cause the respective partial templates (specified by the templateUrl property) to be loaded into a <div ng-view> in the main template, with the specified controllers attached. Unfortunately, there can only be one ng-view in the main template, so routing can only load partial templates into a single part of the main template.

In the app I’m currently working on, the single ng-view is used to load partial templates into the “detail view” of a two-pane list-detail view, in which the list view is a sidebar menu and the detail view takes up the majority of the screen and shows the “detail” corresponding to the current sidebar selection:

two-pane-view

This works well with one key exception: one of the detail views needs to include a number of other detail views in a tabbed area like this:

two-pane-view-with-tabbed-main-view

If we’re to make this work, it would obviously be highly desirable to re-use the same partial template HTML and controller JavaScript regardless of whether the detail view is loaded into the full screen view or the tabbed view of another detail view. With only one supported ng-view, this isn’t currently possible if we use the ngRoute module. Before filling this gap in functionality, it’s worth noting that the AngularUI Router framework already supports this exact type of functionality. Unfortunately, as of writing, the framework still comes with a disclaimer that the API is in flux and that it shouldn’t be used in production unless you’re willing to keep track of the changelog. Similarly, a much more sophisticated router is planned for AngularJS 2.0, and will be backwards compatible with AngularJS 1.3. But, for the time being, there are no “stable” modules that seem to enable this kind of functionality.

Angular AJAX Tabs

Starting with the tabs example on the AngularJS homepage, support for controller and template attributes can easily be added to the pane element. All of the changes to the directive are then concentrated in the link function, which is worth dissecting further:

link: function(scope, element, attrs, tabsCtrl) {
	var templateCtrl, templateScope;

	if (attrs.template && attrs.controller) {
		scope.load = function() {
			$http.get(attrs.template, {cache: $templateCache})
			.then(function(response) {
				templateScope = scope.$new();
				templateScope.isTabbedPane = true;
				templateCtrl = $controller(attrs.controller, {$scope: templateScope});
				
				element.html(response.data);
				element.data('$ngControllerController', templateCtrl);
				element.children().data('$ngControllerController', templateCtrl);
				
				$compile(element.contents())(templateScope);
			});	
		};
	}

	tabsCtrl.addPane(scope);
}

The link function first checks for the template and controller attributes on the element. If they don’t exist, it falls back to the standard tabs behaviour of inlining the HTML into the pane and adding the pane to the panes array in the tab controller’s scope. If both attributes do exist, a load function is added to the passed in scope. (The load function will later be called from the select function in the directive.)

The load function first uses $http.get to retrieve the partial template from the URL specified in the template attribute, adding it to the templateCache if it’s not already there. Once the template’s been retrieved, a new scope is created for the template and a single isTabbedPane property is added.

A new controller is then instantiated based on the controller attribute string, with the new scope injected in.

The HTML from the template is then set as the innerHTML of the pane element using the .html() function from Angular’s built in jqLite library. Similarly, the jqLite .data() function is then used to associate the instantiated controller with all of the pane’s child elements under the “$ngControllerController” key. $ngControllerController is poorly documented and there is certainly some confusion as to its purpose, but from a quick look through the AngularJS source code, it looks like it’s used by the internal jqLiteController function to return the default controller for an element. (jqLiteController provides the functionality underpinning the public controller() method on angular.element)

Finally, the template is compiled with the new template scope and the pane is added to the array in the tab controller’s scope as it would be had we not specified a controller and template.

Elsewhere, there’s also one minor change to the tabs directive, which simply triggers the pane’s load method when the select method is called at the time of a tab switch. The full select method in the tabs directive controller scope is then as follows:

$scope.select = function(pane) {
	angular.forEach(panes, function(pane) {
    	pane.selected = false;
	});
	if (pane.load !== undefined) {
		pane.load();	
	}
	pane.selected = true;
};

With the full module in place, ng-repeats and some structured metadata can be used to generate both the sidebar menu and the tabs themselves:

var tabbedPaneMetaData = [{
	"name": "Pane 1",
	"path": "pane-1",
	"partial": "pane-1.html",
	"controller": "Pane1",
	"includedInTabView": true
}, {
	"name": "Pane 2",
	"path": "pane-2",
	"partial": "pane-2.html",
	"controller": "Pane2",
	"includedInTabView": true
}, {
	"name": "Pane 3",
	"path": "pane-3",
	"partial": "pane-3.html",
	"controller": "Pane3",
	"includedInTabView": false
}];

In the tabbed pane, the following HTML could be added to the tabbed detail view controller:

<tabs>
	<pane ng-repeat="pane in panes | filter:{includedInTabView:true}" 
		tab-title="{{pane.name}}" 
		tabcontroller="{{pane.controller}}" 
		template="{{pane.partial}}">
	</pane>
</tabs>

Et voila, (relatively) straightforward configuration of ng-view partial templates and controllers as both full ng-view citizens and as participants in a tabbed view within another ng-view. The full Angular AJAX Tabs module source code can be found on GitHub with a barebones demo of the full functionality live on Plnkr.

Finally, if you’re already familiar with the concepts behind AngularJS but want to learn more about directives, Josh Kurz has written an excellent book, Mastering AngularJS Directives, that does the deep dive on writing directives, including many of the aspects covered briefly in this post.