jQuery.empty() Kills Event Binding On Persistent Nodes

Posted October 23, 2007 at 6:36 PM by Ben Nadel

Tags: Javascript / DHTML

I am not sure if this is a bug, a gross misunderstanding on my part, or just a side effect of the architecture of the document object model (DOM), but from what I can tell, using jQuery's .empty() method seems to unbind any event handlers to child nodes, even when they have persistent pointers. To show you what I am talking about, take a look at this code:

  • <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  • <html>
  • <head>
  • <title>jQuery Test Binding</title>
  •  
  • <!-- Linked scripts. -->
  • <script type="text/javascript" src="jquery-latest.pack.js"></script>
  • <script type="text/javascript">
  •  
  • $(
  • function(){
  • var jP = $( "p:first" );
  • var jSpan = $( "span:first" );
  • var jAdd = $( "button.add" );
  • var jRemove = $( "button.remove" );
  •  
  •  
  • function Add(){
  • jP.append( jSpan );
  • }
  •  
  • function Remove(){
  • jP.empty();
  • }
  •  
  • function Bold(){
  • jSpan.css( "font-weight", 800 );
  • }
  •  
  • function Unbold(){
  • jSpan.css( "font-weight", 400 );
  • }
  •  
  •  
  • // Add the event binding on the SPAN so that
  • // if you click it, it will remove itself from
  • // the parent container (actually, the parent
  • // will jump empty. Additionally, hook up the
  • // hover (mouse over / out ) to bold text.
  • jSpan
  • .click( Remove )
  • .hover( Bold, Unbold )
  • ;
  •  
  • // Hook up the add button to add the SPAN back
  • // into the paragraph.
  • jAdd.click( Add );
  •  
  • // Hook up the Remove button to clearn the P.
  • jRemove.click( Remove );
  •  
  • }
  • );
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <form>
  •  
  • <p>
  • <span>Click To Remove</span>
  • </p>
  •  
  • <button type="button" class="add">
  • Add
  • </button>
  •  
  • <button type="button" class="remove">
  • Remove
  • </button>
  •  
  • </form>
  •  
  • </body>
  • </html>

Here, when the DOM loads, we are creating a jQuery stack containing the SPAN tag. We are then binding a click event and a hover event (mouse over / mouse out) such that if you click on the span, it causes the parent element to clear itself and if you hover over it, it bolds on the mouse over and unbolds on the mouse out.

Take a look at the Online Demo here.

This works great when the script first fires. However, once the P tag is cleared, taking the SPAN out of the document object model, all event handlers seem to unbind from the nested SPAN tag, even though the SPAN node continues to exist via the pointer, jSpan. Furthermore, you can see that the SPAN node persists because you can hit the Add button to add that jQuery stack back into the P tag using the .append() method. When added back in, the CSS styles persist (it will be bold if you clicked the original SPAN), which is what I would expect.

This is driving me crazy because I simply don't understand why the event handlers unbind even when the bound node continues to persist? The only way I can figure out how to get around this is to re-bind the event handler before I add the SPAN back into the document object model:

  • function Add(){
  • // Rebind the SPAN methods.
  • jSpan
  • .click( Remove )
  • .hover( Bold, Unbold )
  • ;
  •  
  • // Add the Span back in.
  • jP.append( jSpan );
  • }

Take a look at the Online Demo of that page here.

This works, but it seems very ganky. I think I should be able set-and-forget event bindings? Thoughts / tips??




Reader Comments

Oct 23, 2007 at 8:04 PM // reply »
46 Comments

Have you seen the live jQuery plugin? It's really awesome. Rather than BIND the events, you bind them with the live jQuery method. That way, when new nodes come and go, they all get the right stuff.

http://jquery.com/plugins/project/livequery


Oct 23, 2007 at 11:22 PM // reply »
44 Comments

I think I might see the problem... This code runs before you attach the events:

var jSpan = $( "span:first" );

And when you actually "attach" the events you are operating on the DOM using the jQuery object jSpan, but you aren't capturing the stuff that has now been added to the DOM (the click and hover events).

Instead of:

jSpan
.click( Remove )
.hover( Bold, Unbold )
;

Could you try this?:

jSpan = jSpan
.click( Remove )
.hover( Bold, Unbold )
;

Also as Glen suggested have a look at liveQuery, it's a pretty cool plugin :)


Oct 24, 2007 at 7:24 AM // reply »
10,640 Comments

@Justin,

Good thought, but that doesn't work either. I do not believe that it should matter. DOM elements are passed around by reference, not by value. Therefore a jQuery stack that composes a DOM element will contain the same pointers as the one that added the event bindings.

@Glen,

LiveQuery looks very interesting. I got the "click" example to work, but it looks like it doesn't support "hover" binding. Before I would consider using this, however, I want to understand why my code is not working, otherwise, I won't even know what I am fixing.


Oct 24, 2007 at 7:24 AM // reply »
10,640 Comments

Also, it looks like LiveQuery doesn't work with jQuery 1.2, but when I downgraded to 1.1.3, it worked.


Oct 24, 2007 at 8:58 AM // reply »
160 Comments

@Ben:

This was a change in v2:
http://dev.jquery.com/changeset/3159

This change was done to prevent memory leaks. What happens if is if the element is no longer in the DOM, it removes the events so that the browser doesn't leak memory.

Generally when you're using the remove() or empty() you're really want to "kill" the elements. If you just want to them not to be shown, use a method like hide().

If for some reason hide() doesn't work for you, then you could store a copy of the nodes in an element that's hidden--that way the events aren't removed.


