1
1
import { Directive , Output , EventEmitter } from "angular2/core" ;
2
2
import { StateService } from "../state/stateService" ;
3
3
import { UiSref } from "./uiSref" ;
4
- import { UIRouter } from "../router" ;
5
4
import { Node } from "../path/node" ;
6
5
import { TransitionService } from "../transition/transitionService" ;
7
6
import { Transition } from "../transition/transition" ;
8
7
import { TargetState } from "../state/targetState" ;
9
8
import { TreeChanges } from "../transition/interface" ;
10
9
import { State } from "../state/stateObject" ;
11
- import { anyTrueR , tail } from "../common/common" ;
10
+ import { anyTrueR , tail , unnestR } from "../common/common" ;
12
11
import { UIRouterGlobals } from "../globals" ;
12
+ import { Param } from "../params/param" ;
13
+ import { PathFactory } from "../path/pathFactory" ;
13
14
15
+ /**
16
+ * uiSref status booleans
17
+ */
14
18
export interface SrefStatus {
19
+ /** The sref's target state (or one of its children) is currently active */
15
20
active : boolean ;
21
+ /** The sref's target state is currently active */
16
22
exact : boolean ;
23
+ /** A transition is entering the sref's target state */
17
24
entering : boolean ;
25
+ /** A transition is exiting the sref's target state */
18
26
exiting : boolean ;
19
27
}
20
28
21
29
/**
22
- * Emits events when the uiSref status changes
30
+ * A directive (which pairs with a [[UiSref]]) and emits events when the UiSref status changes.
31
+ *
32
+ * The event emitted is of type [[SrefStatus]], and has boolean values for `active`, `exact`, `entering`, and `exiting`
33
+ *
34
+ * The values from this event can be captured and stored on a component, then applied (perhaps using ngClass).
23
35
*
24
36
* This API is subject to change.
25
37
*/
@@ -41,26 +53,29 @@ export class UiSrefStatus {
41
53
private _globals : UIRouterGlobals ,
42
54
private _stateService : StateService ,
43
55
public sref : UiSref ) {
44
- this . _deregisterHook = transitionService . onStart ( { } , ( $transition$ ) => this . _transition ( $transition$ ) ) ;
56
+ this . _deregisterHook = transitionService . onStart ( { } , $transition$ => this . processTransition ( $transition$ ) ) ;
45
57
}
46
58
47
59
ngOnInit ( ) {
48
60
let lastTrans = this . _globals . transitionHistory . peekTail ( ) ;
49
61
if ( lastTrans != null ) {
50
- this . _transition ( lastTrans ) ;
62
+ this . processTransition ( lastTrans ) ;
51
63
}
52
64
}
53
65
54
66
ngOnDestroy ( ) {
55
- this . _deregisterHook ( )
67
+ if ( this . _deregisterHook ) {
68
+ this . _deregisterHook ( ) ;
69
+ }
70
+ this . _deregisterHook = null ;
56
71
}
57
72
58
73
private _setStatus ( status : SrefStatus ) {
59
74
this . status = status ;
60
75
this . uiSrefStatus . emit ( status ) ;
61
76
}
62
77
63
- private _transition ( $transition$ : Transition ) {
78
+ private processTransition ( $transition$ : Transition ) {
64
79
let sref = this . sref ;
65
80
66
81
let status : SrefStatus = < any > {
@@ -70,28 +85,58 @@ export class UiSrefStatus {
70
85
exiting : false
71
86
} ;
72
87
73
- let srefTarget : TargetState = this . _stateService . target ( sref . state , sref . params , sref . options ) ;
88
+ let srefTarget : TargetState = this . _stateService . target ( sref . state , sref . params , sref . getOptions ( ) ) ;
74
89
if ( ! srefTarget . exists ( ) ) {
75
90
return this . _setStatus ( status ) ;
76
91
}
77
92
78
- let tc : TreeChanges = $transition$ . treeChanges ( ) ;
79
- let state : State = srefTarget . $state ( ) ;
80
- const isTarget = ( node : Node ) => node . state === state ;
81
93
82
- status . active = tc . from . map ( isTarget ) . reduce ( anyTrueR , false ) ;
83
- status . exact = tail ( tc . from . map ( isTarget ) ) === true ;
84
- status . entering = tc . entering . map ( isTarget ) . reduce ( anyTrueR , false ) ;
85
- status . exiting = tc . exiting . map ( isTarget ) . reduce ( anyTrueR , false ) ;
94
+ /**
95
+ * Returns a Predicate<Node[]> that returns true when the target state (and any param values)
96
+ * match the (tail of) the path, and the path's param values
97
+ */
98
+ const pathMatches = ( target : TargetState ) => {
99
+ let state : State = target . $state ( ) ;
100
+ let targetParamVals = target . params ( ) ;
101
+ let targetPath : Node [ ] = PathFactory . buildPath ( target ) ;
102
+ let paramSchema : Param [ ] = targetPath . map ( node => node . paramSchema )
103
+ . reduce ( unnestR , [ ] )
104
+ . filter ( ( param : Param ) => targetParamVals . hasOwnProperty ( param . id ) ) ;
105
+
106
+ return ( path : Node [ ] ) => {
107
+ let tailNode = tail ( path ) ;
108
+ if ( ! tailNode || tailNode . state !== state ) return false ;
109
+ var paramValues = PathFactory . paramValues ( path ) ;
110
+ return Param . equals ( paramSchema , paramValues , targetParamVals ) ;
111
+ } ;
112
+ } ;
113
+
114
+ const isTarget = pathMatches ( srefTarget ) ;
115
+
116
+ /**
117
+ * Given path: [c, d] appendTo: [a, b]),
118
+ * Expands the path to [c], [c, d]
119
+ * Then appends each to [a,b,] and returns: [a, b, c], [a, b, c, d]
120
+ */
121
+ function spreadToSubPaths ( path : Node [ ] , appendTo : Node [ ] = [ ] ) : Node [ ] [ ] {
122
+ return path . map ( node => appendTo . concat ( PathFactory . subPath ( path , node . state ) ) ) ;
123
+ }
124
+
125
+ let tc : TreeChanges = $transition$ . treeChanges ( ) ;
126
+ status . active = spreadToSubPaths ( tc . from ) . map ( isTarget ) . reduce ( anyTrueR , false ) ;
127
+ status . exact = isTarget ( tc . from ) ;
128
+ status . entering = spreadToSubPaths ( tc . entering , tc . retained ) . map ( isTarget ) . reduce ( anyTrueR , false ) ;
129
+ status . exiting = spreadToSubPaths ( tc . exiting , tc . retained ) . map ( isTarget ) . reduce ( anyTrueR , false ) ;
86
130
87
131
if ( $transition$ . isActive ( ) ) {
88
132
this . _setStatus ( status ) ;
89
133
}
90
134
91
135
let update = ( currentPath : Node [ ] ) => ( ) => {
136
+ if ( this . _deregisterHook == null ) return ; // destroyed
92
137
if ( ! $transition$ . isActive ( ) ) return ; // superseded
93
- status . active = currentPath . map ( isTarget ) . reduce ( anyTrueR , false ) ;
94
- status . exact = tail ( currentPath . map ( isTarget ) ) === true ;
138
+ status . active = spreadToSubPaths ( currentPath ) . map ( isTarget ) . reduce ( anyTrueR , false ) ;
139
+ status . exact = isTarget ( currentPath ) ;
95
140
status . entering = status . exiting = false ;
96
141
this . _setStatus ( status ) ;
97
142
} ;
0 commit comments