jqueryeventstriggersbindjavascript

JQuery event model and preventing duplicate handlers


Once again I want to load a page which contains its own script into a div using $("divid").load(...). The problem I face is related to events. Let's say we trigger("monkey") from the parent page and on the loaded page we bind("monkey") and just do an alert("monkey bound"). If the same load method is called multiple times, the bind is called multiple times. Now I could just unbind it before I bind it, or check the number of handlers before the bind and then not bind it to prevent this. Neither option is scalable as what if I later want to bind to that trigger in another "sub page" (a page loaded into a div).

What I ideally want to do then is check if the handler I am about to add already exists, but I still WANT to use anonymous handlers... (asking a bit much with that last request I think). Currently I have a workaround by using pre-defined/named methods and then checking this before the bind.

// Found this on StackOverflow
function getFunctionName(fn)
{
 var rgx = /^function\s+([^\(\s]+)/
 var matches = rgx.exec(fn.toString());
 return matches ? matches[1] : "(anonymous)"
}

function HandlerExists(triggerName, handlerName) {
        exists = false;
        if ($(document).data('events') !== undefined) {
            var event = $(document).data('events')[triggerName];
            if(event !== undefined)
            {
                $.each(event, function(i, handler) {
                    alert(handlerName);
                    if (getFunctionName(handler) == handlerName) {
                        exists = true;
                    }
                });
            }
        }
        return exists;
    }

This is a pretty crude way of going about it I feel, but appears to work. I just do the following before the bind as follows:

if (!HandlerExists("test", "theMethod")) {
    $(document).bind("test", theMethod);
}

Does anyone have a more elegant solution? for instance, is there any way to check a particular script is loaded? so I could use getScript() to load the js from the child page on first load, and then simply not load it on subsequent loads (and just fire a trigger which would be handled by he preexisting js)..


Solution

  • Prevent duplicate binding using jQuery's event namespace

    There are actually a couple different ways of preventing duplicate. One is just passing the original handler in the unbind, BUT if it is a copy and not in the same space in memory it will not unbind, the other popular way (using namespaces) is a more certain way of achieving this.

    This is a common issue with events. So I'll explain a little on the jQuery events and using namespace to prevent duplicate bindings.



    ANSWER: (Short and straight to the point)


    // bind handler normally
    $('#myElement').bind('myEvent', myMainHandler);
    
    // bind another using namespace
    $('#myElement').bind('myEvent.myNamespace', myUniqueHandler);
    
    // unbind the unique and rebind the unique
    $('#myElement').unbind('myEvent.myNamespace').bind('myEvent.myNamespace', myUniqueHandler);
    $('#myElement').bind('myEvent.myNamespace', myUniqueHandler);
    
    // trigger event
    $('#myElement').trigger('myEvent');
    
    // output
    myMainHandler() // fires once!
    myUniqueHandler() // fires once!
    



    EXAMPLE OF ANSWER: (Full detailed explanation)


    First let's create an example element to bind to. We will use a button with the id of #button. Then make 3 functions that can and will be used as the handlers to get bound to the event:

    function exampleOne() we will bind with a click. function exampleTwo() we will bind to a namespace of the click. function exampleThree() we will bind to a namepsace of the click, but unbind and bind multiple times without ever removing the other binds which prevents duplicating binding while not removing any other of the bound methods.

    Example Start: (Create element to bind to and some methods to be our handlers)

    <button id="button">click me!</button>
    
    
    // create the example methods for our handlers
    function exampleOne(){ alert('One fired!'); }
    function exampleTwo(){ alert('Two fired!'); }
    function exampleThree(){ alert('Three fired!'); }
    

    Bind exampleOne to click:

    $('#button').bind('click', exampleOne); // bind example one to "click" 
    

    Now if user clicks the button or call $('#button').trigger('click') you will get the alert "One Fired!";

    Bind exampleTwo to a namespace of click: "name is arbitrary, we will use myNamespace2"

    $('#button').bind('click.myNamespace2', exampleTwo);
    

    The cool thing about this is, we can trigger the "click" which will fire exampleOne() AND exampleTwo(), or we can trigger "click.myNamespace2" which will only fire exampleTwo()

    Bind exampleThree to a namespace of click: "again, name is arbitrary as long as it's different from exampleTwo's namespace, we will use myNamespace3"

    $('#button').bind('click.myNamespace3', exampleThree);
    

    Now if 'click' get's triggered ALL three example methods will get fired, or we can target a specific namespace.

    PUT IT ALL TOGETHER TO PREVENT DUPLICATE

    If we were to continue to bind exampleThree() like so:

    $('#button').bind('click.myNamespace3', exampleThree); 
    $('#button').bind('click.myNamespace3', exampleThree);
    $('#button').bind('click.myNamespace3', exampleThree);
    

    They would get fired three times because each time you call bind you add it to the event array. So, really simple. Just unbind for that namespace prior to binding, like so:

    $('#button').unbind('click.myNamespace3').bind('click.myNamespace3', exampleThree); 
    $('#button').bind('click.myNamespace3', exampleThree);
    $('#button').unbind('click.myNamespace3').bind('click.myNamespace3', exampleThree); 
    $('#button').bind('click.myNamespace3', exampleThree);
    $('#button').unbind('click.myNamespace3').bind('click.myNamespace3', exampleThree); 
    $('#button').bind('click.myNamespace3', exampleThree);
    

    If the click function is triggered, exampleOne(), exampleTwo(), and exampleThree() only get fired once.

    To wrap it all together in a simple function:

    var myClickBinding = function(jqEle, handler, namespace){
        if(namespace == undefined){
            jqEle.bind('click', handler);
        }else{
            jqEle.unbind('click.'+namespace).bind('click.'+namespace, handler);
        }
    }   
    

    Summary:

    jQuery event namespaces allow for binding to main event but also allow child namespaces to be created and cleared without effecting sibling namespaces or parent ones which with very minimal creative thinking allows prevention of duplicate bindings.

    For further explanation: http://api.jquery.com/event.namespace/