Skip to content

feat(uiSrefActive): allow active & active-eq on same element #1999

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 22 additions & 21 deletions src/stateDirectives.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,13 +228,14 @@ $StateRefActiveDirective.$inject = ['$state', '$stateParams', '$interpolate'];
function $StateRefActiveDirective($state, $stateParams, $interpolate) {
return {
restrict: "A",
controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
var states = [], activeClass;
controller: ['$scope', '$element', '$attrs', '$timeout', function ($scope, $element, $attrs, $timeout) {
var states = [], activeClass, activeEqClass;

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

// Allow uiSref to communicate with uiSrefActive[Equals]
this.$$addStateInfo = function (newState, newParams) {
Expand All @@ -252,29 +253,29 @@ function $StateRefActiveDirective($state, $stateParams, $interpolate) {

// Update route state
function update() {
if (anyMatch()) {
$element.addClass(activeClass);
} else {
$element.removeClass(activeClass);
}
}

function anyMatch() {
for (var i = 0; i < states.length; i++) {
if (isMatch(states[i].state, states[i].params)) {
return true;
if (anyMatch(states[i].state, states[i].params)) {
addClass($element, activeClass);
} else {
removeClass($element, activeClass);
}
}
return false;
}

function isMatch(state, params) {
if (typeof $attrs.uiSrefActiveEq !== 'undefined') {
return $state.is(state.name, params);
} else {
return $state.includes(state.name, params);
if (exactMatch(states[i].state, states[i].params)) {
addClass($element, activeEqClass);
} else {
removeClass($element, activeEqClass);
}
}
}

function addClass(el, className) { $timeout(function() { el.addClass(className); }); }

function removeClass(el, className) { el.removeClass(className); }

function anyMatch(state, params) { return $state.includes(state.name, params); }

function exactMatch(state, params) { return $state.is(state.name, params); }

}]
};
}
Expand Down
51 changes: 43 additions & 8 deletions test/stateDirectivesSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -420,100 +420,131 @@ describe('uiSrefActive', function() {
document = $document[0];
}));

it('should update class for sibling uiSref', inject(function($rootScope, $q, $compile, $state) {
it('should update class for sibling uiSref', inject(function($rootScope, $q, $compile, $state, $timeout) {
el = angular.element('<div><a ui-sref="contacts.item({ id: 1 })" ui-sref-active="active">Contacts</a><a ui-sref="contacts.item({ id: 2 })" ui-sref-active="active">Contacts</a></div>');
template = $compile(el)($rootScope);
$rootScope.$digest();

expect(angular.element(template[0].querySelector('a')).attr('class')).toBe('');
$state.transitionTo('contacts.item', { id: 1 });
$timeout.flush();
$q.flush();

expect(angular.element(template[0].querySelector('a')).attr('class')).toBe('active');

$state.transitionTo('contacts.item', { id: 2 });
$timeout.flush();
$q.flush();
expect(angular.element(template[0].querySelector('a')).attr('class')).toBe('');
}));

it('should match state\'s parameters', inject(function($rootScope, $q, $compile, $state) {
it('should match state\'s parameters', inject(function($rootScope, $q, $compile, $state, $timeout) {
el = angular.element('<div><a ui-sref="contacts.item.detail({ foo: \'bar\' })" ui-sref-active="active">Contacts</a></div>');
template = $compile(el)($rootScope);
$rootScope.$digest();

expect(angular.element(template[0].querySelector('a')).attr('class')).toBe('');
$state.transitionTo('contacts.item.detail', { id: 5, foo: 'bar' });
$timeout.flush();
$q.flush();
expect(angular.element(template[0].querySelector('a')).attr('class')).toBe('active');

$state.transitionTo('contacts.item.detail', { id: 5, foo: 'baz' });
$timeout.flush();
$q.flush();
expect(angular.element(template[0].querySelector('a')).attr('class')).toBe('');
}));

it('should match on child states', inject(function($rootScope, $q, $compile, $state) {
it('should match on child states', inject(function($rootScope, $q, $compile, $state, $timeout) {
template = $compile('<div><a ui-sref="contacts.item({ id: 1 })" ui-sref-active="active">Contacts</a></div>')($rootScope);
$rootScope.$digest();
var a = angular.element(template[0].getElementsByTagName('a')[0]);

$state.transitionTo('contacts.item.edit', { id: 1 });
$timeout.flush();
$q.flush();
expect(a.attr('class')).toMatch(/active/);

$state.transitionTo('contacts.item.edit', { id: 4 });
$timeout.flush();
$q.flush();
expect(a.attr('class')).not.toMatch(/active/);
}));

it('should NOT match on child states when active-equals is used', inject(function($rootScope, $q, $compile, $state) {
it('should NOT match on child states when active-equals is used', inject(function($rootScope, $q, $compile, $state, $timeout) {
template = $compile('<div><a ui-sref="contacts.item({ id: 1 })" ui-sref-active-eq="active">Contacts</a></div>')($rootScope);
$rootScope.$digest();
var a = angular.element(template[0].getElementsByTagName('a')[0]);

$state.transitionTo('contacts.item', { id: 1 });
$timeout.flush();
$q.flush();
expect(a.attr('class')).toMatch(/active/);

$state.transitionTo('contacts.item.edit', { id: 1 });
$timeout.flush();
$q.flush();
expect(a.attr('class')).not.toMatch(/active/);
}));

it('should resolve relative state refs', inject(function($rootScope, $q, $compile, $state) {
it('should match on child states when active-equals and active-equals-eq is used', inject(function($rootScope, $q, $compile, $state, $timeout) {
template = $compile('<div><a ui-sref="contacts.item({ id: 1 })" ui-sref-active="active" ui-sref-active-eq="active-eq">Contacts</a></div>')($rootScope);
$rootScope.$digest();
var a = angular.element(template[0].getElementsByTagName('a')[0]);

$state.transitionTo('contacts.item', { id: 1 });
$timeout.flush();
$q.flush();
expect(a.attr('class')).toMatch(/active/);
expect(a.attr('class')).toMatch(/active-eq/);

$state.transitionTo('contacts.item.edit', { id: 1 });
$timeout.flush();
$q.flush();
expect(a.attr('class')).toMatch(/active/);
expect(a.attr('class')).not.toMatch(/active-eq/);
}));

it('should resolve relative state refs', inject(function($rootScope, $q, $compile, $state, $timeout) {
el = angular.element('<section><div ui-view></div></section>');
template = $compile(el)($rootScope);
$rootScope.$digest();

$state.transitionTo('contacts');
$timeout.flush();
$q.flush();
expect(angular.element(template[0].querySelector('a')).attr('class')).toBe('ng-scope');

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

$state.transitionTo('contacts.item', { id: 5 });
$timeout.flush();
$q.flush();
expect(angular.element(template[0].querySelector('a')).attr('class')).toBe('ng-scope');
}));

it('should match on any child state refs', inject(function($rootScope, $q, $compile, $state) {
it('should match on any child state refs', inject(function($rootScope, $q, $compile, $state, $timeout) {
el = angular.element('<div ui-sref-active="active"><a ui-sref="contacts.item({ id: 1 })">Contacts</a><a ui-sref="contacts.item({ id: 2 })">Contacts</a></div>');
template = $compile(el)($rootScope);
$rootScope.$digest();

expect(angular.element(template[0]).attr('class')).toBe('ng-scope');

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

$state.transitionTo('contacts.item', { id: 2 });
$timeout.flush();
$q.flush();
expect(angular.element(template[0]).attr('class')).toBe('ng-scope active');
}));

it('should match fuzzy on lazy loaded states', inject(function($rootScope, $q, $compile, $state) {
it('should match fuzzy on lazy loaded states', inject(function($rootScope, $q, $compile, $state, $timeout) {
el = angular.element('<div><a ui-sref="contacts.lazy" ui-sref-active="active">Lazy Contact</a></div>');
template = $compile(el)($rootScope);
$rootScope.$digest();
Expand All @@ -523,15 +554,17 @@ describe('uiSrefActive', function() {
});

$state.transitionTo('contacts.item', { id: 1 });
$timeout.flush();
$q.flush();
expect(angular.element(template[0].querySelector('a')).attr('class')).toBe('');

$state.transitionTo('contacts.lazy');
$timeout.flush();
$q.flush();
expect(angular.element(template[0].querySelector('a')).attr('class')).toBe('active');
}));

it('should match exactly on lazy loaded states', inject(function($rootScope, $q, $compile, $state) {
it('should match exactly on lazy loaded states', inject(function($rootScope, $q, $compile, $state, $timeout) {
el = angular.element('<div><a ui-sref="contacts.lazy" ui-sref-active-eq="active">Lazy Contact</a></div>');
template = $compile(el)($rootScope);
$rootScope.$digest();
Expand All @@ -541,10 +574,12 @@ describe('uiSrefActive', function() {
});

$state.transitionTo('contacts.item', { id: 1 });
$timeout.flush();
$q.flush();
expect(angular.element(template[0].querySelector('a')).attr('class')).toBe('');

$state.transitionTo('contacts.lazy');
$timeout.flush();
$q.flush();
expect(angular.element(template[0].querySelector('a')).attr('class')).toBe('active');
}));
Expand Down