136

I am binding a click event with a button:

$('#myButton').bind('click',  onButtonClicked);

In one scenario, this is getting called multiple times, so when I do a trigger I see multiple ajax calls which I want to prevent.

How do I bind only if its not bound before.

Dusty
  • 2,159
  • 3
  • 23
  • 26
  • 1
    Man, I think +Konrad Garus has the safest anwser (http://stackoverflow.com/a/6930078/842768), consider changing your accepted answer. Cheers! UPDATE: Check +Herbert Peters' comment as well! That's the best approach. – giovannipds Aug 02 '16 at 12:57
  • For current standards, see @A Morel's answer below (https://stackoverflow.com/a/50097988/1163705). Less coding and takes all the guess work, algorithms and/or searching for existing elements out of the equation. – Xandor Apr 25 '19 at 20:38

13 Answers13

187

One more way - mark such buttons with a CSS class and filter:

$('#myButton:not(.bound)').addClass('bound').bind('click',  onButtonClicked);

In recent jQuery versions replace bind with on:

$('#myButton:not(.bound)').addClass('bound').on('click',  onButtonClicked);
Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
Konrad Garus
  • 53,145
  • 43
  • 157
  • 230
  • 4
    Excellent! Thanks Konrad, this hits the sweet spot. I love elegant and simple approaches like this. I am pretty sure it is more performant as well, as every single element (which already has click events handlers attached) does not have to do a full search in some big events bucket comparing each one. In my implementation I named the added helper class "click-bound", which I think is a more maintainable option: $(".list-item:not(.click-bound)").addClass("click-bound"); – Nicholas Petersen Sep 19 '12 at 15:53
  • 1
    As was stated in the accepted answer: "It would be best, of course, if you could structure your application so this code only gets called once." This accomplishes that. – Jeff Dege Jan 21 '14 at 19:39
  • +1 in my situation, off/on and bind/unbind did prevent multiple calls. This was the solution that worked. – Jay Jun 27 '14 at 15:02
  • 2
    This is the better solution for performance because the code can check for the presence of the CSS class and slip binding of already present. Other solution execute an unbind-then-rebind process with (at least fr4om what I can tell) more overhead. – Phil Nicholas Apr 09 '15 at 21:01
  • 1
    2 issues. (when using it in a plug-in that can be bound to several elements) Will not work when binding a resize event to the window object. Use data( 'bound', 1 ) instead of addClass ('bound') is another approach that works. When destroying the event it might do so while other instances depend on it. So checking if other instances are still in use is advisable. – Herbert Peters Sep 23 '15 at 00:31
  • This is short clean solution. I do not need to write more custom functions, just using existing jQuery. – Thomas.Benz Nov 29 '16 at 16:15
120

Update 24 Aug '12: In jQuery 1.8, it is no longer possible to access the element's events using .data('events'). (See this bug for details.) It is possible to access the same data with jQuery._data(elem, 'events'), an internal data structure, which is undocumented and therefore not 100% guaranteed to remain stable. This shouldn't, however, be a problem, and the relevant line of the plugin code above can be changed to the following:

var data = jQuery._data(this[0], 'events')[type];

jQuery events are stored in a data object called events, so you could search in this:

var button = $('#myButton');
if (-1 !== $.inArray(onButtonClicked, button.data('events').click)) {
    button.click(onButtonClicked);
}

It would be best, of course, if you could structure your application so this code only gets called once.


This could be encapsulated into a plugin:

$.fn.isBound = function(type, fn) {
    var data = this.data('events')[type];

    if (data === undefined || data.length === 0) {
        return false;
    }

    return (-1 !== $.inArray(fn, data));
};

You could then call:

var button = $('#myButton');
if (!button.isBound('click', onButtonClicked)) {
    button.click(onButtonClicked);
}
lonesomeday
  • 233,373
  • 50
  • 316
  • 318
  • 10
    +1 for the edit. Readging this post today and i'm using 1.8. Thx. – Gigi2m02 Aug 24 '12 at 09:35
  • 2
  • I would wrap the line `var data = jQuery._data(this[0], 'events')[type];` in a try catch and return false in the catch. If no events are bound to this[0] than a call to [type] will cause some variation of "Unable to get property 'click' of undefined or null reference" and obviously that also tells you what you need to know. – Robb Vandaveer Jan 10 '14 at 03:19
  • I am not sure if the _data object changed in jQuery 2.0.3 but I could not use $.inArray for this. The function you want to compare is in a property of each data item called "handler". I modified it to use a simple for statement and checked for string equivalence, what I'm assume inArray did. `for (var i = 0; i < data.length; i++) { if (data[i].handler.toString() === fn.toString()) { return true; } }` – Robb Vandaveer Jan 10 '14 at 21:26
  • why not move your update/edit to the top?... It's the actual correct answer. – Don Cheadle Aug 08 '16 at 18:09
  • @mmcrae Because it feels dishonest to modify an answer that's already highly voted. – lonesomeday Aug 10 '16 at 07:56
  • @lonesomeday - I understand that. But I also think the slight change of emphasizing the up-to-date (and more likely to be actually helpful) part will ensure this helps members of the community. e.g. when I came across this answer, I tried the first part, and it didn't work. I could've naturally navigated away to a different page, but I happened to catch the bottom part. It should be the top... – Don Cheadle Aug 10 '16 at 14:23
  • @mmcrae OK, that makes sense. Edited accordingly. – lonesomeday Aug 10 '16 at 17:18
64

If using jQuery 1.7+:

You can call off before on:

$('#myButton').off('click', onButtonClicked) // remove handler
              .on('click', onButtonClicked); // add handler

If not:

You can just unbind it first event:

$('#myButton').unbind('click', onButtonClicked) //remove handler
              .bind('click', onButtonClicked);  //add handler
Naftali
  • 144,921
  • 39
  • 244
  • 303
  • 1
    This isn't exactly a lovely solution, but it would be improved by only unbinding the one handler: `.unbind('click', onButtonClicked)`. [See the manual](http://api.jquery.com/unbind/) – lonesomeday Jun 15 '11 at 17:08
  • 16
    You can actually namespace your handlers as you add them, then unbinding becomes pretty simple. `$(sel).unbind("click.myNamespace");` – Marc Jun 15 '11 at 17:11
  • @lonesomeday was your example using 'unbind' meant to show anything different? Isn't .unbind effectively the same as .off so you'd have to write `$('#myButton').unbind('click', onButtonClicked).bind('click', onButtonClicked);` – Jim Jan 07 '15 at 12:44
  • 1
    @Jim You'll see the question was edited three years after my comment! (And also in the minutes immediately after my comment. The content that I was commenting on no longer exists, which is why it probably doesn't seem to make much sense.) – lonesomeday Jan 07 '15 at 13:08
  • @lonesomeday hmmm I am not sure why it was edited, do you think I should rollback? – Naftali Jan 07 '15 at 13:44
  • @Neal Well, it's the right answer for the current jQuery API, but isn't really an answer to the question any more! Maybe edit to include both? – lonesomeday Jan 07 '15 at 14:19
  • @Jim I updated my answer to also include the older version that was for some reason edited out. – Naftali Jan 07 '15 at 14:33
  • It appears that in later version of jQuery you can also call .unbind with a single argument. e.g. `$('#myButton').unbind('click')`. It seems to unbind all functions for that event. – AJFaraday Oct 29 '15 at 10:12
