Skip to content

Commit 3d7f267

Browse files
joshuahigginschristopherthielen
authored andcommitted
feat(uiSrefActive): allow active & active-eq on same element
closes #1997
1 parent 6ae6fe2 commit 3d7f267

File tree

2 files changed

+66
-23
lines changed

2 files changed

+66
-23
lines changed

src/stateDirectives.ts

+22-21
Original file line numberDiff line numberDiff line change
@@ -222,13 +222,14 @@ $StateRefActiveDirective.$inject = ['$state', '$stateParams', '$interpolate'];
222222
function $StateRefActiveDirective($state, $stateParams, $interpolate) {
223223
return {
224224
restrict: "A",
225-
controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
226-
var states = [], activeClass;
225+
controller: ['$scope', '$element', '$attrs', '$timeout', function ($scope, $element, $attrs, $timeout) {
226+
var states = [], activeClass, activeEqClass;
227227

228228
// There probably isn't much point in $observing this
229229
// uiSrefActive and uiSrefActiveEq share the same directive object with some
230230
// slight difference in logic routing
231-
activeClass = $interpolate($attrs.uiSrefActiveEq || $attrs.uiSrefActive || '', false)($scope);
231+
activeClass = $interpolate($attrs.uiSrefActive || '', false)($scope);
232+
activeEqClass = $interpolate($attrs.uiSrefActiveEq || '', false)($scope);
232233

233234
// Allow uiSref to communicate with uiSrefActive[Equals]
234235
this.$$addStateInfo = function (newState, newParams) {
@@ -246,34 +247,34 @@ function $StateRefActiveDirective($state, $stateParams, $interpolate) {
246247

247248
// Update route state
248249
function update() {
249-
if (anyMatch()) {
250-
$element.addClass(activeClass);
251-
} else {
252-
$element.removeClass(activeClass);
253-
}
254-
}
255-
256-
function anyMatch() {
257250
for (var i = 0; i < states.length; i++) {
258-
if (isMatch(states[i].state, states[i].params)) {
259-
return true;
251+
if (anyMatch(states[i].state, states[i].params)) {
252+
addClass($element, activeClass);
253+
} else {
254+
removeClass($element, activeClass);
260255
}
261-
}
262-
return false;
263-
}
264256

265-
function isMatch(state, params) {
266-
if (typeof $attrs.uiSrefActiveEq !== 'undefined') {
267-
return $state.is(state.name, params);
268-
} else {
269-
return $state.includes(state.name, params);
257+
if (exactMatch(states[i].state, states[i].params)) {
258+
addClass($element, activeEqClass);
259+
} else {
260+
removeClass($element, activeEqClass);
261+
}
270262
}
271263
return $state.includes(state.name) && matchesParams();
272264
}
273265

274266
function matchesParams() {
275267
return !params || equalForKeys(params, $stateParams);
276268
}
269+
270+
function addClass(el, className) { $timeout(function () { el.addClass(className); }); }
271+
272+
function removeClass(el, className) { el.removeClass(className); }
273+
274+
function anyMatch(state, params) { return $state.includes(state.name, params); }
275+
276+
function exactMatch(state, params) { return $state.is(state.name, params); }
277+
277278
}]
278279
};
279280
}

test/stateDirectivesSpec.js

+44-2
Original file line numberDiff line numberDiff line change
@@ -426,8 +426,16 @@ describe('uiSrefActive', function() {
426426
});
427427
}));
428428

