7
7
$TransitionProvider . $inject = [ ] ;
8
8
function $TransitionProvider ( ) {
9
9
10
- var $transition = { } , events , stateMatcher = angular . noop , abstractKey = 'abstract' ;
10
+ var $transition = { } , stateMatcher = angular . noop , abstractKey = 'abstract' ;
11
+ var transitionEvents = { on : [ ] , entering : [ ] , exiting : [ ] , success : [ ] , error : [ ] } ;
11
12
12
- function matchState ( current , states ) {
13
- var toMatch = angular . isArray ( states ) ? states : [ states ] ;
13
+ function matchState ( state , globStrings ) {
14
+ var toMatch = angular . isArray ( globStrings ) ? globStrings : [ globStrings ] ;
14
15
15
16
for ( var i = 0 ; i < toMatch . length ; i ++ ) {
16
17
var glob = GlobBuilder . fromString ( toMatch [ i ] ) ;
17
18
18
- if ( ( glob && glob . matches ( current . name ) ) || ( ! glob && toMatch [ i ] === current . name ) ) {
19
+ if ( ( glob && glob . matches ( state . name ) ) || ( ! glob && toMatch [ i ] === state . name ) ) {
19
20
return true ;
20
21
}
21
22
}
22
23
return false ;
23
24
}
24
25
25
- // $transitionProvider.on({ from: "home", to: "somewhere.else" }, function($transition$, $http) {
26
- // // ...
27
- // });
28
- this . on = function ( states , callback ) {
29
- } ;
30
-
31
- // $transitionProvider.onEnter({ from: "home", to: "somewhere.else" }, function($transition$, $http) {
32
- // // ...
33
- // });
34
- this . entering = function ( states , callback ) {
35
- } ;
36
-
37
- // $transitionProvider.onExit({ from: "home", to: "somewhere.else" }, function($transition$, $http) {
38
- // // ...
39
- // });
40
- this . exiting = function ( states , callback ) {
41
- } ;
42
-
43
- // $transitionProvider.onSuccess({ from: "home", to: "somewhere.else" }, function($transition$, $http) {
44
- // // ...
45
- // });
46
- this . onSuccess = function ( states , callback ) {
47
- } ;
48
-
49
- // $transitionProvider.onError({ from: "home", to: "somewhere.else" }, function($transition$, $http) {
50
- // // ...
51
- // });
52
- this . onError = function ( states , callback ) {
53
- } ;
26
+ // Return a registration function of the requested type.
27
+ function registerEventHook ( eventType ) {
28
+ return function ( stateGlobs , callback ) {
29
+ transitionEvents [ eventType ] . push ( new EventHook ( stateGlobs , callback ) ) ;
30
+ } ;
31
+ }
32
+
33
+ /**
34
+ * @ngdoc function
35
+ * @name ui.router.state.$transitionProvider#on
36
+ * @methodOf ui.router.state.$transitionProvider
37
+ *
38
+ * @description
39
+ * Registers a function to be injected and invoked when a transition between the matched 'to' and 'from' states
40
+ * starts.
41
+ *
42
+ * @param {object } transitionCriteria An object that specifies which transitions to invoke the callback for.
43
+ *
44
+ * - **`to`** - {string} - A glob string that matches the 'to' state's name.
45
+ * - **`from`** - {string|RegExp} - A glob string that matches the 'from' state's name.
46
+ *
47
+ * @param {function } callback The function which will be injected and invoked, when a matching transition is started.
48
+ *
49
+ * @return {boolean|object|array } May optionally return:
50
+ * - **`false`** to abort the current transition
51
+ * - **A promise** to suspend the current transition until the promise resolves
52
+ * - **Array of Resolvable objects** to add additional resolves to the current transition, which will be available
53
+ * for injection to further steps in the transition.
54
+ */
55
+ this . on = registerEventHook ( "on" ) ;
56
+
57
+ /**
58
+ * @ngdoc function
59
+ * @name ui.router.state.$transitionProvider#entering
60
+ * @methodOf ui.router.state.$transitionProvider
61
+ *
62
+ * @description
63
+ * Registers a function to be injected and invoked during a transition between the matched 'to' and 'from' states,
64
+ * when the matched 'to' state is being entered. This function is in injected with the entering state's resolves.
65
+ * @param {object } transitionCriteria See transitionCriteria in {@link ui.router.state.$transitionProvider#on $transitionProvider.on}.
66
+ * @param {function } callback See callback in {@link ui.router.state.$transitionProvider#on $transitionProvider.on}.
67
+ *
68
+ * @return {boolean|object|array } May optionally return:
69
+ * - **`false`** to abort the current transition
70
+ * - **A promise** to suspend the current transition until the promise resolves
71
+ * - **Array of Resolvable objects** to add additional resolves to the current transition, which will be available
72
+ * for injection to further steps in the transition.
73
+ */
74
+ this . entering = registerEventHook ( "entering" ) ;
75
+
76
+ /**
77
+ * @ngdoc function
78
+ * @name ui.router.state.$transitionProvider#exiting
79
+ * @methodOf ui.router.state.$transitionProvider
80
+ *
81
+ * @description
82
+ * Registers a function to be injected and invoked during a transition between the matched 'to' and 'from states,
83
+ * when the matched 'from' state is being exited. This function is in injected with the exiting state's resolves.
84
+ * @param {object } transitionCriteria See transitionCriteria in {@link ui.router.state.$transitionProvider#on $transitionProvider.on}.
85
+ * @param {function } callback See callback in {@link ui.router.state.$transitionProvider#on $transitionProvider.on}.
86
+ *
87
+ * @return {boolean|object|array } May optionally return:
88
+ * - **`false`** to abort the current transition
89
+ * - **A promise** to suspend the current transition until the promise resolves
90
+ * - **Array of Resolvable objects** to add additional resolves to the current transition, which will be available
91
+ * for injection to further steps in the transition.
92
+ */
93
+ this . exiting = registerEventHook ( "exiting" ) ;
94
+
95
+ /**
96
+ * @ngdoc function
97
+ * @name ui.router.state.$transitionProvider#onSuccess
98
+ * @methodOf ui.router.state.$transitionProvider
99
+ *
100
+ * @description
101
+ * Registers a function to be injected and invoked when a transition has successfully completed between the matched
102
+ * 'to' and 'from' state is being exited.
103
+ * This function is in injected with the 'to' state's resolves.
104
+ * @param {object } transitionCriteria See transitionCriteria in {@link ui.router.state.$transitionProvider#on $transitionProvider.on}.
105
+ * @param {function } callback See callback in {@link ui.router.state.$transitionProvider#on $transitionProvider.on}.
106
+ */
107
+ this . onSuccess = registerEventHook ( "success" ) ;
108
+
109
+ /**
110
+ * @ngdoc function
111
+ * @name ui.router.state.$transitionProvider#onError
112
+ * @methodOf ui.router.state.$transitionProvider
113
+ *
114
+ * @description
115
+ * Registers a function to be injected and invoked when a transition has failed for any reason between the matched
116
+ * 'to' and 'from' state is being exited. This function is in injected with the 'to' state's resolves. The transition
117
+ * rejection reason is injected as `$transitionError$`.
118
+ * @param {object } transitionCriteria See transitionCriteria in {@link ui.router.state.$transitionProvider#on $transitionProvider.on}.
119
+ * @param {function } callback See callback in {@link ui.router.state.$transitionProvider#on $transitionProvider.on}.
120
+ */
121
+ this . onError = registerEventHook ( "error" ) ;
54
122
123
+ function EventHook ( stateGlobs , callback ) {
124
+ this . callback = callback ;
125
+ this . matches = function matches ( to , from ) {
126
+ return matchState ( to , stateGlobs . to ) && matchState ( from , stateGlobs . from ) ;
127
+ } ;
128
+ }
55
129
56
130
/**
57
131
* @ngdoc service
@@ -67,7 +141,6 @@ function $TransitionProvider() {
67
141
this . $get = $get ;
68
142
$get . $inject = [ '$q' , '$injector' , '$resolve' , '$stateParams' ] ;
69
143
function $get ( $q , $injector , $resolve , $stateParams ) {
70
-
71
144
var from = { state : null , params : null } ,
72
145
to = { state : null , params : null } ;
73
146
var _fromPath = null ; // contains resolved data
@@ -142,55 +215,6 @@ function $TransitionProvider() {
142
215
hasCalculated = true ;
143
216
}
144
217
145
- function transitionStep ( fn , resolveContext ) {
146
- return function ( ) {
147
- if ( $transition . transition !== transition ) return transition . SUPERSEDED ;
148
- return pathElement . invokeAsync ( fn , { $stateParams : undefined , $transition$ : transition } , resolveContext )
149
- . then ( function ( result ) {
150
- return result ? result : $q . reject ( transition . ABORTED ) ;
151
- } ) ;
152
- } ;
153
- }
154
-
155
- function buildTransitionSteps ( ) {
156
- // create invokeFn fn.
157
- // - checks if current transition has been superseded
158
- // - invokes Fn async
159
- // - checks result. If falsey, rejects promise
160
-
161
- // get exiting & reverse them
162
- // get entering
163
-
164
- // walk exiting()
165
- // - InvokeAsync
166
- // resolve all eager Path resolvables
167
- // walk entering()
168
- // - resolve PathElement lazy resolvables
169
- // - then, invokeAsync onEnter
170
-
171
- var exitingElements = transition . exiting ( ) . slice ( 0 ) . reverse ( ) . elements ;
172
- var enteringElements = transition . entering ( ) . elements ;
173
- var promiseChain = $q . when ( true ) ;
174
-
175
- forEach ( exitingElements , function ( elem ) {
176
- if ( elem . state . onExit ) {
177
- var nextStep = transitionStep ( elem . state . onExit , fromPath . resolveContext ( elem ) ) ;
178
- promiseChain . then ( nextStep ) ;
179
- }
180
- } ) ;
181
-
182
- forEach ( enteringElements , function ( elem ) {
183
- var resolveContext = toPath . resolveContext ( elem ) ;
184
- promiseChain . then ( function ( ) { return elem . resolve ( resolveContext , { policy : "lazy" } ) ; } ) ;
185
- if ( elem . state . onEnter ) {
186
- var nextStep = transitionStep ( elem . state . onEnter , resolveContext ) ;
187
- promiseChain . then ( nextStep ) ;
188
- }
189
- } ) ;
190
-
191
- return promiseChain ;
192
- }
193
-
194
218
extend ( this , {
195
219
/**
196
220
* @ngdoc function
@@ -341,12 +365,93 @@ function $TransitionProvider() {
341
365
ignored : function ( ) {
342
366
return ( toState === fromState && ! options . reload ) ;
343
367
} ,
368
+
344
369
run : function ( ) {
345
370
calculateTreeChanges ( ) ;
346
- var pathContext = new ResolveContext ( toPath ) ;
347
- return toPath . resolve ( pathContext , { policy : "eager" } )
348
- . then ( buildTransitionSteps ) ;
371
+
372
+ function TransitionStep ( pathElement , fn , locals , resolveContext , otherData ) {
373
+ this . state = pathElement . state ;
374
+ this . otherData = otherData ;
375
+ this . fn = fn ;
376
+
377
+ this . invokeStep = function invokeStep ( ) {
378
+ if ( $transition . transition !== transition ) return transition . SUPERSEDED ;
379
+
380
+ /** Returns a map containing any Resolvables found in result as an object or Array */
381
+ function resolvablesFromResult ( result ) {
382
+ var resolvables = [ ] ;
383
+ if ( result instanceof Resolvable ) {
384
+ resolvables . push ( result ) ;
385
+ } else if ( angular . isArray ( result ) ) {
386
+ resolvables . push ( filter ( result , function ( obj ) { return obj instanceof Resolvable ; } ) ) ;
387
+ }
388
+ return indexBy ( resolvables , 'name' ) ;
389
+ }
390
+
391
+ /** Adds any returned resolvables to the resolveContext for the current state */
392
+ function handleHookResult ( result ) {
393
+ var newResolves = resolvablesFromResult ( result ) ;
394
+ extend ( resolveContext . $$resolvablesByState [ pathElement . state . name ] , newResolves ) ;
395
+ return result === false ? transition . ABORTED : result ;
396
+ }
397
+
398
+ return pathElement . invokeLater ( fn , locals , resolveContext ) . then ( handleHookResult ) ;
399
+ } ;
400
+ }
401
+
402
+ /**
403
+ * returns an array of transition steps (promises) that matched
404
+ * 1) the eventType
405
+ * 2) the to state
406
+ * 3) the from state
407
+ */
408
+ function makeSteps ( eventType , to , from , pathElement , locals , resolveContext ) {
409
+ var extraData = { eventType : eventType , to : to , from : from , pathElement : pathElement , locals : locals , resolveContext : resolveContext } ; // internal debugging stuff
410
+ var hooks = transitionEvents [ eventType ] ;
411
+ var matchingHooks = filter ( hooks , function ( hook ) { return hook . matches ( to , from ) ; } ) ;
412
+ return map ( matchingHooks , function ( hook ) {
413
+ return new TransitionStep ( pathElement , hook . callback , locals , resolveContext , extraData ) ;
414
+ } ) ;
415
+ }
416
+
417
+ var tLocals = { $transition$ : transition } ;
418
+ var rootPE = new PathElement ( stateMatcher ( "" , { } ) ) ;
419
+ var rootPath = new Path ( [ rootPE ] ) ;
420
+ var exitingElements = transition . exiting ( ) . slice ( 0 ) . reverse ( ) . elements ;
421
+ var enteringElements = transition . entering ( ) . elements ;
422
+ var to = transition . to ( ) , from = transition . from ( ) ;
423
+
424
+ // Build a bunch of arrays of promises for each step of the transition
425
+ var transitionOnHooks = makeSteps ( "on" , to , from , rootPE , tLocals , rootPath . resolveContext ( ) ) ;
426
+
427
+ var exitingStateHooks = map ( exitingElements , function ( elem ) {
428
+ var enterLocals = extend ( { } , tLocals , { $stateParams : $stateParams . $localize ( elem . state , $stateParams ) } ) ;
429
+ return makeSteps ( "exiting" , to , from , elem , enterLocals , fromPath . resolveContext ( elem ) ) ;
430
+ } ) ;
431
+ var enteringStateHooks = map ( enteringElements , function ( elem ) {
432
+ var exitLocals = extend ( { } , tLocals , { $stateParams : $stateParams . $localize ( elem . state , $stateParams ) } ) ;
433
+ return makeSteps ( "entering" , to , from , elem , exitLocals , toPath . resolveContext ( elem ) ) ;
434
+ } ) ;
435
+
436
+ var successHooks = makeSteps ( "onSuccess" , to , from , rootPE , tLocals , rootPath . resolveContext ( ) ) ;
437
+ var errorHooks = makeSteps ( "onError" , to , from , rootPE , tLocals , rootPath . resolveContext ( ) ) ;
438
+
439
+ var eagerResolves = function ( ) { return toPath . resolve ( toPath . resolveContext ( ) , { policy : "eager" } ) ; } ;
440
+
441
+ var allSteps = flatten ( transitionOnHooks , eagerResolves , exitingStateHooks , enteringStateHooks , successHooks ) ;
442
+
443
+
444
+ // Set up a promise chain. Add the promises in appropriate order to the promise chain.
445
+ var chain = $q . when ( true ) ;
446
+ forEach ( allSteps , function ( step ) {
447
+ chain . then ( step . invokeStep ) ;
448
+ } ) ;
449
+
450
+ // TODO: call errorHooks.
451
+
452
+ return chain ;
349
453
} ,
454
+
350
455
begin : function ( compare , exec ) {
351
456
if ( ! compare ( ) ) return this . SUPERSEDED ;
352
457
if ( ! exec ( ) ) return this . ABORTED ;
@@ -377,7 +482,8 @@ function $TransitionProvider() {
377
482
378
483
$transition . start = function start ( state , params , options ) {
379
484
to = { state : state , params : params || { } } ;
380
- return new Transition ( from . state , from . params , state , params || { } , options || { } ) ;
485
+ this . transition = new Transition ( from . state , from . params , state , params || { } , options || { } ) ;
486
+ return this . transition ;
381
487
} ;
382
488
383
489
$transition . isActive = function isActive ( ) {
0 commit comments