Skip to content

Instantly share code, notes, and snippets.

@Centril
Created February 28, 2015 23:46
Show Gist options
  • Save Centril/e468dd8633c18856a279 to your computer and use it in GitHub Desktop.
Save Centril/e468dd8633c18856a279 to your computer and use it in GitHub Desktop.
Old jQuery hack

Applied to jQuery 1.7 dev. This was just an idea i made long ago, dumping for keepsake.

/*
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 ) );
}
}
}
},
/*
* -------------------------------------------------
*/
(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