forked from angular-ui/ui-router
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhookRegistry.ts
152 lines (133 loc) · 5.86 KB
/
hookRegistry.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/** @module transition */ /** for typedoc */
import {extend, removeFrom, allTrueR, tail} from "../common/common";
import {isString, isFunction} from "../common/predicates";
import {PathNode} from "../path/node";
import {TransitionStateHookFn, TransitionHookFn} from "./interface"; // has or is using
import {
HookRegOptions, HookMatchCriteria, IEventHook, IHookRegistry, IHookRegistration, TreeChanges,
HookMatchCriterion, IMatchingNodes, HookFn
} from "./interface";
import {Glob} from "../common/glob";
import {State} from "../state/stateObject";
/**
* Determines if the given state matches the matchCriteria
*
* @hidden
*
* @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: 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);
}
/** @hidden */
export class EventHook implements IEventHook {
callback: HookFn;
matchCriteria: HookMatchCriteria;
priority: number;
bind: any;
_deregistered: boolean;
constructor(matchCriteria: HookMatchCriteria, callback: HookFn, 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;
this._deregistered = false;
}
private static _matchingNodes(nodes: PathNode[], criterion: HookMatchCriterion): PathNode[] {
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 [[PathNode]]s for each [[HookMatchCriterion]] (to, from, exiting, retained, entering)
*/
matches(treeChanges: TreeChanges): IMatchingNodes {
let mc = this.matchCriteria, _matchingNodes = EventHook._matchingNodes;
let matches: IMatchingNodes = {
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;
}
}
/** @hidden */
interface ITransitionEvents { [key: string]: IEventHook[]; }
/** @hidden 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() {
eventHook._deregistered = true;
removeFrom(hooks[name])(eventHook);
};
};
}
/**
* Mixin class acts as a Transition Hook registry.
*
* Holds the registered [[HookFn]] objects.
* Exposes functions to register new hooks.
*
* This is a Mixin class which can be applied to other objects.
*
* The hook registration functions are [[onBefore]], [[onStart]], [[onEnter]], [[onRetain]], [[onExit]], [[onFinish]], [[onSuccess]], [[onError]].
*
* This class is mixed into both the [[TransitionService]] and every [[Transition]] object.
* Global hooks are added to the [[TransitionService]].
* Since each [[Transition]] is itself a `HookRegistry`, hooks can also be added to individual Transitions
* (note: the hook criteria still must match the Transition).
*/
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];
/** @inheritdoc */
onBefore = makeHookRegistrationFn(this._transitionEvents, "onBefore");
/** @inheritdoc */
onStart = makeHookRegistrationFn(this._transitionEvents, "onStart");
/** @inheritdoc */
onEnter = makeHookRegistrationFn(this._transitionEvents, "onEnter");
/** @inheritdoc */
onRetain = makeHookRegistrationFn(this._transitionEvents, "onRetain");
/** @inheritdoc */
onExit = makeHookRegistrationFn(this._transitionEvents, "onExit");
/** @inheritdoc */
onFinish = makeHookRegistrationFn(this._transitionEvents, "onFinish");
/** @inheritdoc */
onSuccess = makeHookRegistrationFn(this._transitionEvents, "onSuccess");
/** @inheritdoc */
onError = makeHookRegistrationFn(this._transitionEvents, "onError");
}