My favorite Javascript pattern
Often in Javascript you want to assemble some parts of the page using string operations or string-based templating or server-side code, all of which are more efficient and less wordy than DOM operations. But, in doing so, it becomes difficult or tedious to attach or maintain listeners on elements within that HTML. (How do you locate each needed node in the DOM and associate it back to the data it represents?)
A related problem is when you have many elements that you want to listen to (say, a list of items), and you don’t want to inefficiently attach a listener to each element. This is especially bad if you use onclick=”foo()” which would require exposing foo in the global namespace.
The solution to both problems is already used in various places, and discussed on the web. It involves inspecting the element passed in the click event, and acting on that node or information attached to that node.
When I had the pleasure of working with Steffen Meschkat on Google Maps, I learned about the jsAction pattern, which generalizes this idea. For any node you want to put an action on, just add two attributes: jsAction=”actionName” and, optionally jsValue=”someStringThatCanGetYouBackToTheData”.
Then, in code add an onclick handler, e.g.
var html = [];
for (var beer in beers) {
html.push('<div jsaction="drinkBeer" jsvalue="',
beer, '">My goodness, my, ',
beer, '</div>'];
}
parentDomNode.innerHTML = html.join('');
goog.events.listen(parentDomNode,
goog.events.EventType.CLICK,
clickListener);
You can pull out a generalized jsaction function, e.g.
function getJsAction(event) {
var targetAction = null;
var targeValue = null;
var t = event.target;
while (t && !targetAction) {
targetAction = t.getAttribute('jsaction');
targetValue = t.getAttribute('jsvalue');
t = t.parentNode;
}
return { action: targetAction, value: targetValue };
}
Then, in click listener:
function clickListener(event) {
var jsAction = getJsAction(event);
if (jsAction.action == 'drinkBeer') {
var beerType = jsAction.value;
doDrinkBeer(beerType);
}...
}
You can even take this pattern a step further by creating one or more registries for dispatch based on action, e.g.
var jsActionCallbacks = {}
...
jsActionCallbacks['drinkBeer'] = doDrinkBeer;
....
This is a really performant but real-life-friendly way of adding many click handlers through HTML strings and associating them back to data. Strongly recommended.