Skip to content

Commit

Permalink
Merge pull request #13 from adamsilverstein/add-wp-hooks
Browse files Browse the repository at this point in the history
Add the WordPress hook library from 21170-core in preparation for beta npm release
  • Loading branch information
Adam Silverstein authored Sep 22, 2017
2 parents d7c1b5e + 7465667 commit 18fed13
Show file tree
Hide file tree
Showing 15 changed files with 1,061 additions and 1 deletion.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"jest": "^20.0.4",
"lerna": "^2.0.0",
"mkdirp": "^0.5.1",
"rimraf": "^2.6.1"
"rimraf": "^2.6.1",
"babel-loader": "^7.1.1"
},
"jest": {
"collectCoverageFrom": [
Expand Down
34 changes: 34 additions & 0 deletions packages/hooks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# WP-JS-Hooks

A lightweight & efficient EventManager for JavaScript in WordPress.


### API Usage
API functions can be called via the global `wp.hooks` like this `wp.hooks.addAction()`, etc.

* `addAction( 'hook', 'vendor/plugin/function', callback, priority )`
* `addFilter( 'hook', 'vendor/plugin/function', callback, priority )`
* `removeAction( 'hook', 'vendor/plugin/function' )`
* `removeFilter( 'hook', 'vendor/plugin/function' )`
* `removeAllActions( 'hook' )`
* `removeAllFilters( 'hook' )`
* `doAction( 'hook', arg1, arg2, moreArgs, finalArg )`
* `applyFilters( 'hook', content, arg1, arg2, moreArgs, finalArg )`
* `doingAction( 'hook' )`
* `doingFilter( 'hook' )`
* `didAction( 'hook' )`
* `didFilter( 'hook' )`
* `hasAction( 'hook' )`
* `hasFilter( 'hook' )`


### Background
See ticket [#21170](http://core.trac.wordpress.org/ticket/21170) for more information.


### Features

* Fast and lightweight.
* Priorities system ensures hooks with lower integer priority are fired first.
* Uses native object hash lookup for finding hook callbacks.
* Utilizes insertion sort for keeping priorities correct. Best Case: O(n), worst case: O(n^2)
14 changes: 14 additions & 0 deletions packages/hooks/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "@wordpress/hooks",
"version": "0.1.0",
"repository": {
"type": "git",
"url": "https://github.com/WordPress/packages.git"
},
"description": "WordPress Hooks library",
"main": "build/index.js",
"module": "build-module/index.js",
"browser": "build-browser/index.js",
"author": "WordPress",
"license": "GPL-2.0+"
}
74 changes: 74 additions & 0 deletions packages/hooks/src/createAddHook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import validateNamespace from './validateNamespace.js';
import validateHookName from './validateHookName.js';

/**
* Returns a function which, when invoked, will add a hook.
*
* @param {Object} hooks Stored hooks, keyed by hook name.
*
* @return {Function} Function that adds a new hook.
*/
function createAddHook( hooks ) {
/**
* Adds the hook to the appropriate hooks container.
*
* @param {string} hookName Name of hook to add
* @param {string} namespace The unique namespace identifying the callback in the form `vendor/plugin/function`.
* @param {Function} callback Function to call when the hook is run
* @param {?number} priority Priority of this hook (default=10)
*/
return function addHook( hookName, namespace, callback, priority = 10 ) {

if ( ! validateHookName( hookName ) ) {
return;
}

if ( ! validateNamespace( namespace ) ) {
return;
}

if ( 'function' !== typeof callback ) {
console.error( 'The hook callback must be a function.' );
return;
}

// Validate numeric priority
if ( 'number' !== typeof priority ) {
console.error( 'If specified, the hook priority must be a number.' );
return;
}

const handler = { callback, priority, namespace };

if ( hooks.hasOwnProperty( hookName ) ) {
// Find the correct insert index of the new hook.
const handlers = hooks[ hookName ].handlers;
let i = 0;
while ( i < handlers.length ) {
if ( handlers[ i ].priority > priority ) {
break;
}
i++;
}
// Insert (or append) the new hook.
handlers.splice( i, 0, handler );
// We may also be currently executing this hook. If the callback
// we're adding would come after the current callback, there's no
// problem; otherwise we need to increase the execution index of
// any other runs by 1 to account for the added element.
( hooks.__current || [] ).forEach( hookInfo => {
if ( hookInfo.name === hookName && hookInfo.currentIndex >= i ) {
hookInfo.currentIndex++;
}
} );
} else {
// This is the first hook of its type.
hooks[ hookName ] = {
handlers: [ handler ],
runs: 0,
};
}
};
}

export default createAddHook;
27 changes: 27 additions & 0 deletions packages/hooks/src/createCurrentHook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Returns a function which, when invoked, will return the name of the
* currently running hook, or `null` if no hook of the given type is currently
* running.
*
* @param {Object} hooks Stored hooks, keyed by hook name.
*
* @return {Function} Function that returns the current hook.
*/
function createCurrentHook( hooks, returnFirstArg ) {
/**
* Returns the name of the currently running hook, or `null` if no hook of
* the given type is currently running.
*
* @return {?string} The name of the currently running hook, or
* `null` if no hook is currently running.
*/
return function currentHook() {
if ( ! hooks.__current || ! hooks.__current.length ) {
return null;
}

return hooks.__current[ hooks.__current.length - 1 ].name;
};
}

export default createCurrentHook;
31 changes: 31 additions & 0 deletions packages/hooks/src/createDidHook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import validateHookName from './validateHookName.js';

/**
* Returns a function which, when invoked, will return the number of times a
* hook has been called.
*
* @param {Object} hooks Stored hooks, keyed by hook name.
*
* @return {Function} Function that returns a hook's call count.
*/
function createDidHook( hooks ) {
/**
* Returns the number of times an action has been fired.
*
* @param {string} hookName The hook name to check.
*
* @return {number} The number of times the hook has run.
*/
return function didHook( hookName ) {

if ( ! validateHookName( hookName ) ) {
return;
}

return hooks.hasOwnProperty( hookName ) && hooks[ hookName ].runs
? hooks[ hookName ].runs
: 0;
};
}

export default createDidHook;
32 changes: 32 additions & 0 deletions packages/hooks/src/createDoingHook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Returns a function which, when invoked, will return whether a hook is
* currently being executed.
*
* @param {Object} hooks Stored hooks, keyed by hook name.
*
* @return {Function} Function that returns whether a hook is currently
* being executed.
*/
function createDoingHook( hooks ) {
/**
* Returns whether a hook is currently being executed.
*
* @param {?string} hookName The name of the hook to check for. If
* omitted, will check for any hook being executed.
*
* @return {bool} Whether the hook is being executed.
*/
return function doingHook( hookName ) {
// If the hookName was not passed, check for any current hook.
if ( 'undefined' === typeof hookName ) {
return 'undefined' !== typeof hooks.__current[0];
}

// Return the __current hook.
return hooks.__current[0]
? hookName === hooks.__current[0].name
: false;
};
}

export default createDoingHook;
26 changes: 26 additions & 0 deletions packages/hooks/src/createHasHook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Returns a function which, when invoked, will return whether any handlers are
* attached to a particular hook.
*
* @param {Object} hooks Stored hooks, keyed by hook name.
*
* @return {Function} Function that returns whether any handlers are
* attached to a particular hook.
*/
function createHasHook( hooks ) {
/**
* Returns how many handlers are attached for the given hook.
*
* @param {string} hookName The name of the hook to check for.
*
* @return {number} The number of handlers that are attached to
* the given hook.
*/
return function hasHook( hookName ) {
return hooks.hasOwnProperty( hookName )
? hooks[ hookName ].handlers.length
: 0;
};
}

export default createHasHook;
73 changes: 73 additions & 0 deletions packages/hooks/src/createRemoveHook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import validateNamespace from './validateNamespace.js';
import validateHookName from './validateHookName.js';

/**
* Returns a function which, when invoked, will remove a specified hook or all
* hooks by the given name.
*
* @param {Object} hooks Stored hooks, keyed by hook name.
* @param {bool} removeAll Whether to remove all callbacks for a hookName, without regard to namespace. Used to create `removeAll*` functions.
*
* @return {Function} Function that removes hooks.
*/
function createRemoveHook( hooks, removeAll ) {
/**
* Removes the specified callback (or all callbacks) from the hook with a
* given hookName and namespace.
*
* @param {string} hookName The name of the hook to modify.
* @param {string} namespace The unique namespace identifying the callback in the form `vendor/plugin/function`.
*
* @return {number} The number of callbacks removed.
*/
return function removeHook( hookName, namespace ) {

if ( ! validateHookName( hookName ) ) {
return;
}

if ( ! removeAll && ! validateNamespace( namespace ) ) {
return;
}

// Bail if no hooks exist by this name
if ( ! hooks.hasOwnProperty( hookName ) ) {
return 0;
}

let handlersRemoved = 0;

if ( removeAll ) {
handlersRemoved = hooks[ hookName ].handlers.length;
hooks[ hookName ] = {
runs: hooks[ hookName ].runs,
handlers: [],
};
} else {
// Try to find the specified callback to remove.
const handlers = hooks[ hookName ].handlers;
for ( let i = handlers.length - 1; i >= 0; i-- ) {
if (
handlers[ i ].namespace === namespace
) {
handlers.splice( i, 1 );
handlersRemoved++;
// This callback may also be part of a hook that is
// currently executing. If the callback we're removing
// comes after the current callback, there's no problem;
// otherwise we need to decrease the execution index of any
// other runs by 1 to account for the removed element.
( hooks.__current || [] ).forEach( hookInfo => {
if ( hookInfo.name === hookName && hookInfo.currentIndex >= i ) {
hookInfo.currentIndex--;
}
} );
}
}
}

return handlersRemoved;
};
}

export default createRemoveHook;
Loading

0 comments on commit 18fed13

Please sign in to comment.