Using Composed Javascript UI Controllers With Event Delegation

In the past couple of days, I've been experimenting with using composed Javascript UI controllers in an attempt to create more maintainable code behind complex user interface modules. In these experiments, I've been creating event bindings at the composed element level such that the user-interaction events could be directly handled by one of the composed controllers. The down side to this approach is that I now have to create event bindings for every single composed element - something that I am told can actually have performance implications in a large DOM. To get around this, and back to good, I wanted to try using event delegation in the context of composed Javascript UI controllers.

Before we get into this, I think I should be completely transparent and say that I have never personally noticed a performance degradation when using a large number of event bindings. Delegating events is supposed to minimize the number of event bindings and therefore increase performance; but, I am taking the benefits of this based on expert advice and not necessarily on personal experience. Of course, I have never really created massively complex user interfaces; so, it is probably just an issue that I have not yet built up to.

NOTE: Event delegation also has the benefit of not having to "configure" each composed DOM node; however, since I am creating composed Javascript controllers, this benefit is not relevant in this situation.

A while back, jQuery introduced the .live() method as a way to perform event delegation at the document root level. This was an interesting concept; but, it was always one that I felt created a somewhat inappropriate coupling of the document to a particular UI module. In jQuery 1.4.2, however, they introduced the .delegate() method which took the event delegation benefits of .live(), but allowed it to be attached to a more context-appropriate module-specific root.

In the following demo, I've still got both my root Javascript controller and my composed Javascript controller; however, this time, I'm not going to perform any click-event binding at the composed level. Rather, I'm going to use jQuery's .delegate() method to listen for click-events at the module root and then manually pass events from the root controller off to the appropriate composed controller.

<!DOCTYPE html>

<html>

<head>

<title>Using Composed UI Controllers And Event Delegation</title>

<style type="text/css">

ul {

height: 105px ;

list-style-type: none ;

margin: 0px 0px 20px 0px ;

padding: 0px 0px 0px 0px ;

}

li {

background-color: #F0F0F0 ;

border: 2px solid #CCCCCC ;

cursor: pointer ;

float: left ;

font-size: 18px ;

height: 100px ;

line-height: 100px ;

margin: 0px 10px 0px 0px ;

padding: 0px 0px 0px 0px ;

text-align: center ;

width: 125px ;

}

li.active {

background-color: #FFF0F0 ;

border-color: #CC0000 ;

font-weight: bold ;

}

</style>

<script type="text/javascript" src="./jquery-1.4.2.js"></script>

</head>

<body>

<h1>

Using Composed UI Controllers And Event Delegation

</h1>

<!--

This UL will be controlled by our list controller and its

composed list item controller.

--->

<ul id="girls">

<li>Sarah</li>

<li>Tricia</li>

<li>Katie</li>

<li>Jill</li>

</ul>

<!-- When the DOM is ready (ie. now), setup scripts. -->

<script type="text/javascript">

// I am the list controller class.

function ListController( target ){

// Store the target collection for our list controller.

// This is the UL we are controlling.

this.target = $( target );

// Store this controller with the DOM node. This way,

// we can get the controller from DOM collections.

this.target.data( "controller", this );

// I am the currently active list item.

this.activeListItem = null;

// I gather up and prepare the composed controllers.

this.initComposedElements();

}

// Define the class methods.

ListController.prototype = {

// I initialize the given composed element, creating

// the appropriate child sub-class of Controller.

initComposedElement: function( childNode ){

new ListItemController( this, childNode );

},

// I gather up and prepare the composed elements for

// further initialization.

initComposedElements: function(){

var self = this;

// For this UL, we're going to collect each direct

// child LI and create a Controller instance for it.

this.target.children().each(

function( index, listItemNode ){

// Create the list item controller, but

// don't store it; since each controller

// is associated with a DOM node, we can

// offload the burden of aggregation to

// the DOM itself.

self.initComposedElement( listItemNode );

}

);

// Now that we have created the composed controllers,

// let's set up event delegation for the clicks. This

// way, we can handle the clicks with a single event

// binding and still delegate to the composed UI

// components.

this.target.delegate(

"li",

"click",

function( event ){

// Get the composed controller.

var controller = $( this ).data( "controller" );

// Pass off click-handling to the LI.

return(

controller.handleClick( event )

);

}

);

},

// I handle requests to make the given list item active.

makeListItemActive: function( listItem ){

// Check to see if we have a currently active item.

if (this.activeListItem){

// Deactivate the currently active list item.

this.activeListItem.deactivate();

}

// Store the new active list item.

this.activeListItem = listItem;

// Activate the given list item.

listItem.activate();

}

};

