Delay Initialization with jQuery Delegation

Delay Initialization with jQuery DelegationAs the internet fills with more and more JavaScript code, we need to become more and more aware of the impact of our code has on performance. One of the big pain points can come from all of your code being initialized and loaded during jQuery.ready() or (if you're a good boy who puts all the code at the end of the document) right away. We can delay some initialization until later, right?

Event Delegation

For a while now, jQuery has had event delegation. If you know what event delegation is and how it works, go ahead and skip to the next section. But, for those of you who don't know, here's a little introductory course.

Normally you would attach an event listener directly to an element, and let the handler go from there. Generally there is absolutely nothing wrong with this, but if the elements that you wish to attach event listeners to are dynamic (they're constantly being created and/or deleted), this can be a hassle. Another time this can be "bad" is when there are many, many elements to attach to, in which case it's just slow. Event delegation was designed for these situations.

The premise behind event delegation is pretty much the opposite of real-world delegation. Rather than delegating things to those below us, we delegate to elements higher in the hierarchy. Sometimes we even delegate all the way up to the CEO (document). Let's take a look at a tiny code sample and walk through it to explain.

// Normal
$('.elements').on('click', function() {
    // Do Something
});

// Delegation
$(document).on('click', '.elements', function() {
    // Do Something
});

With delegation, we attach the listener to an element higher in the hierarchy (document in this case). Then we add another argument to the call to on that specifies a selector that we need to match. Other than that, it's exactly the same as the normal event listener.

Here's how it works:

  1. The document listens for click events. Any click that happens on the page will bubble up to the document (unless it was stopped by another event handler).
  2. When the document hears a click event it checks to see if the event happened on an element that matches the selector we passed in ('.elements' in this case).
  3. If it matches, it fires the event handler.

It's that simple. One of the best parts is that the document is created immediately, so you can attach listeners to it within the head of the document and these will still work. If you want to learn more about event delegation, look here.

Delayed Initialization

Many times the delayed initialization works pretty well when working with jQuery plugins. The best way I can explain this concept is through examples. I'll show two examples of initializing plugins that demonstrate a few of the possible hitches you may run into and how to work with them.

The Lightbox

This first example utilizes the jQuery lightBox plugin, which may not be the best plugin, but it works for my example. This plugin attaches itself to links to images, and then when you click on the link, instead of just following the link, it creates a modal box with the image contained inside it. If you are using this with a large gallery or you are using infinite scrolling to load more images in dynamically, the normal initialization might not be the best bet for you. Try this:

We delegate a click event listener on the document to limit the amount of code that runs right away. This delegation makes sure we don't set the plugin up until we need it and only on the elements that need it at the moment. So, when a gallery link is clicked, we initialize the lightbox plugin on that one link. We need to trigger a new click event on it right away so that lightbox will respond to the click. Then we need to prevent the default action so that we don't follow the link to a different page. The nice thing about the lightbox plugin for this example is that it automatically prevents bubbling, so once the lightbox plugin is initialized on a link, this delegated event handler will never run for that link again. If we weren't using JSFiddle, you'd see that 'init' is only logged the first time that you click an image.

This technique has some pros and cons.

Pros:

  • Really low amount of initial overhead computation.
  • We don't need to wait for DOM ready to set up the event listeners
  • Initialize only the elements you need when you need it.
  • Works for dynamically added content without any additional work.

Cons:

  • The lightbox must be set up when you click, so there could be a delay between the click and the reaction to the click. This is generally unnoticeable.
  • There may be other things that prevent the delegation from reaching the document and there is a bit of overhead associated with bubbling all the way up to the document.
  • A wee bit more code to write.

The Date Picker

This example uses jQuery UI's Date Picker Widget. It was also taken directly from Elijah Manor's post, which was the inspiration of this post. We handle things slightly differently this time.

You'll notice a few distinct differences in implementation between this example and the lightBox example.

  1. We use ":not(.hasDatePicker)" in the selector. Date Picker assigns this class to an element that the widget has already been initialized on, so we can use that to make sure we don't initialize the Date Picker on an element that it has already been initialized on. This is nice because the Date Picker doesn't prevent bubbling like the lightBox did, so we need some other way to know not to initialize the element. What's also nice is that we can use this inefficient selector because it isn't scanning the document for this selector, it's only comparing the element we have to the selector.
  2. We're using a toastr library instead of console so you can actually see when it's initialized and not initialized. This of course, doesn't really matter in real apps.
  3. We don't need to trigger a focus event again. The Date Picker is smart enough to know that it should show because its input is in focus already.
  4. We don't need to prevent the default action. This is because nothing happens by default when something is focused.

Preventing Re-Initialization

That first point above is one of the key points that you'll have to think about each time you attempt to delay initialization like this. You have to find a way to make sure that the initialization doesn't happen multiple times. In the first example, lightBox's prevention of bubbling did that for us. With the Date Picker, we had to check for a class that it adds. For other plugins, you may have to wrap the whole event handler in an if statement that checks for the initialization state somehow. Some plugins do this themselves, so you can call the initializer all you want and it won't matter, but I wouldn't count on it unless you read through the code yourself.

Conclusion

Overall, it's pretty simple to delay initialization of many jQuery plugins and other JavaScript code. In fact, just converting to delegation for many of your event listeners prevents a lot of overhead and initialization code from running. Go out and make your JavaScript faster today! God bless and happy coding.

About the Author

Author: Joe Zim

Joe Zim

Joe Zimmerman has been doing web development ever since he found an HTML book on his dad's shelf when he was 12. Since then, JavaScript has grown in popularity and he has become passionate about it. He also loves to teach others though his blog and other popular blogs. When he's not writing code, he's spending time with his wife and children and leading them in God's Word.


  • elkorn

    Hey Joe!

    How about http://api.jquery.com/one/ as a more generic way of preventing re-initialization?

    Cheers.

  • http://www.joezimjs.com Joe Zimmerman

    That “might” work, but I’m not sure how `one` works with delegation. If it only work once period, then it doesn’t the same way as these examples need, however, if it works once per element click, then it should work great. I’ll have to look into that more.

    • elkorn

      cobbled together a fiddle: http://jsfiddle.net/NxzQj/1/ .

      Turns out You’re right. I got mislead by other sources.

      You never know until You try to find out yourself :)

      • http://www.joezimjs.com Joe Zimmerman

        Or until you read the API docs, though they didn’t say it right out, they hinted at it in a comment in one of the code samples.

        • elkorn

          The thing is, I got interested and read them. Still did not put me off the wrong track though.

  • http://www.joezimjs.com Joe Zimmerman

    After looking into it, `one`’s handler will only be called once and won’t allow for once per item that matches the selector for delegation. That doesn’t mean it won’t work, it just means you can’t use it with delegation and you can’t use it for dynamic elements.