From 9f8cd6c499844451468257140e71f611abb3a040 Mon Sep 17 00:00:00 2001 From: Gianni Chiappetta Date: Tue, 14 Dec 2010 21:53:04 -0500 Subject: [PATCH] Fixing $.proxy to work like (and use) Function.prototype.bind (ticket #7783) http://bugs.jquery.com/ticket/7783 --- src/core.js | 49 +++++++++++++++++++++++++++++------------------ src/event.js | 44 ++++++++++++++++++++++++++---------------- test/unit/core.js | 11 ++++++++--- 3 files changed, 65 insertions(+), 39 deletions(-) diff --git a/src/core.js b/src/core.js index 346e52d70a..74ec4ea036 100644 --- a/src/core.js +++ b/src/core.js @@ -740,31 +740,42 @@ jQuery.extend({ // A global GUID counter for objects guid: 1, - proxy: function( fn, proxy, thisObject ) { - 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; - } + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var args, proxy; + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( ! jQuery.isFunction( fn ) ) { + return undefined; } - if ( !proxy && fn ) { - proxy = function() { - return fn.apply( thisObject || this, arguments ); - }; + if ( jQuery.isFunction( Function.prototype.bind ) ) { + // Native bind + args = slice.call( arguments, 1 ); + proxy = Function.prototype.bind.apply( fn, args ); + } else { + // Simulated bind + args = slice.call( arguments, 2 ); + if ( args.length ) { + proxy = function() { + return arguments.length ? + fn.apply( context, args.concat( slice.call( arguments ) ) ) : + fn.apply( context, args ); + }; + } else { + proxy = function() { + return arguments.length ? + fn.apply( context, arguments ) : + fn.call( context ); + }; + } } // Set the guid of unique handler to the same of original handler, so it can be removed - if ( fn ) { - proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; - } + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; - // So proxy can be declared as an argument return proxy; }, diff --git a/src/event.js b/src/event.js index fd470e718b..82e9b52dbb 100644 --- a/src/event.js +++ b/src/event.js @@ -891,6 +891,8 @@ if ( document.addEventListener ) { jQuery.each(["bind", "one"], function( i, name ) { jQuery.fn[ name ] = function( type, data, fn ) { + var handler; + // Handle object literals if ( typeof type === "object" ) { for ( var key in type ) { @@ -904,10 +906,15 @@ jQuery.each(["bind", "one"], function( i, name ) { data = undefined; } - var handler = name === "one" ? jQuery.proxy( fn, function( event ) { - jQuery( this ).unbind( event, handler ); - return fn.apply( this, arguments ); - }) : fn; + if ( name === "one" ) { + handler = function( event ) { + jQuery( this ).unbind( event, handler ); + return fn.apply( this, arguments ); + }; + handler.guid = fn.guid || jQuery.guid++; + } else { + handler = fn; + } if ( type === "unload" && name !== "one" ) { this.one( type, data, fn ); @@ -971,24 +978,27 @@ jQuery.fn.extend({ toggle: function( fn ) { // Save reference to arguments for access in closure var args = arguments, - i = 1; + guid = fn.guid || jQuery.guid++, + i = 0, + toggler = 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 ); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply( this, arguments ) || false; + }; // link all the functions, so any of them can unbind this click handler + toggler.guid = guid; while ( i < args.length ) { - jQuery.proxy( fn, args[ i++ ] ); + args[ i++ ].guid = guid; } - 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 ); - - // Make sure that clicks stop - event.preventDefault(); - - // and execute the function - return args[ lastToggle ].apply( this, arguments ) || false; - })); + return this.click( toggler ); }, hover: function( fnOver, fnOut ) { diff --git a/test/unit/core.js b/test/unit/core.js index 7057783702..9f9078a49e 100644 --- a/test/unit/core.js +++ b/test/unit/core.js @@ -858,7 +858,7 @@ test("jQuery.isEmptyObject", function(){ }); test("jQuery.proxy", function(){ - expect(4); + expect(5); var test = function(){ equals( this, thisObject, "Make sure that scope is set properly." ); }; var thisObject = { foo: "bar", method: test }; @@ -872,8 +872,13 @@ test("jQuery.proxy", function(){ // 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" )(); + // Partial application + var test2 = function( a ){ equals( a, "pre-applied", "Ensure arguments can be pre-applied." ); }; + jQuery.proxy( test2, null, "pre-applied" )(); + + // Partial application w/ normal arguments + var test3 = function( a, b ){ equals( b, "normal", "Ensure arguments can be pre-applied and passed as usual." ); }; + jQuery.proxy( test3, null, "pre-applied" )( "normal" ); }); test("jQuery.parseJSON", function(){