2012-03-08 23:00:38 +00:00
'use strict' ;
/ * *
* @ ngdoc directive
2012-06-12 06:49:24 +00:00
* @ name ng . directive : ngRepeat
2012-03-08 23:00:38 +00:00
*
* @ description
2012-04-06 23:35:17 +00:00
* The ` ngRepeat ` directive instantiates a template once per item from a collection . Each template
2012-03-08 23:00:38 +00:00
* instance gets its own scope , where the given loop variable is set to the current collection item ,
* and ` $ index ` is set to the item index or key .
*
* Special properties are exposed on the local scope of each template instance , including :
*
2013-05-21 01:00:12 +00:00
* | Variable | Type | Details |
2013-07-09 01:58:14 +00:00
* | -- -- -- -- -- - | -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - |
2013-05-21 01:00:12 +00:00
* | ` $ index ` | { @ type number } | iterator offset of the repeated element ( 0. . length - 1 ) |
* | ` $ first ` | { @ type boolean } | true if the repeated element is first in the iterator . |
* | ` $ middle ` | { @ type boolean } | true if the repeated element is between the first and last in the iterator . |
* | ` $ last ` | { @ type boolean } | true if the repeated element is last in the iterator . |
2013-07-19 15:02:17 +00:00
* | ` $ even ` | { @ type boolean } | true if the iterator position ` $ index ` is even ( otherwise false ) . |
* | ` $ odd ` | { @ type boolean } | true if the iterator position ` $ index ` is odd ( otherwise false ) . |
2012-03-08 23:00:38 +00:00
*
2013-06-05 23:05:38 +00:00
*
* # Special repeat start and end points
* To repeat a series of elements instead of just one parent element , ngRepeat ( as well as other ng directives ) supports extending
* the range of the repeater by defining explicit start and end points by using * * ng - repeat - start * * and * * ng - repeat - end * * respectively .
* The * * ng - repeat - start * * directive works the same as * * ng - repeat * * , but will repeat all the HTML code ( including the tag it ' s defined on )
* up to and including the ending HTML tag where * * ng - repeat - end * * is placed .
*
* The example below makes use of this feature :
* < pre >
* < header ng - repeat - start = "item in items" >
* Header { { item } }
* < / h e a d e r >
* < div class = "body" >
* Body { { item } }
* < / d i v >
* < footer ng - repeat - end >
* Footer { { item } }
* < / f o o t e r >
* < / p r e >
*
* And with an input of { @ type [ 'A' , 'B' ] } for the items variable in the example above , the output will evaluate to :
* < pre >
* < header >
* Header A
* < / h e a d e r >
* < div class = "body" >
* Body A
* < / d i v >
* < footer >
* Footer A
* < / f o o t e r >
* < header >
* Header B
* < / h e a d e r >
* < div class = "body" >
* Body B
* < / d i v >
* < footer >
* Footer B
* < / f o o t e r >
* < / p r e >
*
* The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS ( such
* as * * data - ng - repeat - start * * , * * x - ng - repeat - start * * and * * ng : repeat - start * * ) .
*
2013-03-20 23:24:23 +00:00
* @ animations
* enter - when a new item is added to the list or when an item is revealed after a filter
* leave - when an item is removed from the list or when an item is filtered out
* move - when an adjacent item is filtered out causing a reorder or when the item contents are reordered
2012-03-08 23:00:38 +00:00
*
* @ element ANY
* @ scope
* @ priority 1000
2013-03-20 05:27:27 +00:00
* @ param { repeat _expression } ngRepeat The expression indicating how to enumerate a collection . These
2012-03-08 23:00:38 +00:00
* formats are currently supported :
*
* * ` variable in expression ` – where variable is the user defined loop variable and ` expression `
* is a scope expression giving the collection to enumerate .
*
2013-07-19 01:44:23 +00:00
* For example : ` album in artist.albums ` .
2012-03-08 23:00:38 +00:00
*
* * ` (key, value) in expression ` – where ` key ` and ` value ` can be any user defined identifiers ,
* and ` expression ` is the scope expression giving the collection to enumerate .
*
* For example : ` (name, age) in {'adam':10, 'amalie':12} ` .
*
2013-03-20 05:27:27 +00:00
* * ` variable in expression track by tracking_expression ` – You can also provide an optional tracking function
2013-06-19 14:56:34 +00:00
* which can be used to associate the objects in the collection with the DOM elements . If no tracking function
2013-03-20 05:27:27 +00:00
* is specified the ng - repeat associates elements by identity in the collection . It is an error to have
2013-06-12 10:15:51 +00:00
* more than one tracking function to resolve to the same key . ( This would mean that two distinct objects are
2013-07-19 01:44:23 +00:00
* mapped to the same DOM element , which is not possible . ) Filters should be applied to the expression ,
* before specifying a tracking expression .
2013-03-20 05:27:27 +00:00
*
* For example : ` item in items ` is equivalent to ` item in items track by $ id(item)'. This implies that the DOM elements
* will be associated by item identity in the array .
*
* For example : ` item in items track by $ id(item) ` . A built in ` $ id() ` function can be used to assign a unique
* ` $ $ hashKey ` property to each item in the array . This property is then used as a key to associated DOM elements
* with the corresponding item in the array by identity . Moving the same object in array would move the DOM
2013-11-19 14:11:00 +00:00
* element in the same way in the DOM .
2013-03-20 05:27:27 +00:00
*
2013-07-19 01:44:23 +00:00
* For example : ` item in items track by item.id ` is a typical pattern when the items come from the database . In this
2013-03-20 05:27:27 +00:00
* case the object identity does not matter . Two objects are considered equivalent as long as their ` id `
* property is same .
*
2013-07-19 01:44:23 +00:00
* For example : ` item in items | filter:searchText track by item.id ` is a pattern that might be used to apply a filter
* to items in conjunction with a tracking expression .
*
2012-03-08 23:00:38 +00:00
* @ example
* This example initializes the scope to a list of names and
2012-04-06 23:35:17 +00:00
* then uses ` ngRepeat ` to display every person :
2013-04-02 23:41:16 +00:00
< example animations = "true" >
< file name = "index.html" >
< div ng - init = " friends = [
{ name : 'John' , age : 25 , gender : 'boy' } ,
{ name : 'Jessie' , age : 30 , gender : 'girl' } ,
{ name : 'Johanna' , age : 28 , gender : 'girl' } ,
{ name : 'Joy' , age : 15 , gender : 'girl' } ,
{ name : 'Mary' , age : 28 , gender : 'girl' } ,
{ name : 'Peter' , age : 95 , gender : 'boy' } ,
{ name : 'Sebastian' , age : 50 , gender : 'boy' } ,
{ name : 'Erika' , age : 27 , gender : 'girl' } ,
{ name : 'Patrick' , age : 40 , gender : 'boy' } ,
{ name : 'Samantha' , age : 60 , gender : 'girl' }
] " >
I have { { friends . length } } friends . They are :
< input type = "search" ng - model = "q" placeholder = "filter friends..." / >
2013-07-29 23:45:59 +00:00
< ul class = "example-animate-container" >
feat(ngAnimate): complete rewrite of animations
- ngAnimate directive is gone and was replaced with class based animations/transitions
- support for triggering animations on css class additions and removals
- done callback was added to all animation apis
- $animation and $animator where merged into a single $animate service with api:
- $animate.enter(element, parent, after, done);
- $animate.leave(element, done);
- $animate.move(element, parent, after, done);
- $animate.addClass(element, className, done);
- $animate.removeClass(element, className, done);
BREAKING CHANGE: too many things changed, we'll write up a separate doc with migration instructions
2013-06-18 17:59:57 +00:00
< li class = "animate-repeat" ng - repeat = "friend in friends | filter:q" >
2013-04-02 23:41:16 +00:00
[ { { $index + 1 } } ] { { friend . name } } who is { { friend . age } } years old .
< / l i >
< / u l >
< / d i v >
< / f i l e >
< file name = "animations.css" >
2013-07-29 23:45:59 +00:00
. example - animate - container {
background : white ;
border : 1 px solid black ;
list - style : none ;
margin : 0 ;
2013-11-06 01:19:45 +00:00
padding : 0 10 px ;
2013-07-29 23:45:59 +00:00
}
2013-11-06 01:19:45 +00:00
. animate - repeat {
line - height : 40 px ;
2013-07-29 23:45:59 +00:00
list - style : none ;
2013-11-06 01:19:45 +00:00
box - sizing : border - box ;
2013-07-29 23:45:59 +00:00
}
2013-11-06 01:19:45 +00:00
. animate - repeat . ng - move ,
2013-07-29 23:45:59 +00:00
. animate - repeat . ng - enter ,
2013-11-06 01:19:45 +00:00
. animate - repeat . ng - leave {
2013-04-02 23:41:16 +00:00
- webkit - transition : all linear 0.5 s ;
transition : all linear 0.5 s ;
}
2013-11-06 01:19:45 +00:00
. animate - repeat . ng - leave . ng - leave - active ,
. animate - repeat . ng - move ,
feat(ngAnimate): complete rewrite of animations
- ngAnimate directive is gone and was replaced with class based animations/transitions
- support for triggering animations on css class additions and removals
- done callback was added to all animation apis
- $animation and $animator where merged into a single $animate service with api:
- $animate.enter(element, parent, after, done);
- $animate.leave(element, done);
- $animate.move(element, parent, after, done);
- $animate.addClass(element, className, done);
- $animate.removeClass(element, className, done);
BREAKING CHANGE: too many things changed, we'll write up a separate doc with migration instructions
2013-06-18 17:59:57 +00:00
. animate - repeat . ng - enter {
2013-04-02 23:41:16 +00:00
opacity : 0 ;
2013-11-06 01:19:45 +00:00
max - height : 0 ;
2013-04-02 23:41:16 +00:00
}
2013-11-06 01:19:45 +00:00
. animate - repeat . ng - leave ,
. animate - repeat . ng - move . ng - move - active ,
. animate - repeat . ng - enter . ng - enter - active {
2013-04-02 23:41:16 +00:00
opacity : 1 ;
2013-11-06 01:19:45 +00:00
max - height : 40 px ;
2013-04-02 23:41:16 +00:00
}
< / f i l e >
< file name = "scenario.js" >
it ( 'should render initial data set' , function ( ) {
var r = using ( '.doc-example-live' ) . repeater ( 'ul li' ) ;
expect ( r . count ( ) ) . toBe ( 10 ) ;
expect ( r . row ( 0 ) ) . toEqual ( [ "1" , "John" , "25" ] ) ;
expect ( r . row ( 1 ) ) . toEqual ( [ "2" , "Jessie" , "30" ] ) ;
expect ( r . row ( 9 ) ) . toEqual ( [ "10" , "Samantha" , "60" ] ) ;
expect ( binding ( 'friends.length' ) ) . toBe ( "10" ) ;
} ) ;
it ( 'should update repeater when filter predicate changes' , function ( ) {
var r = using ( '.doc-example-live' ) . repeater ( 'ul li' ) ;
expect ( r . count ( ) ) . toBe ( 10 ) ;
input ( 'q' ) . enter ( 'ma' ) ;
expect ( r . count ( ) ) . toBe ( 2 ) ;
expect ( r . row ( 0 ) ) . toEqual ( [ "1" , "Mary" , "28" ] ) ;
expect ( r . row ( 1 ) ) . toEqual ( [ "2" , "Samantha" , "60" ] ) ;
} ) ;
< / f i l e >
< / e x a m p l e >
2012-03-08 23:00:38 +00:00
* /
feat(ngAnimate): complete rewrite of animations
- ngAnimate directive is gone and was replaced with class based animations/transitions
- support for triggering animations on css class additions and removals
- done callback was added to all animation apis
- $animation and $animator where merged into a single $animate service with api:
- $animate.enter(element, parent, after, done);
- $animate.leave(element, done);
- $animate.move(element, parent, after, done);
- $animate.addClass(element, className, done);
- $animate.removeClass(element, className, done);
BREAKING CHANGE: too many things changed, we'll write up a separate doc with migration instructions
2013-06-18 17:59:57 +00:00
var ngRepeatDirective = [ '$parse' , '$animate' , function ( $parse , $animate ) {
2013-03-20 23:24:23 +00:00
var NG _REMOVED = '$$NG_REMOVED' ;
2013-06-08 01:24:30 +00:00
var ngRepeatMinErr = minErr ( 'ngRepeat' ) ;
2013-03-20 05:27:27 +00:00
return {
transclude : 'element' ,
priority : 1000 ,
terminal : true ,
2013-10-30 22:02:25 +00:00
$$tlb : true ,
2013-11-14 21:50:36 +00:00
link : function ( $scope , $element , $attr , ctrl , $transclude ) {
2013-03-20 05:27:27 +00:00
var expression = $attr . ngRepeat ;
var match = expression . match ( /^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$/ ) ,
2013-09-24 20:51:28 +00:00
trackByExp , trackByExpGetter , trackByIdExpFn , trackByIdArrayFn , trackByIdObjFn ,
lhs , rhs , valueIdentifier , keyIdentifier ,
2013-03-20 05:27:27 +00:00
hashFnLocals = { $id : hashKey } ;
if ( ! match ) {
2013-06-08 01:24:30 +00:00
throw ngRepeatMinErr ( 'iexp' , "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'." ,
2013-05-24 18:00:14 +00:00
expression ) ;
2012-03-08 23:00:38 +00:00
}
2013-03-20 05:27:27 +00:00
lhs = match [ 1 ] ;
rhs = match [ 2 ] ;
trackByExp = match [ 4 ] ;
if ( trackByExp ) {
2013-04-11 23:28:42 +00:00
trackByExpGetter = $parse ( trackByExp ) ;
2013-09-24 20:51:28 +00:00
trackByIdExpFn = function ( key , value , index ) {
2013-03-20 05:27:27 +00:00
// assign key, value, and $index to the locals so that they can be used in hash functions
if ( keyIdentifier ) hashFnLocals [ keyIdentifier ] = key ;
hashFnLocals [ valueIdentifier ] = value ;
hashFnLocals . $index = index ;
2013-04-11 23:28:42 +00:00
return trackByExpGetter ( $scope , hashFnLocals ) ;
2013-03-20 05:27:27 +00:00
} ;
} else {
2013-05-27 17:59:19 +00:00
trackByIdArrayFn = function ( key , value ) {
2013-03-20 05:27:27 +00:00
return hashKey ( value ) ;
2013-10-22 21:41:21 +00:00
} ;
2013-05-27 17:59:19 +00:00
trackByIdObjFn = function ( key ) {
return key ;
2013-10-22 21:41:21 +00:00
} ;
2013-03-20 05:27:27 +00:00
}
2013-04-02 23:41:16 +00:00
2013-03-20 05:27:27 +00:00
match = lhs . match ( /^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/ ) ;
if ( ! match ) {
2013-06-08 01:24:30 +00:00
throw ngRepeatMinErr ( 'iidexp' , "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'." ,
2013-05-24 18:00:14 +00:00
lhs ) ;
2013-03-20 05:27:27 +00:00
}
valueIdentifier = match [ 3 ] || match [ 1 ] ;
keyIdentifier = match [ 2 ] ;
// Store a list of elements from previous run. This is a hash where key is the item from the
// iterator, and the value is objects with following properties.
// - scope: bound scope
// - element: previous element.
// - index: position
var lastBlockMap = { } ;
//watch props
$scope . $watchCollection ( rhs , function ngRepeatAction ( collection ) {
var index , length ,
2013-06-11 20:14:17 +00:00
previousNode = $element [ 0 ] , // current position of the node
nextNode ,
2013-03-20 05:27:27 +00:00
// Same as lastBlockMap but it has the current state. It will become the
// lastBlockMap on the next iteration.
nextBlockMap = { } ,
arrayLength ,
childScope ,
key , value , // key/value of iteration
trackById ,
2013-09-24 20:51:28 +00:00
trackByIdFn ,
2013-03-20 05:27:27 +00:00
collectionKeys ,
block , // last object information {scope, element, id}
2013-09-23 18:24:42 +00:00
nextBlockOrder = [ ] ,
elementsToRemove ;
2013-03-20 05:27:27 +00:00
2013-04-30 23:19:44 +00:00
if ( isArrayLike ( collection ) ) {
2013-03-20 05:27:27 +00:00
collectionKeys = collection ;
2013-09-24 20:51:28 +00:00
trackByIdFn = trackByIdExpFn || trackByIdArrayFn ;
2012-03-08 23:00:38 +00:00
} else {
2013-09-24 20:51:28 +00:00
trackByIdFn = trackByIdExpFn || trackByIdObjFn ;
2013-03-20 05:27:27 +00:00
// if object, extract keys, sort them and use to determine order of iteration over obj props
collectionKeys = [ ] ;
for ( key in collection ) {
if ( collection . hasOwnProperty ( key ) && key . charAt ( 0 ) != '$' ) {
collectionKeys . push ( key ) ;
}
}
collectionKeys . sort ( ) ;
2012-03-08 23:00:38 +00:00
}
2013-03-20 05:27:27 +00:00
arrayLength = collectionKeys . length ;
// locate existing items
length = nextBlockOrder . length = collectionKeys . length ;
for ( index = 0 ; index < length ; index ++ ) {
key = ( collection === collectionKeys ) ? index : collectionKeys [ index ] ;
value = collection [ key ] ;
trackById = trackByIdFn ( key , value , index ) ;
2013-10-05 09:49:09 +00:00
assertNotHasOwnProperty ( trackById , '`track by` id' ) ;
2013-04-11 23:28:42 +00:00
if ( lastBlockMap . hasOwnProperty ( trackById ) ) {
2013-10-22 21:41:21 +00:00
block = lastBlockMap [ trackById ] ;
2013-03-20 05:27:27 +00:00
delete lastBlockMap [ trackById ] ;
nextBlockMap [ trackById ] = block ;
nextBlockOrder [ index ] = block ;
} else if ( nextBlockMap . hasOwnProperty ( trackById ) ) {
// restore lastBlockMap
forEach ( nextBlockOrder , function ( block ) {
2013-06-11 20:14:17 +00:00
if ( block && block . startNode ) lastBlockMap [ block . id ] = block ;
2013-03-20 05:27:27 +00:00
} ) ;
// This is a duplicate and we need to throw an error
2013-06-08 01:24:30 +00:00
throw ngRepeatMinErr ( 'dupes' , "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}" ,
2013-05-24 18:00:14 +00:00
expression , trackById ) ;
2013-03-20 05:27:27 +00:00
} else {
// new never before seen block
nextBlockOrder [ index ] = { id : trackById } ;
2013-04-11 23:28:42 +00:00
nextBlockMap [ trackById ] = false ;
2013-03-20 05:27:27 +00:00
}
}
// remove existing items
for ( key in lastBlockMap ) {
2013-10-05 09:49:09 +00:00
// lastBlockMap is our own object so we don't need to use special hasOwnPropertyFn
2013-03-20 05:27:27 +00:00
if ( lastBlockMap . hasOwnProperty ( key ) ) {
block = lastBlockMap [ key ] ;
2013-09-23 18:24:42 +00:00
elementsToRemove = getBlockElements ( block ) ;
$animate . leave ( elementsToRemove ) ;
forEach ( elementsToRemove , function ( element ) { element [ NG _REMOVED ] = true ; } ) ;
2013-03-20 05:27:27 +00:00
block . scope . $destroy ( ) ;
}
2012-03-08 23:00:38 +00:00
}
2013-03-20 05:27:27 +00:00
// we are not using forEach for perf reasons (trying to avoid #call)
for ( index = 0 , length = collectionKeys . length ; index < length ; index ++ ) {
key = ( collection === collectionKeys ) ? index : collectionKeys [ index ] ;
value = collection [ key ] ;
block = nextBlockOrder [ index ] ;
2013-09-23 18:24:42 +00:00
if ( nextBlockOrder [ index - 1 ] ) previousNode = nextBlockOrder [ index - 1 ] . endNode ;
2013-03-20 05:27:27 +00:00
2013-06-11 20:14:17 +00:00
if ( block . startNode ) {
2013-03-20 05:27:27 +00:00
// if we have already seen this object, then we need to reuse the
// associated scope/element
childScope = block . scope ;
2013-06-11 20:14:17 +00:00
nextNode = previousNode ;
2013-03-20 23:24:23 +00:00
do {
2013-06-11 20:14:17 +00:00
nextNode = nextNode . nextSibling ;
} while ( nextNode && nextNode [ NG _REMOVED ] ) ;
2013-03-20 23:24:23 +00:00
2013-10-22 21:41:21 +00:00
if ( block . startNode != nextNode ) {
2013-03-20 05:27:27 +00:00
// existing item which got moved
2013-09-23 18:24:42 +00:00
$animate . move ( getBlockElements ( block ) , null , jqLite ( previousNode ) ) ;
2013-03-20 05:27:27 +00:00
}
2013-06-11 20:14:17 +00:00
previousNode = block . endNode ;
2013-03-20 05:27:27 +00:00
} else {
// new item which we don't know about
childScope = $scope . $new ( ) ;
2012-03-08 23:00:38 +00:00
}
2013-03-20 05:27:27 +00:00
childScope [ valueIdentifier ] = value ;
if ( keyIdentifier ) childScope [ keyIdentifier ] = key ;
childScope . $index = index ;
childScope . $first = ( index === 0 ) ;
childScope . $last = ( index === ( arrayLength - 1 ) ) ;
childScope . $middle = ! ( childScope . $first || childScope . $last ) ;
2013-10-22 21:41:21 +00:00
// jshint bitwise: false
childScope . $odd = ! ( childScope . $even = ( index & 1 ) === 0 ) ;
// jshint bitwise: true
2013-03-20 05:27:27 +00:00
2013-06-11 20:14:17 +00:00
if ( ! block . startNode ) {
2013-11-14 21:50:36 +00:00
$transclude ( childScope , function ( clone ) {
2013-09-23 18:24:42 +00:00
clone [ clone . length ++ ] = document . createComment ( ' end ngRepeat: ' + expression + ' ' ) ;
feat(ngAnimate): complete rewrite of animations
- ngAnimate directive is gone and was replaced with class based animations/transitions
- support for triggering animations on css class additions and removals
- done callback was added to all animation apis
- $animation and $animator where merged into a single $animate service with api:
- $animate.enter(element, parent, after, done);
- $animate.leave(element, done);
- $animate.move(element, parent, after, done);
- $animate.addClass(element, className, done);
- $animate.removeClass(element, className, done);
BREAKING CHANGE: too many things changed, we'll write up a separate doc with migration instructions
2013-06-18 17:59:57 +00:00
$animate . enter ( clone , null , jqLite ( previousNode ) ) ;
2013-06-11 20:14:17 +00:00
previousNode = clone ;
2013-03-20 05:27:27 +00:00
block . scope = childScope ;
2013-09-23 18:24:42 +00:00
block . startNode = previousNode && previousNode . endNode ? previousNode . endNode : clone [ 0 ] ;
2013-06-11 20:14:17 +00:00
block . endNode = clone [ clone . length - 1 ] ;
2013-03-20 05:27:27 +00:00
nextBlockMap [ block . id ] = block ;
} ) ;
}
}
lastBlockMap = nextBlockMap ;
} ) ;
}
} ;
} ] ;
2013-03-20 23:24:23 +00:00