@@ -14,6 +14,43 @@ function stateContext(el) {
14
14
}
15
15
}
16
16
17
+ function getTypeInfo ( el ) {
18
+ // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
19
+ var isSvg = Object . prototype . toString . call ( el . prop ( 'href' ) ) === '[object SVGAnimatedString]' ;
20
+ var isForm = el [ 0 ] . nodeName === "FORM" ;
21
+
22
+ return {
23
+ attr : isForm ? "action" : ( isSvg ? 'xlink:href' : 'href' ) ,
24
+ isAnchor : el . prop ( "tagName" ) . toUpperCase ( ) === "A" ,
25
+ clickable : ! isForm
26
+ } ;
27
+ }
28
+
29
+ function clickHook ( el , $state , $timeout , type , current ) {
30
+ return function ( e ) {
31
+ var button = e . which || e . button , target = current ( ) ;
32
+
33
+ if ( ! ( button > 1 || e . ctrlKey || e . metaKey || e . shiftKey || el . attr ( 'target' ) ) ) {
34
+ // HACK: This is to allow ng-clicks to be processed before the transition is initiated:
35
+ var transition = $timeout ( function ( ) {
36
+ $state . go ( target . state , target . params , target . options ) ;
37
+ } ) ;
38
+ e . preventDefault ( ) ;
39
+
40
+ // if the state has no URL, ignore one preventDefault from the <a> directive.
41
+ var ignorePreventDefaultCount = type . isAnchor && ! target . href ? 1 : 0 ;
42
+
43
+ e . preventDefault = function ( ) {
44
+ if ( ignorePreventDefaultCount -- <= 0 ) $timeout . cancel ( transition ) ;
45
+ } ;
46
+ }
47
+ } ;
48
+ }
49
+
50
+ function defaultOpts ( el , $state ) {
51
+ return { relative : stateContext ( el ) || $state . $current , inherit : true } ;
52
+ }
53
+
17
54
/**
18
55
* @ngdoc directive
19
56
* @name ui.router.state.directive:ui-sref
@@ -24,40 +61,40 @@ function stateContext(el) {
24
61
* @restrict A
25
62
*
26
63
* @description
27
- * A directive that binds a link (`<a>` tag) to a state. If the state has an associated
28
- * URL, the directive will automatically generate & update the `href` attribute via
29
- * the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking
30
- * the link will trigger a state transition with optional parameters.
64
+ * A directive that binds a link (`<a>` tag) to a state. If the state has an associated
65
+ * URL, the directive will automatically generate & update the `href` attribute via
66
+ * the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking
67
+ * the link will trigger a state transition with optional parameters.
31
68
*
32
- * Also middle-clicking, right-clicking, and ctrl-clicking on the link will be
69
+ * Also middle-clicking, right-clicking, and ctrl-clicking on the link will be
33
70
* handled natively by the browser.
34
71
*
35
- * You can also use relative state paths within ui-sref, just like the relative
72
+ * You can also use relative state paths within ui-sref, just like the relative
36
73
* paths passed to `$state.go()`. You just need to be aware that the path is relative
37
- * to the state that the link lives in, in other words the state that loaded the
74
+ * to the state that the link lives in, in other words the state that loaded the
38
75
* template containing the link.
39
76
*
40
77
* You can specify options to pass to {@link ui.router.state.$state#go $state.go()}
41
78
* using the `ui-sref-opts` attribute. Options are restricted to `location`, `inherit`,
42
79
* and `reload`.
43
80
*
44
81
* @example
45
- * Here's an example of how you'd use ui-sref and how it would compile. If you have the
82
+ * Here's an example of how you'd use ui-sref and how it would compile. If you have the
46
83
* following template:
47
84
* <pre>
48
85
* <a ui-sref="home">Home</a> | <a ui-sref="about">About</a> | <a ui-sref="{page: 2}">Next page</a>
49
- *
86
+ *
50
87
* <ul>
51
88
* <li ng-repeat="contact in contacts">
52
89
* <a ui-sref="contacts.detail({ id: contact.id })">{{ contact.name }}</a>
53
90
* </li>
54
91
* </ul>
55
92
* </pre>
56
- *
93
+ *
57
94
* Then the compiled html would be (assuming Html5Mode is off and current state is contacts):
58
95
* <pre>
59
96
* <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>
60
- *
97
+ *
61
98
* <ul>
62
99
* <li ng-repeat="contact in contacts">
63
100
* <a href="#/contacts/1" ui-sref="contacts.detail({ id: contact.id })">Joe</a>
@@ -78,78 +115,86 @@ function stateContext(el) {
78
115
*/
79
116
$StateRefDirective . $inject = [ '$state' , '$timeout' ] ;
80
117
function $StateRefDirective ( $state , $timeout ) {
81
- var allowedOptions = [ 'location' , 'inherit' , 'reload' , 'absolute' ] ;
82
-
83
118
return {
84
119
restrict : 'A' ,
85
120
require : [ '?^uiSrefActive' , '?^uiSrefActiveEq' ] ,
86
121
link : function ( scope , element , attrs , uiSrefActive ) {
87
- var ref = parseStateRef ( attrs . uiSref , $state . current . name ) ;
88
- var params = null , url = null , base = stateContext ( element ) || $state . $current ;
89
- // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
90
- var hrefKind = Object . prototype . toString . call ( element . prop ( 'href' ) ) === '[object SVGAnimatedString]' ?
91
- 'xlink:href' : 'href' ;
92
- var newHref = null , isAnchor = element . prop ( "tagName" ) . toUpperCase ( ) === "A" ;
93
- var isForm = element [ 0 ] . nodeName === "FORM" ;
94
- var attr = isForm ? "action" : hrefKind , nav = true ;
95
-
96
- var options = { relative : base , inherit : true } ;
97
- var optionsOverride = scope . $eval ( attrs . uiSrefOpts ) || { } ;
98
-
99
- angular . forEach ( allowedOptions , function ( option ) {
100
- if ( option in optionsOverride ) {
101
- options [ option ] = optionsOverride [ option ] ;
102
- }
103
- } ) ;
122
+ var ref = parseStateRef ( attrs . uiSref , $state . current . name ) ;
123
+ var def = { state : ref . state , href : null , nav : true , params : null } ;
124
+ var type = getTypeInfo ( element ) ;
125
+ var active = uiSrefActive [ 1 ] || uiSrefActive [ 0 ] ;
104
126
105
- var update = function ( newVal ) {
106
- if ( newVal ) params = angular . copy ( newVal ) ;
107
- if ( ! nav ) return ;
127
+ def . options = extend ( defaultOpts ( element , $state ) , attrs . uiSrefOpts ? scope . $eval ( attrs . uiSrefOpts ) : { } ) ;
108
128
109
- newHref = $state . href ( ref . state , params , options ) ;
129
+ var update = function ( val ) {
130
+ if ( val ) def . params = angular . copy ( val ) ;
131
+ if ( ! def . nav ) return ;
110
132
111
- var activeDirective = uiSrefActive [ 1 ] || uiSrefActive [ 0 ] ;
112
- if ( activeDirective ) {
113
- activeDirective . $$addStateInfo ( ref . state , params ) ;
114
- }
115
- if ( newHref === null ) {
116
- nav = false ;
117
- return false ;
118
- }
119
- attrs . $set ( attr , newHref ) ;
133
+ def . href = $state . href ( ref . state , def . params , def . options ) ;
134
+ def . nav = ( def . href !== null ) ;
135
+
136
+ if ( active ) active . $$addStateInfo ( ref . state , def . params ) ;
137
+ if ( def . nav ) attrs . $set ( type . attr , def . href ) ;
120
138
} ;
121
139
122
140
if ( ref . paramExpr ) {
123
- scope . $watch ( ref . paramExpr , function ( newVal , oldVal ) {
124
- if ( newVal !== params ) update ( newVal ) ;
125
- } , true ) ;
126
- params = angular . copy ( scope . $eval ( ref . paramExpr ) ) ;
141
+ scope . $watch ( ref . paramExpr , function ( val ) { if ( val !== def . params ) update ( val ) ; } , true ) ;
142
+ def . params = angular . copy ( scope . $eval ( ref . paramExpr ) ) ;
127
143
}
128
144
update ( ) ;
129
145
130
- if ( isForm ) return ;
131
-
132
- element . bind ( "click" , function ( e ) {
133
- var button = e . which || e . button ;
134
- if ( ! ( button > 1 || e . ctrlKey || e . metaKey || e . shiftKey || element . attr ( 'target' ) ) ) {
135
- // HACK: This is to allow ng-clicks to be processed before the transition is initiated:
136
- var transition = $timeout ( function ( ) {
137
- $state . go ( ref . state , params , options ) ;
138
- } ) ;
139
- e . preventDefault ( ) ;
140
-
141
- // if the state has no URL, ignore one preventDefault from the <a> directive.
142
- var ignorePreventDefaultCount = isAnchor && ! newHref ? 1 : 0 ;
143
- e . preventDefault = function ( ) {
144
- if ( ignorePreventDefaultCount -- <= 0 )
145
- $timeout . cancel ( transition ) ;
146
- } ;
147
- }
148
- } ) ;
146
+ if ( ! type . clickable ) return ;
147
+ element . bind ( "click" , clickHook ( element , $state , $timeout , type , function ( ) { return def ; } ) ) ;
149
148
}
150
149
} ;
151
150
}
152
151
152
+ /**
153
+ * @ngdoc directive
154
+ * @name ui.router.state.directive:ui-state
155
+ *
156
+ * @requires ui.router.state.uiSref
157
+ *
158
+ * @restrict A
159
+ *
160
+ * @description
161
+ * Much like ui-sref, but will accept named $scope properties to evaluate for a state definition,
162
+ * params and override options.
163
+ *
164
+ * @param {string } ui-state 'stateName' can be any valid absolute or relative state
165
+ * @param {Object } ui-state-params params to pass to {@link ui.router.state.$state#href $state.href()}
166
+ * @param {Object } ui-state-opts options to pass to {@link ui.router.state.$state#go $state.go()}
167
+ */
168
+ $StateRefDynamicDirective . $inject = [ '$state' , '$timeout' ] ;
169
+ function $StateRefDynamicDirective ( $state , $timeout ) {
170
+ return {
171
+ restrict : 'A' ,
172
+ require : [ '?^uiSrefActive' , '?^uiSrefActiveEq' ] ,
173
+ link : function ( scope , element , attrs , uiSrefActive ) {
174
+ var type = getTypeInfo ( element ) ;
175
+ var active = uiSrefActive [ 1 ] || uiSrefActive [ 0 ] ;
176
+ var group = [ attrs . uiState , attrs . uiStateParams || null , attrs . uiStateOpts || null ] ;
177
+ var watch = '[' + group . map ( function ( val ) { return val || 'null' ; } ) . join ( ', ' ) + ']' ;
178
+ var def = { state : null , params : null , options : null , href : null } ;
179
+
180
+ function runStateRefLink ( group ) {
181
+ def . state = group [ 0 ] ; def . params = group [ 1 ] ; def . options = group [ 2 ] ;
182
+ def . href = $state . href ( def . state , def . params , def . options ) ;
183
+
184
+ if ( active ) active . $$addStateInfo ( ref . state , def . params ) ;
185
+ if ( def . href ) attrs . $set ( type . attr , def . href ) ;
186
+ }
187
+
188
+ scope . $watch ( watch , runStateRefLink , true ) ;
189
+ runStateRefLink ( scope . $eval ( watch ) ) ;
190
+
191
+ if ( ! type . clickable ) return ;
192
+ element . bind ( "click" , clickHook ( element , $state , $timeout , type , function ( ) { return def ; } ) ) ;
193
+ }
194
+ } ;
195
+ }
196
+
197
+
153
198
/**
154
199
* @ngdoc directive
155
200
* @name ui.router.state.directive:ui-sref-active
@@ -269,18 +314,15 @@ function $StateRefActiveDirective($state, $stateParams, $interpolate) {
269
314
}
270
315
271
316
function addClass ( el , className ) { $timeout ( function ( ) { el . addClass ( className ) ; } ) ; }
272
-
273
317
function removeClass ( el , className ) { el . removeClass ( className ) ; }
274
-
275
318
function anyMatch ( state , params ) { return $state . includes ( state . name , params ) ; }
276
-
277
319
function exactMatch ( state , params ) { return $state . is ( state . name , params ) ; }
278
-
279
320
} ]
280
321
} ;
281
322
}
282
323
283
324
angular . module ( 'ui.router.state' )
284
325
. directive ( 'uiSref' , $StateRefDirective )
285
326
. directive ( 'uiSrefActive' , $StateRefActiveDirective )
286
- . directive ( 'uiSrefActiveEq' , $StateRefActiveDirective ) ;
327
+ . directive ( 'uiSrefActiveEq' , $StateRefActiveDirective )
328
+ . directive ( 'uiState' , $StateRefDynamicDirective ) ;
0 commit comments