Oct 24, 2007 at 9:07 AM // reply »
10,640 Comments

@Dan,

That sounds valid (their reason for removing the event bindings). My problem was that I was building a plug-in that was re-ordering the nodes in a container. To do this, it would pull all the nodes out, shuffle them, and then re-append them to the container.

It worked because all the elements were "float: left". I have updated the plug-in so that the elements are absolutely positioned and to shuffle, I am merely changing the "top" and "left" css. The works for my scenario since the shuffling is a visual effect, not a DOM-related affect.

Still, I wonder if there is a way to swap DOM nodes without killing the event bindings. Looking at the link you sent me (how on earth did you even find that link ???) it looks like you can hack it by keeping hidden containers and then just append items to it (via appendTo()), but this seems kludgy. Oh well, I guess it needs to be that way.

Thanks for the great tip.


Oct 1, 2009 at 1:09 PM // reply »
1 Comments

Hi,

I had a similar problem, but didn't want to refactor too much, so I kept looking.

This lead me to find jQuery.live() which was added in 1.3.x.

http://docs.jquery.com/Events/live

But this blog/article made me ask Google better questions ;)

Thanks

- Nicolai


Oct 1, 2009 at 1:31 PM // reply »
10,640 Comments

@Nicolai,

Yeah, the Live() event is definitely interesting. I've only played around with it a bit.


Jan 15, 2010 at 1:25 PM // reply »
3 Comments

Hey,

If you are happy to refactor code, you could try using the .sort() method of the Array object with a jQuery object. e.g.

$('.container .node').sort(function(a, b) {
// A and B are two Dom elements

// If you are shuffling then sort randomly
var move = Math.random() - 0.5;

// Move them accordingly
if(Math.random() > 0) {

// Move A left 30px here

// Move B right 30px here

return true;
}
// Else leave in the same positions

});


Jan 16, 2010 at 4:19 PM // reply »
10,640 Comments

@Hugheth,

I am not sure what sorting the elements does? Can you please explain further?


Jan 16, 2010 at 5:57 PM // reply »
3 Comments

@Ben Nadel,

The sort function I suggested is the native javascript sort function, a tutorial for that can be found here: http://www.javascriptkit.com/javatutors/arraysort.shtml

The example I posted earlier was more a rough suggestion of its functionality so I've written a quick implementation here:

http://hugheth.com/Shuffler.html

Please feel free to use and distribute.

The advantage of the sort function is that it can also be used to sort things alphabetically, classes etc.


Jan 17, 2010 at 11:23 AM // reply »
10,640 Comments

@Hugheth,

I am sorry, I did not communicate well; I understand the benefits of sorting arrays in Javascript. What I was asking you was how the array sorting related to this blog post? I am not seeing the connection?


Jan 17, 2010 at 6:05 PM // reply »
3 Comments

@Ben Nadel,

You mentioned reordering nodes inside a container, I thought it might have some relevance. I understand it doesn't directly solve the problem at hand though!

If you wanted to write a script that acted purely on the DOM rather than using CSS, I think DOM elements keep their events when moved so would it be possible to just re-"append()" elements in a new order to their parent node?


Jan 20, 2010 at 9:58 AM // reply »
10,640 Comments

I know this post is over two years old, but jQuery 1.4 now has a way to remove elements from the DOM while keeping their event bindings and data bindings intact:

http://www.bennadel.com/blog/1822-Learning-jQuery-1-4-Remove-vs-Detach-.htm



Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
InVision App - Prototyping Made Beautiful With Prototyping Tools Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Feb 12, 2012 at 3:37 AM
Learning ColdFusion 8: CFImage Part III - Watermarks And Transparency
Hi Ben, Just to ask currently it is placed bottom right corner, if i need to replace the same rendered image on the bottom left side or in the bottom center, how that can be calculated. bottom ce ... read »
Feb 11, 2012 at 9:29 PM
Use jQuery's SlideDown() With Fixed-Width Elements To Prevent Jumping
I can't say how glad I am that I found your post. Thank you very much. ... read »
Feb 10, 2012 at 7:21 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
Update! Instead of $(eval(options.insertAfter)).after(data['insertData']); I now use: var ajaxNode = document.createElement('span'); var parent = $(eval(options.insertAfter))[0].parentNode; ... read »
Feb 10, 2012 at 6:18 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
encountered this same, what I consider, jQuery bug last week. I'm building a site in which I load some content via AJAX. This content contains Linkedin share button placeholders which Linkedin API ne ... read »
Feb 10, 2012 at 11:30 AM
Cross-Origin Resource Sharing (CORS) AJAX Requests Between jQuery And Node.js
After you understand the concepts here, this is an awesome cheatsheet for enabling CORS in just about anything http://enable-cors.org/ ... read »
JM
Feb 10, 2012 at 9:10 AM
My Safari Browser SQLite Database Hello World Example
@Amy, Here is a very good tutorial on how to use JOIN: http://www.sqltutorial.org/sqljoin-innerjoin.aspx ... read »
Feb 10, 2012 at 4:42 AM
Building A Twitter-Inspired RESTful API Architecture In ColdFusion
This is great, very useful Ben. I spotted a small typo in the api.cgm listing: <cfthrow type="Unauthroized" /> Cheers Stefan ... read »
Feb 9, 2012 at 10:35 PM
CFDirectory Filtering Uses Pipe Character For Multiple Filters (Thanks Steve Withington)
I was wondering if there would be a filter you could apply so that you got everything but what you included in the filter. As in show me all docs that are not a .pdf. ... read »