11
function bubble(content, triggerElm){
  this.element = $('<div class="bubble" />').html(content);
  this.element.css(.....) // here is positioned based on triggerElm
}

bubble.prototype.show = function(){
  $(document).on('click', this._click.bind(this));
  this.element.css(....)
};

bubble.prototype.hide = function(){
  $(document).off('click', this._click.bind(this));
  this.element.css(....)
};  

bubble.prototype._click = function(event){
  console.log('click', this);

  if(this.element.is(event.target) || (this.element.has(event.target).length > 0))
    return true;

  this.hide();
};

var b = new bubble();
b.show();
b.hide();

I keep seeing click in the console, so the click does not unbind. But if I remove the bind() call the click is unbinded. Does anyone know why? I need a way to be able to change "this" inside my test function, that's why I'm using bind().

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Elfy
  • 1,733
  • 6
  • 19
  • 39

6 Answers6

6

The problem is that this._click.bind() creates a new function every time it's called. In order to detach a specific event handler, you need to pass in the original function that was used to create the event handler and that's not happening here, so the handler is not removed.

If there are only going to be a few bubbles in your app, you could and simply not use this. That will remove a lot of the confusion about what this is referring to and ensure that each bubble retains a reference to its own click function that can be used to remove the event as needed:

function bubble(content, triggerElm) {
    var element = $('<div class="bubble" />').html(content);
    element.css(.....); // here is positioned based on triggerElm

    function click(event) {
        console.log('click', element);
        if (element.is(event.target) || 
            element.has(event.target).length > 0) {
            return true;
        }
        hide();
    }

    function show() {
        $(document).on('click', click);
        element.css(....);
    }

    function hide() {
        $(document).off('click', click);
        element.css(....);
    } 

    return {
        show: show,
        hide: hide
    };
}

var b1 = bubble(..., ...);
b1.show();

var b2 = bubble(..., ...);
b2.show();

See how this frees you from using contrivances like .bind() and underscore-prefixed methods.

JLRishe
  • 99,490
  • 19
  • 131
  • 169
  • but I can't access the "this" instance inside the click – Elfy Mar 02 '15 at 00:46
  • @Elfy Yes, that's true. What do you need it for? – JLRishe Mar 02 '15 at 00:47
  • Well, I need to be able to tell if the click was outside the bubble element, and that is part of the instance. Basically inside the click function Im checking if e.target is this.element or inside it – Elfy Mar 02 '15 at 00:48
  • @Elfy I don't see any `this.element` in your example. Can you show us where that comes into play (where it is introduced and how the `click()` function uses it)? I am fairly sure that my example can be adapted to accomodate it. – JLRishe Mar 02 '15 at 00:51
  • it's in the constructor function. I don't think its possible to have it global bc there should be possible to have multiple bubbles on the page visible at the same time – Elfy Mar 02 '15 at 00:53
  • @Elfy There are no globals in my code and nothing preventing you from using multiple bubbles at the same time. Can you show me how `this.element` is used? – JLRishe Mar 02 '15 at 00:59
  • interesting :) I haven't see any code like that b4. But I'll have to study it more to see what are the drawbacks compared to the prototype thing – Elfy Mar 02 '15 at 01:13
  • @Elfy The main benefit of using a prototype is that it saves memory by having one copy of each function shared among all instances instead of a separate copy for each instance. But that's pretty much only relevant if you're going to have hundreds or thousands of `bubble`s at the same time. – JLRishe Mar 02 '15 at 01:20
5

One option would be to namespace the event:

$(document).on('click.name', test.bind(this));
$(document).off('click.name');

Example Here

Josh Crozier
  • 233,099
  • 56
  • 391
  • 304
4

try use jQuery's proxy to get a unique reference of your function.

In this way, when you call $.proxy(test, this), it will check if this function has already been referenced before. If yes, proxy will return you that reference, otherwise it will create one and return it to you. So that, you can always get your original function, rather than create it over and over again (like using bind).

Therefore, when you call off(), and pass it the reference of your test function, off() will remove your function from click event.

And also, your test function should be declared before use it.

var test = function(){
      console.log('click');
};    

$(document).on('click', $.proxy(test, this));
$(document).off('click', $.proxy(test, this));

http://jsfiddle.net/aw50yj7f/

Jeff Chen
  • 736
  • 1
  • 8
  • 20
3

Please read https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

bind creates a new function therefore doing $(document).on('click', test.bind(this)); is like $(document).on('click', function(){}); and each time you execute it you invoke a new anonymous function thus you dont have a reference to unbind.

If you would do something like:

var test = function(){
     console.log('click');
};

var newFunct = test.bind(this);
$(document).on('click', newFunct );
$(document).off('click', newFunct );

It should work fine

e.g: http://jsfiddle.net/508dr0hv/

Also - using bind is not recommended, its slow and not supported in some browsers.

Neta Meta
  • 4,001
  • 9
  • 42
  • 67
  • thanks. but what if my test function is part of a object, how to bind it then? I mean where do I do that :) – Elfy Mar 02 '15 at 00:15
  • 1
    Elfy add that case to your question to demonstrate what you mean. – Neta Meta Mar 02 '15 at 00:15
  • 1
    for your update i would either use Josh Crozier's solution, or have both show/hide point to another function with a param indicating show or hide. and do the binding there or do the whole binding outside of the class. – Neta Meta Mar 02 '15 at 00:29
0

rather than binding this to the event, send this as a parameter:

$("#DOM").on("click",{
'_this':this
},myFun);

myFun(e){
 console.info(e.data._this);
$("#DOM").off("click",myFun);
}
shamaseen
  • 2,193
  • 2
  • 21
  • 35
0

If you've run into this problem when using classes, an alternative I found is to replace the handler definition with an assignment to a bound function:

//Replace this
onclick(e) {
    //...
}

//With this:
onclick = function(e) {
    //...
}.bind(this);

It's not very pretty, but it does save a refactor if you've already written the code.

AvahW
  • 2,083
  • 3
  • 24
  • 30