1
1
/** @module state */ /** for typedoc */
2
2
/// <reference path='../../typings/angularjs/angular.d.ts' />
3
- import { copy , defaults , forEach , toJson } from "../common/common" ;
3
+ import { extend , copy , defaults , forEach , toJson } from "../common/common" ;
4
4
import { isString , isObject } from "../common/predicates" ;
5
5
import { defaultTransOpts } from "../transition/module" ;
6
6
@@ -20,6 +20,43 @@ function stateContext(el) {
20
20
}
21
21
}
22
22
23
+ function getTypeInfo ( el ) {
24
+ // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
25
+ var isSvg = Object . prototype . toString . call ( el . prop ( 'href' ) ) === '[object SVGAnimatedString]' ;
26
+ var isForm = el [ 0 ] . nodeName === "FORM" ;
27
+
28
+ return {
29
+ attr : isForm ? "action" : ( isSvg ? 'xlink:href' : 'href' ) ,
30
+ isAnchor : el . prop ( "tagName" ) . toUpperCase ( ) === "A" ,
31
+ clickable : ! isForm
32
+ } ;
33
+ }
34
+
35
+ function clickHook ( el , $state , $timeout , type , current ) {
36
+ return function ( e ) {
37
+ var button = e . which || e . button , target = current ( ) ;
38
+
39
+ if ( ! ( button > 1 || e . ctrlKey || e . metaKey || e . shiftKey || el . attr ( 'target' ) ) ) {
40
+ // HACK: This is to allow ng-clicks to be processed before the transition is initiated:
41
+ var transition = $timeout ( function ( ) {
42
+ $state . go ( target . state , target . params , target . options ) ;
43
+ } ) ;
44
+ e . preventDefault ( ) ;
45
+
46
+ // if the state has no URL, ignore one preventDefault from the <a> directive.
47
+ var ignorePreventDefaultCount = type . isAnchor && ! target . href ? 1 : 0 ;
48
+
49
+ e . preventDefault = function ( ) {
50
+ if ( ignorePreventDefaultCount -- <= 0 ) $timeout . cancel ( transition ) ;
51
+ } ;
52
+ }
53
+ } ;
54
+ }
55
+
56
+ function defaultOpts ( el , $state ) {
57
+ return { relative : stateContext ( el ) || $state . $current , inherit : true } ;
58
+ }
59
+
23
60
/**
24
61
* @ngdoc directive
25
62
* @name ui.router.state.directive:ui-sref
@@ -30,40 +67,40 @@ function stateContext(el) {
30
67
* @restrict A
31
68
*
32
69
* @description
33
- * A directive that binds a link (`<a>` tag) to a state. If the state has an associated
34
- * URL, the directive will automatically generate & update the `href` attribute via
35
- * the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking
36
- * the link will trigger a state transition with optional parameters.
70
+ * A directive that binds a link (`<a>` tag) to a state. If the state has an associated
71
+ * URL, the directive will automatically generate & update the `href` attribute via
72
+ * the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking
73
+ * the link will trigger a state transition with optional parameters.
37
74
*
38
- * Also middle-clicking, right-clicking, and ctrl-clicking on the link will be
75
+ * Also middle-clicking, right-clicking, and ctrl-clicking on the link will be
39
76
* handled natively by the browser.
40
77
*
41
- * You can also use relative state paths within ui-sref, just like the relative
78
+ * You can also use relative state paths within ui-sref, just like the relative
42
79
* paths passed to `$state.go()`. You just need to be aware that the path is relative
43
- * to the state that the link lives in, in other words the state that loaded the
80
+ * to the state that the link lives in, in other words the state that loaded the
44
81
* template containing the link.
45
82
*
46
83
* You can specify options to pass to {@link ui.router.state.$state#go $state.go()}
47
84
* using the `ui-sref-opts` attribute. Options are restricted to `location`, `inherit`,
48
85
* and `reload`.
49
86
*
50
87
* @example
51
- * Here's an example of how you'd use ui-sref and how it would compile. If you have the
88
+ * Here's an example of how you'd use ui-sref and how it would compile. If you have the
52
89
* following template:
53
90
* <pre>
54
91
* <a ui-sref="home">Home</a> | <a ui-sref="about">About</a> | <a ui-sref="{page: 2}">Next page</a>
55
- *
92
+ *
56
93
* <ul>
57
94
* <li ng-repeat="contact in contacts">
58
95
* <a ui-sref="contacts.detail({ id: contact.id })">{{ contact.name }}</a>
59
96
* </li>
60
97
* </ul>
61
98
* </pre>
62
- *
99
+ *
63
100
* Then the compiled html would be (assuming Html5Mode is off and current state is contacts):
64
101
* <pre>
65
102
* <a href="#/home" ui-sref="home">Home</a> | <a href="#/about" ui-sref="about">About</a> | <a href="#/contacts?page=2" ui-sref="{page: 2}">Next page</a>
66
- *
103
+ *
67
104
* <ul>
68
105
* <li ng-repeat="contact in contacts">
69
106
* <a href="#/contacts/1" ui-sref="contacts.detail({ id: contact.id })">Joe</a>
@@ -88,62 +125,79 @@ function $StateRefDirective($state, $timeout) {
88
125
restrict : 'A' ,
89
126
require : [ '?^uiSrefActive' , '?^uiSrefActiveEq' ] ,
90
127
link : function ( scope , element , attrs , uiSrefActive ) {
91
- let ref = parseStateRef ( attrs . uiSref , $state . current . name ) ;
92
- let params = null , base = stateContext ( element ) || $state . $current ;
93
- let newHref = null , isAnchor = element . prop ( "tagName" ) === "A" ;
94
- let isForm = element [ 0 ] . nodeName === "FORM" ;
95
- let attr = isForm ? "action" : "href" , nav = true ;
96
-
97
- let srefOpts = scope . $eval ( attrs . uiSrefOpts ) ;
98
- let defaultSrefOpts = { relative : base , inherit : true } ;
99
- let options = defaults ( srefOpts , defaultSrefOpts , defaultTransOpts ) ;
128
+ var ref = parseStateRef ( attrs . uiSref , $state . current . name ) ;
129
+ var def = { state : ref . state , href : null , params : null , options : null } ;
130
+ var type = getTypeInfo ( element ) ;
131
+ var active = uiSrefActive [ 1 ] || uiSrefActive [ 0 ] ;
100
132
101
- let update = function ( newVal ?: any ) {
102
- if ( newVal ) params = copy ( newVal ) ;
103
- if ( ! nav ) return ;
133
+ def . options = extend ( defaultOpts ( element , $state ) , attrs . uiSrefOpts ? scope . $eval ( attrs . uiSrefOpts ) : { } ) ;
104
134
105
- newHref = $state . href ( ref . state , params , options ) ;
135
+ var update = function ( val ?) {
136
+ if ( val ) def . params = angular . copy ( val ) ;
137
+ def . href = $state . href ( ref . state , def . params , def . options ) ;
106
138
107
- let activeDirective = uiSrefActive [ 1 ] || uiSrefActive [ 0 ] ;
108
- if ( activeDirective ) {
109
- activeDirective . $$addStateInfo ( ref . state , params ) ;
110
- }
111
- if ( newHref === null ) {
112
- nav = false ;
113
- return false ;
114
- }
115
- attrs . $set ( attr , newHref ) ;
139
+ if ( active ) active . $$addStateInfo ( ref . state , def . params ) ;
140
+ if ( def . href !== null ) attrs . $set ( type . attr , def . href ) ;
116
141
} ;
117
142
118
143
if ( ref . paramExpr ) {
119
- scope . $watch ( ref . paramExpr , newVal => { if ( newVal !== params ) update ( newVal ) ; } , true ) ;
120
- params = copy ( scope . $eval ( ref . paramExpr ) ) ;
144
+ scope . $watch ( ref . paramExpr , function ( val ) { if ( val !== def . params ) update ( val ) ; } , true ) ;
145
+ def . params = angular . copy ( scope . $eval ( ref . paramExpr ) ) ;
121
146
}
122
147
update ( ) ;
123
148
124
- if ( isForm ) return ;
125
-
126
- element . bind ( "click" , function ( e ) {
127
- let button = e . which || e . button ;
128
- if ( ! ( button > 1 || e . ctrlKey || e . metaKey || e . shiftKey || element . attr ( 'target' ) ) ) {
129
- // HACK: This is to allow ng-clicks to be processed before the transition is initiated:
130
- let transition = $timeout ( function ( ) {
131
- $state . go ( ref . state , params , options ) ;
132
- } ) ;
133
- e . preventDefault ( ) ;
134
-
135
- // if the state has no URL, ignore one preventDefault from the <a> directive.
136
- let ignorePreventDefaultCount = isAnchor && ! newHref ? 1 : 0 ;
137
- e . preventDefault = function ( ) {
138
- if ( ignorePreventDefaultCount -- <= 0 )
139
- $timeout . cancel ( transition ) ;
140
- } ;
141
- }
142
- } ) ;
149
+ if ( ! type . clickable ) return ;
150
+ element . bind ( "click" , clickHook ( element , $state , $timeout , type , function ( ) { return def ; } ) ) ;
143
151
}
144
152
} ;
145
153
}
146
154
155
+ /**
156
+ * @ngdoc directive
157
+ * @name ui.router.state.directive:ui-state
158
+ *
159
+ * @requires ui.router.state.uiSref
160
+ *
161
+ * @restrict A
162
+ *
163
+ * @description
164
+ * Much like ui-sref, but will accept named $scope properties to evaluate for a state definition,
165
+ * params and override options.
166
+ *
167
+ * @param {string } ui-state 'stateName' can be any valid absolute or relative state
168
+ * @param {Object } ui-state-params params to pass to {@link ui.router.state.$state#href $state.href()}
169
+ * @param {Object } ui-state-opts options to pass to {@link ui.router.state.$state#go $state.go()}
170
+ */
171
+ $StateRefDynamicDirective . $inject = [ '$state' , '$timeout' ] ;
172
+ function $StateRefDynamicDirective ( $state , $timeout ) {
173
+ return {
174
+ restrict : 'A' ,
175
+ require : [ '?^uiSrefActive' , '?^uiSrefActiveEq' ] ,
176
+ link : function ( scope , element , attrs , uiSrefActive ) {
177
+ var type = getTypeInfo ( element ) ;
178
+ var active = uiSrefActive [ 1 ] || uiSrefActive [ 0 ] ;
179
+ var group = [ attrs . uiState , attrs . uiStateParams || null , attrs . uiStateOpts || null ] ;
180
+ var watch = '[' + group . map ( function ( val ) { return val || 'null' ; } ) . join ( ', ' ) + ']' ;
181
+ var def = { state : null , params : null , options : null , href : null } ;
182
+
183
+ function runStateRefLink ( group ) {
184
+ def . state = group [ 0 ] ; def . params = group [ 1 ] ; def . options = group [ 2 ] ;
185
+ def . href = $state . href ( def . state , def . params , def . options ) ;
186
+
187
+ if ( active ) active . $$addStateInfo ( def . state , def . params ) ;
188
+ if ( def . href ) attrs . $set ( type . attr , def . href ) ;
189
+ }
190
+
191
+ scope . $watch ( watch , runStateRefLink , true ) ;
192
+ runStateRefLink ( scope . $eval ( watch ) ) ;
193
+
194
+ if ( ! type . clickable ) return ;
195
+ element . bind ( "click" , clickHook ( element , $state , $timeout , type , function ( ) { return def ; } ) ) ;
196
+ }
197
+ } ;
198
+ }
199
+
200
+
147
201
/**
148
202
* @ngdoc directive
149
203
* @name ui.router.state.directive:ui-sref-active
@@ -236,23 +290,29 @@ function $StateRefDirective($state, $timeout) {
236
290
* when the exact target state used in the `ui-sref` is active; no child states.
237
291
*
238
292
*/
239
- $StateRefActiveDirective . $inject = [ '$state' , '$stateParams' , '$interpolate' ] ;
240
- function $StateRefActiveDirective ( $state , $stateParams , $interpolate ) {
293
+ $StateRefActiveDirective . $inject = [ '$state' , '$stateParams' , '$interpolate' , '$transitions' ] ;
294
+ function $StateRefActiveDirective ( $state , $stateParams , $interpolate , $transitions ) {
241
295
return {
242
296
restrict : "A" ,
243
- controller : [ '$scope' , '$element' , '$attrs' , '$timeout' , '$transitions' , function ( $scope , $element , $attrs , $timeout , $transitions ) {
244
- let states = [ ] , activeClasses = { } , activeEqClass ;
297
+ controller : [ '$scope' , '$element' , '$attrs' , '$timeout' , function ( $scope , $element , $attrs , $timeout ) {
298
+ var states = [ ] , activeClasses = { } , activeEqClass , uiSrefActive ;
245
299
246
300
// There probably isn't much point in $observing this
247
301
// uiSrefActive and uiSrefActiveEq share the same directive object with some
248
302
// slight difference in logic routing
249
303
activeEqClass = $interpolate ( $attrs . uiSrefActiveEq || '' , false ) ( $scope ) ;
250
304
251
- let uiSrefActive = $scope . $eval ( $attrs . uiSrefActive ) || $interpolate ( $attrs . uiSrefActive || '' , false ) ( $scope ) ;
305
+ try {
306
+ uiSrefActive = $scope . $eval ( $attrs . uiSrefActive ) ;
307
+ } catch ( e ) {
308
+ // Do nothing. uiSrefActive is not a valid expression.
309
+ // Fall back to using $interpolate below
310
+ }
311
+ uiSrefActive = uiSrefActive || $interpolate ( $attrs . uiSrefActive || '' , false ) ( $scope ) ;
252
312
if ( isObject ( uiSrefActive ) ) {
253
313
forEach ( uiSrefActive , function ( stateOrName , activeClass ) {
254
314
if ( isString ( stateOrName ) ) {
255
- let ref = parseStateRef ( stateOrName , $state . current . name ) ;
315
+ var ref = parseStateRef ( stateOrName , $state . current . name ) ;
256
316
addState ( ref . state , $scope . $eval ( ref . paramExpr ) , activeClass ) ;
257
317
}
258
318
} ) ;
@@ -270,10 +330,13 @@ function $StateRefActiveDirective($state, $stateParams, $interpolate) {
270
330
} ;
271
331
272
332
$scope . $on ( '$stateChangeSuccess' , update ) ;
333
+ let updateAfterTransition = [ '$transition$' , function ( $transition$ ) { $transition$ . promise . then ( update ) ; } ] ;
334
+ let deregisterFn = $transitions . onStart ( { } , updateAfterTransition ) ;
335
+ $scope . $on ( '$destroy' , deregisterFn ) ;
273
336
274
337
function addState ( stateName , stateParams , activeClass ) {
275
- let state = $state . get ( stateName , stateContext ( $element ) ) ;
276
- let stateHash = createStateHash ( stateName , stateParams ) ;
338
+ var state = $state . get ( stateName , stateContext ( $element ) ) ;
339
+ var stateHash = createStateHash ( stateName , stateParams ) ;
277
340
278
341
states . push ( {
279
342
state : state || { name : stateName } ,
@@ -284,11 +347,11 @@ function $StateRefActiveDirective($state, $stateParams, $interpolate) {
284
347
activeClasses [ stateHash ] = activeClass ;
285
348
}
286
349
287
- updateAfterTransition . $inject = [ '$transition$' ] ;
288
- function updateAfterTransition ( $transition$ ) { $transition$ . promise . then ( update ) ; }
289
- let deregisterFn = $transitions . onStart ( { } , updateAfterTransition ) ;
290
- $scope . $on ( '$destroy' , deregisterFn ) ;
291
-
350
+ /**
351
+ * @param { string } state
352
+ * @param { Object|string } [params]
353
+ * @return { string }
354
+ */
292
355
function createStateHash ( state , params ) {
293
356
if ( ! isString ( state ) ) {
294
357
throw new Error ( 'state should be a string' ) ;
@@ -305,7 +368,7 @@ function $StateRefActiveDirective($state, $stateParams, $interpolate) {
305
368
306
369
// Update route state
307
370
function update ( ) {
308
- for ( let i = 0 ; i < states . length ; i ++ ) {
371
+ for ( var i = 0 ; i < states . length ; i ++ ) {
309
372
if ( anyMatch ( states [ i ] . state , states [ i ] . params ) ) {
310
373
addClass ( $element , activeClasses [ states [ i ] . hash ] ) ;
311
374
} else {
@@ -320,13 +383,9 @@ function $StateRefActiveDirective($state, $stateParams, $interpolate) {
320
383
}
321
384
}
322
385
323
-
324
386
function addClass ( el , className ) { $timeout ( function ( ) { el . addClass ( className ) ; } ) ; }
325
-
326
387
function removeClass ( el , className ) { el . removeClass ( className ) ; }
327
-
328
388
function anyMatch ( state , params ) { return $state . includes ( state . name , params ) ; }
329
-
330
389
function exactMatch ( state , params ) { return $state . is ( state . name , params ) ; }
331
390
332
391
update ( ) ;
@@ -335,6 +394,7 @@ function $StateRefActiveDirective($state, $stateParams, $interpolate) {
335
394
}
336
395
337
396
angular . module ( 'ui.router.state' )
338
- . directive ( 'uiSref' , $StateRefDirective )
339
- . directive ( 'uiSrefActive' , $StateRefActiveDirective )
340
- . directive ( 'uiSrefActiveEq' , $StateRefActiveDirective ) ;
397
+ . directive ( 'uiSref' , $StateRefDirective )
398
+ . directive ( 'uiSrefActive' , $StateRefActiveDirective )
399
+ . directive ( 'uiSrefActiveEq' , $StateRefActiveDirective )
400
+ . directive ( 'uiState' , $StateRefDynamicDirective ) ;
0 commit comments