开发者

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 preven开发者_JS百科t 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)..


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/


An excellent answer from

bind event only once

copyPastedInfo:

if you can apply it, probably want to take a look at event.preventDefaultt and event.stopPropagation OR unbind and bind each time, within the method like

function someMethod()
{
  $(obj).off('click').on('click', function {});
}


Ummm how about using one()? http://api.jquery.com/one/

Or am i completely misunderstanding you?


This isn't a full answer, but I quickly found how to fix all my code with minimal changes. In my "base" js class (this is an object which is used on every page for standard button hookups by class name etc) I added the following method which does the check for dupe handlers:

BindOnce: function(triggerName, fn) {
    function handlerExists(triggerName, theHandler) {
        function getFunctionName(fn) {
            var rgx = /^function\s+([^\(\s]+)/
            var matches = rgx.exec(fn.toString());
            return matches ? matches[1] : "(anonymous)"
        }
        exists = false;
        var handlerName = getFunctionName(theHandler);
        if ($(document).data('events') !== undefined) {
            var event = $(document).data('events')[triggerName];
            if (event !== undefined) {
                $.each(event, function(i, handler) {
                    if (getFunctionName(handler) == handlerName) {
                        exists = true;
                    }
                });
            }
        }
        return exists;
    }
    if (!handlerExists(triggerName, fn)) {
        $(document).bind(triggerName, fn);
    }
},

Then I just invoke it instead of the bind method!

$.mystuff.BindOnce("TheTrigger", OnTriggered)

Note that you can't use anon methods here as they would just be called 1, 2, 3 etc and the only way to check for dupes would be with a toString() on the method, which would be pretty slow in a complex application


About a hundred years too late, but if you're not using anonymous functions you can do this much more simply by using unbind first:

$.fn.eventWillOnlySubscribeOnce = function () {
    return this.each(function () {
        var oneHandler = (function () {
             HANDLER CODE
        });

    $(this).unbind("submit", oneHandler);
    $(this).bind("submit", oneHandler);
});
};

This implementation will work in Firefox 4, but not in many other browsers - because the handler variable is created new each time so the unbind can't find a matching handler to unbind. Moving the handler into the parent scope fixes the problem: i.e.

var oneHandler = (function () {
    HANDLER CODE
});

$.fn.eventWillOnlySubscribeOnce = function () {
    return this.each(function () {
    $(this).unbind("submit", oneHandler);
    $(this).bind("submit", oneHandler);
});
};

Presumably Firefox is doing some clever optimisation which means the handler is held as a constant variable somewhere as it never changes.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