429-
beforeEach(inject(function($document) {
429+
beforeEach(inject(function($document, $timeout) {
430430
document = $document[0];
431+
timeoutFlush = function () {
432+
try {
433+
$timeout.flush();
434+
} catch (e) {
435+
// Angular 1.0.8 throws 'No deferred tasks to be flushed' if there is nothing in queue.
436+
// Behave as Angular >=1.1.5 and do nothing in such case.
437+
}
438+
}
431439
}));
432440

433441
it('should update class for sibling uiSref', inject(function($rootScope, $q, $compile, $state) {
@@ -438,11 +446,12 @@ describe('uiSrefActive', function() {
438446
expect(angular.element(template[0].querySelector('a')).attr('class')).toBe('');
439447
$state.transitionTo('contacts.item', { id: 1 });
440448
$q.flush();
441-
449+
timeoutFlush();
442450
expect(angular.element(template[0].querySelector('a')).attr('class')).toBe('active');
443451

444452
$state.transitionTo('contacts.item', { id: 2 });
445453
$q.flush();
454+
timeoutFlush();
446455
expect(angular.element(template[0].querySelector('a')).attr('class')).toBe('');
447456
}));
448457

@@ -454,10 +463,12 @@ describe('uiSrefActive', function() {
454463
expect(angular.element(template[0].querySelector('a')).attr('class')).toBe('');
455464
$state.transitionTo('contacts.item.detail', { id: 5, foo: 'bar' });
456465
$q.flush();
466+
timeoutFlush();
457467
expect(angular.element(template[0].querySelector('a')).attr('class')).toBe('active');
458468

459469
$state.transitionTo('contacts.item.detail', { id: 5, foo: 'baz' });
460470
$q.flush();
471+
timeoutFlush();
461472
expect(angular.element(template[0].querySelector('a')).attr('class')).toBe('');
462473
}));
463474

@@ -468,10 +479,12 @@ describe('uiSrefActive', function() {
468479

469480
$state.transitionTo('contacts.item.edit', { id: 1 });
470481
$q.flush();
482+
timeoutFlush();
471483
expect(a.attr('class')).toMatch(/active/);
472484

473485
$state.transitionTo('contacts.item.edit', { id: 4 });
474486
$q.flush();
487+
timeoutFlush();
475488
expect(a.attr('class')).not.toMatch(/active/);
476489
}));
477490

@@ -482,28 +495,51 @@ describe('uiSrefActive', function() {
482495

483496
$state.transitionTo('contacts.item', { id: 1 });
484497
$q.flush();
498+
timeoutFlush();
485499
expect(a.attr('class')).toMatch(/active/);
486500

487501
$state.transitionTo('contacts.item.edit', { id: 1 });
488502
$q.flush();
503+
timeoutFlush();
489504
expect(a.attr('class')).not.toMatch(/active/);
490505
}));
491506

507+
it('should match on child states when active-equals and active-equals-eq is used', inject(function($rootScope, $q, $compile, $state, $timeout) {
508+
template = $compile('<div><a ui-sref="contacts.item({ id: 1 })" ui-sref-active="active" ui-sref-active-eq="active-eq">Contacts</a></div>')($rootScope);
509+
$rootScope.$digest();
510+
var a = angular.element(template[0].getElementsByTagName('a')[0]);
511+
512+
$state.transitionTo('contacts.item', { id: 1 });
513+
$q.flush();
514+
timeoutFlush();
515+
expect(a.attr('class')).toMatch(/active/);
516+
expect(a.attr('class')).toMatch(/active-eq/);
517+
518+
$state.transitionTo('contacts.item.edit', { id: 1 });
519+
$q.flush();
520+
timeoutFlush();
521+
expect(a.attr('class')).toMatch(/active/);
522+
expect(a.attr('class')).not.toMatch(/active-eq/);
523+
}));
524+
492525
it('should resolve relative state refs', inject(function($rootScope, $q, $compile, $state) {
493526
el = angular.element('<section><div ui-view></div></section>');
494527
template = $compile(el)($rootScope);
495528
$rootScope.$digest();
496529

497530
$state.transitionTo('contacts');
498531
$q.flush();
532+
timeoutFlush();
499533
expect(angular.element(template[0].querySelector('a')).attr('class')).toBe('ng-scope');
500534

501535
$state.transitionTo('contacts.item', { id: 6 });
502536
$q.flush();
537+
timeoutFlush();
503538
expect(angular.element(template[0].querySelector('a')).attr('class')).toBe('ng-scope active');
504539

505540
$state.transitionTo('contacts.item', { id: 5 });
506541
$q.flush();
542+
timeoutFlush();
507543
expect(angular.element(template[0].querySelector('a')).attr('class')).toBe('ng-scope');
508544
}));
509545

@@ -516,10 +552,12 @@ describe('uiSrefActive', function() {
516552

517553
$state.transitionTo('contacts.item', { id: 1 });
518554
$q.flush();
555+
timeoutFlush();
519556
expect(angular.element(template[0]).attr('class')).toBe('ng-scope active');
520557

521558
$state.transitionTo('contacts.item', { id: 2 });
522559
$q.flush();
560+
timeoutFlush();
523561
expect(angular.element(template[0]).attr('class')).toBe('ng-scope active');
524562
}));
525563

@@ -534,10 +572,12 @@ describe('uiSrefActive', function() {
534572

535573
$state.transitionTo('contacts.item', { id: 1 });
536574
$q.flush();
575+
timeoutFlush();
537576
expect(angular.element(template[0].querySelector('a')).attr('class')).toBe('');
538577

539578
$state.transitionTo('contacts.lazy');
540579
$q.flush();
580+
timeoutFlush();
541581
expect(angular.element(template[0].querySelector('a')).attr('class')).toBe('active');
542582
}));
543583

@@ -552,10 +592,12 @@ describe('uiSrefActive', function() {
552592

553593
$state.transitionTo('contacts.item', { id: 1 });
554594
$q.flush();
595+
timeoutFlush();
555596
expect(angular.element(template[0].querySelector('a')).attr('class')).toBe('');
556597

557598
$state.transitionTo('contacts.lazy');
558599
$q.flush();
600+
timeoutFlush();
559601
expect(angular.element(template[0].querySelector('a')).attr('class')).toBe('active');
560602
}));
561603
});

0 commit comments

Comments
 (0)