11/**
22 * State-based routing for AngularJS
3- * @version v0.2.5
3+ * @version v0.2.6
44 * @link http://angular-ui.github.com/
55 * @license MIT License, http://www.opensource.org/licenses/MIT
66 */
@@ -57,6 +57,24 @@ function ancestors(first, second) {
5757 return path ;
5858}
5959
60+ /**
61+ * IE8-safe wrapper for `Object.keys()`.
62+ *
63+ * @param {Object } object A JavaScript object.
64+ * @return {Array } Returns the keys of the object as an array.
65+ */
66+ function keys ( object ) {
67+ if ( Object . keys ) {
68+ return Object . keys ( object ) ;
69+ }
70+ var result = [ ] ;
71+
72+ angular . forEach ( object , function ( val , key ) {
73+ result . push ( key ) ;
74+ } ) ;
75+ return result ;
76+ }
77+
6078/**
6179 * IE8-safe wrapper for `Array.prototype.indexOf()`.
6280 *
@@ -104,6 +122,61 @@ function inheritParams(currentParams, newParams, $current, $to) {
104122 return extend ( { } , inherited , newParams ) ;
105123}
106124
125+ /**
126+ * Normalizes a set of values to string or `null`, filtering them by a list of keys.
127+ *
128+ * @param {Array } keys The list of keys to normalize/return.
129+ * @param {Object } values An object hash of values to normalize.
130+ * @return {Object } Returns an object hash of normalized string values.
131+ */
132+ function normalize ( keys , values ) {
133+ var normalized = { } ;
134+
135+ forEach ( keys , function ( name ) {
136+ var value = values [ name ] ;
137+ normalized [ name ] = ( value != null ) ? String ( value ) : null ;
138+ } ) ;
139+ return normalized ;
140+ }
141+
142+ /**
143+ * Performs a non-strict comparison of the subset of two objects, defined by a list of keys.
144+ *
145+ * @param {Object } a The first object.
146+ * @param {Object } b The second object.
147+ * @param {Array } keys The list of keys within each object to compare. If the list is empty or not specified,
148+ * it defaults to the list of keys in `a`.
149+ * @return {Boolean } Returns `true` if the keys match, otherwise `false`.
150+ */
151+ function equalForKeys ( a , b , keys ) {
152+ if ( ! keys ) {
153+ keys = [ ] ;
154+ for ( var n in a ) keys . push ( n ) ; // Used instead of Object.keys() for IE8 compatibility
155+ }
156+
157+ for ( var i = 0 ; i < keys . length ; i ++ ) {
158+ var k = keys [ i ] ;
159+ if ( a [ k ] != b [ k ] ) return false ; // Not '===', values aren't necessarily normalized
160+ }
161+ return true ;
162+ }
163+
164+ /**
165+ * Returns the subset of an object, based on a list of keys.
166+ *
167+ * @param {Array } keys
168+ * @param {Object } values
169+ * @return {Boolean } Returns a subset of `values`.
170+ */
171+ function filterByKeys ( keys , values ) {
172+ var filtered = { } ;
173+
174+ forEach ( keys , function ( name ) {
175+ filtered [ name ] = values [ name ] ;
176+ } ) ;
177+ return filtered ;
178+ }
179+
107180angular . module ( 'ui.router.util' , [ 'ng' ] ) ;
108181angular . module ( 'ui.router.router' , [ 'ui.router.util' ] ) ;
109182angular . module ( 'ui.router.state' , [ 'ui.router.router' , 'ui.router.util' ] ) ;
@@ -799,7 +872,7 @@ angular.module('ui.router.router').provider('$urlRouter', $UrlRouterProvider);
799872$StateProvider . $inject = [ '$urlRouterProvider' , '$urlMatcherFactoryProvider' , '$locationProvider' ] ;
800873function $StateProvider ( $urlRouterProvider , $urlMatcherFactory , $locationProvider ) {
801874
802- var root , states = { } , $state , queue = { } ;
875+ var root , states = { } , $state , queue = { } , abstractKey = 'abstract' ;
803876
804877 // Builds state properties from definition passed to registerState()
805878 var stateBuilder = {
@@ -976,10 +1049,10 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
9761049 states [ name ] = state ;
9771050
9781051 // Register the state in the global state list and with $urlRouter if necessary.
979- if ( ! state [ 'abstract' ] && state . url ) {
1052+ if ( ! state [ abstractKey ] && state . url ) {
9801053 $urlRouterProvider . when ( state . url , [ '$match' , '$stateParams' , function ( $match , $stateParams ) {
9811054 if ( $state . $current . navigable != state || ! equalForKeys ( $match , $stateParams ) ) {
982- $state . transitionTo ( state , $match , false ) ;
1055+ $state . transitionTo ( state , $match , { location : false } ) ;
9831056 }
9841057 } ] ) ;
9851058 }
@@ -1116,7 +1189,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
11161189 throw new Error ( "No such state '" + to + "'" ) ;
11171190 }
11181191 }
1119- if ( toState [ 'abstract' ] ) throw new Error ( "Cannot transition to abstract state '" + to + "'" ) ;
1192+ if ( toState [ abstractKey ] ) throw new Error ( "Cannot transition to abstract state '" + to + "'" ) ;
11201193 if ( options . inherit ) toParams = inheritParams ( $stateParams , toParams || { } , $state . $current , toState ) ;
11211194 to = toState ;
11221195
@@ -1286,13 +1359,13 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
12861359 return url ;
12871360 } ;
12881361
1289- $state . get = function ( stateOrName ) {
1362+ $state . get = function ( stateOrName , context ) {
12901363 if ( ! isDefined ( stateOrName ) ) {
12911364 var list = [ ] ;
12921365 forEach ( states , function ( state ) { list . push ( state . self ) ; } ) ;
12931366 return list ;
12941367 }
1295- var state = findState ( stateOrName ) ;
1368+ var state = findState ( stateOrName , context ) ;
12961369 return ( state && state . self ) ? state . self : null ;
12971370 } ;
12981371
@@ -1344,39 +1417,6 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
13441417 return $state ;
13451418 }
13461419
1347- function normalize ( keys , values ) {
1348- var normalized = { } ;
1349-
1350- forEach ( keys , function ( name ) {
1351- var value = values [ name ] ;
1352- normalized [ name ] = ( value != null ) ? String ( value ) : null ;
1353- } ) ;
1354- return normalized ;
1355- }
1356-
1357- function equalForKeys ( a , b , keys ) {
1358- // If keys not provided, assume keys from object 'a'
1359- if ( ! keys ) {
1360- keys = [ ] ;
1361- for ( var n in a ) keys . push ( n ) ; // Used instead of Object.keys() for IE8 compatibility
1362- }
1363-
1364- for ( var i = 0 ; i < keys . length ; i ++ ) {
1365- var k = keys [ i ] ;
1366- if ( a [ k ] != b [ k ] ) return false ; // Not '===', values aren't necessarily normalized
1367- }
1368- return true ;
1369- }
1370-
1371- function filterByKeys ( keys , values ) {
1372- var filtered = { } ;
1373-
1374- forEach ( keys , function ( name ) {
1375- filtered [ name ] = values [ name ] ;
1376- } ) ;
1377- return filtered ;
1378- }
1379-
13801420 function shouldTriggerReload ( to , from , locals , options ) {
13811421 if ( to === from && ( ( locals === from . locals && ! options . reload ) || ( to . self . reloadOnSearch === false ) ) ) {
13821422 return true ;
@@ -1420,9 +1460,7 @@ angular.module('ui.router.state').provider('$view', $ViewProvider);
14201460
14211461$ViewDirective . $inject = [ '$state' , '$compile' , '$controller' , '$injector' , '$anchorScroll' ] ;
14221462function $ViewDirective ( $state , $compile , $controller , $injector , $anchorScroll ) {
1423- // TODO: Change to $injector.has() when we version bump to Angular 1.1.5.
1424- // See: https://github.com/angular/angular.js/blob/master/CHANGELOG.md#115-triangle-squarification-2013-05-22
1425- var $animator ; try { $animator = $injector . get ( '$animator' ) ; } catch ( e ) { /* do nothing */ }
1463+ var $animator = $injector . has ( '$animator' ) ? $injector . get ( '$animator' ) : null ;
14261464 var viewIsUpdating = false ;
14271465
14281466 var directive = {
@@ -1435,7 +1473,7 @@ function $ViewDirective( $state, $compile, $controller, $injector, $an
14351473 var viewScope , viewLocals ,
14361474 name = attr [ directive . name ] || attr . name || '' ,
14371475 onloadExp = attr . onload || '' ,
1438- animate = isDefined ( $animator ) && $animator ( scope , attr ) ,
1476+ animate = $animator && $animator ( scope , attr ) ,
14391477 initialView = transclude ( scope ) ;
14401478
14411479 // Returns a set of DOM manipulation functions based on whether animation
@@ -1543,22 +1581,25 @@ function parseStateRef(ref) {
15431581 return { state : parsed [ 1 ] , paramExpr : parsed [ 3 ] || null } ;
15441582}
15451583
1546- $StateRefDirective . $inject = [ '$state' ] ;
1547- function $StateRefDirective ( $state ) {
1584+ function stateContext ( el ) {
1585+ var stateData = el . parent ( ) . inheritedData ( '$uiView' ) ;
1586+
1587+ if ( stateData && stateData . state && stateData . state . name ) {
1588+ return stateData . state ;
1589+ }
1590+ }
1591+
1592+ $StateRefDirective . $inject = [ '$state' , '$timeout' ] ;
1593+ function $StateRefDirective ( $state , $timeout ) {
15481594 return {
15491595 restrict : 'A' ,
1550- link : function ( scope , element , attrs ) {
1596+ require : '?^uiSrefActive' ,
1597+ link : function ( scope , element , attrs , uiSrefActive ) {
15511598 var ref = parseStateRef ( attrs . uiSref ) ;
1552- var params = null , url = null , base = $state . $current ;
1599+ var params = null , url = null , base = stateContext ( element ) || $state . $current ;
15531600 var isForm = element [ 0 ] . nodeName === "FORM" ;
15541601 var attr = isForm ? "action" : "href" , nav = true ;
15551602
1556- var stateData = element . parent ( ) . inheritedData ( '$uiView' ) ;
1557-
1558- if ( stateData && stateData . state && stateData . state . name ) {
1559- base = stateData . state ;
1560- }
1561-
15621603 var update = function ( newVal ) {
15631604 if ( newVal ) params = newVal ;
15641605 if ( ! nav ) return ;
@@ -1570,6 +1611,9 @@ function $StateRefDirective($state) {
15701611 return false ;
15711612 }
15721613 element [ 0 ] [ attr ] = newHref ;
1614+ if ( uiSrefActive ) {
1615+ uiSrefActive . $$setStateInfo ( ref . state , params ) ;
1616+ }
15731617 } ;
15741618
15751619 if ( ref . paramExpr ) {
@@ -1586,8 +1630,11 @@ function $StateRefDirective($state) {
15861630 var button = e . which || e . button ;
15871631
15881632 if ( ( button === 0 || button == 1 ) && ! e . ctrlKey && ! e . metaKey && ! e . shiftKey ) {
1589- scope . $evalAsync ( function ( ) {
1590- $state . go ( ref . state , params , { relative : base } ) ;
1633+ // HACK: This is to allow ng-clicks to be processed before the transition is initiated:
1634+ $timeout ( function ( ) {
1635+ scope . $apply ( function ( ) {
1636+ $state . go ( ref . state , params , { relative : base } ) ;
1637+ } ) ;
15911638 } ) ;
15921639 e . preventDefault ( ) ;
15931640 }
@@ -1596,7 +1643,44 @@ function $StateRefDirective($state) {
15961643 } ;
15971644}
15981645
1599- angular . module ( 'ui.router.state' ) . directive ( 'uiSref' , $StateRefDirective ) ;
1646+ $StateActiveDirective . $inject = [ '$state' , '$stateParams' , '$interpolate' ] ;
1647+ function $StateActiveDirective ( $state , $stateParams , $interpolate ) {
1648+ return {
1649+ restrict : "A" ,
1650+ controller : function ( $scope , $element , $attrs ) {
1651+ var state , params , activeClass ;
1652+
1653+ // There probably isn't much point in $observing this
1654+ activeClass = $interpolate ( $attrs . uiSrefActive || '' , false ) ( $scope ) ;
1655+
1656+ // Allow uiSref to communicate with uiSrefActive
1657+ this . $$setStateInfo = function ( newState , newParams ) {
1658+ state = $state . get ( newState , stateContext ( $element ) ) ;
1659+ params = newParams ;
1660+ update ( ) ;
1661+ } ;
1662+
1663+ $scope . $on ( '$stateChangeSuccess' , update ) ;
1664+
1665+ // Update route state
1666+ function update ( ) {
1667+ if ( $state . $current . self === state && matchesParams ( ) ) {
1668+ $element . addClass ( activeClass ) ;
1669+ } else {
1670+ $element . removeClass ( activeClass ) ;
1671+ }
1672+ }
1673+
1674+ function matchesParams ( ) {
1675+ return ! params || equalForKeys ( params , $stateParams ) ;
1676+ }
1677+ }
1678+ } ;
1679+ }
1680+
1681+ angular . module ( 'ui.router.state' )
1682+ . directive ( 'uiSref' , $StateRefDirective )
1683+ . directive ( 'uiSrefActive' , $StateActiveDirective ) ;
16001684
16011685$RouteProvider . $inject = [ '$stateProvider' , '$urlRouterProvider' ] ;
16021686function $RouteProvider ( $stateProvider , $urlRouterProvider ) {
0 commit comments