Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(356)

Side by Side Diff: third_party/pkg/angular/lib/directive/ng_repeat.dart

Issue 176943008: Update the Angular/DI tests to latest from github. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 part of angular.directive; 1 part of angular.directive;
2 2
3 class _Row { 3 class _Row {
4 var id; 4 var id;
5 Scope scope; 5 Scope scope;
6 Block block; 6 Block block;
7 dom.Element startNode; 7 dom.Element startNode;
8 dom.Element endNode; 8 dom.Element endNode;
9 List<dom.Element> elements; 9 List<dom.Element> elements;
10 10
11 _Row(this.id); 11 _Row(this.id);
12 } 12 }
13 13
14 /** 14 /**
15 * The `ngRepeat` directive instantiates a template once per item from a collect ion. Each template 15 * The `ngRepeat` directive instantiates a template once per item from a
16 * instance gets its own scope, where the given loop variable is set to the curr ent collection item, 16 * collection. Each template instance gets its own scope, where the given loop
17 * and `$index` is set to the item index or key. 17 * variable is set to the current collection item, and `$index` is set to the
18 * item index or key.
18 * 19 *
19 * Special properties are exposed on the local scope of each template instance, including: 20 * Special properties are exposed on the local scope of each template instance,
21 * including:
20 * 22 *
21 * <table> 23 * <table>
22 * <tr><th> Variable </th><th> Type </th><th> Details <th></tr> 24 * <tr><th> Variable </th><th> Type </th><th> Details <th></tr>
23 * <tr><td> `$index` </td><td>[num] </td><td> iterator offset of the repeated e lement (0..length-1) <td></tr> 25 * <tr><td> `$index` </td><td>[num] </td><td> iterator offset of the repeated e lement (0..length-1) <td></tr>
24 * <tr><td> `$first` </td><td>[bool]</td><td> true if the repeated element is f irst in the iterator. <td></tr> 26 * <tr><td> `$first` </td><td>[bool]</td><td> true if the repeated element is f irst in the iterator. <td></tr>
25 * <tr><td> `$middle` </td><td>[bool]</td><td> true if the repeated element is b etween the first and last in the iterator. <td></tr> 27 * <tr><td> `$middle` </td><td>[bool]</td><td> true if the repeated element is b etween the first and last in the iterator. <td></tr>
26 * <tr><td> `$last` </td><td>[bool]</td><td> true if the repeated element is l ast in the iterator. <td></tr> 28 * <tr><td> `$last` </td><td>[bool]</td><td> true if the repeated element is l ast in the iterator. <td></tr>
27 * <tr><td> `$even` </td><td>[bool]</td><td> true if the iterator position `$i ndex` is even (otherwise false). <td></tr> 29 * <tr><td> `$even` </td><td>[bool]</td><td> true if the iterator position `$i ndex` is even (otherwise false). <td></tr>
28 * <tr><td> `$odd` </td><td>[bool]</td><td> true if the iterator position `$i ndex` is odd (otherwise false). <td></tr> 30 * <tr><td> `$odd` </td><td>[bool]</td><td> true if the iterator position `$i ndex` is odd (otherwise false). <td></tr>
29 * </table> 31 * </table>
30 * 32 *
31 * 33 *
32 * [repeat_expression] ngRepeat The expression indicating how to enumerate a col lection. These 34 * [repeat_expression] ngRepeat The expression indicating how to enumerate a
33 * formats are currently supported: 35 * collection. These formats are currently supported:
34 * 36 *
35 * * `variable in expression` – where variable is the user defined loop variab le and `expression` 37 * * `variable in expression` – where variable is the user defined loop
36 * is a scope expression giving the collection to enumerate. 38 * variable and `expression` is a scope expression giving the collection to
39 * enumerate.
37 * 40 *
38 * For example: `album in artist.albums`. 41 * For example: `album in artist.albums`.
39 * 42 *
40 * * `variable in expression track by tracking_expression` – You can also prov ide an optional 43 * * `variable in expression track by tracking_expression` – You can also
41 * tracking function which can be used to associate the objects in the colle ction with the DOM 44 * provide an optional tracking function which can be used to associate the
42 * elements. If no tracking function is specified the ng-repeat associates e lements by identity 45 * objects in the collection with the DOM elements. If no tracking function is
43 * in the collection. It is an error to have more than one tracking function to resolve to the 46 * specified the ng-repeat associates elements by identity in the collection.
44 * same key. (This would mean that two distinct objects are mapped to the sa me DOM element, 47 * It is an error to have more than one tracking function to resolve to the
45 * which is not possible.) Filters should be applied to the expression, bef ore specifying a 48 * same key. (This would mean that two distinct objects are mapped to the same
46 * tracking expression. 49 * DOM element, which is not possible.) Filters should be applied to the
50 * expression, before specifying a tracking expression.
47 * 51 *
48 * For example: `item in items` is equivalent to `item in items track by $id (item)'. This 52 * For example: `item in items` is equivalent to `item in items track by
49 * implies that the DOM elements will be associated by item identity in the array. 53 * $id(item)`. This implies that the DOM elements will be associated by item
54 * identity in the array.
50 * 55 *
51 * For example: `item in items track by $id(item)`. A built in `$id()` funct ion can be used 56 * For example: `item in items track by $id(item)`. A built in `$id()`
52 * to assign a unique `$$hashKey` property to each item in the array. This p roperty is then 57 * function can be used to assign a unique `$$hashKey` property to each item
53 * used as a key to associated DOM elements with the corresponding item in t he array by 58 * in the array. This property is then used as a key to associated DOM
54 * identity. Moving the same object in array would move the DOM element in t he same way ian the 59 * elements with the corresponding item in the array by identity. Moving the
60 * same object in array would move the DOM element in the same way ian the
55 * DOM. 61 * DOM.
56 * 62 *
57 * For example: `item in items track by item.id` is a typical pattern when t he items come from 63 * For example: `item in items track by item.id` is a typical pattern when
58 * the database. In this case the object identity does not matter. Two objec ts are considered 64 * the items come from the database. In this case the object identity does
59 * equivalent as long as their `id` property is same. 65 * not matter. Two objects are considered equivalent as long as their `id`
66 * property is same.
60 * 67 *
61 * For example: `item in items | filter:searchText track by item.id` is a pa ttern that might be 68 * For example: `item in items | filter:searchText track by item.id` is a
62 * used to apply a filter to items in conjunction with a tracking expression . 69 * pattern that might be used to apply a filter to items in conjunction with
63 * 70 * a tracking expression.
64 * 71 *
65 * # Example: 72 * # Example:
66 * 73 *
67 * <ul ng-repeat="item in ['foo', 'bar', 'baz']"> 74 * <ul>
68 * <li>{{$item}}</li> 75 * <li ng-repeat="item in ['foo', 'bar', 'baz']">{{item}}</li>
69 * </ul> 76 * </ul>
70 */ 77 */
71 78
72 @NgDirective( 79 @NgDirective(
73 children: NgAnnotation.TRANSCLUDE_CHILDREN, 80 children: NgAnnotation.TRANSCLUDE_CHILDREN,
74 selector: '[ng-repeat]', 81 selector: '[ng-repeat]',
75 map: const {'.': '@expression'}) 82 map: const {'.': '@expression'})
76 class NgRepeatDirective extends AbstractNgRepeatDirective { 83 class NgRepeatDirective extends AbstractNgRepeatDirective {
77 NgRepeatDirective(BlockHole blockHole, 84 NgRepeatDirective(BlockHole blockHole,
78 BoundBlockFactory boundBlockFactory, 85 BoundBlockFactory boundBlockFactory,
79 Scope scope): super(blockHole, boundBlockFactory, scope); 86 Scope scope,
80 get _shalow => false; 87 Parser parser,
88 AstParser astParser)
89 : super(blockHole, boundBlockFactory, scope, parser, astParser);
81 } 90 }
82 91
83 /** 92 /**
84 * *EXPERIMENTAL:* This feature is experimental. We reserve the right to change or delete it. 93 * *EXPERIMENTAL:* This feature is experimental. We reserve the right to change
94 * or delete it.
85 * 95 *
86 * [ng-shallow-repeat] is same as [ng-repeat] with some tradeoffs designed for s peed. Use 96 * [ng-shallow-repeat] is same as [ng-repeat] with some tradeoffs designed for
87 * [ng-shollow-repeat] when you expect that your items you are repeating over do not change 97 * speed. Use [ng-shallow-repeat] when you expect that your items you are
88 * during the repeater lifetime. 98 * repeating over do not change during the repeater lifetime.
89 * 99 *
90 * The shallow repeater introduces these changes: 100 * The shallow repeater introduces these changes:
91 * 101 *
92 * * The repeater only fires if the identity of the list changes or if the list [length] property 102 * * The repeater only fires if the identity of the list changes or if the list
93 * changes. This means that the repeater will still see additions and deletio ns but not changes 103 * [length] property changes. This means that the repeater will still see
94 * to the array. 104 * additions and deletions but not changes to the array.
95 * * The child scopes for each item are created in the lazy mode (see [Scope.$n ew]). This means 105 * * The child scopes for each item are created in the lazy mode
96 * the scopes are effectivly taken out of the digest cycle and will not updat e on changes 106 * (see [Scope.$new]). This means the scopes are effectively taken out of the
97 * to the model. 107 * digest cycle and will not update on changes to the model.
98 * 108 *
99 */ 109 */
110 @deprecated
100 @NgDirective( 111 @NgDirective(
101 children: NgAnnotation.TRANSCLUDE_CHILDREN, 112 children: NgAnnotation.TRANSCLUDE_CHILDREN,
102 selector: '[ng-shallow-repeat]', 113 selector: '[ng-shallow-repeat]',
103 map: const {'.': '@expression'}) 114 map: const {'.': '@expression'})
104 class NgShalowRepeatDirective extends AbstractNgRepeatDirective { 115 //TODO(misko): delete me, since we can no longer do shallow digest.
105 NgShalowRepeatDirective(BlockHole blockHole, 116 class NgShallowRepeatDirective extends AbstractNgRepeatDirective {
117 NgShallowRepeatDirective(BlockHole blockHole,
106 BoundBlockFactory boundBlockFactory, 118 BoundBlockFactory boundBlockFactory,
107 Scope scope): super(blockHole, boundBlockFactory, scop e); 119 Scope scope,
108 get _shalow => true; 120 Parser parser,
121 AstParser astParser)
122 : super(blockHole, boundBlockFactory, scope, parser, astParser)
123 {
124 print('DEPRECATED: [ng-shallow-repeat] use [ng-repeat]');
125 }
109 } 126 }
110 127
111 abstract class AbstractNgRepeatDirective { 128 abstract class AbstractNgRepeatDirective {
112 static RegExp _SYNTAX = new RegExp(r'^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+ (.+)\s*)?(\s+lazily\s*)?$'); 129 static RegExp _SYNTAX = new RegExp(r'^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+ (.+)\s*)?(\s+lazily\s*)?$');
113 static RegExp _LHS_SYNTAX = new RegExp(r'^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\ w]+)\))$'); 130 static RegExp _LHS_SYNTAX = new RegExp(r'^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\ w]+)\))$');
114 131
115 BlockHole _blockHole; 132 final BlockHole _blockHole;
116 BoundBlockFactory _boundBlockFactory; 133 final BoundBlockFactory _boundBlockFactory;
117 Scope _scope; 134 final Scope _scope;
135 final Parser _parser;
136 final AstParser _astParser;
118 137
119 String _expression; 138 String _expression;
120 String _valueIdentifier; 139 String _valueIdentifier;
121 String _keyIdentifier; 140 String _keyIdentifier;
122 String _listExpr; 141 String _listExpr;
123 Map<Object, _Row> _rows = new Map<dynamic, _Row>(); 142 Map<dynamic, _Row> _rows = {};
124 Function _trackByIdFn = (key, value, index) => value; 143 Function _trackByIdFn = (key, value, index) => value;
125 Function _removeWatch = () => null; 144 Watch _watch = null;
126 Iterable _lastCollection; 145 Iterable _lastCollection;
127 146
128 AbstractNgRepeatDirective(BlockHole this._blockHole, 147 AbstractNgRepeatDirective(this._blockHole, this._boundBlockFactory,
129 BoundBlockFactory this._boundBlockFactory, 148 this._scope, this._parser, this._astParser);
130 Scope this._scope);
131
132 get _shalow;
133 149
134 set expression(value) { 150 set expression(value) {
135 _expression = value; 151 _expression = value;
136 _removeWatch(); 152 if (_watch != null) _watch.remove();
137 Match match = _SYNTAX.firstMatch(_expression); 153 Match match = _SYNTAX.firstMatch(_expression);
138 if (match == null) { 154 if (match == null) {
139 throw "[NgErr7] ngRepeat error! Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '$_expression'."; 155 throw "[NgErr7] ngRepeat error! Expected expression in form of '_item_ "
156 "in _collection_[ track by _id_]' but got '$_expression'.";
140 } 157 }
141 _listExpr = match.group(2); 158 _listExpr = match.group(2);
159 var trackByExpr = match.group(4);
160 if (trackByExpr != null) {
161 Expression trackBy = _parser(trackByExpr);
162 _trackByIdFn = ((key, value, index) {
163 final trackByLocals = <String, Object>{};
164 if (_keyIdentifier != null) trackByLocals[_keyIdentifier] = key;
165 trackByLocals
166 ..[_valueIdentifier] = value
167 ..[r'$index'] = index
168 ..[r'$id'] = (obj) => obj;
169 return relaxFnArgs(trackBy.eval)(new ScopeLocals(_scope.context, trackBy Locals));
170 });
171 }
142 var assignExpr = match.group(1); 172 var assignExpr = match.group(1);
143 match = _LHS_SYNTAX.firstMatch(assignExpr); 173 match = _LHS_SYNTAX.firstMatch(assignExpr);
144 if (match == null) { 174 if (match == null) {
145 throw "[NgErr8] ngRepeat error! '_item_' in '_item_ in _collection_' shoul d be an identifier or '(_key_, _value_)' expression, but got '$assignExpr'."; 175 throw "[NgErr8] ngRepeat error! '_item_' in '_item_ in _collection_' "
176 "should be an identifier or '(_key_, _value_)' expression, but got "
177 "'$assignExpr'.";
146 } 178 }
147 _valueIdentifier = match.group(3); 179 _valueIdentifier = match.group(3);
148 if (_valueIdentifier == null) _valueIdentifier = match.group(1); 180 if (_valueIdentifier == null) _valueIdentifier = match.group(1);
149 _keyIdentifier = match.group(2); 181 _keyIdentifier = match.group(2);
150 182
151 _removeWatch = _scope.$watchCollection(_listExpr, _onCollectionChange, value , _shalow); 183 _watch = _scope.watch(
184 _astParser(_listExpr, collection: true),
185 (CollectionChangeRecord collection, _) {
186 //TODO(misko): we should take advantage of the CollectionChangeRecord!
187 _onCollectionChange(collection == null ? [] : collection.iterable);
188 }
189 );
152 } 190 }
153 191
154 List<_Row> _computeNewRows(Iterable collection, trackById) { 192 List<_Row> _computeNewRows(Iterable collection, trackById) {
155 List<_Row> newRowOrder = []; 193 final newRowOrder = new List<_Row>(collection.length);
156 // Same as lastBlockMap but it has the current state. It will become the 194 // Same as lastBlockMap but it has the current state. It will become the
157 // lastBlockMap on the next iteration. 195 // lastBlockMap on the next iteration.
158 Map<dynamic, _Row> newRows = new Map<dynamic, _Row>(); 196 final newRows = <dynamic, _Row>{};
159 var arrayLength = collection.length;
160 // locate existing items 197 // locate existing items
161 var length = newRowOrder.length = collection.length; 198 for (var index = 0; index < newRowOrder.length; index++) {
162 for (var index = 0; index < length; index++) {
163 var value = collection.elementAt(index); 199 var value = collection.elementAt(index);
164 trackById = _trackByIdFn(index, value, index); 200 trackById = _trackByIdFn(index, value, index);
165 if (_rows.containsKey(trackById)) { 201 if (_rows.containsKey(trackById)) {
166 var row = _rows[trackById]; 202 var row = _rows[trackById];
167 _rows.remove(trackById); 203 _rows.remove(trackById);
168 newRows[trackById] = row; 204 newRows[trackById] = row;
169 newRowOrder[index] = row; 205 newRowOrder[index] = row;
170 } else if (newRows.containsKey(trackById)) { 206 } else if (newRows.containsKey(trackById)) {
171 // restore lastBlockMap 207 // restore lastBlockMap
172 newRowOrder.forEach((row) { 208 newRowOrder.forEach((row) {
173 if (row != null && row.startNode != null) { 209 if (row != null && row.startNode != null) _rows[row.id] = row;
174 _rows[row.id] = row;
175 }
176 }); 210 });
177 // This is a duplicate and we need to throw an error 211 // This is a duplicate and we need to throw an error
178 throw "[NgErr50] ngRepeat error! Duplicates in a repeater are not allowe d. Use 'track by' expression to specify unique keys. Repeater: $_expression, Dup licate key: $trackById"; 212 throw "[NgErr50] ngRepeat error! Duplicates in a repeater are not "
213 "allowed. Use 'track by' expression to specify unique keys. "
214 "Repeater: $_expression, Duplicate key: $trackById";
179 } else { 215 } else {
180 // new never before seen row 216 // new never before seen row
181 newRowOrder[index] = new _Row(trackById); 217 newRowOrder[index] = new _Row(trackById);
182 newRows[trackById] = null; 218 newRows[trackById] = null;
183 } 219 }
184 } 220 }
185 // remove existing items 221 // remove existing items
186 _rows.forEach((key, row){ 222 _rows.forEach((key, row) {
187 row.block.remove(); 223 row.block.remove();
188 row.scope.$destroy(); 224 row.scope.destroy();
189 }); 225 });
190 _rows = newRows; 226 _rows = newRows;
191 return newRowOrder; 227 return newRowOrder;
192 } 228 }
193 229
194 _onCollectionChange(Iterable collection) { 230 _onCollectionChange(Iterable collection) {
195 var previousNode = _blockHole.elements[0], // current position of the no de 231 dom.Node previousNode = _blockHole.elements[0]; // current position of the n ode
196 nextNode, 232 dom.Node nextNode;
197 childScope, 233 Scope childScope;
198 trackById, 234 Map childContext;
199 cursor = _blockHole, 235 Scope trackById;
200 arrayChange = _lastCollection != collection; 236 ElementWrapper cursor = _blockHole;
201
202 if (arrayChange) { _lastCollection = collection; }
203 if (collection is! Iterable) {
204 collection = [];
205 }
206 237
207 List<_Row> newRowOrder = _computeNewRows(collection, trackById); 238 List<_Row> newRowOrder = _computeNewRows(collection, trackById);
208 239
209 for (var index = 0, length = collection.length; index < length; index++) { 240 for (var index = 0; index < collection.length; index++) {
210 var key = index;
211 var value = collection.elementAt(index); 241 var value = collection.elementAt(index);
212 _Row row = newRowOrder[index]; 242 _Row row = newRowOrder[index];
213 243
214 if (row.startNode != null) { 244 if (row.startNode != null) {
215 // if we have already seen this object, then we need to reuse the 245 // if we have already seen this object, then we need to reuse the
216 // associated scope/element 246 // associated scope/element
217 childScope = row.scope; 247 childScope = row.scope;
248 childContext = childScope.context as Map;
218 249
219 nextNode = previousNode; 250 nextNode = previousNode;
220 do { 251 do {
221 nextNode = nextNode.nextNode; 252 nextNode = nextNode.nextNode;
222 } while(nextNode != null); 253 } while(nextNode != null);
223 254
224 if (row.startNode == nextNode) { 255 // existing item which got moved
225 // do nothing 256 if (row.startNode != nextNode) row.block.moveAfter(cursor);
226 } else {
227 // existing item which got moved
228 row.block.moveAfter(cursor);
229 }
230 previousNode = row.endNode; 257 previousNode = row.endNode;
231 } else { 258 } else {
232 // new item which we don't know about 259 // new item which we don't know about
233 childScope = _scope.$new(lazy:_shalow); 260 childScope = _scope.createChild(childContext = new PrototypeMap(_scope.c ontext));
234 } 261 }
235 262
236 if (!identical(childScope[_valueIdentifier], value)) { 263 if (!identical(childScope.context[_valueIdentifier], value)) {
237 childScope[_valueIdentifier] = value; 264 childContext[_valueIdentifier] = value;
238 childScope.$dirty();
239 } 265 }
240 childScope[r'$index'] = index; 266 var first = (index == 0);
241 childScope[r'$first'] = (index == 0); 267 var last = (index == collection.length - 1);
242 childScope[r'$last'] = (index == (collection.length - 1)); 268 childContext
243 childScope[r'$middle'] = !(childScope.$first || childScope.$last); 269 ..[r'$index'] = index
244 childScope[r'$odd'] = index & 1 == 1; 270 ..[r'$first'] = first
245 childScope[r'$even'] = index & 1 == 0; 271 ..[r'$last'] = last
246 if (arrayChange && _shalow) { 272 ..[r'$middle'] = !first && !last
247 childScope.$dirty(); 273 ..[r'$odd'] = index & 1 == 1
248 } 274 ..[r'$even'] = index & 1 == 0;
249 275
250 if (row.startNode == null) { 276 if (row.startNode == null) {
251 _rows[row.id] = row;
252 var block = _boundBlockFactory(childScope); 277 var block = _boundBlockFactory(childScope);
253 row..block = block 278 _rows[row.id] = row
254 ..scope = childScope 279 ..block = block
255 ..elements = block.elements 280 ..scope = childScope
256 ..startNode = row.elements[0] 281 ..elements = block.elements
257 ..endNode = row.elements[row.elements.length - 1]; 282 ..startNode = row.elements[0]
283 ..endNode = row.elements[row.elements.length - 1];
258 block.insertAfter(cursor); 284 block.insertAfter(cursor);
259 } 285 }
260 cursor = row.block; 286 cursor = row.block;
261 } 287 }
262 } 288 }
263 } 289 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698