WordPress.org

Make WordPress Core

Ticket #21170: 21170.4.diff

File 21170.4.diff, 15.6 KB (added by adamsilverstein, 3 years ago)
  • src/wp-includes/js/wp-hooks.js

     
     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                 * @param {object} [context] Only hooks matching this context will be remved. Optional.
     130                 * @private
     131                 */
     132                function _removeHook( type, hook, callback, context ) {
     133                        var handlers, handler, i;
     134
     135                        if ( !STORAGE[ type ][ hook ] ) {
     136                                return;
     137                        }
     138                        if ( !callback ) {
     139                                STORAGE[ type ][ hook ] = [];
     140                        } else {
     141                                handlers = STORAGE[ type ][ hook ];
     142                                if ( !context ) {
     143                                        for ( i = handlers.length; i--; ) {
     144                                                if ( handlers[i].callback === callback ) {
     145                                                        handlers.splice( i, 1 );
     146                                                }
     147                                        }
     148                                }
     149                                else {
     150                                        for ( i = handlers.length; i--; ) {
     151                                                handler = handlers[i];
     152                                                if ( handler.callback === callback && handler.context === context) {
     153                                                        handlers.splice( i, 1 );
     154                                                }
     155                                        }
     156                                }
     157                        }
     158                }
     159
     160
     161                /**
     162                 * Adds the hook to the appropriate storage container
     163                 *
     164                 * @param {string}   type      The hook type: 'actions' or 'filters'
     165                 * @param {string}   hook      The hook (namespace.identifier) to add to our event manager
     166                 * @param {function} callback  The function that will be called when the hook is executed.
     167                 * @param {number}   priority  The priority of this hook. Must be an integer.
     168                 * @param {mixed}    [context] A value to be used for `this`. Optional.
     169                 * @private
     170                 */
     171                function _addHook( type, hook, callback, priority, context ) {
     172                        var hookObject = {
     173                                callback : callback,
     174                                priority : priority,
     175                                context : context
     176                        };
     177
     178                        // Utilize 'prop itself' : http://jsperf.com/hasownproperty-vs-in-vs-undefined/19
     179                        var hooks = STORAGE[ type ][ hook ];
     180                        if ( hooks ) {
     181                                hooks.push( hookObject );
     182                                hooks = _hookInsertSort( hooks );
     183                        }
     184                        else {
     185                                hooks = [ hookObject ];
     186                        }
     187
     188                        STORAGE[ type ][ hook ] = hooks;
     189                }
     190
     191                /**
     192                 * Use an insert sort for keeping our hooks organized based on priority.
     193                 * This function is ridiculously faster than bubble sort, etc: http://jsperf.com/javascript-sort
     194                 *
     195                 * @param {array} hooks The custom array containing all of the appropriate hooks to perform an insert sort on.
     196                 * @private
     197                 */
     198                function _hookInsertSort( hooks ) {
     199                        var tmpHook, j, prevHook;
     200                        for( var i = 1, len = hooks.length; i < len; i++ ) {
     201                                tmpHook = hooks[ i ];
     202                                j = i;
     203                                while( ( prevHook = hooks[ j - 1 ] ) &&  prevHook.priority > tmpHook.priority ) {
     204                                        hooks[ j ] = hooks[ j - 1 ];
     205                                        --j;
     206                                }
     207                                hooks[ j ] = tmpHook;
     208                        }
     209
     210                        return hooks;
     211                }
     212
     213                /**
     214                 * Runs the specified hook. If it is an action, the value is not modified but if it is a filter, it is.
     215                 *
     216                 * @param {string} type 'actions' or 'filters'
     217                 * @param {string} hook The hook to run.
     218                 * @param {...*}   args Arguments to pass to the action/filter.
     219                 * @private
     220                 */
     221                function _runHook( type, hook, args ) {
     222                        var handlers = STORAGE[ type ][ hook ], i, len;
     223
     224                        if ( ! handlers ) {
     225                                return ( type === 'filters' ) ? args[0] : false;
     226                        }
     227
     228                        len = handlers.length;
     229                        if ( type === 'filters' ) {
     230                                for ( i = 0; i < len; i++ ) {
     231                                        args[ 0 ] = handlers[ i ].callback.apply( handlers[ i ].context, args );
     232                                }
     233                        } else {
     234                                for ( i = 0; i < len; i++ ) {
     235                                        handlers[ i ].callback.apply( handlers[ i ].context, args );
     236                                }
     237                        }
     238
     239                        return ( type === 'filters' ) ? args[ 0 ] : true;
     240                }
     241
     242                MethodsAvailable = {
     243                        removeFilter : removeFilter,
     244                        applyFilters : applyFilters,
     245                        addFilter : addFilter,
     246                        removeAction : removeAction,
     247                        doAction : doAction,
     248                        addAction : addAction
     249                };
     250
     251                // return all of the publicly available methods
     252                return MethodsAvailable;
     253
     254        };
     255
     256        window.wp = window.wp || {};
     257        window.wp.hooks = new EventManager();
     258
     259
     260} )( window );
  • src/wp-includes/script-loader.php

     
    8585
    8686        $scripts->add( 'wp-a11y', "/wp-includes/js/wp-a11y$suffix.js", array( 'jquery' ), false, 1 );
    8787
     88        $scripts->add( 'wp-hooks', "/wp-includes/js/wp-hooks$suffix.js", array(), false, 1 );
     89
    8890        $scripts->add( 'sack', "/wp-includes/js/tw-sack$suffix.js", array(), '1.6.1', 1 );
    8991
    9092        $scripts->add( 'quicktags', "/wp-includes/js/quicktags$suffix.js", array(), false, 1 );
  • tests/qunit/index.html

     
    4242                <script src="../../src/wp-includes/js/customize-base.js"></script>
    4343                <script src="../../src/wp-includes/js/customize-models.js"></script>
    4444                <script src="../../src/wp-includes/js/shortcode.js"></script>
     45                <script src="../../src/wp-includes/js/wp-hooks.js"></script>
    4546                <script src="../../src/wp-admin/js/customize-controls.js"></script>
    4647
    4748                <script type='text/javascript' src='../../src/wp-includes/js/jquery/ui/core.js'></script>
     
    6162                <script src="wp-admin/js/customize-base.js"></script>
    6263                <script src="wp-admin/js/customize-header.js"></script>
    6364                <script src="wp-includes/js/shortcode.js"></script>
     65                <script src="wp-includes/js/wp-hooks.js"></script>
    6466                <script src="wp-admin/js/customize-controls.js"></script>
    6567                <script src="wp-admin/js/customize-controls-utils.js"></script>
    6668                <script src="wp-admin/js/customize-nav-menus.js"></script>
  • tests/qunit/wp-includes/js/wp-hooks.js

     
     1/* global wp */
     2( function( QUnit ) {
     3
     4        QUnit.module( 'wp-hooks' );
     5
     6        function filter_a( str ) {
     7                return str + 'a';
     8        }
     9        function filter_b( str ) {
     10                return str + 'b';
     11        }
     12        function filter_c( str ) {
     13                return str + 'c';
     14        }
     15        function action_a( value ) {
     16                window.actionValue += 'a';
     17        }
     18        function action_b( value ) {
     19                window.actionValue += 'b';
     20        }
     21        function action_c( value ) {
     22                window.actionValue += 'c';
     23        }
     24        window.actionValue = '';
     25
     26        QUnit.test( 'add and remove a filter', function() {
     27                expect(1);
     28                wp.hooks.addFilter( 'test.filter', filter_a );
     29                wp.hooks.removeFilter( 'test.filter' );
     30                equal( wp.hooks.applyFilters( 'test.filter', 'test' ), 'test' );
     31        } );
     32
     33        QUnit.test( 'add a filter and run it', function() {
     34                expect(1);
     35                wp.hooks.addFilter( 'test.filter', filter_a );
     36                equal( wp.hooks.applyFilters( 'test.filter', 'test' ), 'testa' );
     37                wp.hooks.removeFilter( 'test.filter' );
     38        } );
     39
     40        QUnit.test( 'add 2 filters in a row and run them', function() {
     41                expect(1);
     42                wp.hooks.addFilter( 'test.filter', filter_a );
     43                wp.hooks.addFilter( 'test.filter', filter_b );
     44                equal( wp.hooks.applyFilters( 'test.filter', 'test' ), 'testab' );
     45                wp.hooks.removeFilter( 'test.filter' );
     46        } );
     47
     48        QUnit.test( 'add 3 filters with different priorities and run them', function() {
     49                expect(1);
     50                wp.hooks.addFilter( 'test.filter', filter_a );
     51                wp.hooks.addFilter( 'test.filter', filter_b, 2 );
     52                wp.hooks.addFilter( 'test.filter', filter_c, 8 );
     53                equal( wp.hooks.applyFilters( 'test.filter', 'test' ), 'testbca' );
     54                wp.hooks.removeFilter( 'test.filter' );
     55        } );
     56
     57        QUnit.test( 'chain 3 filters with different priorities and then run them', function() {
     58                expect(1);
     59                wp.hooks
     60                        .addFilter( 'test.filter', filter_a )
     61                        .addFilter( 'test.filter', filter_b, 2 )
     62                        .addFilter( 'test.filter', filter_c, 8 );
     63                equal( wp.hooks.applyFilters( 'test.filter', 'test' ), 'testbca' );
     64                wp.hooks.removeFilter( 'test.filter' );
     65        } );
     66
     67        QUnit.test( 'add and remove an action', function() {
     68                expect(1);
     69                window.actionValue = '';
     70                wp.hooks.addAction( 'test.action', action_a );
     71                wp.hooks.removeAction( 'test.action' );
     72                wp.hooks.doAction( 'test.action' );
     73                equal( window.actionValue, '' );
     74        } );
     75
     76        QUnit.test( 'add an action and run it', function() {
     77                expect(1);
     78                window.actionValue = '';
     79                wp.hooks.addAction( 'test.action', action_a );
     80                wp.hooks.doAction( 'test.action' );
     81                equal( window.actionValue, 'a' );
     82                wp.hooks.removeAction( 'test.action' );
     83        } );
     84
     85        QUnit.test( 'add 2 actions in a row and then run them', function() {
     86                expect(1);
     87                window.actionValue = '';
     88                wp.hooks.addAction( 'test.action', action_a );
     89                wp.hooks.addAction( 'test.action', action_b );
     90                wp.hooks.doAction( 'test.action' );
     91                equal( window.actionValue, 'ab' );
     92                wp.hooks.removeAction( 'test.action' );
     93        } );
     94
     95        QUnit.test( 'add 3 actions with different priorities and run them', function() {
     96                expect(1);
     97                window.actionValue = '';
     98                wp.hooks.addAction( 'test.action', action_a );
     99                wp.hooks.addAction( 'test.action', action_b, 2 );
     100                wp.hooks.addAction( 'test.action', action_c, 8 );
     101                wp.hooks.doAction( 'test.action' );
     102                equal( window.actionValue, 'bca' );
     103                wp.hooks.removeAction( 'test.action' );
     104        } );
     105
     106        QUnit.test( 'chain 3 actions with different priorities and run them', function() {
     107                expect(1);
     108                window.actionValue = '';
     109                wp.hooks
     110                        .addAction( 'test.action', action_a )
     111                        .addAction( 'test.action', action_b, 2 )
     112                        .addAction( 'test.action', action_c, 8 )
     113                        .doAction( 'test.action' );
     114                equal( window.actionValue, 'bca' );
     115                wp.hooks.removeAction( 'test.action' );
     116        } );
     117
     118        QUnit.test( 'pass in two arguments to an action', function() {
     119                var arg1 = 10,
     120                        arg2 = 20;
     121
     122                expect(4);
     123
     124                wp.hooks.addAction( 'test.action', function( a, b ) {
     125                        equal( arg1, a );
     126                        equal( arg2, b );
     127                } );
     128                wp.hooks.doAction( 'test.action', arg1, arg2 );
     129                wp.hooks.removeAction( 'test.action' );
     130
     131                equal( arg1, 10 );
     132                equal( arg2, 20 );
     133        } );
     134
     135
     136        QUnit.test( 'call action with no hooks', function() {
     137                expect(1);
     138
     139                ok( wp.hooks.doAction( 'test.noHooks' ) );
     140        } );
     141
     142        QUnit.test( 'fire action multiple times', function() {
     143                var func;
     144                expect(2);
     145
     146                func = function() {
     147                        ok( true );
     148                };
     149
     150                wp.hooks.addAction( 'test.action', func );
     151                wp.hooks.doAction( 'test.action' );
     152                wp.hooks.doAction( 'test.action' );
     153                wp.hooks.removeAction( 'test.action' );
     154        } );
     155
     156        QUnit.test( 'fire action using method with context', function() {
     157                var obj;
     158                expect(1);
     159
     160                obj = {
     161                        foo: 10,
     162                        method: function() {
     163                                equal( this.foo, 10 );
     164                        }
     165                };
     166
     167                wp.hooks.addAction( 'test.action', obj.method, 10, obj );
     168                wp.hooks.doAction( 'test.action' );
     169                wp.hooks.removeAction( 'test.action' );
     170
     171        } );
     172
     173        QUnit.test( 'remove specific action callback', function() {
     174                expect(1);
     175                window.actionValue = '';
     176                wp.hooks
     177                        .addAction( 'test.action', action_a )
     178                        .addAction( 'test.action', action_b, 2 )
     179                        .addAction( 'test.action', action_c, 8 );
     180
     181                wp.hooks.removeAction( 'test.action', action_b );
     182                wp.hooks.doAction( 'test.action' );
     183                equal( window.actionValue, 'ca' );
     184                wp.hooks.removeAction( 'test.action' );
     185
     186        } );
     187
     188        QUnit.test( 'remove specific filter callback', function() {
     189                expect(1);
     190                wp.hooks.addFilter( 'test.filter', filter_a )
     191                        .addFilter( 'test.filter', filter_b, 2 )
     192                        .addFilter( 'test.filter', filter_c, 8 );
     193
     194                wp.hooks.removeFilter( 'test.filter', filter_b );
     195                equal( wp.hooks.applyFilters( 'test.filter', 'test' ), 'testca' );
     196                wp.hooks.removeFilter( 'test.filter' );
     197
     198        } );
     199
     200} )( window.QUnit );