@@ -14,6 +14,43 @@ function stateContext(el) {
1414 }
1515}
1616
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+
1754/**
1855 * @ngdoc directive
1956 * @name ui.router.state.directive:ui-sref
@@ -24,40 +61,40 @@ function stateContext(el) {
2461 * @restrict A
2562 *
2663 * @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.
3168 *
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
3370 * handled natively by the browser.
3471 *
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
3673 * 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
3875 * template containing the link.
3976 *
4077 * You can specify options to pass to {@link ui.router.state.$state#go $state.go()}
4178 * using the `ui-sref-opts` attribute. Options are restricted to `location`, `inherit`,
4279 * and `reload`.
4380 *
4481 * @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
4683 * following template:
4784 * <pre>
4885 * <a ui-sref="home">Home</a> | <a ui-sref="about">About</a> | <a ui-sref="{page: 2}">Next page</a>
49- *
86+ *
5087 * <ul>
5188 * <li ng-repeat="contact in contacts">
5289 * <a ui-sref="contacts.detail({ id: contact.id })">{{ contact.name }}</a>
5390 * </li>
5491 * </ul>
5592 * </pre>
56- *
93+ *
5794 * Then the compiled html would be (assuming Html5Mode is off and current state is contacts):
5895 * <pre>
5996 * <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+ *
6198 * <ul>
6299 * <li ng-repeat="contact in contacts">
63100 * <a href="#/contacts/1" ui-sref="contacts.detail({ id: contact.id })">Joe</a>
@@ -78,78 +115,86 @@ function stateContext(el) {
78115 */
79116$StateRefDirective . $inject = [ '$state' , '$timeout' ] ;
80117function $StateRefDirective ( $state , $timeout ) {
81- var allowedOptions = [ 'location' , 'inherit' , 'reload' , 'absolute' ] ;
82-
83118 return {
84119 restrict : 'A' ,
85120 require : [ '?^uiSrefActive' , '?^uiSrefActiveEq' ] ,
86121 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 ] ;
104126
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 ) : { } ) ;
108128
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 ;
110132
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 ) ;
120138 } ;
121139
122140 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 ) ) ;
127143 }
128144 update ( ) ;
129145
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 ; } ) ) ;
149148 }
150149 } ;
151150}
152151
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+
153198/**
154199 * @ngdoc directive
155200 * @name ui.router.state.directive:ui-sref-active
@@ -269,18 +314,15 @@ function $StateRefActiveDirective($state, $stateParams, $interpolate) {
269314 }
270315
271316 function addClass ( el , className ) { $timeout ( function ( ) { el . addClass ( className ) ; } ) ; }
272-
273317 function removeClass ( el , className ) { el . removeClass ( className ) ; }
274-
275318 function anyMatch ( state , params ) { return $state . includes ( state . name , params ) ; }
276-
277319 function exactMatch ( state , params ) { return $state . is ( state . name , params ) ; }
278-
279320 } ]
280321 } ;
281322}
282323
283324angular . module ( 'ui.router.state' )
284325 . directive ( 'uiSref' , $StateRefDirective )
285326 . directive ( 'uiSrefActive' , $StateRefActiveDirective )
286- . directive ( 'uiSrefActiveEq' , $StateRefActiveDirective ) ;
327+ . directive ( 'uiSrefActiveEq' , $StateRefActiveDirective )
328+ . directive ( 'uiState' , $StateRefDynamicDirective ) ;
0 commit comments