12

I created a fiddle to try to debug a problem I'm having where once I rearrange html elements with jQuery, hover events on those elements don't work anymore.

However, I came across this interesting situation here: http://jsfiddle.net/4yv1trj4/

I have a main div that changes color once I hover over it.

$("#block").hover(function() {
     $(this).css("backgroundColor", "red");
}, function() {
    $(this).css("backgroundColor", "#888");        
});

If you click the button, the main div's ID changes to block2:

$("#block").attr("id","block2");

but $("#block").hover() still fires when I hover over #block2. Also, all hover calls on #block2 do not work. Is there a fundamental principle of how jQuery works that would explain this?

ZygD
  • 22,092
  • 39
  • 79
  • 102
mcheah
  • 1,209
  • 11
  • 28

4 Answers4

18

When you do this:

$("#block").hover(function() {
    $(this).css("backgroundColor", "red");
}, function() {
    $(this).css("backgroundColor", "#888");        
});

you're telling jQuery to look for the element with the block ID and bind the hover event to it. Once this is done, the event will remain bound to that element, no matter what happens to its ID afterwards.

That is, unless you have some code that unbinds it, of course.

lucasnadalutti
  • 5,818
  • 1
  • 28
  • 48
  • Thanks, that helps me to understand it a little bit. http://jsfiddle.net/4yv1trj4/5/ I updated the jsfiddle to unbind and re?-bind but code looks ugly as hell and I can't help feeling that there could be much better ways of doing this. Do you think technophobia's answer would be a better option to slim down my code? – mcheah Jun 24 '15 at 17:34
  • I like technophobia's workaround, although I wouldn't recommend it for your use case due to the issues stated by canon (http://stackoverflow.com/questions/31032004/change-element-id-but-jquery-still-fires-event-calling-old-id-why-does-this-wor/31032094#comment50091306_31032395). I think your solution was not that bad. – lucasnadalutti Jun 24 '15 at 18:05
  • Clearly there is a trade of that you will have to contend with - I personally have used `.on()` container bindings in countless projects with minimal impact on performance. – technophobia Jun 24 '15 at 19:56
  • 'trade of' was meant to be 'trade-off' – technophobia Jun 24 '15 at 20:09
9

As an extension to lucasnadalutti's answer: It's worth noting that you can add the binding to the container and yield the result you expect:

Example:

$("body").on("mouseenter", "#block", function () {
    $(this).css("backgroundColor", "red");
}).on('mouseleave', "#block", function () {
    $(this).css("backgroundColor", "#888");
});

Notice the binding on the body, not the actual element. A trick to keep in your back pocket.

Demo: jsFiddle


Update:

From the comment section, it's clear a warning is needed - you should bind to the nearest element on the page. body was used here as a simple example.

Although I think this solution would suit you elegantly, you should read Should all jquery events be bound to $(document)? before you start abusing that power.

Community
  • 1
  • 1
technophobia
  • 2,644
  • 1
  • 20
  • 29
  • interesting, I had never knew this was an option. Is any part of doing this considered to be bad practice if I replace all of my click() and hover() events with this, considering i'd have to add a lot of binds and unbinds in my code if I were to go that route? Thanks! – mcheah Jun 24 '15 at 17:16
  • @Matt yes, there are issues. It essentially means that your events have to bubble all the way up through the DOM to `body` before they can be handled. If anything stops propagation along the way, you're in trouble. Generally, this pattern is reserved for dynamic content or to avoid attaching a large number of handlers, i.e.: one for each list-item in a list. Regardless, if you use [event delegation](http://learn.jquery.com/events/event-delegation/), you'll always want to select the nearest reliable ancestor rather than simply using `body`. – canon Jun 24 '15 at 17:42
  • When testing this on a small page, these results will probably be fine; however, as the page grows, each event attached to your body tag (or whichever you attach it to using canon's recommendation) will take more and more resources. So ... for future expansion, you may want to use lucasnadalutti's recommendation to use the tags directly and unbind them as needed (unless of course, you know the relevant tag's content will remain relatively small) – technosaurus Jun 24 '15 at 18:05
  • @Matt I would bind to the nearest container - the performance is perfectly acceptable for most use cases. However, I'd still highly recommend you read the link I've posted at the bottom of the answer. Good luck. – technophobia Jun 24 '15 at 19:53
  • Thanks for adding that link. I think it helped me understand a little bit better. If you wouldn't mind indulging me, why does this example of delegation not work? http://jsfiddle.net/4yv1trj4/8/ Passing a variable as a selector in this case would end up being the easiest way to take care of my current problem (rearranging a lot of HTML content around the page based on user input) but it seems like when that variable changes, the function won't execute for that selector. Thanks! – mcheah Jun 24 '15 at 21:45
2

If you remove the jQuery hover listener and add a CSS hover, it works the way you want:

#block:hover {
    background-color:red;
    width:300px;
    height:300px;
}
Michael
  • 8,362
  • 6
  • 61
  • 88
depperm
  • 10,606
  • 4
  • 43
  • 67
2

$("#block") is looking for a specific DOM object. .hover() will bind a hover event to that DOM object. $("#block").attr("id","block2"); will change an attribute (id) of that DOM object, but the hover event is still bound to it.

Yay295
  • 1,628
  • 3
  • 17
  • 29
GingerBear
  • 936
  • 6
  • 4