11/// <reference path='../../typings/angularjs/angular.d.ts' />
2- import { copy , defaults } from "../common/common" ;
2+ import { copy , defaults , isString , isObject , forEach , toJson } from "../common/common" ;
33import { defaultTransOpts } from "../transition/transitionService" ;
44
55function parseStateRef ( ref , current ) {
@@ -199,6 +199,24 @@ function $StateRefDirective($state, $timeout) {
199199 * </li>
200200 * </ul>
201201 * </pre>
202+ *
203+ * It is also possible to pass ui-sref-active an expression that evaluates
204+ * to an object hash, whose keys represent active class names and whose
205+ * values represent the respective state names/globs.
206+ * ui-sref-active will match if the current active state **includes** any of
207+ * the specified state names/globs, even the abstract ones.
208+ *
209+ * @Example
210+ * Given the following template, with "admin" being an abstract state:
211+ * <pre>
212+ * <div ui-sref-active="{'active': 'admin.*'}">
213+ * <a ui-sref-active="active" ui-sref="admin.roles">Roles</a>
214+ * </div>
215+ * </pre>
216+ *
217+ * When the current state is "admin.roles" the "active" class will be applied
218+ * to both the <div> and <a> elements. It is important to note that the state
219+ * names/globs passed to ui-sref-active shadow the state provided by ui-sref.
202220 */
203221
204222/**
@@ -221,37 +239,74 @@ function $StateRefActiveDirective($state, $stateParams, $interpolate) {
221239 return {
222240 restrict : "A" ,
223241 controller : [ '$scope' , '$element' , '$attrs' , '$timeout' , '$transitions' , function ( $scope , $element , $attrs , $timeout , $transitions ) {
224- let states = [ ] , activeClass , activeEqClass ;
242+ let states = [ ] , activeClasses = { } , activeEqClass ;
225243
226244 // There probably isn't much point in $observing this
227245 // uiSrefActive and uiSrefActiveEq share the same directive object with some
228246 // slight difference in logic routing
229- activeClass = $interpolate ( $attrs . uiSrefActive || '' , false ) ( $scope ) ;
230247 activeEqClass = $interpolate ( $attrs . uiSrefActiveEq || '' , false ) ( $scope ) ;
231248
249+ var uiSrefActive = $scope . $eval ( $attrs . uiSrefActive ) || $interpolate ( $attrs . uiSrefActive || '' , false ) ( $scope ) ;
250+ if ( isObject ( uiSrefActive ) ) {
251+ forEach ( uiSrefActive , function ( stateOrName , activeClass ) {
252+ if ( isString ( stateOrName ) ) {
253+ var ref = parseStateRef ( stateOrName , $state . current . name ) ;
254+ addState ( ref . state , $scope . $eval ( ref . paramExpr ) , activeClass ) ;
255+ }
256+ } ) ;
257+ }
258+
232259 // Allow uiSref to communicate with uiSrefActive[Equals]
233260 this . $$addStateInfo = function ( newState , newParams ) {
234- let state = $state . get ( newState , stateContext ( $element ) ) ;
261+ // we already got an explicit state provided by ui-sref-active, so we
262+ // shadow the one that comes from ui-sref
263+ if ( isObject ( uiSrefActive ) && states . length > 0 ) {
264+ return ;
265+ }
266+ addState ( newState , newParams , uiSrefActive ) ;
267+ update ( ) ;
268+ } ;
269+
270+ $scope . $on ( '$stateChangeSuccess' , update ) ;
271+
272+ function addState ( stateName , stateParams , activeClass ) {
273+ var state = $state . get ( stateName , stateContext ( $element ) ) ;
274+ var stateHash = createStateHash ( stateName , stateParams ) ;
235275
236276 states . push ( {
237- state : state || { name : newState } ,
238- params : newParams
277+ state : state || { name : stateName } ,
278+ params : stateParams ,
279+ hash : stateHash
239280 } ) ;
240281
241- update ( ) ;
242- } ;
282+ activeClasses [ stateHash ] = activeClass ;
283+ }
243284
244285 let updateAfterTransition = function ( $transition$ ) { $transition$ . promise . then ( update ) ; } ;
245286 let deregisterFn = $transitions . onStart ( { } , updateAfterTransition ) ;
246287 $scope . $on ( '$destroy' , deregisterFn ) ;
247288
289+ function createStateHash ( state , params ) {
290+ if ( ! isString ( state ) ) {
291+ throw new Error ( 'state should be a string' ) ;
292+ }
293+ if ( isObject ( params ) ) {
294+ return state + toJson ( params ) ;
295+ }
296+ params = $scope . $eval ( params ) ;
297+ if ( isObject ( params ) ) {
298+ return state + toJson ( params ) ;
299+ }
300+ return state ;
301+ }
302+
248303 // Update route state
249304 function update ( ) {
250305 for ( let i = 0 ; i < states . length ; i ++ ) {
251306 if ( anyMatch ( states [ i ] . state , states [ i ] . params ) ) {
252- addClass ( $element , activeClass ) ;
307+ addClass ( $element , activeClasses [ states [ i ] . hash ] ) ;
253308 } else {
254- removeClass ( $element , activeClass ) ;
309+ removeClass ( $element , activeClasses [ states [ i ] . hash ] ) ;
255310 }
256311
257312 if ( exactMatch ( states [ i ] . state , states [ i ] . params ) ) {
0 commit comments