Using .delegate in jQuery

734 Views Asked by At

I've got a function that works great on creating the custom tooltips I need for an event calendar. The problem is that when a user clicks to go to the next month on the calendar, a new set of links is made and jQuery is no longer selecting those links.

Here is my original function:

jQuery(function(){
        var tip = jQuery("#tip");
        var myTitle = "";

        jQuery(".eventful-pre a, .eventful a").hover(function(e){

        toolTip = "<ul>";
           jQuery.each(jQuery(this).attr("title").split(","),function(ind, val){
               toolTip  = toolTip +"<li>"+val +"</li>";
            });
            toolTip = toolTip +"</ul>";

            tip.html(toolTip);
            myTitle = jQuery(this).attr("title");

            jQuery(this).attr("title", "");

            tip.css("top",(e.pageY+5)+"px")
                .css("left",(e.pageX+5)+"px")
                .fadeIn("slow");

        }, function() {
                jQuery("#tip").fadeOut("fast");

                jQuery(this).attr("title", myTitle);
        });

it works just how I want it. I think .delegate is what I want to be using to grab the new elements when they appear, but I'm probably doing it wrong because it's not working.

jQuery("table").delegate("a.em-calnav", function(){
        in here I pasted my previous function.
    }

the calendar can be seen at http://dev.adabible.org/about-ada/

I must be doing the .delegate wrong and also there has to be a better way to go about things than pasting my function again and having it twice in the same script.

Thank you in advance oh wise ones!

2

There are 2 best solutions below

2
On BEST ANSWER

jQuery's delegate method actually assigns an event listener. You used jQuery("table").delegate(...), so you told jQuery to assign an event listener to every table on the page.

So the first step is to figure out which event you want to listen for. You're doing tooltips, so currently you're using "hover". Nothing wrong with that, except that "hover" is actually two events in one: "mouseenter" and "mouseleave". You can use "hover" with delegate, but only if you have one function to handle both "enter" and "leave" events. It looks like you're using two different functions: one to build/fade in the tooltip, and one to destroy it.

You could use delegate twice, once for "enter" and once for "leave," except that (according to this comment on jQuery) that's not actually supported on all browsers. (QuirksMode has a good set of compatibility tables regarding "enter"/"leave", and an explanation of why they're awesome.)

So it looks like you'll have to use "mouseover" and "mouseout," which are quite a bit more complicated because of how event bubbling works. BUT! If you're only using tooltips on simple links (e.g., a elements that contain text and nothing else, or one image and nothing else), things should work out okay.

The next step requires knowledge of event bubbling, another subject that QuirksMode explains really well. When you move your mouse over an element, a "mouseover" event is fired. If that element has a "mouseover" event listener, then it is executed. The event then moves up to the element's parent. If the parent has a "mouseover" event listener, then that one is executed as well. This continues until the event goes through every parent, all the way up to document.

So when you use delegate, you need to tell jQuery which elements you're actually looking for. If you're waiting for a "mouseover" event that originated on an a element with a class of "hover," then you'll using something like this:

jQuery(document).delegate('a.hover', 'mouseover', ...);

Note that the first part could be anything that contains all of your a.hover elements. These would all work about the same:

jQuery('body').delegate('a.hover', 'mouseover', ...);
jQuery(document).delegate('a.hover', 'mouseover', ...);
jQuery('div.some_container').delegate('a.hover', 'mouseover', ...);

The reason I'm bringing this up is because in your current function, you use the selector ".eventful-pre a, .eventful a". But then later, when you attempted delegation, you used "a.em-calnav". Which of these selectors do you actually want to target? If all of the links that could have tooltips also have the class "em-calnav," then I'd go with that, just because you wouldn't have to hassle with descendant selectors. But either one is valid.

So what we end up with is something like this:

jQuery(function () {
    var tip = jQuery("#tip");
    var myTitle = "";

    jQuery("table").delegate("YOUR_SELECTOR", "mouseover", function (e) {
        var toolTip = "<ul>";
        jQuery.each(jQuery(this).attr("title").split(","), function (ind, val) {
            toolTip = toolTip + "<li>" + val + "</li>";
        });
        toolTip = toolTip + "</ul>";

        tip.html(toolTip);
        myTitle = jQuery(this).attr("title");
        jQuery(this).attr("title", "");

        tip.css("top", (e.pageY + 5) + "px")
            .css("left", (e.pageX + 5) + "px")
            .fadeIn("slow");

    }).delegate("YOUR_SELECTOR", "mouseout", function () {
        jQuery("#tip").fadeOut("fast");
        jQuery(this).attr("title", myTitle);
    });
});

Your problem was that when a new calendar was loaded, the links no longer had the "hover" event listeners. This solution here delegates the event to the table. But if, in the course of building your new calendar, you also build a new table, then this solution doesn't really do anything. You'd have to delegate the events to the table's parent, or something. Basically, you delegate to elements that your JavaScript doesn't really change. (That's why document or "body" are so common in event delegation.)

Also, I didn't really change the functions themselves; I just moved them to the appropriate place. But there are some things you could do a little more efficiently. The biggest problem I saw was the way you build the tooltip. Instead of calling jQuery.each, you could probably do something like this:

var toolTip = "<ul>";
myTitle = jQuery(this).attr("title");

toolTip += "<li>" + myTitle.split(",").join("</li><li>") + "</li></ul>";

Hope I was able to help. Good luck.

0
On

First thing that comes to mind is that you've failed to specify the event for the delegate.

You should use it this:

jQuery("table").delegate("a.em-calnav", 'click', function(){
    in here I pasted my previous function.
}

Note the added click (should be whichever event you want to delegate).