Skip to content

Commit c5b2bf0

Browse files
committed
Make ng:repeat expose $position.
- $position is a textual representation of the position of repeated item ('first', 'middle', 'last') - added specs for $index
1 parent 0499c47 commit c5b2bf0

File tree

2 files changed

+88
-29
lines changed

2 files changed

+88
-29
lines changed

src/directives.js

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,10 @@ angularWidget("@ng:non-bindable", noop);
434434
* scope expression giving the collection to enumerate.
435435
* For example: `(name, age) in {'adam':10, 'amalie':12}`.
436436
*
437+
* Special properties set on the local scope:
438+
* * {number} $index - iterator offset of the repeated element (0..length-1)
439+
* * {string} $position - position of the repeated element in the iterator ('first', 'middle', 'last')
440+
*
437441
* @exampleDescription
438442
* This example initializes the scope to a list of names and
439443
* than uses `ng:repeat` to display every person.
@@ -477,9 +481,24 @@ angularWidget("@ng:repeat", function(expression, element){
477481

478482
var children = [], currentScope = this;
479483
this.$onEval(function(){
480-
var index = 0, childCount = children.length, childScope, lastElement = reference,
481-
collection = this.$tryEval(rhs, reference), is_array = isArray(collection);
482-
for ( var key in collection) {
484+
var index = 0,
485+
childCount = children.length,
486+
lastElement = reference,
487+
collection = this.$tryEval(rhs, reference),
488+
is_array = isArray(collection),
489+
collectionLength = 0,
490+
childScope,
491+
key;
492+
493+
if (is_array) {
494+
collectionLength = collection.length;
495+
} else {
496+
for (key in collection)
497+
if (collection.hasOwnProperty(key))
498+
collectionLength++;
499+
}
500+
501+
for (key in collection) {
483502
if (!is_array || collection.hasOwnProperty(key)) {
484503
if (index < childCount) {
485504
// reuse existing child
@@ -493,6 +512,9 @@ angularWidget("@ng:repeat", function(expression, element){
493512
if (keyIdent) childScope[keyIdent] = key;
494513
lastElement.after(childScope.$element);
495514
childScope.$index = index;
515+
childScope.$position = index == 0 ?
516+
'first' :
517+
(index == collectionLength - 1 ? 'last' : 'middle');
496518
childScope.$element.attr('ng:repeat-index', index);
497519
childScope.$init();
498520
children.push(childScope);

test/directivesSpec.js

Lines changed: 63 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -135,39 +135,76 @@ describe("directives", function(){
135135
expect(element.text()).toEqual('');
136136
});
137137

138-
it('should ng:repeat over array', function(){
139-
var scope = compile('<ul><li ng:repeat="item in items" ng:init="suffix = \';\'" ng:bind="item + suffix"></li></ul>');
140138

141-
Array.prototype.extraProperty = "should be ignored";
142-
scope.items = ['misko', 'shyam'];
143-
scope.$eval();
144-
expect(element.text()).toEqual('misko;shyam;');
145-
delete Array.prototype.extraProperty;
139+
describe('ng:repeat', function() {
146140

147-
scope.items = ['adam', 'kai', 'brad'];
148-
scope.$eval();
149-
expect(element.text()).toEqual('adam;kai;brad;');
141+
it('should ng:repeat over array', function(){
142+
var scope = compile('<ul><li ng:repeat="item in items" ng:init="suffix = \';\'" ng:bind="item + suffix"></li></ul>');
150143

151-
scope.items = ['brad'];
152-
scope.$eval();
153-
expect(element.text()).toEqual('brad;');
154-
});
144+
Array.prototype.extraProperty = "should be ignored";
145+
scope.items = ['misko', 'shyam'];
146+
scope.$eval();
147+
expect(element.text()).toEqual('misko;shyam;');
148+
delete Array.prototype.extraProperty;
155149

156-
it('should ng:repeat over object', function(){
157-
var scope = compile('<ul><li ng:repeat="(key, value) in items" ng:bind="key + \':\' + value + \';\' "></li></ul>');
158-
scope.$set('items', {misko:'swe', shyam:'set'});
159-
scope.$eval();
160-
expect(element.text()).toEqual('misko:swe;shyam:set;');
161-
});
150+
scope.items = ['adam', 'kai', 'brad'];
151+
scope.$eval();
152+
expect(element.text()).toEqual('adam;kai;brad;');
153+
154+
scope.items = ['brad'];
155+
scope.$eval();
156+
expect(element.text()).toEqual('brad;');
157+
});
158+
159+
it('should ng:repeat over object', function(){
160+
var scope = compile('<ul><li ng:repeat="(key, value) in items" ng:bind="key + \':\' + value + \';\' "></li></ul>');
161+
scope.$set('items', {misko:'swe', shyam:'set'});
162+
scope.$eval();
163+
expect(element.text()).toEqual('misko:swe;shyam:set;');
164+
});
162165

163-
it('should error on wrong parsing of ng:repeat', function(){
164-
var scope = compile('<ul><li ng:repeat="i dont parse"></li></ul>');
165-
var log = "";
166-
log += element.attr('ng-exception') + ';';
167-
log += element.hasClass('ng-exception') + ';';
168-
expect(log).toEqual("\"Expected ng:repeat in form of 'item in collection' but got 'i dont parse'.\";true;");
166+
it('should error on wrong parsing of ng:repeat', function(){
167+
var scope = compile('<ul><li ng:repeat="i dont parse"></li></ul>');
168+
var log = "";
169+
log += element.attr('ng-exception') + ';';
170+
log += element.hasClass('ng-exception') + ';';
171+
expect(log).toEqual("\"Expected ng:repeat in form of 'item in collection' but got 'i dont parse'.\";true;");
172+
});
173+
174+
it('should expose iterator offset as $index when iterating over arrays', function() {
175+
var scope = compile('<ul><li ng:repeat="item in items" ' +
176+
'ng:bind="item + $index + \'|\'"></li></ul>');
177+
scope.items = ['misko', 'shyam', 'frodo'];
178+
scope.$eval();
179+
expect(element.text()).toEqual('misko0|shyam1|frodo2|');
180+
});
181+
182+
it('should expose iterator offset as $index when iterating over objects', function() {
183+
var scope = compile('<ul><li ng:repeat="(key, val) in items" ' +
184+
'ng:bind="key + \':\' + val + $index + \'|\'"></li></ul>');
185+
scope.items = {'misko':'m', 'shyam':'s', 'frodo':'f'};
186+
scope.$eval();
187+
expect(element.text()).toEqual('misko:m0|shyam:s1|frodo:f2|');
188+
});
189+
190+
it('should expose iterator position as $position when iterating over arrays', function() {
191+
var scope = compile('<ul><li ng:repeat="item in items" ' +
192+
'ng:bind="item + \':\' + $position + \'|\'"></li></ul>');
193+
scope.items = ['misko', 'shyam', 'doug', 'frodo'];
194+
scope.$eval();
195+
expect(element.text()).toEqual('misko:first|shyam:middle|doug:middle|frodo:last|');
196+
});
197+
198+
it('should expose iterator position as $position when iterating over objects', function() {
199+
var scope = compile('<ul><li ng:repeat="(key, val) in items" ' +
200+
'ng:bind="key + \':\' + val + \':\' + $position + \'|\'"></li></ul>');
201+
scope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f'};
202+
scope.$eval();
203+
expect(element.text()).toEqual('misko:m:first|shyam:s:middle|doug:d:middle|frodo:f:last|');
204+
});
169205
});
170206

207+
171208
it('should ng:watch', function(){
172209
var scope = compile('<div ng:watch="i: count = count + 1" ng:init="count = 0">');
173210
scope.$eval();

0 commit comments

Comments
 (0)