1
1
/// <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" ;
3
3
import { defaultTransOpts } from "../transition/transitionService" ;
4
4
5
5
function parseStateRef ( ref , current ) {
@@ -199,6 +199,24 @@ function $StateRefDirective($state, $timeout) {
199
199
* </li>
200
200
* </ul>
201
201
* </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.
202
220
*/
203
221
204
222
/**
@@ -221,37 +239,74 @@ function $StateRefActiveDirective($state, $stateParams, $interpolate) {
221
239
return {
222
240
restrict : "A" ,
223
241
controller : [ '$scope' , '$element' , '$attrs' , '$timeout' , '$transitions' , function ( $scope , $element , $attrs , $timeout , $transitions ) {
224
- let states = [ ] , activeClass , activeEqClass ;
242
+ let states = [ ] , activeClasses = { } , activeEqClass ;
225
243
226
244
// There probably isn't much point in $observing this
227
245
// uiSrefActive and uiSrefActiveEq share the same directive object with some
228
246
// slight difference in logic routing
229
- activeClass = $interpolate ( $attrs . uiSrefActive || '' , false ) ( $scope ) ;
230
247
activeEqClass = $interpolate ( $attrs . uiSrefActiveEq || '' , false ) ( $scope ) ;
231
248
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
+
232
259
// Allow uiSref to communicate with uiSrefActive[Equals]
233
260
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 ) ;
235
275
236
276
states . push ( {
237
- state : state || { name : newState } ,
238
- params : newParams
277
+ state : state || { name : stateName } ,
278
+ params : stateParams ,
279
+ hash : stateHash
239
280
} ) ;
240
281
241
- update ( ) ;
242
- } ;
282
+ activeClasses [ stateHash ] = activeClass ;
283
+ }
243
284
244
285
let updateAfterTransition = function ( $transition$ ) { $transition$ . promise . then ( update ) ; } ;
245
286
let deregisterFn = $transitions . onStart ( { } , updateAfterTransition ) ;
246
287
$scope . $on ( '$destroy' , deregisterFn ) ;
247
288
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
+
248
303
// Update route state
249
304
function update ( ) {
250
305
for ( let i = 0 ; i < states . length ; i ++ ) {
251
306
if ( anyMatch ( states [ i ] . state , states [ i ] . params ) ) {
252
- addClass ( $element , activeClass ) ;
307
+ addClass ( $element , activeClasses [ states [ i ] . hash ] ) ;
253
308
} else {
254
- removeClass ( $element , activeClass ) ;
309
+ removeClass ( $element , activeClasses [ states [ i ] . hash ] ) ;
255
310
}
256
311
257
312
if ( exactMatch ( states [ i ] . state , states [ i ] . params ) ) {
0 commit comments