// -------------------------------------------------- //

// -------------------------------------------------- //

// -------------------------------------------------- //

// I am the list item controller class.

function ListItemController( listController, target ){

// Store the parent controller.

this.listController = listController;

// Store the target collection for our list item.

this.target = $( target );

// Store this controller with the DOM node. This way,

// we can get the controller from DOM collections.

this.target.data( "controller", this );

// NOTE: We are NOT going to set up any CLICK handling.

// The click event will be caught at the root controller

// level and then delegated to the appropriate list item.

}

// Define the class methods.

ListItemController.prototype = {

// I activate this list item.

activate: function(){

this.target.addClass( "active" );

},

// I deactivate this list item.

deactivate: function(){

this.target.removeClass( "active" );

},

// I handle the mouse click event.

handleClick: function( event ){

// We want to make THIS list item active; but, that

// decision is not part of the local business logic.

// This needs to be passed up to the parent

// controller.

this.listController.makeListItemActive( this );

}

};

// -------------------------------------------------- //

// -------------------------------------------------- //

// -------------------------------------------------- //

// Create our list controller.

var listController = new ListController(

$( "#girls" )

);

</script>

</body>

</html>

If you look at the ListItemController class in this demo, you'll see that this composed Javascript controller defines an event handler - handleClick; however, you'll also see that it doesn't actually bind to any click events. The event binding is performed at the root controller level as part of the composed collection initialization. Once the root controller observes a click event on the list element, it manually invokes the handleClick event handler on the most appropriate composed controller, passing along the event object.

When using this approach, we get all the benefits of using composed Javascript UI controllers without the cost of large-scale event binding. The only part of this that I wasn't crazy about was the fact that ListItemController needed to be aware that event delegation is taking place. Of course, since these are composed controllers, there is a certain amount of coupling that is to be expected. If the pair of classes is going to work as a team, it makes sense that they will need to cooperate with each other (and delegate responsibilities).

Reader Comments

This is sweet stuff! I've been following Rebecca Murphy's dialog on "enterprise jQuery" with a lot of interest. She raises a lot of good points, especially regarding jQuery's out-of-the-box suitability for complex UI's. It's awesome to see some examples of scalable code organization that use jQuery for what it's good for (i.e., dom manip), but appropriately recognize the need for some additional structuring.

I have also been following Rebecca's thoughts on that matter as much as I can (not sure if I've missed recent updates). I think she raises some good points; certainly, she made me reflect on ColdFusion itself and what a "roll your own" solution can be:

As I've read through the last couple of post about the UI controls, one thought that I had was that the ListController has a dependency on the ListItemController class/object. Perhaps the ListItemController could be a parameter of the ListController constructor, or settable via a method similar to the following:

As long as the ItemController has the activate, deactive, and handleClick methods (which you could check for in the setItmeController method if you wanted to be defensive about it and throw an error if it doesn't have those methods), then everything should work.

I suppose you could go further and have the ListController publish events to the ListItemController, and if the specific ListItemController doesn't have a deactivate event, then nothing happens, and no error is generated. But maybe that's going to far for what you're trying to go for in these posts.

Definitely the dependency between the two classes is something that I have thought about - going back and forth in my mind. At the end of the day, I think that because these ultimately make up one cohesive user interface (UI) widget, I think that the dependency is OK.

That said, I think you have a pretty good idea there; the only change I would make is that the ListItemController class might be better passed in as a constructor argument for the ListController class. The reason I say that is that the initialization of the composed elements happens as part of the root class; as such, if you have to define the composed class through a setter call, you might already be too late.

Good point about the constructor creating all of the list items controllers when itself is created, so agree probably better to have it as a construction parameter.

I suppose that the setter for the item controller could be useful if you want to replace the controller on the fly for whatever reason, perhaps when the user logs in via ajax and is an admin, but not really necessary to worry about.

However, thinking about it more, if you ever added a new list item, then there wouldn't be a controller for that item, then this.target.delegate() call will fail. So perhaps ListController needs the ability to add/remove list items, with the ability to insert at any location, as well as set the state of the injected item(s). But again, thinking beyond what you're actually trying to get across, I'm just making it more difficult than it needs to be.

Excellent point regarding new items. Using something like .delegate() or .live() allows us to handle the events on new elements; but, there's nothing that says there's *actually* going to be a composed Controller to handle it. As you are saying, the root controller would need some kind of way to inject new elements that would also be configured.

Given that you're talking about performance issues, one thing you could do to optimize a bit:

var controller = $( this ).data( "controller" );

That line is creating a jQuery object that gets used one time. That's all well and good, but if all you're going to do is fish out the piece of data, you can do it just a bit more efficiently:

var controller = $.data(this, "controller");

That way, you're not actually creating an otherwise unnecessary jQuery object, saving on some execution.

Again, a minute optimization, but one that can be made. :)

I've been using a system kind of comparable to this lately in one application, where I use data to bind a good bit of metadata to list items within very large lists (a few hundred list items per list, with two lists on the page), and moving from binding to the LIs and using delegate on the UL instead, combined with a shift to cutting the occasional jQuery object here and there and leaning on $.data instead of $().data, and I managed to cut a good two seconds off of page startup.

Oooh, I like that! I'm definitely very conscious of creation of extraneous jQuery objects. I always knew that a lot of the .fn. methods had core parallels as well, but I've never really looked into them. This is a great idea - thanks for the insight!

I too have been following you and Rebecca's blogs on this topic. As I read your code I was pondering more complicated scenarios where the child elements were not all of the same type. One idea that occurred to me was to pass in a factory function to the container controller constructor that would let you dynamically choose which item controller to attach to each child.

Interesting; I had not even gotten to the point of considering collections that were composed of different types of elements. But, I suppose that could happen. A factory approach could work. I'm still not sold on the best way to go about decoupling the root controller from the composed controllers. The more variety of composed controller, the less I am sure as to whether that tie should be cut.... this stuff is complicated.

Nice and clean. Although, maybe I'm missing something. I thought that this can all be done through jQuery's own `$.delegate`. It is 5am, but I don't really see a benefit here in rewriting the delegation wheel. Please correct me if I'm wrong.

I whipped up the same thing, plus custom events (per list, not per node) in less, but just as clear code. http://jsfiddle.net/DWqm4/5/ Don't get me wrong, this is not to say "mine is better than yours" in anyway, I'm just try to understand the necessity for your approach.

Very cool stuff! I really like the way you have organized your code. I hadn't even gotten to the point where I was gonna think about using default configuration options. Nicely done.

It's an interesting question as to whether or not a composed class controller is even needed. As you have demonstrated here, a lot can be placed directly into the root controller (with the code being kept clean and well organized). But, I wonder, as the context gets more complex, will a single controller be as easy to maintain?

I suppose, you can ultimately place a lot of stuff in the .data() of the individual LI elements as you need to maintain state; but, I think there is something satisfying about keeping state data in a JS object rather than in the data() collection... of course, that could just be my deep-seated desire to learn more about OOP (something that I have never felt all that confident with).

You have asked a tough question and I don't have a great answer for you :) I can tell you that having to deal with the composed class Type has become somewhat of a point of contention. In my next demo (which I haven't blogged yet), I take this and start passing the composed class Type in as one of the constructor arguments. But, I'm not crazy about that idea either.

When I post next, I'll let you know - would love your feedback. Thanks for asking the hard-hitting questions.

