Event Delegation in JavaScript

This is the second post in a series on bubbling, delegation and how to delegate events with jQuery. It assumes you’ve already read the first post What does event bubbling mean, or already have a grasp on event bubbling in JavaScript.

Event Delegation

When providing examples, I’m going to refer to the HTML we used as an example in the first post (shown below):

<div>
    <h1>
        <a href="#">
            <span>Hello</span>
        </a>
    </h1>
</div>

Also note that I’m still not interested in adding support to older versions of IE (<9). You’ll have to add the normal fallback to attachEvent instead of addEventListener if you want to support them, and find an alternative for querySelector/ querySelectorAll. The event target property exists as srcTarget in older versions of IE.

So now we understand event bubbling… but how does it help us?

It means if we want to add an event handler for a click on the <span> element in the above example, we don’t need to add it to the <span> element; we can add it to any of it’s ancestors… as shown here;

window.addEventListener('load', function () {
    document.querySelector('div').addEventListener('click', function (e) {
        if (e.target.nodeName === "SPAN") {
            alert('Click event fired on the SPAN element'); 
        }
    }, false);
});

But yes, I hear you ask me again… how does this help us?

Imagine you have a table with hundreds of rows. Each row contains a <a /> to which you want to attach a click handler to. With no event-bubbling you’d have to bind the event handler to each <a />; which involves iterating over each element and adding an event handler individually to each one. See it in action here. Does it feel efficient to you?;

window.addEventListener('load', function () {
    // Add loads of rows
    var rows = '';

    for (var i = 0; i < 100; i++) {
        rows += '<tr><td>' + i + '</td><td><a href="#">Click Here</a></td></tr>';
    }

    document.querySelector("table").innerHTML = rows;
    // End setup

    // Attach event to each element
    var elements = document.querySelectorAll('#the-table a');

    for (var i=0;i<elements.length;i++) {
        elements[i].addEventListener('click', function (e) {
            alert('You clicked row #' + this.parentNode.previousSibling.innerText);
        }, false);
    };

    alert('Event handler bound to ' + elements.length + ' elements');
});

Instead, what we could do is bind one click handler to the <table />.

document.querySelector('#the-table').addEventListener('click', function (e) {
    if (e.target.nodeName === "A") {
        alert('You clicked row #' + e.target.parentNode.previousSibling.innerText);
    }
});

See this in action here.

Secondly, imagine you load some content dynamically and you want some capture some events on it. You can only add event handlers to elements once they exist (makes sense right?), so without event-bubbling you’d have to re-bind the same event handlers to your content each time you add the content.You can see this in an example here, where we add rows to the table programmatically when you click a button.

However, because the <table /> element does exist in the DOM when the page is rendered, we can add an event handler to this no problem, as shown here. This is a common problem when loading elements via AJAX as well; and attaching the event handler to an element that is in the DOM from the page load is the solution.

So in summary, you should be taking advantage of event bubbling by using event delegation if you want to handle an event for multiple elements, or you want to bind events to dynamically loaded data.

Now move onto the next article; Event Delegation with jQuery

What does “event bubbling” mean?

“Delegation” and “bubbling” are terms that gets thrown round a lot in JavaScript; but what exactly do these terms mean?

This is the first in a series of posts on bubbling, delegation and how to delegate events with jQuery; What does event bubbling mean, Event Delegation in JavaScript and Event Delegation with jQuery

Event Bubbling

In JavaScript, events bubble. This means that an event propagates through the ancestors of the element the event fired on. Lets show what this means using the HTML markup below;

<div>
    <h1>
        <a href="#">
            <span>Hello</span>
        </a>
    </h1>
</div>

Lets assume we click the span, which causes a click event to be fired on the span; nothing revolutionary so far. However, the event then propagates (or bubbles) to the parent of the span (the <a>), and a click event is fired on that. This process repeats for the next parent (or ancestor) up to the document element.

You can see this in action here. Click “Hello” and see the events as they get fired. The code used is shown below;

window.addEventListener("load", function () {
    var els = document.querySelectorAll("*");

    for (var i = 0; i < els.length; i++) {
        els[i].addEventListener("click", function () {
            alert('Click event fired on the ' + this.nodeName + ' element');
        });
    }
});

Note that I’m not interested in adding support to older versions of IE (<9). You’ll have to add the normal fallback to attachEvent instead of addEventListener if you want to support them, and find an alternative for querySelectorquerySelectorAll.

That’s all event bubbling is; an event fired on an element bubbles through its ancestor chain (i.e. the event is also fired on those elements). It’s important to note that this isn’t a jQuery feature, nor is it something that a developer must turn on; it’s a fundamental part of JavaScript that has always existed.

Ok, that’s a little bit of a lie… sort of.

By default, not all events bubble. For instance submit does not normally bubble, nor does change. However, jQuery masks this in the event handling code using all sorts of voodoo, so it will seem that they do bubble when using jQuery.

Now move onto the next article; Event Delegation in JavaScript

Sending FormData with jQuery.ajax()

This post explains how you can send the HTML5 FormData object as an AJAX request with jQuery. If you’re simply looking to learn how you can submit a form via AJAX using jQuery please checkout my other blog post instead; Submitting a form as an AJAX request using jQuery.


