| | 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 | window.wp = window.wp || {}; |
| | 246 | window.wp.hooks = new EventManager(); |
| | 247 | |
| | 248 | } )( window ); |