Grow your CSS skills. Land your dream job.

Just One Of Those Things You Need To Understand About JavaScript

Published by Chris Coyier

Ever since I've published the article Dynamic Page / Replacing Content, I've gotten quite a few emails that come in from people who are trying to use it in conjunction with some other JavaScript stuff and having trouble. Most of the time, it's some kind of lightbox effect. One of their pages has a bunch of thumbnails on it, and when they load that page in, the lightbox effect doesn't work.

The problem is that when the thumbnails are loaded onto the page (via Ajax, i.e. jQuery's .load() function) they do not have any events bound to them.

/* Your lightbox plugin */
$("photos a").ceebox();  

/* Basics of Ajax */
$("nav a").click(function(e) {
    e.preventDefault();
    $("#main-content").load(this.href);  /* Thumbnails loaded from here */
});

The way that the lightbox plugin (probably) works is that it binds click events to the elements you passed in that selector (the thumbnails) when the page loads, and those click events do the lightbox action. Since the newly loaded thumbnails have no click event, the lightbox action does't work.

One way to fix it is to call the lightbox plugin after the content loads, in the callback function of the load function:

$("photos a").ceebox();  

$("nav a").click(function(e) {
    e.preventDefault();
    $("#main-content").load(this.href, function() {

              /* Callback function */
              $("photos a").ceebox();  /* Call this again */

    });
});

A little repetitive, but that'll do the trick.

A Better Way with Delegate

While this is "just how JavaScript works" it's a known pain in the butt. Since jQuery is a library that exists to ease pains like this, of course it has a better way. That way is the .delegate() function, where instead of binding events to the individual elements, you bind an event to an element higher up the DOM tree, which isn't likely to be replaced via Ajax, which watches for those clicks.

This relies on something called event bubbling, which is a neat and important concept in JavaScript (really: the DOM model). If you click on a thumbnail, it will trigger a click event on that element, then a click event on it's parent element, and a click event on it's parent's parent element, all the way up to the root element. Because of this, we can watch for clicks on deeper-down elements from higher-up elements.

Unfortunately with our lightbox example, you would have to alter the plugin itself to get it to use delegate instead of binding directly to the elements. Definitely do-able but trickier, since you probably aren't as intimately familiar with that plugin's code as you are your own.

Listen to Remy Sharp

As this article was drafting, Remy Sharp put out a video screencast about this exact topic. He's way better at explaining it than me, so please go watch that.

Comments

  1. Permalink to comment#

    I think explaining the difference between $(…).live(); and $(…).delegate(); would be a good next step to this article. Because you could use…

    $('img').live('click', function(){ ... });

    To do the same thing.

    Explaining what Delegate does vs what Live does would be helpful to most people.

    – Just a thought

    • I’m pretty sure that the difference is that .live() automatically binds the event to the document. That means that events need to bubble up all the way there in order to be handled. That is less efficient than with delegate where you can bind to the element only as high up as you need.

      Smack me down somebody if I’m wrong about that.

    • Permalink to comment#

      Dead on, Chris. The .delegate() function is much more efficient.

      Also, you can’t use chaining with .live() which can be a pain.

    • Sean
      Permalink to comment#

      As of 1.4, you can force live to stop at a certain element, though looking at the code it seems significantly more cumbersome than delegate.

      However, another advantage to delegate is that you can chain selectors, which gives you some flexibility in terms of limiting scope of the event.

    • Joseph Silber
      Permalink to comment#

      @chris

      I don’t get. The event bubbles up regardless of whether there’s an event listener attached. How does attaching the event listener lower in the chain increase performance?

      The only way I see it (smack me if I’m wrong), is that since live binds to the root element, it’ll listen to every single click on the page, and then check if the target element was the one specified in the selector. That, in my opinion, is the biggest hit on performance. It’s even worse for $(‘#whatever’).live(‘hover’, function…, as it keeps on listening everywhere you move on the page!!!

      Someone correct me if I’m wrong on any of this…

    • I THINK… that when the event is bound at the document, it has to kinda check every single level down on it’s way to finding the element that it’s supposed to react for. Because obviously every click on the document doesn’t fire the event, you only want it to fire when it’s a click inside the element you gave it. All that recursive checking is what makes it less efficient.

    • Joseph Silber
      Permalink to comment#

      @chris

      The way I see it (again, not that I read the code, it’s just the way I think it’s implemented):

      When you use live/delegate, what it does behind the scenes is that it attaches an event listener to the specified ancestor element (or the root), something akin to this (assuming you’re using an ID selector. Any other selector, and it gets a bit more complicated):


      $('#ancestor').click(function(e){
      if(e.target === $('#descendant')[0]) {
      // execute your anonymous function
      }
      });

      so that there is no need to find that element.

      Again, this is not what I dug out from the code, just what I assume.
      Correct me if I’m wrong…

    • Glenn
      Permalink to comment#

      @joseph maybe you can just spend 5 minutes on google and answer your own question?

      http://www.alfajango.com/blog/the-difference-between-jquerys-bind-live-and-delegate/

      Gives a very good explanation.

  2. What you could do with delegate is something like

    $("#some_container").delegate("nav", "load", function(){
        $("photos a").ceebox();
    });

    With this, if I’m not completely mistaken, when the “nav” element is loaded into your page it will fire the load event and then inside that you can call your lightbox or whatever plugin. (I couldn’t exactly test this peace of code, but

    Another alternative would be to call that on your AJAX callback function, that way you will also ensure that all DOM is loaded and all images are in place before calling the plugin.

    Anyways, nice article Chris!

    • Joseph Silber
      Permalink to comment#

      I didn’t test this now (posting from my iPhone), but I don’t think the ‘load’ event bubbles up…

    • Can’t seem to fix this: I want to use jQuery onClick function to change instances of class=”display” to class=”displaynone” When user clicks ‘next’ or ‘previous’. Tried to follow what this article said, but it’s not making much sense to me. Any advice would be extremely appreciated.
      The script in my is

      $(function() {
        $('a[@rel*=lightbox]').lightBox(); 
      });

      And my body markup is:

      .displaynone {display:none;}
      .display {}
      
      
       
      	
      		
      	
      
      
      	
      		
      	
      
      
      previous
      next

      I tried to use .mousedown since it probably wasn’t bound by lightbox. Didn’t work. This is what I tried:

      $('#next-thumb').mousedown(function() {
      	$('#thumb1').removeClass('display').addClass('displaynone');
      	$('#thumb2').removeClass('displaynone').addClass('display');
      	$('#next-thumb').removeClass('display').addClass('displaynone');
      	$('#prev-thumb').removeClass('displaynone').addClass('display');
      });
      $('#prev-thumb').mouseup(function() {
      	$('#thumb1').removeClass('displaynone').addClass('display');
      	$('#thumb2').removeClass('display').addClass('displaynone');
      	$('#prev-thumb').removeClass('display').addClass('displaynone');
      	$('#next-thumb').removeClass('displaynone').addClass('display');
      });

      This didn’t work either. I’m really at a loss
      :(
      Css-Tricks Community, I need your help!

  3. Alexey
    Permalink to comment#

    JavaScript is not jQuery.

  4. i m trying for 5 hours to make a sence with that javascript .
    I have some questions that i want from you to be answered in order if you can to help me first of all
    i run the script using

      
        $(function() {
            $('#gallery a').lightBox();
        });
        

    at the head section of my page
    also i use the script of the dynamic page through

    where i have to put delegate in order to work the lightbox , i tried in dynamic page .js after the $(“nav”).delegate(“a”, “click”, function() {
    but nothing happened i also tried to put before this the $(‘#gallery a’).lightBox(); and after this as the first example but nothing happened again. i saw the video and i understand what happens with dellegate.
    Plz i want some explanation to understand it i cant make it happen i tried everything and i watched the video 5 time undestanding it and again i cant make it . thanx a lot

  5. jim vlagas
    Permalink to comment#

    also i tried the first method that i cant find the part of code that you mention in your dynamic page js file in order to put $(‘#gallery a’).lightBox(); . it is in a seperated file is that the problem ? dont think so. whats happening? i tried to change the plugin lightbox with delegate make all bind delegate but nothing happened ..what else to try?

    • Kim Andersen
      Permalink to comment#

      Make sure your DOM is loaded before you init your lighbox: if you are running the code within the HEAD tag try moving it to the end of the page just before the tag.

  6. Phil
    Permalink to comment#

    I recently ran into an issue like this where I wanted to hide/show ajax content inside a modal window, but I did do it properly- I bound the click events of the ajax content inside the callback function of .load() and it works perfect in all browsers but IE7 throws an error object does not support method. I have not tried .delegate() or .live() on it yet, so I guess that might be the issue- I’ll certainly give it a try tonight.

    • Phil
      Permalink to comment#

      I tried .delegate() and .live() and got the same results- work everywhere but IE7. I keep getting the same two errors- “object does not support property or method” and one on the jQuery source- “Exception thrown and not caught”.

  7. Yes, javascript is easy but on basic, I prefer jquery 1000 times, not so much code lines work very fast to and easy to understand :-) and prototype is also very nice. What is your favourite jquery or proto?

  8. Permalink to comment#

    Hi All,

    I have a question about how Google, Bing and the other search engines react to JavaScript. I have a Slideshow on my website, which I built with JavaScript, without any Flash. Now, will Google & co. read the links and the text out of it? Please take a look at the Slideshow:

    Thanks,
    John

  9. Jake
    Permalink to comment#

    Event bubbling is a function of the browser’s DOM model, not Javascript.

    • Yes good point. While “events” are a JavaScript concept, so they are closely tied. I made a note in the article though.

  10. Jake
    Permalink to comment#

    Well, actually, events aren’t a Javascript concept. Again, it’s all about the DOM. The browser fires an event on a DOM node, and if someone has happened to associate a JS method with the event, that method will be invoked.

    It is possible to implement event handlers in any language a browser supports. For example, IE supports JS, VBScript and C++ via COM. If .net is installed then pretty much any .net language can be used as well.

    Firefox supports JS and C++ via XPCOM. There are addins for Firefox that allow handlers to be written in Python.

  11. hugo
    Permalink to comment#

    That exactly what I needed. Thanks

Leave a Comment

Current day month ye@r *

*May or may not contain any actual "CSS" or "Tricks".