Applied to jQuery 1.7 dev. This was just an idea i made long ago, dumping for keepsake.
Created
February 28, 2015 23:46
-
-
Save Centril/e468dd8633c18856a279 to your computer and use it in GitHub Desktop.
Old jQuery hack
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Dear jQuery Core team. | |
After reading http://benalman.com/news/2010/03/jquery-special-events/#event-delegation | |
I decided to try this feature of jQuery - but to my dissapointment there was no way to chain setup/teardown/add/remove/_default/trigger/handle handlers. | |
Chaining is required if you want to provide a plugin that adds to an event that already has special event handlers defined. | |
For example, I wanted to provide a plugin that adds to the default functionality of focus/focusin. | |
You may reach my by email: [email protected] | |
This enhancement provides 2 methods: addHandler & removeHandler to jQuery.event.special: | |
They can be called as: | |
$.event.special.addHandler('focus.focusFrom focusin.focusFrom', 'setup', function() {}); | |
$.event.special.addHandler('focus.focusFrom focusin.focusFrom', 'setup', [function() {}, ...]); | |
$.event.special.addHandler('focus.focusFrom focusin.focusFrom', 'setup', { | |
requireNamespace : true, | |
handlers: [function() {}, ...] | |
}); | |
$.event.special.addHandler( | |
'focus.focusFrom', | |
{ // You could have provided a function that returns an object, which then would get executed in addHandler. | |
requireNamespace: true, | |
setup: function() {} // or [function(){}, ...] | |
teardown: function {}, | |
//... | |
}); | |
$.event.special.addHandler({ // You could have provided a function that returns an object, which then would get executed in addHandler. | |
'focus' : { | |
add : [...] | |
}, | |
'click' : { | |
setup : [...], | |
_default : [...] | |
} | |
}); | |
$.event.special.removeHandler('.focusFrom'); | |
$.event.special.removeHandler('focus click', 'setup add teardown'); | |
$.event.special.removeHandler('focus click', 'setup add teardown', function() {}); | |
// like addHandler... but requireNamespace has no meaning here. | |
$.event.special.removeHandler('focus click', { | |
// ... | |
}); | |
$.event.special.removeHandler({ | |
// ... | |
}); | |
*/ | |
/* | |
* After the hoverHack function ending on Line 2884, | |
* add the following: | |
* ------------------------------------------------- | |
*/ | |
eventSpecialHandle = function( callbacks, context, params ) { | |
/* | |
* Calls a chain of special events handlers. | |
* Passes an additional param to handler with the result of previous handler in chain. | |
*/ | |
var namespace, first = params[0], | |
c, current, result = false; | |
if( !handlers.length ) { | |
return; | |
} | |
if ( first instanceof jQuery.Event ) { | |
namespace = first.handleObj.namespace; // handle, trigger, _default | |
} else if ( jQuery.isPlainObject( first ) ) { | |
namespace = first.namespace; // add/remove. | |
} else { | |
namespace = (params[1] || first || []).join( "." ); // setup/teardown. | |
} | |
handlers = putInArray( handlers ); | |
for ( c in handlers ) { | |
current = handlers[c]; | |
// If not function or if the required namespace-match didn't occur. | |
if ( !jQuery.isFunction( current ) || current.requireNamespace && | |
!(namespace.length && (new RegExp( "(^|\\.)" + current.namespace.split( "." ).join( "\\.(?:.*\\.)?" ) + "(\\.|$)" )).test( namespace )) ) { | |
continue; | |
} | |
result = current.apply( context, $.merge( params, [result] ) ); | |
// If [-1, X] is returned, break the chain - returning X. | |
if ( jQuery.isArray( result ) && result[0] === -1 ) { | |
return result[1]; | |
} | |
} | |
return result; | |
}, | |
stringToPropKey = function( key, val ) { | |
// If key is an object- return key, else a single-property object: { key : val }. | |
// eval("{'" + key + "':" + val + "}") is evil & unefficient. | |
if ( jQuery.isPlainObject( key ) ) { | |
return key; | |
} | |
var obj = {}; | |
obj[key] = val; | |
return obj; | |
}, | |
splitFilterEmpty = function( str ) { | |
// Split string by space and trim, & return non-empty elements. | |
return jQuery.grep( jQuery.map( str.toString().split( " " ), jQuery.trim ), function(val) { return val.length } ); | |
}, | |
putInArray = function( val ) { | |
return val === undefined ? [] : (jQuery.isArray( val ) ? val : [val]); | |
} | |
/* ------------------------------------------------- | |
* Replace the Line: 2976 ("if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {") with: | |
* "if ( !special.setup || eventSpecialHandle( special.setup, elem, [data, namespaces, eventHandle] ) === false ) {" | |
* | |
* Replace the Line: 2988 ("special.add.call( elem, handleObj );") with: | |
* "eventSpecialHandle( special.add, elem, [handleObj] );" | |
* | |
* Replace the Line: 3059 ("special.remove.call( elem, handleObj );") with: | |
* "eventSpecialHandle( special.remove, elem, [handleObj] );" | |
* | |
* Replace the Line: 3073 ("if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {") with: | |
* "if ( !special.teardown || eventSpecialHandle( special.teardown, elem, [handleObj.namespace.split( "." )] ) === false ) {" | |
* | |
* Replace the Line: 3177 ("if ( special.trigger && special.trigger.apply( elem, data ) === false ) {") with: | |
* "if ( special.trigger && eventSpecialHandle( special.trigger, elem, data ) === false ) {" | |
* | |
* Replace the Line: 3223 ("if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&") with: | |
* "if ( (!special._default || eventSpecialHandle( special._default, elem.ownerDocument, data ) === false) &&" | |
* | |
* Replace the Line: 3319 ("ret = ( specialHandle || handleObj.handler ).apply( matched.elem, args );") with: | |
* "ret = eventSpecialHandle(specialHandle || handleObj.handler, matched.elem, args);" | |
* ------------------------------------------------- | |
*/ | |
/* | |
* Add the following after Line 3422 | |
* (where jQuery.event.special is defined): | |
* ------------------------------------------------- | |
*/ | |
validStates: ["setup", "teardown", "add", "remove", "_default", "trigger", "handle"], | |
addHandler: function( types, states, handler) { | |
var t, type, tns, namespace, requireNamespace = false, | |
event, stateName, state, | |
h, handlers, handler; | |
if( jQuery.isFunction( types ) ) { | |
types = types(); | |
} | |
// If types is a plain object - add it recursively. | |
if ( jQuery.isPlainObject( types ) ) { | |
for ( type in types ) { | |
this.addHandler( type, types[type] ); | |
} | |
} else if ( states && jQuery.type( types ) === "string" && (types = splitFilterEmpty( hoverHack( types ) )).length ) { | |
if ( handler && jQuery.isPlainObject( handler ) ) { | |
if ( handler.requireNamespace ) { | |
requireNamespace = handler.requireNamespace === true; | |
} | |
if ( !handler.handlers ) { | |
return; | |
} | |
handler = handler.handlers; | |
} | |
/* | |
* If we've got a function - execute it and wish for an object in return. | |
* If we've got one state to set - make it into a 1 prop object. | |
*/ | |
states = jQuery.isFunction( states ) ? states() : stringToPropKey( states, handler ); | |
// Make sure we've got a set of states. | |
if ( !jQuery.isPlainObject( states ) || jQuery.isEmptyObject( states ) ) { | |
return; | |
} | |
for ( t = 0; t < types.length; t++ ) { | |
// Split namespace & type. | |
tns = rtypenamespace.exec( types[t] ) || []; | |
type = tns[1]; | |
namespace = ( tns[2] || "" ).split( "." ).sort().join( "." ); | |
// If first time this event get a state-handler added, init. | |
event = this[type] || (this[type] = {}); | |
for ( stateName in states ) { | |
// If first time - validate the state to add. | |
handlers = states[stateName]; | |
if ( !handlers.validated ) { | |
if ( stateName === "requireNamespace" ) { | |
requireNamespace = handlers === true; | |
handlers = []; | |
} else { | |
handlers = states[stateName] = jQuery.inArray( stateName, this.validStates ) !== -1 | |
// Remove any handler that ain't a function, if handler-list is not an array - then wrap an array around it. | |
? jQuery.grep( putInArray( handlers ), jQuery.isFunction ) | |
// State is invalid. | |
: []; | |
} | |
// Delete state if empty. | |
if ( !handlers.length ) { | |
delete states[stateName], handlers; | |
// Quit if this was the last state to be removed. | |
if ( jQuery.isEmptyObject( states ) ) { | |
return; | |
} | |
continue; | |
} | |
handlers.validated = true; | |
} | |
if ( event[stateName] === undefined ) { | |
// First time, init list. | |
event[stateName] = []; | |
} else if( !jQuery.isArray( event[stateName] ) ) { | |
// Backwards compability. | |
event[stateName] = [event[stateName]]; | |
} | |
state = event[stateName]; | |
// Add all handlers to list. | |
for ( h = 0; h < handlers.length; h++) { | |
handler = handlers[h]; | |
if ( namespace.length ) { | |
handler.namespace = namespace; | |
if ( requireNamespace ) { | |
handler.requireNamespace = true; | |
} | |
} | |
state.push( handler ); | |
} | |
} | |
} | |
} | |
}, | |
removeHandler: function( types, states, handler ) { | |
var t, type, tns, namespace, | |
event, stateName, | |
handlers, deleter; | |
// Removes an entire state. | |
function deleteStates( namespace, i, stateName ) { | |
var state = this[stateName]; | |
if ( state ) { | |
namespace.length ? deleteHandlers.call( this, namespace, stateName, state ) : delete this[stateName]; | |
} | |
} | |
// Removes specific callbacks. | |
function deleteHandlers( namespace, stateName, handlers ) { | |
var h, handler, state = this[stateName]; | |
if ( state ) { | |
handlers = putInArray( handlers ); | |
if ( jQuery.isArray( state ) ) { | |
// Go through all handlers passed & remove 'em if they match & have the correct namespace (or none). | |
for ( h = 0; h < handlers.length; h++ ) { | |
handler = handlers[h]; | |
if ( !namespace.length || (handler.namespace && handler.namespace === namespace) ) { | |
state.splice( state.indexOf( handler ), 1 ); | |
} | |
} | |
} else { | |
// Backwards compability with older plugins... | |
for ( h = 0; h < handlers.length; h++ ) { | |
if ( state === handlers[h] ) { | |
state = []; | |
} | |
} | |
} | |
} | |
} | |
if( jQuery.isFunction( types ) ) { | |
types = types(); | |
} | |
// If types is a plain object - remove it recursively. | |
if ( jQuery.isPlainObject( types ) ) { | |
for ( type in types ) { | |
this.removeHandler( type, types[type] ); | |
} | |
} else if ( jQuery.type( types ) === "string" && (types = splitFilterEmpty( hoverHack( types ) )).length ) { | |
if ( jQuery.isFunction( states ) ) { | |
states = states(); | |
} | |
if ( states === undefined ) { | |
// Remove all handlers for all states for this event. | |
states = this.validStates; | |
} else if ( jQuery.type( states ) === "string" && handlers === undefined ) { | |
// Remove all handlers for some states. | |
if ( !(states = splitFilterEmpty( states )).length ) { | |
return; | |
} | |
} else if ( !jQuery.isPlainObject( states = stringToPropKey( states, handlers ) ) || jQuery.isEmptyObject( states ) ) { | |
// Remove specific handlers. | |
return; | |
} | |
deleter = jQuery.isArray( states ) ? deleteStates : deleteHandlers; | |
for ( t = 0; t < types.length; t++ ) { | |
// Split namespace & type. | |
tns = rtypenamespace.exec( types[t] ) || []; | |
type = tns[1]; | |
namespace = ( tns[2] || "" ).split( "." ).sort().join( "." ); | |
if ( !type ) { | |
for ( type in this ) { | |
if ( jQuery.isPlainObject( event = this[type] ) ) { | |
jQuery.each( states, jQuery.proxy( deleter, event, namespace ) ); | |
} | |
} | |
} else if ( (event = this[type]) ) { | |
jQuery.each( states, jQuery.proxy( deleter, event, namespace ) ); | |
} | |
} | |
} | |
}, | |
/* | |
* ------------------------------------------------- | |
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function(jQuery) { | |
var original, special, store, | |
eventSpecialHandle = function( handlers, context, params ) { | |
/* | |
* Calls a chain of special events handlers. | |
* Passes an additional param to handler with the result of previous handler in chain. | |
*/ | |
var namespace, first = params[0], | |
c, current, result = false; | |
handlers = putInArray( handlers ); | |
if( !handlers.length ) { | |
return false; | |
} | |
if ( first instanceof jQuery.Event ) { | |
namespace = first.handleObj.namespace; // handle, trigger, _default | |
} else if ( jQuery.isPlainObject( first ) ) { | |
namespace = first.namespace; // add/remove. | |
} else { | |
namespace = (params[1] || []).join( "." ); // setup. can't be impl for teardown. | |
} | |
for ( c in handlers ) { | |
current = handlers[c]; | |
// If not function or if the required namespace-match didn't occur. | |
if ( !jQuery.isFunction( current ) || current.requireNamespace && | |
!(namespace.length && (new RegExp( "(^|\\.)" + current.namespace.split( "." ).join( "\\.(?:.*\\.)?" ) + "(\\.|$)" )).test( namespace )) ) { | |
continue; | |
} | |
result = current.apply( context, $.merge( params, [result] ) ); | |
// If [-1, X] is returned, break the chain - returning X. | |
if ( jQuery.isArray( result ) && result[0] === -1 ) { | |
return result[1]; | |
} | |
} | |
return result; | |
}, | |
stringToPropKey = function( key, val ) { | |
// If key is an object- return key, else a single-property object: { key : val }. | |
// eval("{'" + key + "':" + val + "}") is evil & unefficient. | |
if ( jQuery.isPlainObject( key ) ) { | |
return key; | |
} | |
var obj = {}; | |
obj[key] = val; | |
return obj; | |
}, | |
rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/, | |
rhoverHack = /\bhover(\.\S+)?/, | |
hoverHack = function( events ) { | |
return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" ); | |
}, | |
splitFilterEmpty = function( str ) { | |
// Split string by space and trim, & return non-empty elements. | |
return jQuery.grep( jQuery.map( str.toString().split( " " ), jQuery.trim ), function(val) { return val.length } ); | |
}, | |
putInArray = function( val ) { | |
return val === undefined ? [] : (jQuery.isArray( val ) ? val : [val]); | |
}; | |
// Save original. | |
original = jQuery.event.special; | |
store = jQuery.event.specialHandlers = {}; | |
// Replace original. | |
special = jQuery.event.special = { | |
validStates: ["setup", "teardown", "add", "remove", "_default", "trigger", "handle"], | |
addHandler: function( types, states, handler) { | |
var t, type, tns, namespace, requireNamespace = false, | |
event, eventStore, stateName, state, | |
h, handlers, handler; | |
if( jQuery.isFunction( types ) ) { | |
types = types(); | |
} | |
// If types is a plain object - add it recursively. | |
if ( jQuery.isPlainObject( types ) ) { | |
for ( type in types ) { | |
this.addHandler( type, types[type] ); | |
} | |
} else if ( states && jQuery.type( types ) === "string" && (types = splitFilterEmpty( hoverHack( types ) )).length ) { | |
// Allow for specifying requireNamespace in handler argument. | |
if ( handler && jQuery.isPlainObject( handler ) ) { | |
if ( handler.requireNamespace ) { | |
requireNamespace = handler.requireNamespace === true; | |
} | |
if ( !handler.handlers ) { | |
return; | |
} | |
handler = handler.handlers; | |
} | |
// If we've got a function - execute it and wish for an object in return. | |
// If we've got one state to set - make it into a 1 prop object. | |
states = jQuery.isFunction( states ) ? states() : stringToPropKey( states, handler ); | |
// Make sure we've got a set of states. | |
if ( !jQuery.isPlainObject( states ) || jQuery.isEmptyObject( states ) ) { | |
return; | |
} | |
for ( t = 0; t < types.length; t++ ) { | |
// Split namespace & type. | |
tns = rtypenamespace.exec( types[t] ) || []; | |
type = tns[1]; | |
namespace = ( tns[2] || "" ).split( "." ).sort().join( "." ); | |
// If first time this event get a state-handler added, init. | |
event = this[type] || (this[type] = {}); | |
eventStore = store[type] || (store[type] = {}); | |
for ( stateName in states ) { | |
// If first time - validate the state to add. | |
handlers = states[stateName]; | |
if ( !handlers.validated ) { | |
if ( stateName === "requireNamespace" ) { | |
requireNamespace = handlers === true; | |
handlers = []; | |
} else { | |
handlers = states[stateName] = jQuery.inArray( stateName, this.validStates ) !== -1 | |
// Remove any handler that ain't a function, if handler-list is not an array - then wrap an array around it. | |
? jQuery.grep( putInArray( handlers ), jQuery.isFunction ) | |
// State is invalid. | |
: []; | |
} | |
// Delete state if empty. | |
if ( !handlers.length ) { | |
delete states[stateName]; | |
// Quit if this was the last state to be removed. | |
if ( jQuery.isEmptyObject( states ) ) { | |
return; | |
} | |
continue; | |
} | |
handlers.validated = true; | |
} | |
state = eventStore[stateName] || (eventStore[stateName] = []); | |
if ( !state.length ) { | |
// First time, set router. | |
event[stateName] = function() { | |
return eventSpecialHandle( state, this, arguments ); | |
}; | |
} | |
// Add all handlers to list. | |
for ( h = 0; h < handlers.length; h++) { | |
handler = handlers[h]; | |
if ( namespace.length ) { | |
handler.namespace = namespace; | |
if ( requireNamespace ) { | |
handler.requireNamespace = true; | |
} | |
} | |
state.push( handler ); | |
} | |
} | |
} | |
} | |
}, | |
removeHandler: function( types, states, handler ) { | |
var t, type, tns, namespace, | |
eventStore, stateName, | |
handlers, deleter; | |
// Removes an entire state. | |
function deleteStates( eventStore, namespace, i, stateName ) { | |
var state = eventStore[stateName]; | |
if ( state ) { | |
namespace.length ? deleteHandlers.call( this, eventStore, namespace, stateName, state ) : delete eventStore[stateName], this[stateName]; | |
} | |
} | |
// Removes specific callbacks. | |
function deleteHandlers( eventStore, namespace, stateName, handlers ) { | |
var h, handler, state = eventStore[stateName]; | |
if ( state ) { | |
handlers = putInArray( handlers ); | |
// Go through all handlers passed & remove 'em if they match & have the correct namespace (or none). | |
for ( h = 0; h < handlers.length; h++ ) { | |
handler = handlers[h]; | |
if ( !namespace.length || (handler.namespace && handler.namespace === namespace) ) { | |
state.splice( state.indexOf( handler ), 1 ); | |
if ( !state.length ) { | |
// All handlers were removed, so remove state. | |
delete eventStore[stateName], this[stateName]; | |
} | |
} | |
} | |
} | |
} | |
if( jQuery.isFunction( types ) ) { | |
types = types(); | |
} | |
// If types is a plain object - remove it recursively. | |
if ( jQuery.isPlainObject( types ) ) { | |
for ( type in types ) { | |
this.removeHandler( type, types[type] ); | |
} | |
} else if ( jQuery.type( types ) === "string" && (types = splitFilterEmpty( hoverHack( types ) )).length ) { | |
if ( jQuery.isFunction( states ) ) { | |
states = states(); | |
} | |
if ( states === undefined ) { | |
// Remove all handlers for all states for this event. | |
states = this.validStates; | |
} else if ( jQuery.type( states ) === "string" && handlers === undefined ) { | |
// Remove all handlers for some states. | |
if ( !(states = splitFilterEmpty( states )).length ) { | |
return; | |
} | |
} else if ( !jQuery.isPlainObject( states = stringToPropKey( states, handlers ) ) || jQuery.isEmptyObject( states ) ) { | |
// Remove specific handlers. | |
return; | |
} | |
deleter = jQuery.isArray( states ) ? deleteStates : deleteHandlers; | |
for ( t = 0; t < types.length; t++ ) { | |
// Split namespace & type. | |
tns = rtypenamespace.exec( types[t] ) || []; | |
type = tns[1]; | |
namespace = ( tns[2] || "" ).split( "." ).sort().join( "." ); | |
if ( !type ) { | |
for ( type in store ) { | |
if ( jQuery.isPlainObject( eventStore = store[type] ) ) { | |
jQuery.each( states, jQuery.proxy( deleter, this[type], eventStore, namespace ) ); | |
} | |
} | |
} else if ( jQuery.isPlainObject( eventStore = store[type] ) ) { | |
jQuery.each( states, jQuery.proxy( deleter, this[type], eventStore, namespace ) ); | |
} | |
} | |
} | |
} | |
}; | |
// Apply for all event-states attached by jQuery | |
for ( e in original ) { | |
states = original[e]; | |
special[e] = special[e] || {}; | |
for ( s in states ) { | |
if ( jQuery.inArray( s, special.validStates ) === -1 ) { | |
special[e][s] = states[s]; | |
} else { | |
special.addHandler( e, s, states[s] ); | |
} | |
} | |
} | |
})(jQuery); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment