How can you bind an event handler only if it doesn't already exist?
Asked Answered
M

3

27

I have a page that I'd like to bind drag/drop events to. I'd like the entire page in the browser to be the drop target, so I've bound my events to the document object. My application's content is loaded in the main content area via AJAX, though, and I'd only like these event handlers to be active when the upload page is currently visible.

Right now, I'm binding the event handlers when the upload page is retrieved; however, each time the upload page becomes active, it binds a new event handler, which is causing the handler to fire multiple times when the user goes to the upload page, leaves, and then comes back. I was thinking I could resolve this if I could make the handler bind only when it's not already bound. Is this possible, or am I overlooking a better alternative?

Relevant code, if it helps:

$(document).bind('dragenter', function(e) {
    e.stopPropagation();
    e.preventDefault();
}).bind('dragover', function(e) {
    e.stopPropagation();
    e.preventDefault();
}).bind('drop', function(e) {
    e.stopPropagation();
    e.preventDefault();
    self._handleFileSelections(e.originalEvent.dataTransfer.files);
});
Mink answered 23/7, 2012 at 12:50 Comment(1)
possible duplicate of jQuery check if event exists on elementVery
C
45

Unbind existing event handlers before you bind the new ones. This is really straightforward with namespaced events [docs]:

$(document)
  .off('.upload') // remove all events in namespace upload
  .on({
      'dragenter.upload': function(e) {
          e.stopPropagation();
          e.preventDefault();
      },
      'dragover.upload': function(e) {
          e.stopPropagation();
          e.preventDefault();
      },
      // ...
  });
Crampon answered 23/7, 2012 at 12:58 Comment(3)
I had to use bind() and unbind() instead of on() and off(), but this method worked well. Thanks!Mink
Yes, if your are stuck with an older version of jQuery (passing an object to bind should work as well though). Namespaced events have been introduced in jQuery 1.2 so that should definitely work :) Happy coding!Crampon
+1 upvote this answer helped me and I found it to work, perfectly, thank you. If it's any use to anyone else, for particular my situation, I am making a mobile toggle menu, and so I applied this answer inside a jquery resize event listener function callback, $(window).resize(checkSizeFunctionCallback); (ref: fourfront.us/blog/jquery-window-width-and-media-queries ) for a css class being added resulting from a media query breakpoint. My function added some markup and then I binded a click event to that markup using the answer above, but wanted to be sure it was only added once.Quiteris
B
4

For those who want to add the event only once, avoiding using the unbind()/off() and bind()/on() methods for each new cloned element.

I found on the Code Project website the article "Binding Events to Not Yet Added DOM Elements with JQuery (by Khrystyna Popadyuk)" that shows how to register the event only once, even before the element exists. Read his full article for details.

$("body").on("click", ".your-style", function(event) {
       // your code ...
});
Blockade answered 4/4, 2020 at 13:31 Comment(0)
T
0

I've written a "uon" (unique on) jquery extension that does this:

$.fn.extend({
    uon:function(eventname,fn) {
        this.each(function() {
            var alreadyexists=false;
            var exevby=$._data(this,'events');
            for(var xevn in exevby) {
                if(xevn==eventname) {
                    exevby[xevn].forEach(evobj=>{
                        if(evobj.handler==fn) alreadyexists=true;
                    });
                }
            }
            if(!alreadyexists) $(this).on(eventname,fn);
        });
    }
});

which means you can say:

$(this).uon('click',doSomething);
$(this).uon('click',doSomething);
$(this).uon('click',doSomething);

and it'll only doSomething once when clicked on - not 3 times.

It's particularly good with this object:

var JQEvent={
    ons:[],
    onload:function(sel,fn) {
        JQEvent.ons.push({sel:sel,fn:fn});
    },
    apply:function($el) {
        JQEvent.ons.forEach(on => {
            $el.find(on.sel).each(on.fn);
        });
    }
}

which means you can say

JQEvent.onload('input.field',function() {
   $(this).uon('click',doSomething);
}

which might be you want throughout your system to doSomething when you click on an input.field.

Then when you dynamically load some HTML or build some dom elements in javascript then you can just apply the generic:

JQuery.apply('div.new-container-element');

and any child div of that element will have those have those functions applied to it.

Taggart answered 14/7, 2023 at 5:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.