events_filter.js

import assign from '../data/object-assign';

/**
 * @description Holds the list of filters currently subscribed to events. Added to global scope so that it is available when accessed from outside of this module. i.e. gform.utils.addFilter().
 * @type {Array} Associative array of current filters
 */
window.gform = window.gform || {};
window.gform.instances = window.gform.instances || {};
window.gform.instances.filters = window.gform.instances.filters || [];
const filters = window.gform.instances.filters;

/**
 * @module filter
 * @description Triggers a filter event, allowing event subscribers to modify the specified data object. Each subscribed callback will be executed in order in a blocking/synchronous way.
 *
 * @since 3.0.1
 *
 * @param {object} opts       The options object.
 * @param {object} opts.data  Data object sent to callbacks to be filtered.
 * @param {string} opts.event The event name.
 *
 * @requires assign
 *
 * @return {object} Returns the filtered data object.
 *
 * @example
 * import { filter } from "@gravityforms/utils";
 *
 * async function Example() {
 *     let filteredData = 'hello';
 *
 *     filteredData = await filter( { event: 'gform/example/event_name', data: filteredData } );
 * };
 *
 * // elsewhere in the codebase
 *
 * addFilter( 'gform/example/event_name', ( data ) => {
 * 		data = 'hello again!';
 * 		return data;
 * 	}, 11 );
 *
 */
const filter = async function( opts = {} ) {
	const options = assign(
		{
			data: {},
			event: '',
		}, opts );

	if ( filters[ options.event ] !== undefined ) {
		const callbacks = filters[ options.event ];

		//sort by priority.
		callbacks.sort( ( a, b ) => a.priority - b.priority );

		// execute callbacks synchronously and in order.
		for ( let i = 0; i < callbacks.length; i++ ) {
			const callback = callbacks[ i ];
			if ( callback.isAsync ) {
				options.data = await callback.callable( options.data );
			} else {
				options.data = callback.callable( options.data );
			}
		}
	}

	return options.data;
};

/**
 * @function addAsyncFilter
 * @description Adds an async callback function to a filter event.
 *
 * @since 3.0.1
 *
 * @param {string}   event    The event name.
 * @param {Function} callable The callback function to be executed. The callback function takes a 'data' parameter as argument that can be of any type (depends on the event being fired). Defaults to 10.
 * @param {int}      priority The filter priority. This determines the execution order. Lower priority filters execute first.
 *
 * @return {void}
 *
 * @example
 * import { addAsyncFilter } from "@gravityforms/utils";
 *
 * function Example() {
 *
 *      addAsyncFilter( 'gform/example/event_name', async function ( data ) {
 * 		    data = 'filtered data';
 * 		    return data;
 * 	    }, 10 );
 *
 * };
 *
 * // elsewhere in the codebase
 * let filteredData = 'hello';
 * filteredData = await filter( { event: 'gform/example/event_name', data: filteredData } );*
 *
 */
const addAsyncFilter = function( event, callable, priority = 10 ) {
	addFilter( event, callable, priority, true );
};

/**
 * @function addFilter
 * @description Adds a callback function to a filter event.
 *
 * @since 3.0.1
 *
 * @param {string}   event    The event name.
 * @param {Function} callable The callback function to be executed. The callback function takes a 'data' parameter as argument that can be of any type (depends on the event being fired). Defaults to 10.
 * @param {int}      priority The filter priority. This determines the execution order. Lower priority filters execute first.
 * @param {boolean}  isAsync  Wether or not the callable parameter is an async function. Defaults to false.
 *
 * @return {void}
 *
 * @example
 * import { addFilter } from "@gravityforms/utils";
 *
 * function Example() {
 *
 *      addFilter( 'gform/example/event_name', function ( data ) {
 * 		    data = 'filtered data';
 * 		    return data;
 * 	    } );
 *
 * };
 *
 * // elsewhere in the codebase
 * let filteredData = 'hello';
 * filteredData = await filter( { event: 'gform/example/event_name', data: filteredData } );*
 *
 */
const addFilter = function( event, callable, priority = 10, isAsync = false ) {
	if ( filters[ event ] === undefined ) {
		filters[ event ] = [];
	}
	const tag = event + '_' + filters[ event ].length;

	filters[ event ].push( { tag, callable, priority, isAsync } );
};

/**
 * @function removeFilter
 * @description Removes a callback function from the list of filters.
 *
 * @since 3.0.1
 *
 * @param {string} event    The event name.
 * @param {int}    priority The filter priority. Removes filters of this priority. If ommitted, filters of any priority will be removed.
 * @param {string} tag      Removes functions of this tag. If ommitted, functions of any tag will be removed. Tag is formatted as 'event-name_function-index'.
 *
 * @return {void}
 *
 * @example
 * import { removeFilter } from "@gravityforms/utils";
 *
 * function Example() {
 *
 *   // removes all filters that are subscribed to 'gform/example/event_name' and that have priority 10.
 *   removeFilter( 'gform/example/event_name', 10);
 * };
 *
 */
const removeFilter = function( event, priority = null, tag = null ) {
	if ( filters[ event ] === undefined ) {
		return;
	}

	const callbacks = filters[ event ];
	for ( let i = callbacks.length - 1; i >= 0; i-- ) {
		if ( ( tag === null || tag === callbacks[ i ].tag ) && ( priority === null || parseInt( callbacks[ i ].priority ) === parseInt( priority ) ) ) {
			callbacks.splice( i, 1 );
		}
	}
};

export { filter, addAsyncFilter, addFilter, removeFilter };