Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Commit

Permalink
fix(ngClass): handle ngClassOdd/Even affecting the same classes
Browse files Browse the repository at this point in the history
The basic approach is to introduce a new elt.data() called $classCounts that keeps
track of how many times ngClass, ngClassEven, or ngClassOdd tries to add a given class.
The class is added only when the count goes from 0 to 1, and removed only when the
count hits 0.

To avoid duplicating work, some of the logic for checking which classes
to add/remove move into this directive and the directive calls $animate.

Closes #5271
  • Loading branch information
btford committed Apr 3, 2014
1 parent f4c08fe commit 55fe6d6
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 25 deletions.
104 changes: 79 additions & 25 deletions src/ng/directive/ngClass.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

function classDirective(name, selector) {
name = 'ngClass' + name;
return function() {
return ['$animate', function($animate) {
return {
restrict: 'AC',
link: function(scope, element, attr) {
Expand All @@ -20,46 +20,100 @@ function classDirective(name, selector) {
// jshint bitwise: false
var mod = $index & 1;
if (mod !== old$index & 1) {
var classes = flattenClasses(scope.$eval(attr[name]));
var classes = arrayClasses(scope.$eval(attr[name]));
mod === selector ?
attr.$addClass(classes) :
attr.$removeClass(classes);
addClasses(classes) :
removeClasses(classes);
}
});
}

function addClasses(classes) {
var newClasses = digestClassCounts(classes, 1);
attr.$addClass(newClasses);
}

function removeClasses(classes) {
var newClasses = digestClassCounts(classes, -1);
attr.$removeClass(newClasses);
}

function digestClassCounts (classes, count) {
var classCounts = element.data('$classCounts') || {};
var classesToUpdate = [];
forEach(classes, function (className) {
if (count > 0 || classCounts[className]) {
classCounts[className] = (classCounts[className] || 0) + count;
if (classCounts[className] === +(count > 0)) {
classesToUpdate.push(className);
}
}
});
element.data('$classCounts', classCounts);
return classesToUpdate.join(' ');
}

function updateClasses (oldClasses, newClasses) {
var toAdd = arrayDifference(newClasses, oldClasses);
var toRemove = arrayDifference(oldClasses, newClasses);
toRemove = digestClassCounts(toRemove, -1);
toAdd = digestClassCounts(toAdd, 1);

if (toAdd.length === 0) {
$animate.removeClass(element, toRemove);
} else if (toRemove.length === 0) {
$animate.addClass(element, toAdd);
} else {
$animate.setClass(element, toAdd, toRemove);
}
}

function ngClassWatchAction(newVal) {
if (selector === true || scope.$index % 2 === selector) {
var newClasses = flattenClasses(newVal || '');
if(!oldVal) {
attr.$addClass(newClasses);
} else if(!equals(newVal,oldVal)) {
attr.$updateClass(newClasses, flattenClasses(oldVal));
var newClasses = arrayClasses(newVal || []);
if (!oldVal) {
addClasses(newClasses);
} else if (!equals(newVal,oldVal)) {
var oldClasses = arrayClasses(oldVal);
updateClasses(oldClasses, newClasses);
}
}
oldVal = copy(newVal);
}
}
};

function arrayDifference(tokens1, tokens2) {
var values = [];

function flattenClasses(classVal) {
if(isArray(classVal)) {
return classVal.join(' ');
} else if (isObject(classVal)) {
var classes = [], i = 0;
forEach(classVal, function(v, k) {
if (v) {
classes.push(k);
}
});
return classes.join(' ');
}

return classVal;
outer:
for(var i = 0; i < tokens1.length; i++) {
var token = tokens1[i];
for(var j = 0; j < tokens2.length; j++) {
if(token == tokens2[j]) continue outer;
}
values.push(token);
}
};
};
return values;
}

function arrayClasses (classVal) {
if (isArray(classVal)) {
return classVal;
} else if (isString(classVal)) {
return classVal.split(' ');
} else if (isObject(classVal)) {
var classes = [], i = 0;
forEach(classVal, function(v, k) {
if (v) {
classes.push(k);
}
});
return classes;
}
return classVal;
}
}];
}

/**
Expand Down
13 changes: 13 additions & 0 deletions test/ng/directive/ngClassSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,19 @@ describe('ngClass', function() {
}));


it("should allow ngClassOdd/Even on the same element with overlapping classes", inject(function($rootScope, $compile, $animate) {
var className;

element = $compile('<ul><li ng-repeat="i in [0,1,2]" ng-class-odd="\'same odd\'" ng-class-even="\'same even\'"></li><ul>')($rootScope);
$rootScope.$digest();
var e1 = jqLite(element[0].childNodes[1]);
var e2 = jqLite(element[0].childNodes[5]);
expect(e1.hasClass('same')).toBeTruthy();
expect(e1.hasClass('odd')).toBeTruthy();
expect(e2.hasClass('same')).toBeTruthy();
expect(e2.hasClass('odd')).toBeTruthy();
}));

it('should allow both ngClass and ngClassOdd/Even with multiple classes', inject(function($rootScope, $compile) {
element = $compile('<ul>' +
'<li ng-repeat="i in [0,1]" ng-class="[\'A\', \'B\']" ' +
Expand Down

0 comments on commit 55fe6d6

Please sign in to comment.