jQuery: Extending bind() to define context of execution (scope)
One of the main advantages of jQuery is that it does a lot of thinking for you, especially with more complex things like event handling. Unfortunately, as you move into more advanced application building, the jQuery developer is left without a standard method of applying context to an event.
Although creating a generic bind function to use the Function object’s call or apply methods is always an option, it suffers from two problems:
- Not integrated with the library, which can cause developer confusion and creates hard to read code
- Unbinding that handler becomes problematic without additional bookkeeping
While many have called for scope of execution to be added to bind, you can simply extend the functionality yourself with the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | function() { jQuery.extend(jQuery.event, { add: function(elem, types, handler, data, scope) { if ( elem.nodeType == 3 || elem.nodeType == 8 ) return; // For whatever reason, IE has trouble passing the window object // around, causing it to be cloned in the process if ( elem.setInterval && elem != window ) elem = window; // Make sure that the function being executed has a unique ID if ( !handler.guid ) handler.guid = this.guid++; // if data or scope is defined, use a proxy function if( data !== undefined || scope !== undefined) { // Create temporary function pointer to original handler var fn = handler; // Create unique handler function, wrapped around original handler if ( scope !== undefined) { // Create proxy function to apply correct context determined by scope var proxyFn = function(){ return fn.apply(scope, arguments); }; handler = this.proxy( fn, proxyFn ); } else { handler = this.proxy( fn ); } } // if data is passed, bind to handler if ( data !== undefined ) { // Store data in unique handler handler.data = data; } // Init the element's event structure var events = jQuery.data(elem, "events") || jQuery.data(elem, "events", {}), handle = jQuery.data(elem, "handle") || jQuery.data(elem, "handle", function(){ // Handle the second event of a trigger and when // an event is called after a page has unloaded return typeof jQuery !== "undefined" && !jQuery.event.triggered ? jQuery.event.handle.apply(arguments.callee.elem, arguments) : undefined; }); // Add elem as a property of the handle function // This is to prevent a memory leak with non-native // event in IE. handle.elem = elem; // Handle multiple events separated by a space // jQuery(...).bind("mouseover mouseout", fn); jQuery.each(types.split(/\s+/), function(index, type) { // Namespaced event handlers var namespaces = type.split("."); type = namespaces.shift(); handler.type = namespaces.slice().sort().join("."); // Get the current list of functions bound to this event var handlers = events[type]; if ( jQuery.event.specialAll[type] ) jQuery.event.specialAll[type].setup.call(elem, data, namespaces); // Init the event handler queue if (!handlers) { handlers = events[type] = {}; // Check for a special event handler // Only use addEventListener/attachEvent if the special // events handler returns false if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem, data, namespaces) === false ) { // Bind the global event handler to the element if (elem.addEventListener) elem.addEventListener(type, handle, false); else if (elem.attachEvent) elem.attachEvent("on" + type, handle); } } // Add the function to the element's handler list handlers[handler.guid] = handler; // Keep track of which events have been used, for global triggering jQuery.event.global[type] = true; }); // Nullify elem to prevent memory leaks in IE elem = null; } }); jQuery.fn.extend({ bind: function( type, data, fn, scope ) { return type == "unload" ? this.one(type, data, fn, fn && scope) : this.each(function(){ jQuery.event.add( this, type, fn || data, fn && data, (fn && data) && scope ); }); } }); }(); |
As you can see, you simply use bind as you always have, but, if you wish to define scope, pass the context in as the last parameter. You ARE required to pass something for data with this method. If you have nothing to pass, simply send an empty object: {}. The bookkeeping is done by jQuery, and you should be able to unbind without any headaches.
Please note: This isn’t fully tested, but it’s based off changes I made elsewhere. I will update this later if I find issues with it.
April 4th, 2009 at 12:27 pm
Very cool extension! I have been wondering why similar functionality isn’t built into jQuery core code. Scoping for objects is part of mootools and has been what I miss the most in my switch from mootools to jquery. I am getting sick of adding var self = this; when I need to refer to my object.
p.s. your code contains & not &
July 3rd, 2009 at 9:56 am
Can anyone clarify for me WHY this functionality is not included in jQuery already? Binding event handlers and parameters to a particular scope is a royal pain in jQuery, and its something I have to do quite frequently.
July 5th, 2009 at 11:17 pm
@Brian, from what I’ve heard/read, its simply unnecessary within the jQuery paradigm. And this makes sense: all jQuery methods are called on an array of selected elements, and ‘this’, in this context, should reference the current element being manipulated.
That being said, there’s no reason this shouldn’t be in the library. It’s something that makes doing complex OOP with jQuery more difficult than it needs to be. There are plenty of others who’ve also expressed interest in seeing this, but apparently not enough…
@gregory, sorry for the crappy formatting, I need to find a new wordpress plugin…
July 30th, 2009 at 10:12 am
this is going to be in the library as of jquery 1.3.3
http://brandonaaron.net/blog/2009/05/12/jquery-edge-bind-with-a-different-this#comments
September 17th, 2009 at 1:14 pm
Just an update, this was included in 1.3.3. Described here with other updates: http://www.slideshare.net/jeresig/recent-changes-to-jquerys-internals
April 17th, 2012 at 12:56 am
Proxy Seite…
[...]jQuery: Extending bind() to define context of execution (scope) | sanghvi labs[...]…
April 22nd, 2013 at 4:00 am
You’re truly a good webmaster. The website loading pace is amazing. It sort of feels that you’re doing any distinctive trick. Furthermore, The contents are masterwork. you have performed a great activity in this topic!