| 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 | |
| 16 | /** |
| 17 | * Contains the hooks that get registered with this EventManager. The array for storage utilizes a "flat" |
| 18 | * object literal such that looking up the hook utilizes the native object literal hash. |
| 19 | */ |
| 20 | var STORAGE = { |
| 21 | actions : {}, |
| 22 | filters : {} |
| 23 | }; |
| 24 | |
| 25 | /** |
| 26 | * Adds an action to the event manager. |
| 27 | * |
| 28 | * @param {string} action The action to perform. |
| 29 | * @param {Function} callback Must be a valid callback function before this action is added |
| 30 | * @param {number} [priority=10] Controls when the function is executed in relation to other callbacks bound |
| 31 | * to the same hook. Optional, defaults to 10. |
| 32 | * @param {Object} [context=this] The context to bind when executing the callback.Optionsl, defaults to `this`. |
| 33 | */ |
| 34 | function addAction( action, callback, priority, context ) { |
| 35 | if ( typeof action === 'string' && typeof callback === 'function' ) { |
| 36 | priority = parseInt( ( priority || 10 ), 10 ); |
| 37 | _addHook( 'actions', action, callback, priority, context || this ); |
| 38 | } |
| 39 | |
| 40 | return MethodsAvailable; |
| 41 | } |
| 42 | |
| 43 | /** |
| 44 | * Performs an action if it exists. You can pass as many arguments as you want to this function. |
| 45 | * The only rule is that the first argument must always be the action. |
| 46 | * |
| 47 | * @param {string} action The action to perform. |
| 48 | * @param {...*} args Optional args to pass to the action. |
| 49 | */ |
| 50 | function doAction( /* action, arg1, arg2, ... */ ) { |
| 51 | var args = slice.call( arguments ); |
| 52 | var action = args.shift(); |
| 53 | |
| 54 | if ( typeof action === 'string' ) { |
| 55 | _runHook( 'actions', action, args ); |
| 56 | } |
| 57 | |
| 58 | return MethodsAvailable; |
| 59 | } |
| 60 | |
| 61 | /** |
| 62 | * Removes the specified action if it exists. |
| 63 | * |
| 64 | * @param {string} action The action to remove. |
| 65 | * @param {Function} [callback] Callback function to remove. Optional. |
| 66 | */ |
| 67 | function removeAction( action, callback ) { |
| 68 | if ( typeof action === 'string' ) { |
| 69 | _removeHook( 'actions', action, callback ); |
| 70 | } |
| 71 | |
| 72 | return MethodsAvailable; |
| 73 | } |
| 74 | |
| 75 | /** |
| 76 | * Adds a filter to the event manager. |
| 77 | * |
| 78 | * @param {string} filter The filter to add. |
| 79 | * @param {Function} callback The function to call with this filter. |
| 80 | * @param [priority=10] Used to control when the function is executed in relation to other callbacks bound to the same hook |
| 81 | * @param [context] Supply a value to be used for this |
| 82 | */ |
| 83 | function addFilter( filter, callback, priority, context ) { |
| 84 | if ( typeof filter === 'string' && typeof callback === 'function' ) { |
| 85 | priority = parseInt( ( priority || 10 ), 10 ); |
| 86 | _addHook( 'filters', filter, callback, priority, context ); |
| 87 | } |
| 88 | |
| 89 | return MethodsAvailable; |
| 90 | } |
| 91 | |
| 92 | /** |
| 93 | * Performs a filter if it exists. You should only ever pass 1 argument to be filtered. |
| 94 | * The only rule is that the first argument must always be the filter. |
| 95 | * |
| 96 | * @param {string} action The action to perform. |
| 97 | * @param {...*} args Optional args to pass to the action. |
| 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 {string} filter The action to remove. |
| 114 | * @param [callback] Callback function to remove. Optional. |
| 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 {string} type Type of hook, either 'actions' or 'filters'. |
| 128 | * @param {string} hook The hook (namespace.identifier) to remove. |
| 129 | * @private |
| 130 | */ |
| 131 | function _removeHook( type, hook, callback ) { |
| 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 ) { |
| 152 | handlers.splice( i, 1 ); |
| 153 | } |
| 154 | } |
| 155 | } |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | /** |
| 160 | * Adds the hook to the appropriate storage container |
| 161 | * |
| 162 | * @param {string} type The hook type: 'actions' or 'filters' |
| 163 | * @param {string} hook The hook (namespace.identifier) to add to our event manager |
| 164 | * @param {function} callback The function that will be called when the hook is executed. |
| 165 | * @param {number} priority The priority of this hook. Must be an integer. |
| 166 | * @param {mixed} [context] A value to be used for `this`. Optional. |
| 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. |
| 191 | * This function is ridiculously faster than bubble sort, etc: http://jsperf.com/javascript-sort |
| 192 | * |
| 193 | * @param {array} 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 {string} type 'actions' or 'filters' |
| 215 | * @param {string} hook The hook to run. |
| 216 | * @param {...*} args Arguments to pass to the action/filter. |
| 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 | window.wp = window.wp || {}; |
| 246 | window.wp.hooks = new EventManager(); |
| 247 | |
| 248 | MethodsAvailable = { |
| 249 | removeFilter : removeFilter, |
| 250 | applyFilters : applyFilters, |
| 251 | addFilter : addFilter, |
| 252 | removeAction : removeAction, |
| 253 | doAction : doAction, |
| 254 | addAction : addAction |
| 255 | }; |
| 256 | |
| 257 | } )( window ); |