mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-23 01:40:25 +00:00
feat($compile): support compile animation hooks classes
This commit is contained in:
parent
d45ac7707e
commit
f2dfa8916f
3 changed files with 158 additions and 52 deletions
|
|
@ -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;
|
||||
|
|
@ -287,6 +287,42 @@ function $CompileProvider($provide) {
|
|||
$normalize: directiveNormalize,
|
||||
|
||||
|
||||
/**
|
||||
* @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.
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
this[key] = value;
|
||||
|
||||
// translate normalized key to actual key
|
||||
if (attrName) {
|
||||
this.$attr[key] = attrName;
|
||||
//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 {
|
||||
attrName = this.$attr[key];
|
||||
if (!attrName) {
|
||||
this.$attr[key] = attrName = snake_case(key, '-');
|
||||
var booleanKey = getBooleanAttrName(this.$$element[0], key),
|
||||
normalizedVal,
|
||||
nodeName;
|
||||
|
||||
if (booleanKey) {
|
||||
this.$$element.prop(key, value);
|
||||
attrName = booleanKey;
|
||||
}
|
||||
}
|
||||
|
||||
nodeName = nodeName_(this.$$element);
|
||||
this[key] = value;
|
||||
|
||||
// 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;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
|||
};
|
||||
}
|
||||
};
|
||||
}];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue