ui-router
Version:
State-based routing for Javascript
218 lines (191 loc) • 9.53 kB
text/typescript
/** @module transition */ /** for typedoc */
import {IInjectable, extend, removeFrom, anyTrueR, allTrueR, tail} from "../common/common";
import {isString, isFunction} from "../common/predicates";
import {val} from "../common/hof";
import {Node} from "../path/node";
import {HookRegOptions, HookMatchCriteria, IStateMatch, IEventHook, IHookRegistry, IHookRegistration, TreeChanges, HookMatchCriterion, IMatchingNodes} from "./interface";
import {Glob} from "../common/glob";
import {State} from "../state/stateObject";
/**
* Determines if the given state matches the matchCriteria
* @param state a State Object to test against
* @param criterion
* - If a string, matchState uses the string as a glob-matcher against the state name
* - If an array (of strings), matchState uses each string in the array as a glob-matchers against the state name
* and returns a positive match if any of the globs match.
* - If a function, matchState calls the function with the state and returns true if the function's result is truthy.
* @returns {boolean}
*/
export function matchState(state: State, criterion: HookMatchCriterion) {
let toMatch = isString(criterion) ? [criterion] : criterion;
function matchGlobs(_state) {
let globStrings = <string[]> toMatch;
for (let i = 0; i < globStrings.length; i++) {
let glob = Glob.fromString(globStrings[i]);
if ((glob && glob.matches(_state.name)) || (!glob && globStrings[i] === _state.name)) {
return true;
}
}
return false;
}
let matchFn = <any> (isFunction(toMatch) ? toMatch : matchGlobs);
return !!matchFn(state);
}
export class EventHook implements IEventHook {
callback: IInjectable;
matchCriteria: HookMatchCriteria;
priority: number;
bind: any;
constructor(matchCriteria: HookMatchCriteria, callback: IInjectable, options: HookRegOptions = <any>{}) {
this.callback = callback;
this.matchCriteria = extend({ to: true, from: true, exiting: true, retained: true, entering: true }, matchCriteria);
this.priority = options.priority || 0;
this.bind = options.bind || null;
}
private static _matchingNodes(nodes: Node[], criterion: HookMatchCriterion): Node[] {
if (criterion === true) return nodes;
let matching = nodes.filter(node => matchState(node.state, criterion));
return matching.length ? matching : null;
}
/**
* Determines if this hook's [[matchCriteria]] match the given [[TreeChanges]]
*
* @returns an IMatchingNodes object, or null. If an IMatchingNodes object is returned, its values
* are the matching [[Node]]s for each [[HookMatchCriterion]] (to, from, exiting, retained, entering)
*/
matches(treeChanges: TreeChanges): IMatchingNodes {
let mc = this.matchCriteria, _matchingNodes = EventHook._matchingNodes;
let matches = {
to: _matchingNodes([tail(treeChanges.to)], mc.to),
from: _matchingNodes([tail(treeChanges.from)], mc.from),
exiting: _matchingNodes(treeChanges.exiting, mc.exiting),
retained: _matchingNodes(treeChanges.retained, mc.retained),
entering: _matchingNodes(treeChanges.entering, mc.entering),
};
// Check if all the criteria matched the TreeChanges object
let allMatched: boolean = ["to", "from", "exiting", "retained", "entering"]
.map(prop => matches[prop])
.reduce(allTrueR, true);
return allMatched ? matches : null;
}
}
interface ITransitionEvents { [key: string]: IEventHook[]; }
// Return a registration function of the requested type.
function makeHookRegistrationFn(hooks: ITransitionEvents, name: string): IHookRegistration {
return function (matchObject, callback, options = {}) {
let eventHook = new EventHook(matchObject, callback, options);
hooks[name].push(eventHook);
return function deregisterEventHook() {
removeFrom(hooks[name])(eventHook);
};
};
}
export class HookRegistry implements IHookRegistry {
static mixin(source: HookRegistry, target: IHookRegistry) {
Object.keys(source._transitionEvents).concat(["getHooks"]).forEach(key => target[key] = source[key]);
}
private _transitionEvents: ITransitionEvents = {
onBefore: [], onStart: [], onEnter: [], onRetain: [], onExit: [], onFinish: [], onSuccess: [], onError: []
};
getHooks = (name: string) => this._transitionEvents[name];
onBefore = makeHookRegistrationFn(this._transitionEvents, "onBefore");
onStart = makeHookRegistrationFn(this._transitionEvents, "onStart");
/**
* @ngdoc function
* @name ui.router.state.$transitionsProvider#onEnter
* @methodOf ui.router.state.$transitionsProvider
*
* @description
* Registers a function to be injected and invoked during a transition between the matched 'to' and 'from' states,
* when the matched 'to' state is being entered. This function is injected with the entering state's resolves.
*
* This function can be injected with two additional special value:
* - **`$transition$`**: The current transition
* - **`$state$`**: The state being entered
*
* @param {object} matchObject See transitionCriteria in {@link ui.router.state.$transitionsProvider#on $transitionsProvider.on}.
* @param {function} callback See callback in {@link ui.router.state.$transitionsProvider#on $transitionsProvider.on}.
*/
onEnter = makeHookRegistrationFn(this._transitionEvents, "onEnter");
/**
* @ngdoc function
* @name ui.router.state.$transitionsProvider#onRetain
* @methodOf ui.router.state.$transitionsProvider
*
* @description
* Registers a function to be injected and invoked during a transition between the matched 'to' and 'from states,
* when the matched 'from' state is already active and is not being exited nor entered.
*
* This function can be injected with two additional special value:
* - **`$transition$`**: The current transition
* - **`$state$`**: The state that is retained
*
* @param {object} matchObject See transitionCriteria in {@link ui.router.state.$transitionsProvider#on $transitionsProvider.on}.
* @param {function} callback See callback in {@link ui.router.state.$transitionsProvider#on $transitionsProvider.on}.
*/
onRetain = makeHookRegistrationFn(this._transitionEvents, "onRetain");
/**
* @ngdoc function
* @name ui.router.state.$transitionsProvider#onExit
* @methodOf ui.router.state.$transitionsProvider
*
* @description
* Registers a function to be injected and invoked during a transition between the matched 'to' and 'from states,
* when the matched 'from' state is being exited. This function is in injected with the exiting state's resolves.
*
* This function can be injected with two additional special value:
* - **`$transition$`**: The current transition
* - **`$state$`**: The state being entered
*
* @param {object} matchObject See transitionCriteria in {@link ui.router.state.$transitionsProvider#on $transitionsProvider.on}.
* @param {function} callback See callback in {@link ui.router.state.$transitionsProvider#on $transitionsProvider.on}.
*/
onExit = makeHookRegistrationFn(this._transitionEvents, "onExit");
/**
* @ngdoc function
* @name ui.router.state.$transitionsProvider#onFinish
* @methodOf ui.router.state.$transitionsProvider
*
* @description
* Registers a function to be injected and invoked when a transition is finished entering/exiting all states.
*
* This function can be injected with:
* - **`$transition$`**: The current transition
*
* @param {object} matchObject See transitionCriteria in {@link ui.router.state.$transitionsProvider#on $transitionsProvider.on}.
* @param {function} callback See callback in {@link ui.router.state.$transitionsProvider#on $transitionsProvider.on}.
*/
onFinish = makeHookRegistrationFn(this._transitionEvents, "onFinish");
/**
* @ngdoc function
* @name ui.router.state.$transitionsProvider#onSuccess
* @methodOf ui.router.state.$transitionsProvider
*
* @description
* Registers a function to be injected and invoked when a transition has successfully completed between the matched
* 'to' and 'from' state is being exited.
* This function is in injected with the 'to' state's resolves (note: `JIT` resolves are not injected).
*
* This function can be injected with two additional special value:
* - **`$transition$`**: The current transition
*
* @param {object} matchObject See transitionCriteria in {@link ui.router.state.$transitionsProvider#on $transitionsProvider.on}.
* @param {function} callback The function which will be injected and invoked, when a matching transition is started.
* The function's return value is ignored.
*/
onSuccess = makeHookRegistrationFn(this._transitionEvents, "onSuccess");
/**
* @ngdoc function
* @name ui.router.state.$transitionsProvider#onError
* @methodOf ui.router.state.$transitionsProvider
*
* @description
* Registers a function to be injected and invoked when a transition has failed for any reason between the matched
* 'to' and 'from' state. The transition rejection reason is injected as `$error$`.
*
* @param {object} matchObject See transitionCriteria in {@link ui.router.state.$transitionsProvider#on $transitionsProvider.on}.
* @param {function} callback The function which will be injected and invoked, when a matching transition is started.
* The function's return value is ignored.
*/
onError = makeHookRegistrationFn(this._transitionEvents, "onError");
}