9

I wrote a very tiny plugin called "once" which do that. Execute off and on in element.

$.fn.once = function(a, b) {
    return this.each(function() {
        $(this).off(a).on(a,b);
    });
};

And simply:

$(element).once('click', function(){
});
8

The best way I see is to use live() or delegate() to capture the event in a parent and not in each child element.

If your button is inside a #parent element, you can replace:

$('#myButton').bind('click', onButtonClicked);

by

$('#parent').delegate('#myButton', 'click', onButtonClicked);

even if #myButton doesn't exist yet when this code is executed.

Pablonete
  • 1,504
  • 1
  • 14
  • 11
7

Why not use this

unbind() before bind()

$('#myButton').unbind().bind('click',  onButtonClicked);
hassan
  • 7,812
  • 2
  • 25
  • 36
Krillehbg
  • 175
  • 2
  • 9
3

Here's my version:

Utils.eventBoundToFunction = function (element, eventType, fCallback) {
    if (!element || !element.data('events') || !element.data('events')[eventType] || !fCallback) {
        return false;
    }

    for (runner in element.data('events')[eventType]) {
        if (element.data('events')[eventType][runner].handler == fCallback) {
            return true;
        }

    }

    return false;
};

Usage:

Utils.eventBoundToFunction(okButton, 'click', handleOkButtonFunction)
Yair Galler
  • 111
  • 3
  • 4
3

To avoid to check/bind/unbind, you can change your approach! Why don't you use Jquery .on() ?

Since Jquery 1.7, .live(), .delegate() is deprecated, now you can use .on() to

Attach an event handler for all elements which match the current selector, now and in the future

It means that you can attach an event to a parent element that is still existing and attach children elements whether they are present or not!

When you use .on() like this:

$('#Parent').on('click', '#myButton'  onButtonClicked);

You catch event click on parent and it search child '#myButton' if exists...

So when you remove or add a child element, you do not have to worry about whether to add or remove the event binding.

A. Morel
  • 9,210
  • 4
  • 56
  • 45
  • This absolutely is the best answer on here for current standards. I don't think this feature of setting the handler on the parent to watch for the child has been well advertised but is a rather elegant solution. I was having an event fire multiple times and each time the total amount of times it fired kept increasing. This single line did the entire trick and without using `.off` which I believe would remove unrelated handlers in the process. – Xandor Apr 25 '19 at 20:36
2

Based on @konrad-garus answer, but using data, since I believe class should be used mostly for styling.

if (!el.data("bound")) {
  el.data("bound", true);
  el.on("event", function(e) { ... });
}
ariel
  • 15,620
  • 12
  • 61
  • 73
1

Try:

if (typeof($("#myButton").click) != "function") 
{
   $("#myButton").click(onButtonClicked);
}
0
if ($("#btn").data('events') != undefined && $("#btn").data('events').click != undefined) {
    //do nothing as the click event is already there
} else {
    $("#btn").click(function (e) {
        alert('test');
    });
}
Naftali
  • 144,921
  • 39
  • 244
  • 303
Bishoy Hanna
  • 4,539
  • 1
  • 29
  • 31
0

As of June 2019, I've updated the function (and it's working for what I need)

$.fn.isBound = function (type) {
    var data = $._data($(this)[0], 'events');

    if (data[type] === undefined || data.length === 0) {
        return false;
    }
    return true;
};
-3

JQuery has solution:

$( "#foo" ).one( "click", function() {
  alert( "This will be displayed only once." );
}); 

equivalent:

$( "#foo" ).on( "click", function( event ) {
  alert( "This will be displayed only once." );
  $( this ).off( event );
});
Veniamin
  • 466
  • 6
  • 18