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