Let's Make a Framework: More on Chained Events

2010-10-21 00:00:00 +0100 by Alex R. Young

Welcome to part 35 of Let's Make a Framework, the ongoing series about
building a JavaScript framework.

If you haven't been following along, these articles are tagged with
lmaf. The project we're creating is called Turing.

Last week I talked about NodeList and converting it into an
Array. This week I'm going to improve the chained API for

Event Handler Shortcuts and Loop Scoping

The only DOM event handler I added to our chained API was
click. Let's add more of the events named after the HTML
'on' attributes. I used the list on MDC's Event

page as a reference, and set up aliases like this:

events.addDOMethods = function() {
  if (typeof turing.domChain === 'undefined') return;

  turing.domChain.bind = function(type, handler) {
    var element = this.first();
    if (element) {
      turing.events.add(element, type, handler);
      return this;

  var chainedAliases = ('click dblclick mouseover mouseout mousemove ' +
                        'mousedown mouseup blur focus change keydown ' +
                        'keypress keyup resize scroll').split(' ');

  for (var i = 0; i < chainedAliases.length; i++) {
    (function(name) {
      turing.domChain[name] = function(handler) {
        return this.bind(name, handler);

The technique I used to create an array from a string is found
throughout jQuery. It's handy because it has less syntax than lots of
commas and quotes. I use the anonymous function to capture the name
parameter for each alias, doing it with var name =
would bind name to the last
value executed, which isn't what we want.

In jQuery's code they use jQuery.each to iterate over the
event names, which actually reads better. I put our iterators in
turing.enumerable.js and have been avoiding interdependence between modules, so I'm doing it
the old fashioned way.

However, doing it this way does illustrate an interesting point about
JavaScript's lexical scoping and closures. Try this example in a prompt
or browser console:

var methods = {},
    items = ['a', 'b', 'c'];

for (var i = 0; i < items.length; i++) {
  var item = items[i];

  methods[item] = function() {
    console.log('Item is: ' + item);

console.log('After the for loop, item is: ' + item);

for (var name in methods) {
  console.log('Calling: ' + name);

This will result in:

After the for loop, item is: c
Calling: a
Item is: c
Calling: b
Item is: c
Calling: c
Item is: c

Why is each item set to 'c' when each function is called? In JavaScript,
variables declared in for are in the same scope rather than
a new local scope. That means there aren't three item
variables, there is just one. And this is why jQuery's version is more
readable. I've edited this version of jQuery's
events.js to illustrate the point:

jQuery.each(aliases, function(i, name) {
  jQuery.fn[name] = function(data, fn) {
    return this.bind(name, data, fn);

Variables declared inside jQuery.each are effectively in a
different scope, and of course the name parameter passed in
on each iteration is the one we want.

Trigger vs. Bind

Calling jQuery().click() without a handler actually fires
the event, which I've always liked. Can we do something similar?

We just need to check if there's a handler in

if (handler) {
  turing.events.add(element, type, handler);
} else {
  turing.events.fire(element, type);

While I was looking at turing.domChain.bind I changed it to
bind to all elements instead of the first one. I thought that way felt
more natural.

You could do a quick test with this:

$t('p').click(function(event) {
  event.target.style.backgroundColor = '#ff0000'

It'll bind to all of the paragraphs instead of just the first one.


Generating sets of functions or method aliases iteratively is
straightforward in JavaScript, but you need to be aware of scope and pay
attention to closures. It's easy to see why jQuery.each is
in core.js --
it makes framework code simpler, especially given how jQuery handles