Rick Strahl's Web Log

Wind, waves, code and everything in between...
ASP.NET • C# • HTML5 • JavaScript • AngularJs
Contact   •   Articles   •   Products   •   Support   •   Search
Ad-free experience sponsored by:
ASPOSE - the market leader of .NET and Java APIs for file formats – natively work with DOCX, XLSX, PPT, PDF, images and more

Non-Dom Element Event Binding with jQuery


Yesterday I had a short discussion with Dave Reed on Twitter regarding setting up fake ‘events’ on objects that are hookable. jQuery makes it real easy to bind events on DOM elements and with a little bit of extra work (that I didn’t know about) you can also set up binding to non-DOM element ‘event’ bindings.

Assume for a second that you have a simple JavaScript object like this:

var item = { sku: "wwhelp" , 
             foo: function() { alert('orginal foo function'); }              
};

and you want to be notified when the foo function is called.

You can use jQuery to bind the handler like this:

$(item).bind("foo", function () { alert('foo Hook called'); } );

Binding alone won’t actually cause the handler to be triggered so when you call:

item.foo();

you only get the ‘original’ message. In order to fire both the original handler and the bound event hook you have to use the .trigger() function:

$(item).trigger("foo");

Now if you do the following complete sequence:

var item = { sku: "wwhelp" , 
             foo: function() { alert('orginal foo function'); }              
};
$(item).bind("foo", function () { alert('foo hook called'); } );
$(item).trigger("foo");


You’ll see the ‘hook’ message first followed by the ‘original’ message fired in succession. In other words, using this mechanism you can hook standard object functions and chain events to them in a way similar to the way you can do with DOM elements. The main difference is that the ‘event’ has to be explicitly triggered in order for this to happen rather than just calling the method directly.

.trigger() relies on some internal logic that checks for event bindings on the object (attached via an expando property) which .trigger() searches for in its bound event list. Once the ‘event’ is found it’s called prior to execution of the original function.

This is pretty useful as it allows you to create standard JavaScript objects that can act as event handlers and are effectively hookable without having to explicitly override event definitions with JavaScript function handlers. You get all the benefits of jQuery’s event methods including the ability to hook up multiple events to the same handler function and the ability to uniquely identify each specific event instance with post fix string names (ie. .bind("MyEvent.MyName") and .unbind("MyEvent.MyName") to bind MyEvent).

Watch out for an .unbind() Bug

Note that there appears to be a bug with .unbind() in jQuery that doesn’t reliably unbind an event on a non-DOM object and results in a elem.removeEventListener is not a function error. The following code demonstrates:

var item = { sku: "wwhelp",
    foo: function () { alert('orginal foo function'); }
};
$(item).bind("foo.first", function () { alert('foo hook called'); });
$(item).bind("foo.second", function () { alert('foo hook2 called'); });

$(item).trigger("foo");


setTimeout(function () {
    $(item).unbind("foo");
    // $(item).unbind("foo.first");
    //  $(item).unbind("foo.second");

    $(item).trigger("foo");
}, 3000);

The setTimeout call delays the unbinding and is supposed to remove the event binding on the foo function. It fails both with the foo only value (both if assigned only as “foo” or “foo.first/second” as well as when removing both of the postfixed event handlers explicitly. Oddly the following that removes only one of the two handlers works:

setTimeout(function () {
    //$(item).unbind("foo");
    $(item).unbind("foo.first");
    //  $(item).unbind("foo.second");

    $(item).trigger("foo");
}, 3000);

this actually works which is weird as the code in unbind tries to unbind using a DOM method that doesn’t exist. <shrug>

A partial workaround for unbinding all ‘foo’ events is the following:

setTimeout(function () {
    $.event.special.foo = { teardown: function () { alert('teardown'); return true; } };
    $(item).unbind("foo");

    $(item).trigger("foo");
}, 3000);

which is a bit cryptic to say the least but it seems to work more reliably.

I can’t take credit for any of this – thanks to Dave Reed and Damien Edwards who pointed out some of these behaviors. I didn’t find any good descriptions of the process so thought it’d be good to write it down here. Hope some of you find this helpful.

Posted in jQuery  

The Voices of Reason


 

someone
July 29, 2010

# re: Non-Dom Element Event Binding with jQuery

it is better to use triggerHandler than regular trigger for any custom made up events. trigger can cause regular dom event handler to be called which can result in weird behavior especially if you do not know which events are already used by dom or jquery itself.

TheBentArrow
December 15, 2010

# re: Non-Dom Element Event Binding with jQuery

Binding to non-dom elements does not work anymore in jQuery 1.4.4.
 

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2017