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

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

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

Powered by Google App Engine
This is Rietveld 408576698