Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: angular/angular.js
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: d5ccabc
Choose a base ref
...
head repository: angular/angular.js
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 25d1822
Choose a head ref
  • 2 commits
  • 3 files changed
  • 1 contributor

Commits on Oct 26, 2011

  1. fix(ng:repeat) with array ignore properties not representing array el…

    …ements
    
    Along the way I also changed the repeater impl to use for loop instead
    of for in loop.
    
    Iteration over objects is handled by creating an array of keys, which is
    sorted and this array then determines the order of iteration over an
    element. This makes repeating over objects deterministic and
    cross-browser compatible.
    IgorMinar committed Oct 26, 2011

    Verified

    This commit was signed with the committer’s verified signature.
    anthonyfok Anthony Fok
    Copy the full SHA
    3945f88 View commit details
  2. Copy the full SHA
    25d1822 View commit details
Showing with 78 additions and 46 deletions.
  1. +1 −1 src/apis.js
  2. +55 −43 src/widgets.js
  3. +22 −2 test/widgetsSpec.js
2 changes: 1 addition & 1 deletion src/apis.js
Original file line number Diff line number Diff line change
@@ -936,7 +936,7 @@ HashMap.prototype = {
};

/**
* A map where multiple values can be added to the same key such that the form a queue.
* A map where multiple values can be added to the same key such that they form a queue.
* @returns {HashQueueMap}
*/
function HashQueueMap() {}
98 changes: 55 additions & 43 deletions src/widgets.js
Original file line number Diff line number Diff line change
@@ -361,7 +361,7 @@ angularWidget('@ng:repeat', function(expression, element){
// We expect this to be a rare case.
var lastOrder = new HashQueueMap();
this.$watch(function(scope){
var index = 0,
var index, length,
collection = scope.$eval(rhs),
collectionLength = size(collection, true),
childScope,
@@ -372,52 +372,64 @@ angularWidget('@ng:repeat', function(expression, element){
array, last, // last object information {scope, element, index}
cursor = iterStartElement; // current position of the node

for (key in collection) {
if (collection.hasOwnProperty(key) && key.charAt(0) != '$') {
last = lastOrder.shift(value = collection[key]);
if (last) {
// if we have already seen this object, then we need to reuse the
// associated scope/element
childScope = last.scope;
nextOrder.push(value, last);

if (index === last.index) {
// do nothing
cursor = last.element;
} else {
// existing item which got moved
last.index = index;
// This may be a noop, if the element is next, but I don't know of a good way to
// figure this out, since it would require extra DOM access, so let's just hope that
// the browsers realizes that it is noop, and treats it as such.
cursor.after(last.element);
cursor = last.element;
}
} else {
// new item which we don't know about
childScope = parentScope.$new();
if (!isArray(collection)) {
// if object, extract keys, sort them and use to determine order of iteration over obj props
array = [];
for(key in collection) {
if (collection.hasOwnProperty(key) && key.charAt(0) != '$') {
array.push(key);
}
}
array.sort();
} else {
array = collection || [];
}

childScope[valueIdent] = collection[key];
if (keyIdent) childScope[keyIdent] = key;
childScope.$index = index;
childScope.$position = index == 0
? 'first'
: (index == collectionLength - 1 ? 'last' : 'middle');

if (!last) {
linker(childScope, function(clone){
cursor.after(clone);
last = {
scope: childScope,
element: (cursor = clone),
index: index
};
nextOrder.push(value, last);
});
// we are not using forEach for perf reasons (trying to avoid #call)
for (index = 0, length = array.length; index < length; index++) {
key = (collection === array) ? index : array[index];
value = collection[key];
last = lastOrder.shift(value);
if (last) {
// if we have already seen this object, then we need to reuse the
// associated scope/element
childScope = last.scope;
nextOrder.push(value, last);

if (index === last.index) {
// do nothing
cursor = last.element;
} else {
// existing item which got moved
last.index = index;
// This may be a noop, if the element is next, but I don't know of a good way to
// figure this out, since it would require extra DOM access, so let's just hope that
// the browsers realizes that it is noop, and treats it as such.
cursor.after(last.element);
cursor = last.element;
}
} else {
// new item which we don't know about
childScope = parentScope.$new();
}

index ++;
childScope[valueIdent] = value;
if (keyIdent) childScope[keyIdent] = key;
childScope.$index = index;
childScope.$position = index == 0
? 'first'
: (index == collectionLength - 1 ? 'last' : 'middle');

if (!last) {
linker(childScope, function(clone){
cursor.after(clone);
last = {
scope: childScope,
element: (cursor = clone),
index: index
};
nextOrder.push(value, last);
});
}
}

24 changes: 22 additions & 2 deletions test/widgetsSpec.js
Original file line number Diff line number Diff line change
@@ -254,7 +254,7 @@ describe("widget", function() {
'ng:bind="key + \':\' + val + $index + \'|\'"></li></ul>');
scope.items = {'misko':'m', 'shyam':'s', 'frodo':'f'};
scope.$digest();
expect(element.text()).toEqual('misko:m0|shyam:s1|frodo:f2|');
expect(element.text()).toEqual('frodo:f0|misko:m1|shyam:s2|');
});

it('should expose iterator position as $position when iterating over arrays', function() {
@@ -282,7 +282,7 @@ describe("widget", function() {
'</ul>');
scope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f'};
scope.$digest();
expect(element.text()).toEqual('misko:m:first|shyam:s:middle|doug:d:middle|frodo:f:last|');
expect(element.text()).toEqual('doug:d:first|frodo:f:middle|misko:m:middle|shyam:s:last|');

delete scope.items.doug;
delete scope.items.frodo;
@@ -312,6 +312,26 @@ describe("widget", function() {
expect(element.text()).toEqual('a|b|Xc|d|X');
});

it('should ignore non-array element properties when iterating over an array', function() {
var scope = compile('<ul><li ng:repeat="item in array">{{item}}|</li></ul>');
scope.array = ['a', 'b', 'c'];
scope.array.foo = '23';
scope.array.bar = function() {};
scope.$digest();

expect(element.text()).toBe('a|b|c|');
});

it('should iterate over non-existent elements of a sparse array', function() {
var scope = compile('<ul><li ng:repeat="item in array">{{item}}|</li></ul>');
scope.array = ['a', 'b'];
scope.array[4] = 'c';
scope.array[6] = 'd';
scope.$digest();

expect(element.text()).toBe('a|b|||c||d|');
});


describe('stability', function() {
var a, b, c, d, scope, lis;