aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatias Niemelä2013-08-01 20:13:36 -0400
committerMisko Hevery2013-08-03 00:46:18 -0700
commitf2dfa8916f8ed855d55187f5400c4c2566ce9a1b (patch)
treef3fcaa9a956751836da05f0e6eb1383d6421de15
parentd45ac7707eb336d47116558e49893f910fdaf1fb (diff)
downloadangular.js-f2dfa8916f8ed855d55187f5400c4c2566ce9a1b.tar.bz2
feat($compile): support compile animation hooks classes
-rw-r--r--src/ng/compile.js138
-rw-r--r--src/ng/directive/ngClass.js17
-rwxr-xr-xtest/ng/compileSpec.js51
3 files changed, 156 insertions, 50 deletions
diff --git a/src/ng/compile.js b/src/ng/compile.js
index 6aebe537..340263b3 100644
--- a/src/ng/compile.js
+++ b/src/ng/compile.js
@@ -274,9 +274,9 @@ function $CompileProvider($provide) {
this.$get = [
'$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse',
- '$controller', '$rootScope', '$document', '$sce', '$$urlUtils',
+ '$controller', '$rootScope', '$document', '$sce', '$$urlUtils', '$animate',
function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse,
- $controller, $rootScope, $document, $sce, $$urlUtils) {
+ $controller, $rootScope, $document, $sce, $$urlUtils, $animate) {
var Attributes = function(element, attr) {
this.$$element = element;
@@ -288,6 +288,42 @@ function $CompileProvider($provide) {
/**
+ * @ngdoc function
+ * @name ng.$compile.directive.Attributes#$addClass
+ * @methodOf ng.$compile.directive.Attributes
+ * @function
+ *
+ * @description
+ * Adds the CSS class value specified by the classVal parameter to the element. If animations
+ * are enabled then an animation will be triggered for the class addition.
+ *
+ * @param {string} classVal The className value that will be added to the element
+ */
+ $addClass : function(classVal) {
+ if(classVal && classVal.length > 0) {
+ $animate.addClass(this.$$element, classVal);
+ }
+ },
+
+ /**
+ * @ngdoc function
+ * @name ng.$compile.directive.Attributes#$removeClass
+ * @methodOf ng.$compile.directive.Attributes
+ * @function
+ *
+ * @description
+ * Removes the CSS class value specified by the classVal parameter from the element. If animations
+ * are enabled then an animation will be triggered for the class removal.
+ *
+ * @param {string} classVal The className value that will be removed from the element
+ */
+ $removeClass : function(classVal) {
+ if(classVal && classVal.length > 0) {
+ $animate.removeClass(this.$$element, classVal);
+ }
+ },
+
+ /**
* Set a normalized attribute on the element in a way such that all directives
* can share the attribute. This function properly handles boolean attributes.
* @param {string} key Normalized key. (ie ngAttribute)
@@ -297,54 +333,64 @@ function $CompileProvider($provide) {
* @param {string=} attrName Optional none normalized name. Defaults to key.
*/
$set: function(key, value, writeAttr, attrName) {
- var booleanKey = getBooleanAttrName(this.$$element[0], key),
- $$observers = this.$$observers,
- normalizedVal,
- nodeName;
-
- if (booleanKey) {
- this.$$element.prop(key, value);
- attrName = booleanKey;
- }
+ //special case for class attribute addition + removal
+ //so that class changes can tap into the animation
+ //hooks provided by the $animate service
+ if(key == 'class') {
+ value = value || '';
+ var current = this.$$element.attr('class') || '';
+ this.$removeClass(tokenDifference(current, value).join(' '));
+ this.$addClass(tokenDifference(value, current).join(' '));
+ } else {
+ var booleanKey = getBooleanAttrName(this.$$element[0], key),
+ normalizedVal,
+ nodeName;
- this[key] = value;
+ if (booleanKey) {
+ this.$$element.prop(key, value);
+ attrName = booleanKey;
+ }
- // translate normalized key to actual key
- if (attrName) {
- this.$attr[key] = attrName;
- } else {
- attrName = this.$attr[key];
- if (!attrName) {
- this.$attr[key] = attrName = snake_case(key, '-');
+ this[key] = value;
+
+ // translate normalized key to actual key
+ if (attrName) {
+ this.$attr[key] = attrName;
+ } else {
+ attrName = this.$attr[key];
+ if (!attrName) {
+ this.$attr[key] = attrName = snake_case(key, '-');
+ }
}
- }
- nodeName = nodeName_(this.$$element);
-
- // sanitize a[href] and img[src] values
- if ((nodeName === 'A' && key === 'href') ||
- (nodeName === 'IMG' && key === 'src')) {
- // NOTE: $$urlUtils.resolve() doesn't support IE < 8 so we don't sanitize for that case.
- if (!msie || msie >= 8 ) {
- normalizedVal = $$urlUtils.resolve(value);
- if (normalizedVal !== '') {
- if ((key === 'href' && !normalizedVal.match(aHrefSanitizationWhitelist)) ||
- (key === 'src' && !normalizedVal.match(imgSrcSanitizationWhitelist))) {
- this[key] = value = 'unsafe:' + normalizedVal;
+ nodeName = nodeName_(this.$$element);
+
+ // sanitize a[href] and img[src] values
+ if ((nodeName === 'A' && key === 'href') ||
+ (nodeName === 'IMG' && key === 'src')) {
+ // NOTE: $$urlUtils.resolve() doesn't support IE < 8 so we don't sanitize for that case.
+ if (!msie || msie >= 8 ) {
+ normalizedVal = $$urlUtils.resolve(value);
+ if (normalizedVal !== '') {
+ if ((key === 'href' && !normalizedVal.match(aHrefSanitizationWhitelist)) ||
+ (key === 'src' && !normalizedVal.match(imgSrcSanitizationWhitelist))) {
+ this[key] = value = 'unsafe:' + normalizedVal;
+ }
}
}
}
- }
- if (writeAttr !== false) {
- if (value === null || value === undefined) {
- this.$$element.removeAttr(attrName);
- } else {
- this.$$element.attr(attrName, value);
+ if (writeAttr !== false) {
+ if (value === null || value === undefined) {
+ this.$$element.removeAttr(attrName);
+ } else {
+ this.$$element.attr(attrName, value);
+ }
}
}
// fire observers
+ var $$observers = this.$$observers;
$$observers && forEach($$observers[key], function(fn) {
try {
fn(value);
@@ -352,6 +398,22 @@ function $CompileProvider($provide) {
$exceptionHandler(e);
}
});
+
+ function tokenDifference(str1, str2) {
+ var values = [],
+ tokens1 = str1.split(/\s+/),
+ tokens2 = str2.split(/\s+/);
+
+ 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;
+ };
},
diff --git a/src/ng/directive/ngClass.js b/src/ng/directive/ngClass.js
index 001f67bb..38b804b5 100644
--- a/src/ng/directive/ngClass.js
+++ b/src/ng/directive/ngClass.js
@@ -2,7 +2,7 @@
function classDirective(name, selector) {
name = 'ngClass' + name;
- return ['$animate', function($animate) {
+ return function() {
return {
restrict: 'AC',
link: function(scope, element, attr) {
@@ -11,8 +11,7 @@ function classDirective(name, selector) {
scope.$watch(attr[name], ngClassWatchAction, true);
attr.$observe('class', function(value) {
- var ngClass = scope.$eval(attr[name]);
- ngClassWatchAction(ngClass, ngClass);
+ ngClassWatchAction(scope.$eval(attr[name]));
});
@@ -42,18 +41,12 @@ function classDirective(name, selector) {
function removeClass(classVal) {
- classVal = flattenClasses(classVal);
- if(classVal && classVal.length > 0) {
- $animate.removeClass(element, classVal);
- }
+ attr.$removeClass(flattenClasses(classVal));
}
function addClass(classVal) {
- classVal = flattenClasses(classVal);
- if(classVal && classVal.length > 0) {
- $animate.addClass(element, classVal);
- }
+ attr.$addClass(flattenClasses(classVal));
}
function flattenClasses(classVal) {
@@ -73,7 +66,7 @@ function classDirective(name, selector) {
};
}
};
- }];
+ };
}
/**
diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js
index c1dedd4a..5dfad4be 100755
--- a/test/ng/compileSpec.js
+++ b/test/ng/compileSpec.js
@@ -3268,4 +3268,55 @@ describe('$compile', function() {
expect(spans.eq(3)).toBeHidden();
}));
});
+
+ describe('$animate animation hooks', function() {
+
+ beforeEach(module('mock.animate'));
+
+ it('should automatically fire the addClass and removeClass animation hooks',
+ inject(function($compile, $animate, $rootScope) {
+
+ var data, element = jqLite('<div class="{{val1}} {{val2}} fire"></div>');
+ $compile(element)($rootScope);
+
+ $rootScope.$digest();
+ data = $animate.flushNext('removeClass');
+
+ expect(element.hasClass('fire')).toBe(true);
+
+ $rootScope.val1 = 'ice';
+ $rootScope.val2 = 'rice';
+ $rootScope.$digest();
+
+ data = $animate.flushNext('addClass');
+ expect(data.params[1]).toBe('ice rice');
+
+ expect(element.hasClass('ice')).toBe(true);
+ expect(element.hasClass('rice')).toBe(true);
+ expect(element.hasClass('fire')).toBe(true);
+
+ $rootScope.val2 = 'dice';
+ $rootScope.$digest();
+
+ data = $animate.flushNext('removeClass');
+ expect(data.params[1]).toBe('rice');
+ data = $animate.flushNext('addClass');
+ expect(data.params[1]).toBe('dice');
+
+ expect(element.hasClass('ice')).toBe(true);
+ expect(element.hasClass('dice')).toBe(true);
+ expect(element.hasClass('fire')).toBe(true);
+
+ $rootScope.val1 = '';
+ $rootScope.val2 = '';
+ $rootScope.$digest();
+
+ data = $animate.flushNext('removeClass');
+ expect(data.params[1]).toBe('ice dice');
+
+ expect(element.hasClass('ice')).toBe(false);
+ expect(element.hasClass('dice')).toBe(false);
+ expect(element.hasClass('fire')).toBe(true);
+ }));
+ });
});