aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/directives.js45
-rw-r--r--src/jqLite.js22
-rw-r--r--test/BinderSpec.js8
-rw-r--r--test/directivesSpec.js92
-rw-r--r--test/jqLiteSpec.js37
5 files changed, 167 insertions, 37 deletions
diff --git a/src/directives.js b/src/directives.js
index b3584a37..2e1040c0 100644
--- a/src/directives.js
+++ b/src/directives.js
@@ -549,16 +549,11 @@ angularDirective("ng:submit", function(expression, element) {
function ngClass(selector) {
return function(expression, element) {
- var existing = element[0].className + ' ';
return function(element) {
- this.$watch(function(scope) {
+ this.$watch(expression, function(scope, newVal, oldVal) {
if (selector(scope.$index)) {
- var ngClassVal = scope.$eval(element.attr('ng:class'));
- if (isArray(ngClassVal)) ngClassVal = ngClassVal.join(' ');
- var value = scope.$eval(expression);
- if (isArray(value)) value = value.join(' ');
- if (ngClassVal && ngClassVal !== value) value = value + ' ' + ngClassVal;
- element[0].className = trim(existing + value);
+ element.removeClass(isArray(oldVal) ? oldVal.join(' ') : oldVal)
+ element.addClass(isArray(newVal) ? newVal.join(' ') : newVal);
}
});
};
@@ -571,11 +566,17 @@ function ngClass(selector) {
* @name angular.directive.ng:class
*
* @description
- * The `ng:class` allows you to set CSS class on HTML element
- * conditionally.
+ * The `ng:class` allows you to set CSS class on HTML element dynamically by databinding an
+ * expression that represents all classes to be added.
+ *
+ * The directive won't add duplicate classes if a particular class was already set.
+ *
+ * When the expression changes, the previously added classes are removed and only then the classes
+ * new classes are added.
*
* @element ANY
- * @param {expression} expression {@link guide/dev_guide.expressions Expression} to eval.
+ * @param {expression} expression {@link guide/dev_guide.expressions Expression} to eval. The result
+ * of the evaluation can be a string representing space delimited class names or an array.
*
* @example
<doc:example>
@@ -612,12 +613,15 @@ angularDirective("ng:class", ngClass(function(){return true;}));
*
* @description
* The `ng:class-odd` and `ng:class-even` works exactly as
- * `ng:class`, except it works in conjunction with `ng:repeat`
- * and takes affect only on odd (even) rows.
+ * {@link angular.directive.ng:class ng:class}, except it works in conjunction with `ng:repeat` and
+ * takes affect only on odd (even) rows.
+ *
+ * This directive can be applied only within a scope of an
+ * {@link angular.widget.@ng:repeat ng:repeat}.
*
* @element ANY
- * @param {expression} expression {@link guide/dev_guide.expressions Expression} to eval. Must be
- * inside `ng:repeat`.
+ * @param {expression} expression {@link guide/dev_guide.expressions Expression} to eval. The result
+ * of the evaluation can be a string representing space delimited class names or an array.
*
* @example
<doc:example>
@@ -650,12 +654,15 @@ angularDirective("ng:class-odd", ngClass(function(i){return i % 2 === 0;}));
*
* @description
* The `ng:class-odd` and `ng:class-even` works exactly as
- * `ng:class`, except it works in conjunction with `ng:repeat`
- * and takes affect only on odd (even) rows.
+ * {@link angular.directive.ng:class ng:class}, except it works in conjunction with `ng:repeat` and
+ * takes affect only on odd (even) rows.
+ *
+ * This directive can be applied only within a scope of an
+ * {@link angular.widget.@ng:repeat ng:repeat}.
*
* @element ANY
- * @param {expression} expression {@link guide/dev_guide.expressions Expression} to eval. Must be
- * inside `ng:repeat`.
+ * @param {expression} expression {@link guide/dev_guide.expressions Expression} to eval. The result
+ * of the evaluation can be a string representing space delimited class names or an array.
*
* @example
<doc:example>
diff --git a/src/jqLite.js b/src/jqLite.js
index 8cef64da..122684a5 100644
--- a/src/jqLite.js
+++ b/src/jqLite.js
@@ -155,16 +155,24 @@ function JQLiteHasClass(element, selector, _) {
}
function JQLiteRemoveClass(element, selector) {
- element.className = trim(
- (" " + element.className + " ")
- .replace(/[\n\t]/g, " ")
- .replace(" " + selector + " ", " ")
- );
+ if (selector) {
+ forEach(selector.split(' '), function(cssClass) {
+ element.className = trim(
+ (" " + element.className + " ")
+ .replace(/[\n\t]/g, " ")
+ .replace(" " + trim(cssClass) + " ", " ")
+ );
+ });
+ }
}
function JQLiteAddClass(element, selector) {
- if (selector && !JQLiteHasClass(element, selector)) {
- element.className = trim(element.className + ' ' + selector);
+ if (selector) {
+ forEach(selector.split(' '), function(cssClass) {
+ if (!JQLiteHasClass(element, cssClass)) {
+ element.className = trim(element.className + ' ' + trim(cssClass));
+ }
+ });
}
}
diff --git a/test/BinderSpec.js b/test/BinderSpec.js
index ecfe0682..68513f62 100644
--- a/test/BinderSpec.js
+++ b/test/BinderSpec.js
@@ -393,14 +393,6 @@ describe('Binder', function(){
assertHidden(scope.$element);
});
- it('BindClassUndefined', function(){
- var scope = this.compile('<div ng:class="undefined"/>');
- scope.$apply();
-
- assertEquals(
- '<div class="undefined" ng:class="undefined"></div>',
- sortedHtml(scope.$element));
- });
it('BindClass', function(){
var scope = this.compile('<div ng:class="clazz"/>');
diff --git a/test/directivesSpec.js b/test/directivesSpec.js
index 1d26973c..314f9a88 100644
--- a/test/directivesSpec.js
+++ b/test/directivesSpec.js
@@ -196,13 +196,103 @@ describe("directive", function(){
expect(element.hasClass('B')).toBe(false);
});
- it('should support adding multiple classes', function(){
+
+ it('should support adding multiple classes via an array', function(){
var scope = compile('<div class="existing" ng:class="[\'A\', \'B\']"></div>');
scope.$digest();
expect(element.hasClass('existing')).toBeTruthy();
expect(element.hasClass('A')).toBeTruthy();
expect(element.hasClass('B')).toBeTruthy();
});
+
+
+ it('should support adding multiple classes via a space delimited string', function(){
+ var scope = compile('<div class="existing" ng:class="\'A B\'"></div>');
+ scope.$digest();
+ expect(element.hasClass('existing')).toBeTruthy();
+ expect(element.hasClass('A')).toBeTruthy();
+ expect(element.hasClass('B')).toBeTruthy();
+ });
+
+
+ it('should preserve class added post compilation with pre-existing classes', function() {
+ var scope = compile('<div class="existing" ng:class="dynClass"></div>');
+ scope.dynClass = 'A';
+ scope.$digest();
+ expect(element.hasClass('existing')).toBe(true);
+
+ // add extra class, change model and eval
+ element.addClass('newClass');
+ scope.dynClass = 'B';
+ scope.$digest();
+
+ expect(element.hasClass('existing')).toBe(true);
+ expect(element.hasClass('B')).toBe(true);
+ expect(element.hasClass('newClass')).toBe(true);
+ });
+
+
+ it('should preserve class added post compilation without pre-existing classes"', function() {
+ var scope = compile('<div ng:class="dynClass"></div>');
+ scope.dynClass = 'A';
+ scope.$digest();
+ expect(element.hasClass('A')).toBe(true);
+
+ // add extra class, change model and eval
+ element.addClass('newClass');
+ scope.dynClass = 'B';
+ scope.$digest();
+
+ expect(element.hasClass('B')).toBe(true);
+ expect(element.hasClass('newClass')).toBe(true);
+ });
+
+
+ it('should preserve other classes with similar name"', function() {
+ var scope = compile('<div class="ui-panel ui-selected" ng:class="dynCls"></div>');
+ scope.dynCls = 'panel';
+ scope.$digest();
+ scope.dynCls = 'foo';
+ scope.$digest();
+ expect(element.attr('class')).toBe('ui-panel ui-selected ng-directive foo');
+ });
+
+
+ it('should not add duplicate classes', function() {
+ var scope = compile('<div class="panel bar" ng:class="dynCls"></div>');
+ scope.dynCls = 'panel';
+ scope.$digest();
+ expect(element.attr('class')).toBe('panel bar ng-directive');
+ });
+
+
+ it('should remove classes even if it was specified via class attribute', function() {
+ var scope = compile('<div class="panel bar" ng:class="dynCls"></div>');
+ scope.dynCls = 'panel';
+ scope.$digest();
+ scope.dynCls = 'window';
+ scope.$digest();
+ expect(element.attr('class')).toBe('bar ng-directive window');
+ });
+
+
+ it('should remove classes even if they were added by another code', function() {
+ var scope = compile('<div ng:class="dynCls"></div>');
+ scope.dynCls = 'foo';
+ scope.$digest();
+ element.addClass('foo');
+ scope.dynCls = '';
+ scope.$digest();
+ expect(element.attr('class')).toBe('ng-directive');
+ });
+
+
+ it('should convert undefined and null values to an empty string', function() {
+ var scope = compile('<div ng:class="dynCls"></div>');
+ scope.dynCls = [undefined, null];
+ scope.$digest();
+ expect(element.attr('class')).toBe('ng-directive');
+ });
});
diff --git a/test/jqLiteSpec.js b/test/jqLiteSpec.js
index f2a99943..673bee78 100644
--- a/test/jqLiteSpec.js
+++ b/test/jqLiteSpec.js
@@ -190,14 +190,15 @@ describe('jqLite', function(){
});
- describe('addClass', function(){
- it('should allow adding of class', function(){
+ describe('addClass', function() {
+ it('should allow adding of class', function() {
var selector = jqLite([a, b]);
expect(selector.addClass('abc')).toEqual(selector);
expect(jqLite(a).hasClass('abc')).toEqual(true);
expect(jqLite(b).hasClass('abc')).toEqual(true);
});
+
it('should ignore falsy values', function() {
var jqA = jqLite(a);
expect(jqA[0].className).toBe('');
@@ -211,6 +212,28 @@ describe('jqLite', function(){
jqA.addClass(false);
expect(jqA[0].className).toBe('');
});
+
+
+ it('should allow multiple classes to be added in a single string', function() {
+ var jqA = jqLite(a);
+ expect(a.className).toBe('');
+
+ jqA.addClass('foo bar baz');
+ expect(a.className).toBe('foo bar baz');
+ });
+
+
+ it('should not add duplicate classes', function() {
+ var jqA = jqLite(a);
+ expect(a.className).toBe('');
+
+ a.className = 'foo';
+ jqA.addClass('foo');
+ expect(a.className).toBe('foo');
+
+ jqA.addClass('bar foo baz');
+ expect(a.className).toBe('foo bar baz');
+ });
});
@@ -246,6 +269,7 @@ describe('jqLite', function(){
expect(jqLite(b).hasClass('abc')).toEqual(false);
});
+
it('should correctly remove middle class', function() {
var element = jqLite('<div class="foo bar baz"></div>');
expect(element.hasClass('bar')).toBe(true);
@@ -256,6 +280,15 @@ describe('jqLite', function(){
expect(element.hasClass('bar')).toBe(false);
expect(element.hasClass('baz')).toBe(true);
});
+
+
+ it('should remove multiple classes specified as one string', function() {
+ var jqA = jqLite(a);
+
+ a.className = 'foo bar baz';
+ jqA.removeClass('foo baz noexistent');
+ expect(a.className).toBe('bar');
+ });
});
});