@Ben,I started out as a mac pascal/c programmer. Both MacApp and the THINK app frameworks were almost rabidly OO. I have a large js app project coming up in a few months and I'm trying to think through the javascriptish way to do a large app.

Programming examples are necessarily simple so I was thinking about how your example might scale. I don't want to turn js into c, but we need higher levels of abstraction as our app complexity grows.

Hey @Ben, I'm glad you found that useful. Authors tend to get offended when the "why bother" questions come out and god forbid if you say anything about reinventing wheels or otherwise, but I'm glad you're above that.

`ListController` provides a default set of behavior to allow for easy use in the simplest of cases. As code complexity increases, the fact that `ListController` allows for everything to be overwritten offloads complexity to implementation rather than trying to foresee an unknown number of use cases and trying to prepare for them. Sure, we might add a handful of addition helper methods similar to `getListNode` (e.g.: `getItemNodeIndex`), but by being as general as possible the library designer puts the responsibility of implementing complex and application specific code where it belongs: squarely on the shoulders of the application developer and in the application itself.

Depending on your preferred style, one possible change I would make would be to use a separate object to encapsulate the activate/deactivate methods which would allow for more complexity in the application. However, having said that, since `config` is essentially a named parameter list, it is possible to use both approaches. This allow the application developer to choose whether or not he wants to use the activate/deactivate methods (which allows them to easily omit a method in the application and to fallback on the library's default method, a plus in my book), or to use an object that encapsulates and defines both of these methods (e.g.: `ListItemDeleteController`).

As for using `.data()`, I'm all for it, but like everything it has its uses and limitations. It's a logical place to keep data that is relevant to specific nodes and frees you from having to create mappings to external storage (external to the node, that is). Also, a problem with mapping from a node to an object data store is maintaining that mapping. You can't use xpath or an index in a parent node container (e.g. the index of a specific `li` within a `ul`) since those things are subject to change, so you have to use some sort of ID (not necessarily `node.id`, but perhaps `node.application_mapping_id`) which can be clobbered by bad code somewhere else in the application or code that is ignorant of your custom property.

Conversely, I'm all for mapping nodes to data stores but it too has its pros and cons. The main benefit is being able to access and modify node state without having knowledge of the nodes themselves. Also, you can maintain a global state for that node collection, something that can't be done with `.data()` or otherwise storing state data directly in the node.

I'd be happy to talk more with you about this for your next article. Just drop me a line, I'm sure you have my email.

I like to think that what I say on this blog is always half statement / half question :) I'm always open to what people have to say and am happy to chose better approaches even if they are different from what I laid out - the goal is progress :)

As for the config object goes, you are losing me a little bit. It looks like you are using the named-config property collection in lieu of sub-classing? Is that correct? Basically, this is a way to create a more generic list that then can be sub-classed "on the fly" for lack of a better metaphor. It is a very interesting approach. It's kind of like passing in "behaviors" to a an object (I forget what that design pattern is called).

When it comes to the data/node/object relationship, I am not sure that I am too concerned about being able to keep track of the bindings. As long as the controller for a given UI widget is the only one that mutates its composed DOM, then I think it should be ok. I kind of look at the widget DOM as a "data structure" in and of itself. Rather than a collection or array, the controller has a "DOM" that stores its node data. This nice thing about this is that the DOM has all kinds of mutation methods that don't have to be kept in tandem with a collection. Meaning, if I swap the order of two DOM nodes, I don't *also* have to swap the order of two objects in some composed collection.

With all that said, though, I like the idea of pulling a lot of functionality into the root controller; however, I can see situations where it might be nice to have a composed controller that has its own set of data (such as AJAX requests) that call all be stored independently from the root controller. I suppose you could always just store that stuff in the data() set rather than the composed controller... lots to consider.

@Roger,

Completely agree - as these client-side applications get more complex, having a solid Javascript architecture is going to be key.

At this last weekend's jQCon (jQuery Conference 2010), I attended Rebecca Murphey's talk on functional organization. She talked a lot about pub/sub functionality for decoupling components. This is the new topic that I am try to (failing) wrap my head around.