Skip to content

Commit

Permalink
feat(uiSrefActive): allow active & active-eq on same element
Browse files Browse the repository at this point in the history
closes #1997
  • Loading branch information
joshuahiggins committed Jun 4, 2015
1 parent dba25db commit d9a676b
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 23 deletions.
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
46 changes: 44 additions & 2 deletions test/stateDirectivesSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -416,8 +416,16 @@ describe('uiSrefActive', function() {
});
}));

beforeEach(inject(function($document) {
beforeEach(inject(function($document, $timeout) {
document = $document[0];
timeoutFlush = function () {
try {
$timeout.flush();
} catch (e) {
// Angular 1.0.8 throws 'No deferred tasks to be flushed' if there is nothing in queue.
// Behave as Angular >=1.1.5 and do nothing in such case.
}
}
}));

it('should update class for sibling uiSref', inject(function($rootScope, $q, $compile, $state) {
Expand All @@ -428,11 +436,12 @@ describe('uiSrefActive', function() {
expect(angular.element(template[0].querySelector('a')).attr('class')).toBe('');
$state.transitionTo('contacts.item', { id: 1 });
$q.flush();

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

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

Expand All @@ -444,10 +453,12 @@ describe('uiSrefActive', function() {
expect(angular.element(template[0].querySelector('a')).attr('class')).toBe('');
$state.transitionTo('contacts.item.detail', { id: 5, foo: 'bar' });
$q.flush();
timeoutFlush();
expect(angular.element(template[0].querySelector('a')).attr('class')).toBe('active');

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

Expand All @@ -458,10 +469,12 @@ describe('uiSrefActive', function() {

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

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

Expand All @@ -472,28 +485,51 @@ describe('uiSrefActive', function() {

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

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

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 });
$q.flush();
timeoutFlush();
expect(a.attr('class')).toMatch(/active/);
expect(a.attr('class')).toMatch(/active-eq/);

$state.transitionTo('contacts.item.edit', { id: 1 });
$q.flush();
timeoutFlush();
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) {
el = angular.element('<section><div ui-view></div></section>');
template = $compile(el)($rootScope);
$rootScope.$digest();

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

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

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

Expand All @@ -506,10 +542,12 @@ describe('uiSrefActive', function() {

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

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

Expand All @@ -524,10 +562,12 @@ describe('uiSrefActive', function() {

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

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

Expand All @@ -542,10 +582,12 @@ describe('uiSrefActive', function() {

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

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

0 comments on commit d9a676b

Please sign in to comment.