From 66975de2d249643779e2b3daad0457f7f5f92508 Mon Sep 17 00:00:00 2001 From: jeresig Date: Thu, 31 Dec 2009 00:37:23 -0500 Subject: [PATCH 1/4] Remove the .bind(name, fn, thisObject) and promote jQuery.event.proxy() to jQuery.proxy() as alternative to handling scoping on callbacks. Fixes #5736. --- src/event.js | 60 +++++++++++++++++++++++----------------------- test/unit/event.js | 10 ++++---- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/event.js b/src/event.js index 79a6b952..97cb0cca 100644 --- a/src/event.js +++ b/src/event.js @@ -35,7 +35,7 @@ jQuery.event = { var fn = handler; // Create unique handler function, wrapped around original handler - handler = this.proxy( fn ); + handler = jQuery.proxy( fn ); // Store data in unique handler handler.data = data; @@ -405,24 +405,6 @@ jQuery.event = { return event; }, - proxy: function( fn, proxy, thisObject ) { - if ( proxy !== undefined && !jQuery.isFunction( proxy ) ) { - thisObject = proxy; - proxy = undefined; - } - - // FIXME: Should proxy be redefined to be applied with thisObject if defined? - proxy = proxy || function() { - return fn.apply( thisObject !== undefined ? thisObject : this, arguments ); - }; - - // Set the guid of unique handler to the same of original handler, so it can be removed - proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++; - - // So proxy can be declared as an argument - return proxy; - }, - special: { ready: { // Make sure the ready event is setup @@ -474,6 +456,24 @@ jQuery.event = { } }; +jQuery.event.proxy = jQuery.proxy = function( fn, proxy, thisObject ) { + if ( proxy !== undefined && !jQuery.isFunction( proxy ) ) { + thisObject = proxy; + proxy = undefined; + } + + // FIXME: Should proxy be redefined to be applied with thisObject if defined? + proxy = proxy || function() { + return fn.apply( thisObject !== undefined ? thisObject : this, arguments ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.event.guid++; + + // So proxy can be declared as an argument + return proxy; +}; + jQuery.Event = function( src ) { // Allow instantiation without the 'new' keyword if ( !this.preventDefault ) { @@ -759,7 +759,7 @@ if ( document.addEventListener ) { } jQuery.each(["bind", "one"], function( i, name ) { - jQuery.fn[ name ] = function( type, data, fn, thisObject ) { + jQuery.fn[ name ] = function( type, data, fn ) { // Handle object literals if ( typeof type === "object" ) { for ( var key in type ) { @@ -773,12 +773,13 @@ jQuery.each(["bind", "one"], function( i, name ) { fn = data; data = undefined; } - fn = thisObject === undefined ? fn : jQuery.event.proxy( fn, thisObject ); - var handler = name === "one" ? jQuery.event.proxy( fn, function( event ) { + + var handler = name === "one" ? jQuery.proxy( fn, function( event ) { jQuery( this ).unbind( event, handler ); return fn.apply( this, arguments ); }) : fn; - return type === "unload" ? this.one(type, data, handler, thisObject) : this.each(function() { + + return type === "unload" ? this.one(type, data, handler) : this.each(function() { jQuery.event.add( this, type, handler, data ); }); }; @@ -820,10 +821,10 @@ jQuery.fn.extend({ // link all the functions, so any of them can unbind this click handler while ( i < args.length ) { - jQuery.event.proxy( fn, args[ i++ ] ); + jQuery.proxy( fn, args[ i++ ] ); } - return this.click( jQuery.event.proxy( fn, function( event ) { + return this.click( jQuery.proxy( fn, function( event ) { // Figure out which function to execute var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i; jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 ); @@ -840,17 +841,16 @@ jQuery.fn.extend({ return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); }, - live: function( type, data, fn, thisObject ) { + live: function( type, data, fn ) { if ( jQuery.isFunction( data ) ) { - if ( fn !== undefined ) { - thisObject = fn; - } fn = data; data = undefined; } + jQuery( this.context ).bind( liveConvert( type, this.selector ), { data: data, selector: this.selector, live: type - }, fn, thisObject ); + }, fn ); + return this; }, diff --git a/test/unit/event.js b/test/unit/event.js index 70c721f8..8a9e48b0 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -232,8 +232,8 @@ test("bind(), with different this object", function() { }; jQuery("#firstp") - .bind("click", handler1, thisObject).click().unbind("click", handler1) - .bind("click", data, handler2, thisObject).click().unbind("click", handler2); + .bind("click", jQuery.proxy(handler1, thisObject)).click().unbind("click", handler1) + .bind("click", data, jQuery.proxy(handler2, thisObject)).click().unbind("click", handler2); ok( !jQuery.data(jQuery("#firstp")[0], "events"), "Event handler unbound when using different this object and data." ); }); @@ -706,15 +706,15 @@ test(".live()/.die()", function() { jQuery("#foo").trigger("click", true).die("click"); // Test binding with different this object - jQuery("#foo").live("click", function(e){ equals( this.foo, "bar", "live with event scope" ); }, { foo: "bar" }); + jQuery("#foo").live("click", jQuery.proxy(function(e){ equals( this.foo, "bar", "live with event scope" ); }, { foo: "bar" })); jQuery("#foo").trigger("click").die("click"); // Test binding with different this object, event data, and trigger data - jQuery("#foo").live("click", true, function(e, data){ + jQuery("#foo").live("click", true, jQuery.proxy(function(e, data){ equals( e.data, true, "live with with different this object, event data, and trigger data" ); equals( this.foo, "bar", "live with with different this object, event data, and trigger data" ); equals( data, true, "live with with different this object, event data, and trigger data") - }, { foo: "bar" }); + }, { foo: "bar" })); jQuery("#foo").trigger("click", true).die("click"); // Verify that return false prevents default action From a5dbca4a06a930865a17a1d02fd88893b5a2b690 Mon Sep 17 00:00:00 2001 From: jeresig Date: Thu, 31 Dec 2009 15:06:45 -0500 Subject: [PATCH 2/4] Moved jQuery.proxy() into core. --- src/core.js | 20 ++++++++++++++++++++ src/event.js | 23 +++-------------------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/core.js b/src/core.js index 944e8a9b..2c5445ab 100644 --- a/src/core.js +++ b/src/core.js @@ -614,6 +614,26 @@ jQuery.extend({ return ret.concat.apply( [], ret ); }, + // A global GUID counter for objects + guid: 1, + + proxy: function( fn, proxy, thisObject ) { + if ( arguments.length === 2 && proxy && !jQuery.isFunction( proxy ) ) { + thisObject = proxy; + proxy = undefined; + } + + proxy = proxy || function() { + return fn.apply( thisObject || this, arguments ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + + // So proxy can be declared as an argument + return proxy; + }, + // Use of jQuery.browser is frowned upon. // More details: http://docs.jquery.com/Utilities/jQuery.browser browser: { diff --git a/src/event.js b/src/event.js index 97cb0cca..3e3f8344 100644 --- a/src/event.js +++ b/src/event.js @@ -26,7 +26,7 @@ jQuery.event = { // Make sure that the function being executed has a unique ID if ( !handler.guid ) { - handler.guid = this.guid++; + handler.guid = jQuery.guid++; } // if data is passed, bind to handler @@ -114,7 +114,6 @@ jQuery.event = { elem = null; }, - guid: 1, global: {}, // Detach an event or set of events from an element @@ -405,6 +404,8 @@ jQuery.event = { return event; }, + proxy: jQuery.proxy, + special: { ready: { // Make sure the ready event is setup @@ -456,24 +457,6 @@ jQuery.event = { } }; -jQuery.event.proxy = jQuery.proxy = function( fn, proxy, thisObject ) { - if ( proxy !== undefined && !jQuery.isFunction( proxy ) ) { - thisObject = proxy; - proxy = undefined; - } - - // FIXME: Should proxy be redefined to be applied with thisObject if defined? - proxy = proxy || function() { - return fn.apply( thisObject !== undefined ? thisObject : this, arguments ); - }; - - // Set the guid of unique handler to the same of original handler, so it can be removed - proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.event.guid++; - - // So proxy can be declared as an argument - return proxy; -}; - jQuery.Event = function( src ) { // Allow instantiation without the 'new' keyword if ( !this.preventDefault ) { From 1d2b1a57dae0b73b3d99197f73f4edb623b5574a Mon Sep 17 00:00:00 2001 From: jeresig Date: Thu, 31 Dec 2009 15:17:52 -0500 Subject: [PATCH 3/4] Added in jQuery.proxy(obj, name), like the method described in Secrets of the JavaScript Ninja and in Dojo's Hitch, and added in some unit tests. --- src/core.js | 25 ++++++++++++++++++------- test/unit/core.js | 19 +++++++++++++++++++ 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/core.js b/src/core.js index 2c5445ab..3b6cfff4 100644 --- a/src/core.js +++ b/src/core.js @@ -618,17 +618,28 @@ jQuery.extend({ guid: 1, proxy: function( fn, proxy, thisObject ) { - if ( arguments.length === 2 && proxy && !jQuery.isFunction( proxy ) ) { - thisObject = proxy; - proxy = undefined; + if ( arguments.length === 2 ) { + if ( typeof proxy === "string" ) { + thisObject = fn; + fn = thisObject[ proxy ]; + proxy = undefined; + + } else if ( proxy && !jQuery.isFunction( proxy ) ) { + thisObject = proxy; + proxy = undefined; + } } - proxy = proxy || function() { - return fn.apply( thisObject || this, arguments ); - }; + if ( !proxy && fn ) { + proxy = function() { + return fn.apply( thisObject || this, arguments ); + }; + } // Set the guid of unique handler to the same of original handler, so it can be removed - proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + if ( fn ) { + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + } // So proxy can be declared as an argument return proxy; diff --git a/test/unit/core.js b/test/unit/core.js index e3adc604..eb00f23a 100644 --- a/test/unit/core.js +++ b/test/unit/core.js @@ -839,3 +839,22 @@ test("jQuery.isEmptyObject", function(){ // What about this ? // equals(true, jQuery.isEmptyObject(null), "isEmptyObject on null" ); }); + +test("jQuery.proxy", function(){ + expect(4); + + var test = function(){ equals( this, thisObject, "Make sure that scope is set properly." ); }; + var thisObject = { foo: "bar", method: test }; + + // Make sure normal works + test.call( thisObject ); + + // Basic scoping + jQuery.proxy( test, thisObject )(); + + // Make sure it doesn't freak out + equals( jQuery.proxy( null, thisObject ), undefined, "Make sure no function was returned." ); + + // Use the string shortcut + jQuery.proxy( thisObject, "method" )(); +}); From 8db0dd2c64e52e1eebb57d4749d03e276d49fb44 Mon Sep 17 00:00:00 2001 From: jeresig Date: Tue, 5 Jan 2010 19:17:28 -0500 Subject: [PATCH 4/4] Added in a holdover jQuery.event.guid for back-compat (two plugins use it: mousewheel and a datepicker). Plugin authors should work to stop using jQuery.event.guid and jQuery.event.proxy ASAP. --- src/event.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/event.js b/src/event.js index 3e3f8344..7d60c27e 100644 --- a/src/event.js +++ b/src/event.js @@ -404,6 +404,10 @@ jQuery.event = { return event; }, + // Deprecated, use jQuery.guid instead + guid: 1E8, + + // Deprecated, use jQuery.proxy instead proxy: jQuery.proxy, special: {