HTML5 introduces FormData to allow developers to build forms objects dynamically (and can even include files from the user’s file system), and then to send this form object via AJAX. You can easily use FormData with an XMLHttpRequest by passing the FormData instance as the parameter to xhr.send(). However, what if you want to send the FormData using jQuery?

Unfortunately, it’s not as easy as setting the data attribute to the FormData instance; jQuery runs anything that isn’t a string through jQuery.param() to serialize the objects keys into key1=a&key2=b etc; running FormData through this doesn’t end too nicely.

However one of jQuery.ajax()‘s many options is processData, which allows you to turn off this behaviour by setting it’s value to false. So, to use FormData with jQuery.ajax() it’s as simple as:

var formData = new FormData($('form')[0]); // Create an arbitrary FormData instance

jQuery.ajax('/endpoint.php', {
    processData: false,
    contentType: false,
    data: formData
});

Note we’ve also had to set contentType to false as well; unless contentType is specified as an option to jQuery.ajax(), jQuery reverts to the default of "application/x-www-form-urlencoded". By setting contentType to false we prevent this option from being set, and the browser implementation of XMLHttpRequest (which jQuery uses behind the scenes of course) will set the correct Content-Type header for us.

Be aware that no amounts of jQuery will make FormData cross browser. Whilst most browsers have supported FormData since the stone age (Chrome 7, Firefox 4.0 and Safari 5), Internet Explorer and Opera are the noticeable exceptions, with support only being added in Internet Explorer 10 and Opera 12 respectively. As there’s no real polyfill/ fallback you can employ for FormData, you should use feature detection and degrade gracefully as appropriate.

if (!("FormData" in window)) {
    // FormData is not supported; degrade gracefully/ alert the user as appropiate
}

Handling a HTTP redirect in node.js

When making a HTTP request using node’s HTTP module (namely via the http.request() or http.get() methods), it’s possible to receive a status code which indicates a redirect to another URL (301, 302, 307). However unlike the browser’s XMLHttpRequest, node does not do the leg-work of automatically following the redirect and it’s something you have to handle yourself. Thankfully it’s not too hard to do at all.

var http = require('http');

http.get('http://example.com/a-page-which-redirects', function (res) {
    // Detect a redirect
    if (res.statusCode > 300 && res.statusCode < 400 && res.headers.location) {
        // The location for some (most) redirects will only contain the path,  not the hostname;
        // detect this and add the host to the path.
        if (url.parse(res.headers.location).hostname) {
              // Hostname included; make request to res.headers.location
        } else {
              // Hostname not included; get host from requested URL (url.parse()) and prepend to location.
        }

    // Otherwise no redirect; capture the response as normal            
    } else {
        var data = '';

        res.on('data', function (chunk) {
            data += chunk;
        }).on('end', function () {
            // Do something with 'data'
        });
    }
});

It’s important to realise that there’s nothing preventing the URL at res.headers.location redirecting to yet another URL (a re-redirect if you like), so you should consider having some form of loop to repeatedly follow redirect request until you reach an end.

However, this introduces another possible problem you need to be aware of; the infinite redirect loop which can occur if pages redirect between each other (possibly in-directly) infinitely. The HTTP 1.1 standard makes it clear it is the responsibility of the client to detect and handle such situations. It also notes that previous versions of this specification recommended a maximum of five redirections, so it would be a good idea to ensure your implementation allows at least 5 redirects before considering it an infinite loop.

If you’re now wishing there was something out there that did all this for you, look no further than the request module cooked up by mikeal.

var request = require('request');

request('http://example.com/a-page-which-redirects', function (error, response, body) {
    if (!error && response.statusCode === 200) {
        // Use body; no need to handle chunks of data *or* redirects!
    }
});

Links to further reading could include the documentation on the request module and a Stack Overflow post asking how redirects could be followed.

The use of literals vs constructors (new Array vs [] and new Object vs {})

In JavaScript there are two main ways to declare a new object or a new array. One uses literal syntax whilst the other favours the more verbose method of calling the constructor.

var obj = {};
var ar = [];

… or…

var obj = new Object;
var ar = new Array;

Over the years using literal syntax has become the idiomatic way due to the following reasons (and it is because of these reasons that you should also be using them):

  1. The names Object and Array can be overridden, whilst the literal equivalents cannot:

    Array = Object = function () {
        alert('Foo');
    };
    
    var ar = new Array; // alerts "Foo"
    var obj = new Object; // alerts "Foo"
    

    Whilst you may think “I’m never going to override Object or Array so why should I care”, remember there’s nothing to stop 3rd party libraries you include doing this; quite possibly accidentally, but it’ll still break your program.

  2. The object literal allows you to define attributes inline. You cannot do this with the constructor counterpart.

    var obj = {
        prop: 1,
        add: function (val) {
            return this.prop + val;
        }
    };
    
  3. The array constructor has a horrible interface which changes depending on the situation.

    // What do you think ar and ar2 equal?
    var ar = new Array(1);
    var ar2 = new Array(1,2,3,4);
    
    // Probably this?
    alert(ar.join(",") === "1");
    alert(ar2.join(",") === "1,2,3,4");
    

    …. In fact passing one parameter to new Array creates an array whose length is the value passed to the first parameter. The value of each of these elements are undefined. On the other hand passing more than one parameter creates an array whose elements are equal to each of these parameters.