3
3
import { IInjectable , extend , tail , assertPredicate , unnestR , flatten , identity } from "../common/common" ;
4
4
import { isArray } from "../common/predicates" ;
5
5
6
- import { TransitionOptions , TransitionHookOptions , IHookRegistry , TreeChanges , IEventHook , ITransitionService } from "./interface" ;
6
+ import { TransitionOptions , TransitionHookOptions , IHookRegistry , TreeChanges , IEventHook , ITransitionService , IMatchingNodes } from "./interface" ;
7
7
8
8
import { Transition , TransitionHook } from "./module" ;
9
9
import { State } from "../state/module" ;
10
10
import { Node } from "../path/module" ;
11
11
12
- interface IToFrom {
13
- to : State ;
14
- from : State ;
15
- }
16
-
17
12
/**
18
13
* This class returns applicable TransitionHooks for a specific Transition instance.
19
14
*
@@ -36,7 +31,6 @@ export class HookBuilder {
36
31
toState : State ;
37
32
fromState : State ;
38
33
39
-
40
34
constructor ( private $transitions : ITransitionService , private transition : Transition , private baseHookOptions : TransitionHookOptions ) {
41
35
this . treeChanges = transition . treeChanges ( ) ;
42
36
this . toState = tail ( this . treeChanges . to ) . state ;
@@ -48,15 +42,14 @@ export class HookBuilder {
48
42
// onBefore/onStart/onFinish/onSuccess/onError returns an array of hooks
49
43
// onExit/onRetain/onEnter returns an array of arrays of hooks
50
44
51
- getOnBeforeHooks = ( ) => this . _buildTransitionHooks ( "onBefore" , { } , { async : false } ) ;
52
- getOnStartHooks = ( ) => this . _buildTransitionHooks ( "onStart" ) ;
53
- getOnExitHooks = ( ) => this . _buildNodeHooks ( "onExit" , this . treeChanges . exiting . reverse ( ) , ( node ) => this . _toFrom ( { from : node . state } ) ) ;
54
- getOnRetainHooks = ( ) => this . _buildNodeHooks ( "onRetain" , this . treeChanges . retained , ( node ) => this . _toFrom ( ) ) ;
55
- getOnEnterHooks = ( ) => this . _buildNodeHooks ( "onEnter" , this . treeChanges . entering , ( node ) => this . _toFrom ( { to : node . state } ) ) ;
56
- getOnFinishHooks = ( ) => this . _buildTransitionHooks ( "onFinish" , { $treeChanges$ : this . treeChanges } ) ;
57
- getOnSuccessHooks = ( ) => this . _buildTransitionHooks ( "onSuccess" , { } , { async : false , rejectIfSuperseded : false } ) ;
58
- getOnErrorHooks = ( ) => this . _buildTransitionHooks ( "onError" , { } , { async : false , rejectIfSuperseded : false } ) ;
59
-
45
+ getOnBeforeHooks = ( ) => this . _buildNodeHooks ( "onBefore" , "to" , tupleSort ( ) , undefined , { async : false } ) ;
46
+ getOnStartHooks = ( ) => this . _buildNodeHooks ( "onStart" , "to" , tupleSort ( ) ) ;
47
+ getOnExitHooks = ( ) => this . _buildNodeHooks ( "onExit" , "exiting" , tupleSort ( true ) , ( node ) => ( { $state$ : node . state } ) ) ;
48
+ getOnRetainHooks = ( ) => this . _buildNodeHooks ( "onRetain" , "retained" , tupleSort ( ) , ( node ) => ( { $state$ : node . state } ) ) ;
49
+ getOnEnterHooks = ( ) => this . _buildNodeHooks ( "onEnter" , "entering" , tupleSort ( ) , ( node ) => ( { $state$ : node . state } ) ) ;
50
+ getOnFinishHooks = ( ) => this . _buildNodeHooks ( "onFinish" , "to" , tupleSort ( ) , ( node ) => ( { $treeChanges$ : this . treeChanges } ) ) ;
51
+ getOnSuccessHooks = ( ) => this . _buildNodeHooks ( "onSuccess" , "to" , tupleSort ( ) , undefined , { async : false , rejectIfSuperseded : false } ) ;
52
+ getOnErrorHooks = ( ) => this . _buildNodeHooks ( "onError" , "to" , tupleSort ( ) , undefined , { async : false , rejectIfSuperseded : false } ) ;
60
53
61
54
asyncHooks ( ) {
62
55
let onStartHooks = this . getOnStartHooks ( ) ;
@@ -65,75 +58,88 @@ export class HookBuilder {
65
58
let onEnterHooks = this . getOnEnterHooks ( ) ;
66
59
let onFinishHooks = this . getOnFinishHooks ( ) ;
67
60
68
- return flatten ( [ onStartHooks , onExitHooks , onRetainHooks , onEnterHooks , onFinishHooks ] ) . filter ( identity ) ;
69
- }
70
-
71
- private _toFrom ( toFromOverride ?) : IToFrom {
72
- return extend ( { to : this . toState , from : this . fromState } , toFromOverride ) ;
61
+ let asyncHooks = [ onStartHooks , onExitHooks , onRetainHooks , onEnterHooks , onFinishHooks ] ;
62
+ return asyncHooks . reduce ( unnestR , [ ] ) . filter ( identity ) ;
73
63
}
74
64
75
65
/**
76
66
* Returns an array of newly built TransitionHook objects.
77
67
*
78
- * Builds a TransitionHook which cares about the entire Transition, for instance, onActivate
79
- * Finds all registered IEventHooks which matched the hookType and toFrom criteria.
80
- * A TransitionHook is then built from each IEventHook with the context, locals, and options provided.
81
- */
82
- private _buildTransitionHooks ( hookType : string , locals = { } , options : TransitionHookOptions = { } ) {
83
- let context = this . treeChanges . to , node = tail ( context ) ;
84
- options . traceData = { hookType, context } ;
85
-
86
- const transitionHook = eventHook => this . buildHook ( node , eventHook . callback , locals , options ) ;
87
- return this . _matchingHooks ( hookType , this . _toFrom ( ) ) . map ( transitionHook ) ;
88
- }
89
-
90
- /**
91
- * Returns an 2 dimensional array of newly built TransitionHook objects.
92
- * Each inner array contains the hooks for a node in the Path.
68
+ * - Finds all IEventHooks registered for the given `hookType` which matched the transition's [[TreeChanges]].
69
+ * - Finds [[Node]] (or `Node[]`) to use as the TransitionHook context(s)
70
+ * - For each of the [[Node]]s, creates a TransitionHook
93
71
*
94
- * For each Node in the Path:
95
- * Builds the toFrom criteria
96
- * Finds all registered IEventHooks which matched the hookType and toFrom criteria.
97
- * A TransitionHook is then built from each IEventHook with the context, locals, and options provided.
72
+ * @param hookType the name of the hook registration function, e.g., 'onEnter', 'onFinish'.
73
+ * @param matchingNodesProp selects which [[Node]]s from the [[IMatchingNodes]] object to create hooks for.
74
+ * @param getLocals a function which accepts a [[Node]] and returns additional locals to provide to the hook as injectables
75
+ * @param sortHooksFn a function which compares two HookTuple and returns <1, 0, or >1
76
+ * @param options any specific Transition Hook Options
98
77
*/
99
- private _buildNodeHooks ( hookType : string , path : Node [ ] , toFromFn : ( node : Node ) => IToFrom , locals : any = { } , options : TransitionHookOptions = { } ) {
100
- const hooksForNode = ( node : Node ) => {
101
- let toFrom = toFromFn ( node ) ;
102
- options . traceData = { hookType, context : node } ;
103
- locals . $state$ = node . state ;
104
-
105
- const transitionHook = eventHook => this . buildHook ( node , eventHook . callback , locals , options ) ;
106
- return this . _matchingHooks ( hookType , toFrom ) . map ( transitionHook ) ;
78
+ private _buildNodeHooks ( hookType : string ,
79
+ matchingNodesProp : string ,
80
+ sortHooksFn : ( l : HookTuple , r : HookTuple ) => number ,
81
+ getLocals : ( node : Node ) => any = ( node ) => ( { } ) ,
82
+ options : TransitionHookOptions = { } ) : TransitionHook [ ] {
83
+
84
+ // Find all the matching registered hooks for a given hook type
85
+ let matchingHooks = this . _matchingHooks ( hookType , this . treeChanges ) ;
86
+ if ( ! matchingHooks ) return [ ] ;
87
+
88
+ const makeTransitionHooks = ( hook : IEventHook ) => {
89
+ // Fetch the Nodes that caused this hook to match.
90
+ let matches : IMatchingNodes = hook . matches ( this . treeChanges ) ;
91
+ // Select the Node[] that will be used as TransitionHook context objects
92
+ let nodes : Node [ ] = matches [ matchingNodesProp ] ;
93
+
94
+ // Return an array of HookTuples
95
+ return nodes . map ( node => {
96
+ let _options = extend ( { traceData : { hookType, context : node } } , this . baseHookOptions , options ) ;
97
+ let transitionHook = new TransitionHook ( hook . callback , getLocals ( node ) , node . resolveContext , _options ) ;
98
+ return < HookTuple > { hook, node, transitionHook } ;
99
+ } ) ;
107
100
} ;
108
101
109
- return path . map ( hooksForNode ) ;
110
- }
111
-
112
- /** Given a node and a callback function, builds a TransitionHook */
113
- buildHook ( node : Node , fn : IInjectable , locals ?, options : TransitionHookOptions = { } ) : TransitionHook {
114
- let _options = extend ( { } , this . baseHookOptions , options ) ;
115
- return new TransitionHook ( fn , extend ( { } , locals ) , node . resolveContext , _options ) ;
102
+ return matchingHooks . map ( makeTransitionHooks )
103
+ . reduce ( unnestR , [ ] )
104
+ . sort ( sortHooksFn )
105
+ . map ( tuple => tuple . transitionHook ) ;
116
106
}
117
107
118
-
119
108
/**
120
- * returns an array of the IEventHooks from:
109
+ * Finds all IEventHooks from:
121
110
* - The Transition object instance hook registry
122
111
* - The TransitionService ($transitions) global hook registry
112
+ *
123
113
* which matched:
124
114
* - the eventType
125
- * - the matchCriteria to state
126
- * - the matchCriteria from state
115
+ * - the matchCriteria (to, from, exiting, retained, entering)
116
+ *
117
+ * @returns an array of matched [[IEventHook]]s
127
118
*/
128
- private _matchingHooks ( hookName : string , matchCriteria : IToFrom ) : IEventHook [ ] {
129
- const matchFilter = hook => hook . matches ( matchCriteria . to , matchCriteria . from ) ;
130
- const prioritySort = ( l , r ) => r . priority - l . priority ;
131
-
119
+ private _matchingHooks ( hookName : string , treeChanges : TreeChanges ) : IEventHook [ ] {
132
120
return [ this . transition , this . $transitions ] // Instance and Global hook registries
133
121
. map ( ( reg : IHookRegistry ) => reg . getHooks ( hookName ) ) // Get named hooks from registries
134
122
. filter ( assertPredicate ( isArray , `broken event named: ${ hookName } ` ) ) // Sanity check
135
- . reduce ( unnestR ) // Un-nest IEventHook[][] to IEventHook[] array
136
- . filter ( matchFilter ) // Only those satisfying matchCriteria
137
- . sort ( prioritySort ) ; // Order them by .priority field
123
+ . reduce ( unnestR , [ ] ) // Un-nest IEventHook[][] to IEventHook[] array
124
+ . filter ( hook => hook . matches ( treeChanges ) ) ; // Only those satisfying matchCriteria
138
125
}
139
126
}
127
+
128
+ interface HookTuple { hook : IEventHook , node : Node , transitionHook : TransitionHook }
129
+
130
+ /**
131
+ * A factory for a sort function for HookTuples.
132
+ *
133
+ * The sort function first compares the Node depth (how deep in the state tree a node is), then compares
134
+ * the EventHook priority.
135
+ *
136
+ * @param reverseDepthSort a boolean, when true, reverses the sort order for the node depth
137
+ * @returns a tuple sort function
138
+ */
139
+ function tupleSort ( reverseDepthSort = false ) {
140
+ return function nodeDepthThenPriority ( l : HookTuple , r : HookTuple ) : number {
141
+ let factor = reverseDepthSort ? - 1 : 1 ;
142
+ let depthDelta = ( l . node . state . path . length - r . node . state . path . length ) * factor ;
143
+ return depthDelta !== 0 ? depthDelta : r . hook . priority - l . hook . priority ;
144
+ }
145
+ }
0 commit comments