3.2 $.Controller - Templated Event Binding

JavaScriptMVC 3.2 brings a lot of great features and enhancements. So many features that changes to $.Controller didn't make the cut for our upcoming 3.2 article. This article reviews 3.2's $.Controller and talks about templated event binding (something we neglected to write up for 3.1).

JavaScriptMVC 3.2 brings a lot of great features and enhancements. So
many features that changes to
$.Controller
didn’t make the cut for our upcoming 3.2 article. This article reviews
3.2’s $.Controller and talks about templated event binding (something we
neglected to write up for 3.1).

Bind and Memory Leaks

Neglecting to unbind event handlers is the easiest way to create memory
leaks. This is extremely common in an MVC architecture as you constantly
listen for model changes:

Task.bind('created', function(ev, newTask){
// add task to list
})

If your widgets are repeatedly added and removed from the page, you must
remember to unbind these event handlers. People forget it all the time!
This happens because simple jQuery plugins (which people use as template
and learning tools) typically will not leak.

Simple plugins bind only on elements within the element the plugin was
called on. jQuery cleans up these event handlers automatically. For
example, a tabs might look like:

jQuery will remove all event handlers on the element and in the
element’s children. But, if you are listening to anything outside the
widget’s element (say the <body> or a Model event), jQuery will not
remove the event handler. Lets explore this with a leaking tooltip:

A Leaking Tooltip

To understand the problem of event handler leaking, consider a simple
tooltip widget. The tooltip works by calling:

$('#tooltip').tooltip("Here is the text")

This will write “Here is the text” to the bottom right of the #tooltip
element. The code for this tooltip looks like:

See what’s wrong? This code does not error, it leaks! If you can’t spot
the leak, don’t feel bad. We’ve seen this mistake many, many times.

The problem is that although the element is removed, the body’s click
handler is not unbound. This function is still referenced by the DOM.
And worse, this function has the paragraph element in its closure. The
paragraph element and its child nodes will be kept in memory until the
page refreshes.

Unbind with jQuery

jQuery helps you unbind event handlers in a number of ways:

Remove the bound element

If you remove an element from the DOM, all of its event handlers will be
cleaned up. For example:

Note: be very careful to pass in the same function to unbind as you
passed to bind (arguments.callee happens to be this function);
otherwise, jQuery will not unbind your event handler and you will
continue to have a leak.

If you only handle the event once, one(event, handler) will unbind
call for you. We can use that to listen to body clicks and avoid leaking
like:

$(document.body).one(function(){
el.remove();
})

Finally, jQuery provides namespaced event handlers. It let you unbind
all event handlers on an element that match a particular namespace. We
could use namespaces like:

Problems

So far, this might seem ok, but there are lot of potential problems.
Most importantly, there’s a lot of waste! For every bind, there needs to
be an unbind. You are double-coding. In our experience, few think about
memory leaks and cleanup until it’s too late.

Controller

Controller has always been useful for binding and unbinding. 3.1 brought
templated event binding. This lets you bind and delegate on elements
outside the controller’s parent element. We can rewrite tooltip like:

How it works

First, it uses NAME to look up a value on the controller
instance’s options.

window - If a value is not found, it uses NAME to look up a value
on the window object.\

The match can be one of two types:

An object - If an object is found, it binds or delegates on that
object instead of using the controller’s parent element. This could
be another element, or any object that has events triggered on it (a
Model for example).

A string - If a string is found, it just replaces {NAME} with the
value of that string.\

You can use a string template to configure the type of event that hides
the element:

Updating options

New in 3.2 is the ability to update options and templated event handlers
with controller’s update(options) method. If a controller is already
bound to an element, calling its jQuery helper with options calls
update(options). So, we can update the hideEvent and hideElement like:

Templated and MVC

MVC apps are constantly listening to changes in the $.Model layer to
reflect changes in the UI. Templated event handlers make it stupidly
easy to write abstract widgets that work with any model. An abstract
list might look like:

The PlayerMX and
Todo apps are very good
examples of using templated event handlers.

Conclusion

Templated event handlers have made a big difference in how we write our
apps. We’ve abandoned OpenAjax’s pub-sub for direct Model events. We
rarely use callbacks on
model.destroy()
or
model.save()
like:

model.destroy(function(){
// remove element!
})

and instead listen for changes like:

"{model} destroy" : function(){
// remove element!
}

This makes the remove element! code run no matter how the model
instance gets destroyed.

Finally, templated event handlers lead to some of the bigger 3.2
changes:
$.Observe and
$.route.