| OLD | NEW |
| 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 } |
| OLD | NEW |