Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 49eba65

Browse files
committedOct 8, 2014
WIP --- fixups
1 parent 7c73898 commit 49eba65

File tree

5 files changed

+191
-75
lines changed

5 files changed

+191
-75
lines changed
 

‎src/ng/animate.js

+64-43
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,19 @@ var $AnimateProvider = ['$provide', function($provide) {
8686
var currentDefer;
8787
var ELEMENT_NODE = 1;
8888

89+
function extractElementNodes(element) {
90+
var elements = new Array(element.length);
91+
var count = 0;
92+
for(var i = 0; i < element.length; i++) {
93+
var elm = element[i];
94+
if (elm.nodeType == ELEMENT_NODE) {
95+
elements[count++] = elm;
96+
}
97+
}
98+
elements.length = count;
99+
return jqLite(elements);
100+
}
101+
89102
function runAnimationPostDigest(fn) {
90103
var cancelFn, defer = $$q.defer();
91104
defer.promise.$$cancelFn = function ngAnimateMaybeCancel() {
@@ -102,33 +115,31 @@ var $AnimateProvider = ['$provide', function($provide) {
102115
}
103116

104117
function resolveElementClasses(element, cache) {
105-
var map = {};
106-
107-
forEach(cache.add, function(className) {
108-
if (className && className.length) {
109-
map[className] = map[className] || 0;
110-
map[className]++;
111-
}
112-
});
113-
114-
forEach(cache.remove, function(className) {
115-
if (className && className.length) {
116-
map[className] = map[className] || 0;
117-
map[className]--;
118-
}
119-
});
120-
121118
var toAdd = [], toRemove = [];
122-
forEach(map, function(status, className) {
119+
forEach(cache.classes, function(status, className) {
123120
var hasClass = jqLiteHasClass(element[0], className);
124121

125-
if (status < 0 && hasClass) toRemove.push(className);
126-
else if (status > 0 && !hasClass) toAdd.push(className);
122+
// If the most recent class manipulation (via $animate) was to remove the class, and the
123+
// element currently has the class, the class is scheduled for removal. Otherwise, if
124+
// the most recent class manipulation (via $animate) was to add the class, and the
125+
// element does not currently have the class, the class is scheduled to be added.
126+
if (status === false && hasClass) {
127+
toRemove.push(className);
128+
} else if (status === true && !hasClass) {
129+
toAdd.push(className);
130+
}
127131
});
128132

129133
return (toAdd.length + toRemove.length) > 0 && [toAdd.join(' '), toRemove.join(' ')];
130134
}
131135

136+
function cachedClassManipulation(cache, classes, op) {
137+
for (var i=0, ii = classes.length; i < ii; ++i) {
138+
var className = classes[i];
139+
cache[className] = op;
140+
}
141+
}
142+
132143
function asyncPromise() {
133144
// only serve one instance of a promise in order to save CPU cycles
134145
if (!currentDefer) {
@@ -285,40 +296,50 @@ var $AnimateProvider = ['$provide', function($provide) {
285296
* @param {string} remove the CSS class which will be removed from the element
286297
* @return {Promise} the animation callback promise
287298
*/
288-
setClass : function(element, add, remove) {
299+
setClass : function(element, add, remove, runSynchronously) {
289300
var self = this;
290301
var STORAGE_KEY = '$$animateClasses';
291-
element = jqLite(element);
302+
element = extractElementNodes(jqLite(element));
292303

293-
add = isArray(add) ? add : add.split(' ');
294-
remove = isArray(remove) ? remove : remove.split(' ');
304+
if (runSynchronously) {
305+
self.$$addClassImmediately(element, add);
306+
self.$$removeClassImmediately(element, remove);
307+
return asyncPromise();
308+
}
295309

296310
var cache = element.data(STORAGE_KEY);
297-
if (cache) {
298-
cache.add = cache.add.concat(add);
299-
cache.remove = cache.remove.concat(remove);
300-
//the digest cycle will combine all the animations into one function
301-
return cache.promise;
302-
} else {
303-
element.data(STORAGE_KEY, cache = {
304-
add : add,
305-
remove : remove
306-
});
311+
if (!cache) {
312+
cache = {
313+
classes: {}
314+
};
315+
var createdCache = true;
307316
}
308317

309-
return cache.promise = runAnimationPostDigest(function(done) {
310-
var cache = element.data(STORAGE_KEY);
311-
element.removeData(STORAGE_KEY);
318+
var classes = cache.classes;
319+
320+
add = isArray(add) ? add : add.split(' ');
321+
remove = isArray(remove) ? remove : remove.split(' ');
322+
cachedClassManipulation(classes, add, true);
323+
cachedClassManipulation(classes, remove, false);
312324

313-
var classes = cache && resolveElementClasses(element, cache);
325+
if (createdCache) {
326+
cache.promise = runAnimationPostDigest(function(done) {
327+
var cache = element.data(STORAGE_KEY);
328+
element.removeData(STORAGE_KEY);
314329

315-
if (classes) {
316-
if (classes[0]) self.$$addClassImmediately(element, classes[0]);
317-
if (classes[1]) self.$$removeClassImmediately(element, classes[1]);
318-
}
330+
var classes = cache && resolveElementClasses(element, cache);
319331

320-
done();
321-
});
332+
if (classes) {
333+
if (classes[0]) self.$$addClassImmediately(element, classes[0]);
334+
if (classes[1]) self.$$removeClassImmediately(element, classes[1]);
335+
}
336+
337+
done();
338+
});
339+
element.data(STORAGE_KEY, cache);
340+
}
341+
342+
return cache.promise;
322343
},
323344

324345
enabled : noop,

‎src/ngAnimate/animate.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -979,9 +979,7 @@ angular.module('ngAnimate', ['ng'])
979979
element = stripCommentsFromElement(element);
980980

981981
if (classBasedAnimationsBlocked(element)) {
982-
$delegate.$$addClassImmediately(element, add);
983-
$delegate.$$removeClassImmediately(element, remove);
984-
return;
982+
return $delegate.setClass(element, add, remove, true);
985983
}
986984

987985
// we're using a combined array for both the add and remove
@@ -1034,7 +1032,7 @@ angular.module('ngAnimate', ['ng'])
10341032
var classes = resolveElementClasses(element, cache, state.active);
10351033
return !classes
10361034
? done()
1037-
: performAnimation('setClass', classes, element, null, null, function() {
1035+
: performAnimation('setClass', classes, element, parentElement, null, function() {
10381036
if (classes[0]) $delegate.$$addClassImmediately(element, classes[0]);
10391037
if (classes[1]) $delegate.$$removeClassImmediately(element, classes[1]);
10401038
}, done);

‎test/ng/animateSpec.js

+57-7
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ describe("$animate", function() {
5050
expect(element.text()).toBe('21');
5151
}));
5252

53+
5354
it("should still perform DOM operations even if animations are disabled (post-digest)", inject(function($animate, $rootScope) {
5455
$animate.enabled(false);
5556
expect(element).toBeShown();
@@ -141,7 +142,6 @@ describe("$animate", function() {
141142
});
142143
}
143144

144-
145145
it('should defer class manipulation until end of digest', inject(function($rootScope, $animate, log) {
146146
setupClassManipulationLogger(log);
147147
element = jqLite('<p>test</p>');
@@ -171,15 +171,66 @@ describe("$animate", function() {
171171
}));
172172

173173

174-
it('should return a promise which is resolved on a different turn digest', inject(function(log, $animate, $browser, $rootScope) {
174+
it('should defer class manipulation until postDigest when outside of digest', inject(function($rootScope, $animate, log) {
175+
setupClassManipulationLogger(log);
176+
element = jqLite('<p class="test-class4">test</p>');
177+
178+
$animate.addClass(element, 'test-class1');
179+
$animate.removeClass(element, 'test-class1');
180+
$animate.addClass(element, 'test-class2');
181+
$animate.setClass(element, 'test-class3', 'test-class4');
182+
183+
expect(log).toEqual([]);
184+
$rootScope.$digest();
185+
186+
187+
expect(log).toEqual(['addClass(test-class2 test-class3)', 'removeClass(test-class4)']);
188+
expect(element).not.toHaveClass('test-class1');
189+
expect(element).toHaveClass('test-class2');
190+
expect(element).toHaveClass('test-class3');
191+
expect(addClass.callCount).toBe(1);
192+
expect(removeClass.callCount).toBe(1);
193+
}));
194+
195+
196+
it('should perform class manipulation in expected order at end of digest', inject(function($rootScope, $animate, log) {
197+
element = jqLite('<p class="test-class3">test</p>');
198+
199+
setupClassManipulationLogger(log);
200+
201+
$rootScope.$apply(function() {
202+
$animate.addClass(element, 'test-class1');
203+
$animate.addClass(element, 'test-class2');
204+
$animate.removeClass(element, 'test-class1');
205+
$animate.removeClass(element, 'test-class3');
206+
$animate.addClass(element, 'test-class3');
207+
});
208+
expect(log).toEqual(['addClass(test-class2)']);
209+
}));
210+
211+
212+
it('should return a promise which is resolved on a different turn', inject(function(log, $animate, $browser, $rootScope) {
175213
element = jqLite('<p class="test2">test</p>');
176214

177215
$animate.addClass(element, 'test1').then(log.fn('addClass(test1)'));
178216
$animate.removeClass(element, 'test2').then(log.fn('removeClass(test2)'));
179217

180218
$rootScope.$digest();
219+
expect(log).toEqual([]);
181220
$browser.defer.flush();
182221
expect(log).toEqual(['addClass(test1)', 'removeClass(test2)']);
222+
223+
log.reset();
224+
element = jqLite('<p class="test4">test</p>');
225+
226+
$rootScope.$apply(function() {
227+
$animate.addClass(element, 'test3').then(log.fn('addClass(test3)'));
228+
$animate.removeClass(element, 'test4').then(log.fn('removeClass(test4)'));
229+
expect(log).toEqual([]);
230+
});
231+
232+
$browser.defer.flush();
233+
expect(log).toEqual(['addClass(test3)', 'removeClass(test4)']);
183234
}));
184235

185236

@@ -210,10 +261,10 @@ describe("$animate", function() {
210261
}));
211262

212263

213-
it('should defer class manipulation until digest outside of digest for SVG', inject(function($rootScope, $animate, log) {
264+
it('should defer class manipulation until postDigest when outside of digest for SVG', inject(function($rootScope, $animate, log) {
214265
if (!window.SVGElement) return;
215266
setupClassManipulationLogger(log);
216-
element = jqLite('<svg><g></g></svg>');
267+
element = jqLite('<svg><g class="test-class4"></g></svg>');
217268
var target = element.children().eq(0);
218269

219270
$animate.addClass(target, 'test-class1');
@@ -222,15 +273,14 @@ describe("$animate", function() {
222273
$animate.setClass(target, 'test-class3', 'test-class4');
223274

224275
expect(log).toEqual([]);
225-
226276
$rootScope.$digest();
227277

228-
expect(log).toEqual(['addClass(test-class2 test-class3)']);
278+
expect(log).toEqual(['addClass(test-class2 test-class3)', 'removeClass(test-class4)']);
229279
expect(target).not.toHaveClass('test-class1');
230280
expect(target).toHaveClass('test-class2');
231281
expect(target).toHaveClass('test-class3');
232282
expect(addClass.callCount).toBe(1);
233-
expect(removeClass.callCount).toBe(0);
283+
expect(removeClass.callCount).toBe(1);
234284
}));
235285

236286

‎test/ng/directive/formSpec.js

+1
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,7 @@ describe('form', function() {
729729
formCtrl.$setPristine();
730730
scope.$digest();
731731
expect(form).toBePristine();
732+
scope.$digest();
732733
expect(formCtrl.$pristine).toBe(true);
733734
expect(formCtrl.$dirty).toBe(false);
734735
expect(nestedForm).toBePristine();

‎test/ngAnimate/animateSpec.js

+67-21
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
'use strict';
22

33
describe("ngAnimate", function() {
4-
var $animateCore;
4+
var $originalAnimate;
55
beforeEach(module(function($provide) {
66
$provide.decorator('$animate', function($delegate) {
7-
$animateCore = $delegate;
7+
$originalAnimate = $delegate;
88
return $delegate;
99
});
1010
}));
@@ -4892,30 +4892,31 @@ describe("ngAnimate", function() {
48924892

48934893
function setupClassManipulationSpies() {
48944894
inject(function($animate) {
4895-
addClass = spyOn($animateCore, '$$addClassImmediately').andCallThrough();
4896-
removeClass = spyOn($animateCore, '$$removeClassImmediately').andCallThrough();
4895+
addClass = spyOn($originalAnimate, '$$addClassImmediately').andCallThrough();
4896+
removeClass = spyOn($originalAnimate, '$$removeClassImmediately').andCallThrough();
48974897
});
48984898
}
48994899

49004900
function setupClassManipulationLogger(log) {
49014901
inject(function($animate) {
4902-
var addClassImmediately = $animateCore.$$addClassImmediately;
4903-
var removeClassImmediately = $animateCore.$$removeClassImmediately;
4904-
addClass = spyOn($animateCore, '$$addClassImmediately').andCallFake(function(element, classes) {
4902+
var addClassImmediately = $originalAnimate.$$addClassImmediately;
4903+
var removeClassImmediately = $originalAnimate.$$removeClassImmediately;
4904+
addClass = spyOn($originalAnimate, '$$addClassImmediately').andCallFake(function(element, classes) {
49054905
var names = classes;
49064906
if (Object.prototype.toString.call(classes) === '[object Array]') names = classes.join( ' ');
49074907
log('addClass(' + names + ')');
4908-
return addClassImmediately.call($animateCore, element, classes);
4908+
return addClassImmediately.call($originalAnimate, element, classes);
49094909
});
4910-
removeClass = spyOn($animateCore, '$$removeClassImmediately').andCallFake(function(element, classes) {
4910+
removeClass = spyOn($originalAnimate, '$$removeClassImmediately').andCallFake(function(element, classes) {
49114911
var names = classes;
49124912
if (Object.prototype.toString.call(classes) === '[object Array]') names = classes.join( ' ');
49134913
log('removeClass(' + names + ')');
4914-
return removeClassImmediately.call($animateCore, element, classes);
4914+
return removeClassImmediately.call($originalAnimate, element, classes);
49154915
});
49164916
});
49174917
}
49184918

4919+
49194920
it('should defer class manipulation until end of digest', inject(function($rootScope, $animate, log) {
49204921
setupClassManipulationLogger(log);
49214922
element = jqLite('<p>test</p>');
@@ -4945,37 +4946,65 @@ describe("ngAnimate", function() {
49454946
}));
49464947

49474948

4948-
it('should defer class manipulation until digest outside of digest', inject(function($rootScope, $animate, log) {
4949+
it('should defer class manipulation until postDigest when outside of digest', inject(function($rootScope, $animate, log) {
49494950
setupClassManipulationLogger(log);
4950-
element = jqLite('<p>test</p>');
4951+
element = jqLite('<p class="test-class4">test</p>');
49514952

49524953
$animate.addClass(element, 'test-class1');
49534954
$animate.removeClass(element, 'test-class1');
49544955
$animate.addClass(element, 'test-class2');
49554956
$animate.setClass(element, 'test-class3', 'test-class4');
49564957

49574958
expect(log).toEqual([]);
4958-
49594959
$rootScope.$digest();
49604960

4961-
expect(log).toEqual(['addClass(test-class2 test-class3)']);
4961+
expect(log).toEqual(['addClass(test-class2 test-class3)', 'removeClass(test-class4)']);
49624962
expect(element).not.toHaveClass('test-class1');
49634963
expect(element).toHaveClass('test-class2');
49644964
expect(element).toHaveClass('test-class3');
49654965
expect(addClass.callCount).toBe(1);
4966-
expect(removeClass.callCount).toBe(0);
4966+
expect(removeClass.callCount).toBe(1);
49674967
}));
49684968

49694969

4970-
it('should return a promise which is resolved on a different turn digest', inject(function(log, $animate, $browser, $rootScope) {
4970+
it('should perform class manipulation in expected order at end of digest', inject(function($rootScope, $animate, log) {
4971+
element = jqLite('<p class="test-class3">test</p>');
4972+
4973+
setupClassManipulationLogger(log);
4974+
4975+
$rootScope.$apply(function() {
4976+
$animate.addClass(element, 'test-class1');
4977+
$animate.addClass(element, 'test-class2');
4978+
$animate.removeClass(element, 'test-class1');
4979+
$animate.removeClass(element, 'test-class3');
4980+
$animate.addClass(element, 'test-class3');
4981+
});
4982+
expect(log).toEqual(['addClass(test-class2)']);
4983+
}));
4984+
4985+
4986+
it('should return a promise which is resolved on a different turn', inject(function(log, $animate, $browser, $rootScope) {
49714987
element = jqLite('<p class="test2">test</p>');
49724988

49734989
$animate.addClass(element, 'test1').then(log.fn('addClass(test1)'));
49744990
$animate.removeClass(element, 'test2').then(log.fn('removeClass(test2)'));
49754991

49764992
$rootScope.$digest();
4993+
expect(log).toEqual([]);
49774994
$browser.defer.flush();
49784995
expect(log).toEqual(['addClass(test1)', 'removeClass(test2)']);
4996+
4997+
log.reset();
4998+
element = jqLite('<p class="test4">test</p>');
4999+
5000+
$rootScope.$apply(function() {
5001+
$animate.addClass(element, 'test3').then(log.fn('addClass(test3)'));
5002+
$animate.removeClass(element, 'test4').then(log.fn('removeClass(test4)'));
5003+
expect(log).toEqual([]);
5004+
});
5005+
5006+
$browser.defer.flush();
5007+
expect(log).toEqual(['addClass(test3)', 'removeClass(test4)']);
49795008
}));
49805009

49815010

@@ -5006,10 +5035,10 @@ describe("ngAnimate", function() {
50065035
}));
50075036

50085037

5009-
it('should defer class manipulation until digest outside of digest for SVG', inject(function($rootScope, $animate, log) {
5038+
it('should defer class manipulation until postDigest when outside of digest for SVG', inject(function($rootScope, $animate, log) {
50105039
if (!window.SVGElement) return;
50115040
setupClassManipulationLogger(log);
5012-
element = jqLite('<svg><g></g></svg>');
5041+
element = jqLite('<svg><g class="test-class4"></g></svg>');
50135042
var target = element.children().eq(0);
50145043

50155044
$animate.addClass(target, 'test-class1');
@@ -5018,15 +5047,32 @@ describe("ngAnimate", function() {
50185047
$animate.setClass(target, 'test-class3', 'test-class4');
50195048

50205049
expect(log).toEqual([]);
5021-
50225050
$rootScope.$digest();
50235051

5024-
expect(log).toEqual(['addClass(test-class2 test-class3)']);
5052+
expect(log).toEqual(['addClass(test-class2 test-class3)', 'removeClass(test-class4)']);
50255053
expect(target).not.toHaveClass('test-class1');
50265054
expect(target).toHaveClass('test-class2');
50275055
expect(target).toHaveClass('test-class3');
50285056
expect(addClass.callCount).toBe(1);
5029-
expect(removeClass.callCount).toBe(0);
5057+
expect(removeClass.callCount).toBe(1);
5058+
}));
5059+
5060+
5061+
it('should perform class manipulation in expected order at end of digest for SVG', inject(function($rootScope, $animate, log) {
5062+
if (!window.SVGElement) return;
5063+
element = jqLite('<svg><g class="test-class3"></g></svg>');
5064+
var target = element.children().eq(0);
5065+
5066+
setupClassManipulationLogger(log);
5067+
5068+
$rootScope.$apply(function() {
5069+
$animate.addClass(target, 'test-class1');
5070+
$animate.addClass(target, 'test-class2');
5071+
$animate.removeClass(target, 'test-class1');
5072+
$animate.removeClass(target, 'test-class3');
5073+
$animate.addClass(target, 'test-class3');
5074+
});
5075+
expect(log).toEqual(['addClass(test-class2)']);
50305076
}));
50315077
});
50325078
});

0 commit comments

Comments
 (0)
This repository has been archived.