hooks-manager.js (7466B)
1 ( function( window, undefined ) { 2 'use strict'; 3 4 /** 5 * Handles managing all events for whatever you plug it into. Priorities for hooks are based on lowest to highest in 6 * that, lowest priority hooks are fired first. 7 */ 8 var EventManager = function() { 9 var slice = Array.prototype.slice; 10 11 /** 12 * Maintain a reference to the object scope so our public methods never get confusing. 13 */ 14 var MethodsAvailable = { 15 removeFilter : removeFilter, 16 applyFilters : applyFilters, 17 addFilter : addFilter, 18 removeAction : removeAction, 19 doAction : doAction, 20 addAction : addAction 21 }; 22 23 /** 24 * Contains the hooks that get registered with this EventManager. The array for storage utilizes a "flat" 25 * object literal such that looking up the hook utilizes the native object literal hash. 26 */ 27 var STORAGE = { 28 actions : {}, 29 filters : {} 30 }; 31 32 /** 33 * Adds an action to the event manager. 34 * 35 * @param action Must contain namespace.identifier 36 * @param callback Must be a valid callback function before this action is added 37 * @param [priority=10] Used to control when the function is executed in relation to other callbacks bound to the same hook 38 * @param [context] Supply a value to be used for this 39 */ 40 function addAction( action, callback, priority, context ) { 41 if( typeof action === 'string' && typeof callback === 'function' ) { 42 priority = parseInt( ( priority || 10 ), 10 ); 43 _addHook( 'actions', action, callback, priority, context ); 44 } 45 46 return MethodsAvailable; 47 } 48 49 /** 50 * Performs an action if it exists. You can pass as many arguments as you want to this function; the only rule is 51 * that the first argument must always be the action. 52 */ 53 function doAction( /* action, arg1, arg2, ... */ ) { 54 var args = slice.call( arguments ); 55 var action = args.shift(); 56 57 if( typeof action === 'string' ) { 58 _runHook( 'actions', action, args ); 59 } 60 61 return MethodsAvailable; 62 } 63 64 /** 65 * Removes the specified action if it contains a namespace.identifier & exists. 66 * 67 * @param action The action to remove 68 * @param [callback] Callback function to remove 69 */ 70 function removeAction( action, callback ) { 71 if( typeof action === 'string' ) { 72 _removeHook( 'actions', action, callback ); 73 } 74 75 return MethodsAvailable; 76 } 77 78 /** 79 * Adds a filter to the event manager. 80 * 81 * @param filter Must contain namespace.identifier 82 * @param callback Must be a valid callback function before this action is added 83 * @param [priority=10] Used to control when the function is executed in relation to other callbacks bound to the same hook 84 * @param [context] Supply a value to be used for this 85 */ 86 function addFilter( filter, callback, priority, context ) { 87 if( typeof filter === 'string' && typeof callback === 'function' ) { 88 priority = parseInt( ( priority || 10 ), 10 ); 89 _addHook( 'filters', filter, callback, priority, context ); 90 } 91 92 return MethodsAvailable; 93 } 94 95 /** 96 * Performs a filter if it exists. You should only ever pass 1 argument to be filtered. The only rule is that 97 * the first argument must always be the filter. 98 */ 99 function applyFilters( /* filter, filtered arg, arg2, ... */ ) { 100 var args = slice.call( arguments ); 101 var filter = args.shift(); 102 103 if( typeof filter === 'string' ) { 104 return _runHook( 'filters', filter, args ); 105 } 106 107 return MethodsAvailable; 108 } 109 110 /** 111 * Removes the specified filter if it contains a namespace.identifier & exists. 112 * 113 * @param filter The action to remove 114 * @param [callback] Callback function to remove 115 */ 116 function removeFilter( filter, callback ) { 117 if( typeof filter === 'string') { 118 _removeHook( 'filters', filter, callback ); 119 } 120 121 return MethodsAvailable; 122 } 123 124 /** 125 * Removes the specified hook by resetting the value of it. 126 * 127 * @param type Type of hook, either 'actions' or 'filters' 128 * @param hook The hook (namespace.identifier) to remove 129 * @private 130 */ 131 function _removeHook( type, hook, callback, context ) { 132 var handlers, handler, i; 133 134 if ( !STORAGE[ type ][ hook ] ) { 135 return; 136 } 137 if ( !callback ) { 138 STORAGE[ type ][ hook ] = []; 139 } else { 140 handlers = STORAGE[ type ][ hook ]; 141 if ( !context ) { 142 for ( i = handlers.length; i--; ) { 143 if ( handlers[i].callback === callback ) { 144 handlers.splice( i, 1 ); 145 } 146 } 147 } 148 else { 149 for ( i = handlers.length; i--; ) { 150 handler = handlers[i]; 151 if ( handler.callback === callback && handler.context === context) { 152 handlers.splice( i, 1 ); 153 } 154 } 155 } 156 } 157 } 158 159 /** 160 * Adds the hook to the appropriate storage container 161 * 162 * @param type 'actions' or 'filters' 163 * @param hook The hook (namespace.identifier) to add to our event manager 164 * @param callback The function that will be called when the hook is executed. 165 * @param priority The priority of this hook. Must be an integer. 166 * @param [context] A value to be used for this 167 * @private 168 */ 169 function _addHook( type, hook, callback, priority, context ) { 170 var hookObject = { 171 callback : callback, 172 priority : priority, 173 context : context 174 }; 175 176 // Utilize 'prop itself' : http://jsperf.com/hasownproperty-vs-in-vs-undefined/19 177 var hooks = STORAGE[ type ][ hook ]; 178 if( hooks ) { 179 hooks.push( hookObject ); 180 hooks = _hookInsertSort( hooks ); 181 } 182 else { 183 hooks = [ hookObject ]; 184 } 185 186 STORAGE[ type ][ hook ] = hooks; 187 } 188 189 /** 190 * Use an insert sort for keeping our hooks organized based on priority. This function is ridiculously faster 191 * than bubble sort, etc: http://jsperf.com/javascript-sort 192 * 193 * @param hooks The custom array containing all of the appropriate hooks to perform an insert sort on. 194 * @private 195 */ 196 function _hookInsertSort( hooks ) { 197 var tmpHook, j, prevHook; 198 for( var i = 1, len = hooks.length; i < len; i++ ) { 199 tmpHook = hooks[ i ]; 200 j = i; 201 while( ( prevHook = hooks[ j - 1 ] ) && prevHook.priority > tmpHook.priority ) { 202 hooks[ j ] = hooks[ j - 1 ]; 203 --j; 204 } 205 hooks[ j ] = tmpHook; 206 } 207 208 return hooks; 209 } 210 211 /** 212 * Runs the specified hook. If it is an action, the value is not modified but if it is a filter, it is. 213 * 214 * @param type 'actions' or 'filters' 215 * @param hook The hook ( namespace.identifier ) to be ran. 216 * @param args Arguments to pass to the action/filter. If it's a filter, args is actually a single parameter. 217 * @private 218 */ 219 function _runHook( type, hook, args ) { 220 var handlers = STORAGE[ type ][ hook ], i, len; 221 222 if ( !handlers ) { 223 return (type === 'filters') ? args[0] : false; 224 } 225 226 len = handlers.length; 227 if ( type === 'filters' ) { 228 for ( i = 0; i < len; i++ ) { 229 args[ 0 ] = handlers[ i ].callback.apply( handlers[ i ].context, args ); 230 } 231 } else { 232 for ( i = 0; i < len; i++ ) { 233 handlers[ i ].callback.apply( handlers[ i ].context, args ); 234 } 235 } 236 237 return ( type === 'filters' ) ? args[ 0 ] : true; 238 } 239 240 // return all of the publicly available methods 241 return MethodsAvailable; 242 243 }; 244 245 246 window.hooksManager = window.hooksManager || new EventManager(); 247 248 } )( window );