16

I have two divs, one that holds some stuff and the other with all possible stuff. Clicking on one of the divs will transfer items to the other div. The code I came up with is:

$("#holder > *").each(function() {
    $(this).click(function(e) {
        $(this).remove();
        $("#bucket").append(this);
    });
});

$("#bucket > *").each(function() {
    $(this).click(function(e) {
        $(this).remove();
        $("#holder").append(this);
        });
});

This one works perfectly, except that the event handlers need to be refreshed once I append or remove elements. What I mean is, if I first click on an element, it gets added to the other div, but if I click on this element again, nothing happens. I can do this manually but is there a better way to achieve this?

Legend
  • 113,822
  • 119
  • 272
  • 400

6 Answers6

24

Try jquery live events .. the $.live(eventname, function) will bind to any current elements that match as well as elements added to the Dom in the future by javascript manipulation.

example:

$("#holder > *").live("click", function(e) { 
        $(this).remove(); 
        $("#bucket").append(this); 
}); 

$("#bucket > *").live("click", function(e) { 
        $(this).remove(); 
        $("#holder").append(this); 
});

Important:

Note that $.live has since been stripped from jQuery (1.9 onwards) and that you should instead use $.on.

I suggest that you refer to this answer for an updated example.

Community
  • 1
  • 1
John Hartsock
  • 85,422
  • 23
  • 131
  • 146
  • @John: Thanks for the reply. I am not sure if I am missing but when I replace my code with this, nothing happens. I am using jQuery-1.4.2. Am I missing out on something? I mean, even the initial events are not working. – Legend May 25 '10 at 22:10
  • 1
    This won't work. `jQuery.fn.live` needs a selector to work with... it can't work with `$(this)`... – James May 25 '10 at 22:11
  • I guess so. I knew "this" is messing up things. – Legend May 25 '10 at 22:13
  • ok I have corrected the solution to properly work sorry about that – John Hartsock May 25 '10 at 22:15
  • Ok. I guess, instead of `$("#holder > *").each`, just removing that line and substituting `$(this)` with `$("#holder > *")` in the second line does the trick. Thanks John and J-P. – Legend May 25 '10 at 22:15
  • This is MUCH better than re-binding handlers. Thanks :) – David Ryder Oct 18 '12 at 21:08
9

First, live is deprecated. Second, refreshing isn't what you want. You just need to attach the click handler to the right source, in this case: the document.

When you do

$(document).on('click', <id or class of element>, <function>);

the click handler is attached to the document. When the page is loaded, the click handler is attached to a specific instance of an element. When the page is reloaded, that specific instance is gone so the handler isn't going to register any clicks. But the page remains so attach the click handler to the document. Simple and easy.

craned
  • 2,991
  • 2
  • 34
  • 38
6

Here you go, using the more intuitive delegate API:

var holder = $('#holder'),
    bucket = $('#bucket');

holder.delegate('*', 'click', function(e) {
    $(this).remove();
    bucket.append(this);
});

bucket.delegate('*', 'click', function(e) {
    $(this).remove();
    holder.append(this);
});
James
  • 109,676
  • 31
  • 162
  • 175
  • @J-P: Ahh... Just accepted John's solution. +1 for the help. Just as a matter of curiosity, is there any advantage using delegate over live? – Legend May 25 '10 at 22:17
  • @Legend, Yes, it's faster, because it doesn't require you to redundantly select `#header > *` and `#bucket > *` first. – James May 25 '10 at 22:20
  • 2
    `.delegate()` is indeed faster here, just a habit for jQuery 1.3ers to think of `.live()` first (or even the `.livequery()` plugin if from earlier days) – mVChr May 25 '10 at 23:00
2

EDIT: don't use live, it be deprecated!

Take advantage of the fact that events bubble. Using .on():

var  = function( el1, el2 ) {

var things = $('#holder, #bucket');
things.each(function( index ) {
  // for every click on or in this element
  things.eq(index).on('click', '> *', function() {
    // append will remove the element
    // Number( !0 ) => 1, Number( !1 ) => 0
    things.eq( Number(!index) ).append( this );
  });
});

any click on any element (existing at the time of bind or not) will bubble up (assuming you haven't manually captured the event and stopped propagation). Thus, you can use that event delegation to bind only two events, one on each container. Every click that passed the selector test of the 2nd argument (in this case, > *, will remove that element and then append it to the alternate container as accesesed by things.eq( Number(!index) )

Dan Heberden
  • 10,990
  • 3
  • 33
  • 29
  • Actually, it won't. Unless you mean to unbind / rebind when elements are added to the DOM. I think you mean to say .live('click', function(){ – Dan Esparza May 25 '10 at 22:01
  • My understanding was that bind doesn't work with future items but only with elements that currently match a given selector [quote from `live` documentation in jQuery): "This new element also matches the selector .clickme, but since it was added after the call to .bind(), clicks on it will do nothing." – Marek Karbarz May 25 '10 at 22:01
  • I did mean .live, sry for the confusion - i was picking up my girlfriend and was in a hurry typing it in on my phone and typed bind out of habbit, lol – Dan Heberden May 25 '10 at 22:53
0

The most Efficient way (dont load all event for all elements) it:

//NORMAL FUNCTION
function myfunction_click(){
   //custom action
}
$('id_or_class_of_element').on('click', myfunction_click);

//LOAD OR REFRESH EVENT
$(document).on('click', 'id_or_class_of_element', myfunction_click);
codeMaster
  • 176
  • 1
  • 4
-1

Have you looked at jQuery's live function?

Marek Karbarz
  • 28,956
  • 6
  • 53
  • 73