mirror of
https://github.com/zhigang1992/angular.js.git
synced 2026-04-21 10:05:34 +08:00
feat(ngRepeat): use block separator comments
Issue: multi-elements ng-repeat (ng-repeat-start, ng-repeat-end) can contain elements with a trancluding directive. This directive changes content of the row (template) and ng-repeat does not work correctly (when removing/moving rows), because ng-repeat works with the original template (elements). This changes ng-repeat behavior to traverse the DOM to find current elements everytime we are moving/removing rows (if the template has multiple elements). Closes #3104
This commit is contained in:
@@ -96,7 +96,9 @@ describe('Binder', function() {
|
||||
'<ul>' +
|
||||
'<!-- ngRepeat: item in model.items -->' +
|
||||
'<li ng-bind="item.a" ng-repeat="item in model.items">A</li>' +
|
||||
'<!-- end ngRepeat: item in model.items -->' +
|
||||
'<li ng-bind="item.a" ng-repeat="item in model.items">B</li>' +
|
||||
'<!-- end ngRepeat: item in model.items -->' +
|
||||
'</ul>');
|
||||
|
||||
items.unshift({a: 'C'});
|
||||
@@ -105,8 +107,11 @@ describe('Binder', function() {
|
||||
'<ul>' +
|
||||
'<!-- ngRepeat: item in model.items -->' +
|
||||
'<li ng-bind="item.a" ng-repeat="item in model.items">C</li>' +
|
||||
'<!-- end ngRepeat: item in model.items -->' +
|
||||
'<li ng-bind="item.a" ng-repeat="item in model.items">A</li>' +
|
||||
'<!-- end ngRepeat: item in model.items -->' +
|
||||
'<li ng-bind="item.a" ng-repeat="item in model.items">B</li>' +
|
||||
'<!-- end ngRepeat: item in model.items -->' +
|
||||
'</ul>');
|
||||
|
||||
items.shift();
|
||||
@@ -115,7 +120,9 @@ describe('Binder', function() {
|
||||
'<ul>' +
|
||||
'<!-- ngRepeat: item in model.items -->' +
|
||||
'<li ng-bind="item.a" ng-repeat="item in model.items">A</li>' +
|
||||
'<!-- end ngRepeat: item in model.items -->' +
|
||||
'<li ng-bind="item.a" ng-repeat="item in model.items">B</li>' +
|
||||
'<!-- end ngRepeat: item in model.items -->' +
|
||||
'</ul>');
|
||||
|
||||
items.shift();
|
||||
@@ -134,6 +141,7 @@ describe('Binder', function() {
|
||||
'<ul>' +
|
||||
'<!-- ngRepeat: item in model.items -->' +
|
||||
'<li ng-repeat="item in model.items"><span ng-bind="item.a">A</span></li>' +
|
||||
'<!-- end ngRepeat: item in model.items -->' +
|
||||
'</ul>');
|
||||
}));
|
||||
|
||||
@@ -148,15 +156,15 @@ describe('Binder', function() {
|
||||
$rootScope.items = items;
|
||||
|
||||
$rootScope.$apply();
|
||||
expect(element[0].childNodes.length - 1).toEqual(0);
|
||||
expect(element[0].childNodes.length).toEqual(1);
|
||||
|
||||
items.name = 'misko';
|
||||
$rootScope.$apply();
|
||||
expect(element[0].childNodes.length - 1).toEqual(1);
|
||||
expect(element[0].childNodes.length).toEqual(3);
|
||||
|
||||
delete items.name;
|
||||
$rootScope.$apply();
|
||||
expect(element[0].childNodes.length - 1).toEqual(0);
|
||||
expect(element[0].childNodes.length).toEqual(1);
|
||||
}));
|
||||
|
||||
it('IfTextBindingThrowsErrorDecorateTheSpan', function() {
|
||||
@@ -223,13 +231,19 @@ describe('Binder', function() {
|
||||
'<div name="a" ng-repeat="m in model">'+
|
||||
'<!-- ngRepeat: i in m.item -->' +
|
||||
'<ul name="a1" ng-repeat="i in m.item"></ul>'+
|
||||
'<!-- end ngRepeat: i in m.item -->' +
|
||||
'<ul name="a2" ng-repeat="i in m.item"></ul>'+
|
||||
'<!-- end ngRepeat: i in m.item -->' +
|
||||
'</div>'+
|
||||
'<!-- end ngRepeat: m in model -->' +
|
||||
'<div name="b" ng-repeat="m in model">'+
|
||||
'<!-- ngRepeat: i in m.item -->' +
|
||||
'<ul name="b1" ng-repeat="i in m.item"></ul>'+
|
||||
'<!-- end ngRepeat: i in m.item -->' +
|
||||
'<ul name="b2" ng-repeat="i in m.item"></ul>'+
|
||||
'<!-- end ngRepeat: i in m.item -->' +
|
||||
'</div>' +
|
||||
'<!-- end ngRepeat: m in model -->' +
|
||||
'</div>');
|
||||
}));
|
||||
|
||||
@@ -306,15 +320,18 @@ describe('Binder', function() {
|
||||
'<div ng-repeat="i in [0,1]" ng-class-even="\'e\'" ng-class-odd="\'o\'"></div>' +
|
||||
'</div>')($rootScope);
|
||||
$rootScope.$apply();
|
||||
|
||||
var d1 = jqLite(element[0].childNodes[1]);
|
||||
var d2 = jqLite(element[0].childNodes[2]);
|
||||
var d2 = jqLite(element[0].childNodes[3]);
|
||||
expect(d1.hasClass('o')).toBeTruthy();
|
||||
expect(d2.hasClass('e')).toBeTruthy();
|
||||
expect(sortedHtml(element)).toBe(
|
||||
'<div>' +
|
||||
'<!-- ngRepeat: i in [0,1] -->' +
|
||||
'<div class="o" ng-class-even="\'e\'" ng-class-odd="\'o\'" ng-repeat="i in [0,1]"></div>' +
|
||||
'<!-- end ngRepeat: i in [0,1] -->' +
|
||||
'<div class="e" ng-class-even="\'e\'" ng-class-odd="\'o\'" ng-repeat="i in [0,1]"></div>' +
|
||||
'<!-- end ngRepeat: i in [0,1] -->' +
|
||||
'</div>');
|
||||
}));
|
||||
|
||||
@@ -420,7 +437,9 @@ describe('Binder', function() {
|
||||
'<ul>' +
|
||||
'<!-- ngRepeat: (k,v) in {a:0,b:1} -->' +
|
||||
'<li ng-bind=\"k + v\" ng-repeat="(k,v) in {a:0,b:1}">a0</li>' +
|
||||
'<!-- end ngRepeat: (k,v) in {a:0,b:1} -->' +
|
||||
'<li ng-bind=\"k + v\" ng-repeat="(k,v) in {a:0,b:1}">b1</li>' +
|
||||
'<!-- end ngRepeat: (k,v) in {a:0,b:1} -->' +
|
||||
'</ul>');
|
||||
}));
|
||||
|
||||
|
||||
@@ -226,6 +226,17 @@ function sortedHtml(element, showNgClass) {
|
||||
}
|
||||
|
||||
|
||||
function childrenTagsOf(element) {
|
||||
var tags = [];
|
||||
|
||||
forEach(jqLite(element).children(), function(child) {
|
||||
tags.push(child.nodeName.toLowerCase());
|
||||
});
|
||||
|
||||
return tags;
|
||||
}
|
||||
|
||||
|
||||
// TODO(vojta): migrate these helpers into jasmine matchers
|
||||
/**a
|
||||
* This method is a cheap way of testing if css for a given node is not set to 'none'. It doesn't
|
||||
|
||||
@@ -166,7 +166,7 @@ describe('ngClass', function() {
|
||||
element = $compile('<ul><li ng-repeat="i in [0,1]" class="existing" ng-class-odd="\'odd\'" ng-class-even="\'even\'"></li><ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
var e1 = jqLite(element[0].childNodes[1]);
|
||||
var e2 = jqLite(element[0].childNodes[2]);
|
||||
var e2 = jqLite(element[0].childNodes[3]);
|
||||
expect(e1.hasClass('existing')).toBeTruthy();
|
||||
expect(e1.hasClass('odd')).toBeTruthy();
|
||||
expect(e2.hasClass('existing')).toBeTruthy();
|
||||
@@ -181,7 +181,7 @@ describe('ngClass', function() {
|
||||
'<ul>')($rootScope);
|
||||
$rootScope.$apply();
|
||||
var e1 = jqLite(element[0].childNodes[1]);
|
||||
var e2 = jqLite(element[0].childNodes[2]);
|
||||
var e2 = jqLite(element[0].childNodes[3]);
|
||||
|
||||
expect(e1.hasClass('plainClass')).toBeTruthy();
|
||||
expect(e1.hasClass('odd')).toBeTruthy();
|
||||
@@ -199,7 +199,7 @@ describe('ngClass', function() {
|
||||
'<ul>')($rootScope);
|
||||
$rootScope.$apply();
|
||||
var e1 = jqLite(element[0].childNodes[1]);
|
||||
var e2 = jqLite(element[0].childNodes[2]);
|
||||
var e2 = jqLite(element[0].childNodes[3]);
|
||||
|
||||
expect(e1.hasClass('A')).toBeTruthy();
|
||||
expect(e1.hasClass('B')).toBeTruthy();
|
||||
@@ -273,7 +273,7 @@ describe('ngClass', function() {
|
||||
$rootScope.$digest();
|
||||
|
||||
var e1 = jqLite(element[0].childNodes[1]);
|
||||
var e2 = jqLite(element[0].childNodes[2]);
|
||||
var e2 = jqLite(element[0].childNodes[3]);
|
||||
|
||||
expect(e1.hasClass('odd')).toBeTruthy();
|
||||
expect(e1.hasClass('even')).toBeFalsy();
|
||||
@@ -295,7 +295,7 @@ describe('ngClass', function() {
|
||||
$rootScope.$digest();
|
||||
|
||||
var e1 = jqLite(element[0].childNodes[1]);
|
||||
var e2 = jqLite(element[0].childNodes[2]);
|
||||
var e2 = jqLite(element[0].childNodes[3]);
|
||||
|
||||
expect(e1.hasClass('odd')).toBeTruthy();
|
||||
expect(e1.hasClass('even')).toBeFalsy();
|
||||
|
||||
@@ -621,7 +621,9 @@ describe('ngRepeat', function() {
|
||||
'<div>' +
|
||||
'<!-- ngRepeat: i in items -->' +
|
||||
'<div ng-repeat="i in items" rr="">1|</div>' +
|
||||
'<!-- end ngRepeat: i in items -->' +
|
||||
'<div ng-repeat="i in items" rr="">2|</div>' +
|
||||
'<!-- end ngRepeat: i in items -->' +
|
||||
'</div>'
|
||||
);
|
||||
}));
|
||||
@@ -651,7 +653,9 @@ describe('ngRepeat', function() {
|
||||
'<div>' +
|
||||
'<!-- ngRepeat: i in items -->' +
|
||||
'<div ng-repeat="i in items" rr="">1|</div>' +
|
||||
'<!-- end ngRepeat: i in items -->' +
|
||||
'<div ng-repeat="i in items" rr="">2|</div>' +
|
||||
'<!-- end ngRepeat: i in items -->' +
|
||||
'</div>'
|
||||
);
|
||||
}));
|
||||
@@ -748,6 +752,106 @@ describe('ngRepeat', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should add separator comments after each item', inject(function ($compile, $rootScope) {
|
||||
var check = function () {
|
||||
var children = element.find('div');
|
||||
expect(children.length).toBe(3);
|
||||
|
||||
// Note: COMMENT_NODE === 8
|
||||
expect(children[0].nextSibling.nodeType).toBe(8);
|
||||
expect(children[0].nextSibling.nodeValue).toBe(' end ngRepeat: val in values ');
|
||||
expect(children[1].nextSibling.nodeType).toBe(8);
|
||||
expect(children[1].nextSibling.nodeValue).toBe(' end ngRepeat: val in values ');
|
||||
expect(children[2].nextSibling.nodeType).toBe(8);
|
||||
expect(children[2].nextSibling.nodeValue).toBe(' end ngRepeat: val in values ');
|
||||
}
|
||||
|
||||
$rootScope.values = [1, 2, 3];
|
||||
|
||||
element = $compile(
|
||||
'<div>' +
|
||||
'<div ng-repeat="val in values">val:{{val}};</div>' +
|
||||
'</div>'
|
||||
)($rootScope);
|
||||
|
||||
$rootScope.$digest();
|
||||
check();
|
||||
|
||||
$rootScope.values.shift();
|
||||
$rootScope.values.push(4);
|
||||
$rootScope.$digest();
|
||||
check();
|
||||
}));
|
||||
|
||||
|
||||
it('should remove whole block even if the number of elements inside it changes', inject(
|
||||
function ($compile, $rootScope) {
|
||||
|
||||
$rootScope.values = [1, 2, 3];
|
||||
|
||||
element = $compile(
|
||||
'<div>' +
|
||||
'<div ng-repeat-start="val in values"></div>' +
|
||||
'<span>{{val}}</span>' +
|
||||
'<p ng-repeat-end></p>' +
|
||||
'</div>'
|
||||
)($rootScope);
|
||||
|
||||
$rootScope.$digest();
|
||||
|
||||
var ends = element.find('p');
|
||||
expect(ends.length).toBe(3);
|
||||
|
||||
// insert an extra element inside the second block
|
||||
var extra = angular.element('<strong></strong>')[0];
|
||||
element[0].insertBefore(extra, ends[1]);
|
||||
|
||||
$rootScope.values.splice(1, 1);
|
||||
$rootScope.$digest();
|
||||
|
||||
// expect the strong tag to be removed too
|
||||
expect(childrenTagsOf(element)).toEqual([
|
||||
'div', 'span', 'p',
|
||||
'div', 'span', 'p'
|
||||
]);
|
||||
}));
|
||||
|
||||
|
||||
it('should move whole block even if the number of elements inside it changes', inject(
|
||||
function ($compile, $rootScope) {
|
||||
|
||||
$rootScope.values = [1, 2, 3];
|
||||
|
||||
element = $compile(
|
||||
'<div>' +
|
||||
'<div ng-repeat-start="val in values"></div>' +
|
||||
'<span>{{val}}</span>' +
|
||||
'<p ng-repeat-end></p>' +
|
||||
'</div>'
|
||||
)($rootScope);
|
||||
|
||||
$rootScope.$digest();
|
||||
|
||||
var ends = element.find('p');
|
||||
expect(ends.length).toBe(3);
|
||||
|
||||
// insert an extra element inside the third block
|
||||
var extra = angular.element('<strong></strong>')[0];
|
||||
element[0].insertBefore(extra, ends[2]);
|
||||
|
||||
// move the third block to the begining
|
||||
$rootScope.values.unshift($rootScope.values.pop());
|
||||
$rootScope.$digest();
|
||||
|
||||
// expect the strong tag to be moved too
|
||||
expect(childrenTagsOf(element)).toEqual([
|
||||
'div', 'span', 'strong', 'p',
|
||||
'div', 'span', 'p',
|
||||
'div', 'span', 'p'
|
||||
]);
|
||||
}));
|
||||
|
||||
|
||||
describe('stability', function() {
|
||||
var a, b, c, d, lis;
|
||||
|
||||
@@ -871,26 +975,53 @@ describe('ngRepeat', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should grow multi-node repeater', inject(function($compile, $rootScope) {
|
||||
$rootScope.show = false;
|
||||
$rootScope.books = [
|
||||
{title:'T1', description: 'D1'},
|
||||
{title:'T2', description: 'D2'}
|
||||
];
|
||||
element = $compile(
|
||||
'<div>' +
|
||||
'<dt ng-repeat-start="book in books">{{book.title}}:</dt>' +
|
||||
'<dd ng-repeat-end>{{book.description}};</dd>' +
|
||||
'</div>')($rootScope);
|
||||
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toEqual('T1:D1;T2:D2;');
|
||||
$rootScope.books.push({title:'T3', description: 'D3'});
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toEqual('T1:D1;T2:D2;T3:D3;');
|
||||
}));
|
||||
describe('ngRepeatStart', function () {
|
||||
it('should grow multi-node repeater', inject(function($compile, $rootScope) {
|
||||
$rootScope.show = false;
|
||||
$rootScope.books = [
|
||||
{title:'T1', description: 'D1'},
|
||||
{title:'T2', description: 'D2'}
|
||||
];
|
||||
element = $compile(
|
||||
'<div>' +
|
||||
'<dt ng-repeat-start="book in books">{{book.title}}:</dt>' +
|
||||
'<dd ng-repeat-end>{{book.description}};</dd>' +
|
||||
'</div>')($rootScope);
|
||||
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toEqual('T1:D1;T2:D2;');
|
||||
$rootScope.books.push({title:'T3', description: 'D3'});
|
||||
$rootScope.$digest();
|
||||
expect(element.text()).toEqual('T1:D1;T2:D2;T3:D3;');
|
||||
}));
|
||||
|
||||
|
||||
it('should not clobber ng-if when updating collection', inject(function ($compile, $rootScope) {
|
||||
$rootScope.values = [1, 2, 3];
|
||||
$rootScope.showMe = true;
|
||||
|
||||
element = $compile(
|
||||
'<div>' +
|
||||
'<div ng-repeat-start="val in values">val:{{val}};</div>' +
|
||||
'<div ng-if="showMe" ng-repeat-end>if:{{val}};</div>' +
|
||||
'</div>'
|
||||
)($rootScope);
|
||||
|
||||
$rootScope.$digest();
|
||||
expect(element.find('div').length).toBe(6);
|
||||
|
||||
$rootScope.values.shift();
|
||||
$rootScope.values.push(4);
|
||||
|
||||
$rootScope.$digest();
|
||||
expect(element.find('div').length).toBe(6);
|
||||
expect(element.text()).not.toContain('if:1;');
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('ngRepeat animations', function() {
|
||||
var body, element, $rootElement;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user