@@ -83,12 +83,12 @@ class NgRepeatDirective {
8383 String _valueIdentifier;
8484 String _keyIdentifier;
8585 String _listExpr;
86- Map < dynamic , _Row > _rows = {} ;
87- Function _trackByIdFn = (key, value, index) => value;
86+ List < _Row > _rows;
87+ Function _generateId = (key, value, index) => value;
8888 Watch _watch;
8989
9090 NgRepeatDirective (this ._viewPort, this ._boundViewFactory, this ._scope,
91- this ._parser, this ._astParser, this . filters);
91+ this ._parser, this .filters);
9292
9393 set expression (value) {
9494 assert (value != null );
@@ -106,14 +106,14 @@ class NgRepeatDirective {
106106 var trackByExpr = match.group (3 );
107107 if (trackByExpr != null ) {
108108 Expression trackBy = _parser (trackByExpr);
109- _trackByIdFn = ((key, value, index) {
110- final trackByLocals = < String , Object > {};
111- if (_keyIdentifier != null ) trackByLocals[_keyIdentifier ] = key;
112- trackByLocals ..[_valueIdentifier ] = value
113- ..[r'$index ' ] = index
114- ..[ r'$id' ] = (obj) => obj ;
109+ _generateId = ((key, value, index) {
110+ final context = < String , Object > {}
111+ ..[_valueIdentifier ] = value
112+ ..[r'$index' ] = index
113+ ..[r'$id ' ] = (obj) => obj;
114+ if (_keyIdentifier != null ) context[_keyIdentifier ] = key ;
115115 return relaxFnArgs (trackBy.eval)(new ScopeLocals (_scope.context,
116- trackByLocals ));
116+ context ));
117117 });
118118 }
119119
@@ -130,116 +130,113 @@ class NgRepeatDirective {
130130 _keyIdentifier = match.group (2 );
131131
132132 _watch = _scope.watch (
133- _astParser ( _listExpr, collection : true , filters : filters) ,
134- (CollectionChangeRecord changeRecord , _) {
135- //TODO(misko): we should take advantage of the CollectionChangeRecord!
136- if (changeRecord == null ) return ;
137- _onCollectionChange (changeRecord.iterable);
138-
139- }
133+ _listExpr,
134+ (CollectionChangeRecord changes , _) {
135+ if (changes is ! CollectionChangeRecord ) return ;
136+ _onChange (changes) ;
137+ },
138+ collection : true ,
139+ filters : filters
140140 );
141141 }
142142
143-
144- // todo -> collection
145- List <_Row > _computeNewRows (Iterable collection, trackById) {
146- final newRowOrder = new List <_Row >(collection.length);
147- // Same as lastViewMap but it has the current state. It will become the
148- // lastViewMap on the next iteration.
149- final newRows = < dynamic , _Row > {};
150- // locate existing items
151- for (var index = 0 ; index < newRowOrder.length; index++ ) {
152- var value = collection.elementAt (index);
153- trackById = _trackByIdFn (index, value, index);
154- if (_rows.containsKey (trackById)) {
155- var row = _rows.remove (trackById);
156- newRows[trackById] = row;
157- newRowOrder[index] = row;
158- } else if (newRows.containsKey (trackById)) {
159- // restore lastViewMap
160- newRowOrder.forEach ((row) {
161- if (row != null && row.startNode != null ) _rows[row.id] = row;
162- });
163- // This is a duplicate and we need to throw an error
164- throw "[NgErr50] ngRepeat error! Duplicates in a repeater are not "
165- "allowed. Use 'track by' expression to specify unique keys. "
166- "Repeater: $_expression , Duplicate key: $trackById " ;
167- } else {
168- // new never before seen row
169- newRowOrder[index] = new _Row (trackById);
170- newRows[trackById] = null ;
143+ // Computes and executes DOM changes when the item list changes
144+ void _onChange (CollectionChangeRecord changes) {
145+ final int length = changes.iterable.length;
146+ final rows = new List <_Row >(length);
147+ final changeFunctions = new List <Function >(length);
148+ final removedIndexes = < int > [];
149+ final int domLength = _rows == null ? 0 : _rows.length;
150+ final leftInDom = new List .generate (domLength, (i) => domLength - 1 - i);
151+ var domIndex;
152+
153+ var addRow = (int index, value, View previousView) {
154+ var childContext = _updateContext (new PrototypeMap (_scope.context), index,
155+ length)..[_valueIdentifier] = value;
156+ var childScope = _scope.createChild (childContext);
157+ var view = _boundViewFactory (childScope);
158+ var nodes = view.nodes;
159+ rows[index] = new _Row (_generateId (index, value, index))
160+ ..view = view
161+ ..scope = childScope
162+ ..nodes = nodes
163+ ..startNode = nodes.first
164+ ..endNode = nodes.last;
165+ _viewPort.insert (view, insertAfter: previousView);
166+ };
167+
168+ if (_rows == null ) {
169+ _rows = new List <_Row >(length);
170+ for (var i = 0 ; i < length; i++ ) {
171+ changeFunctions[i] = (index, previousView) {
172+ addRow (index, changes.iterable.elementAt (i), previousView);
173+ };
171174 }
175+ } else {
176+ changes.forEachRemoval ((removal) {
177+ var index = removal.previousIndex;
178+ var row = _rows[index];
179+ row.scope.destroy ();
180+ _viewPort.remove (row.view);
181+ leftInDom.removeAt (domLength - 1 - index);
182+ });
183+
184+ changes.forEachAddition ((addition) {
185+ changeFunctions[addition.currentIndex] = (index, previousView) {
186+ addRow (index, addition.item, previousView);
187+ };
188+ });
189+
190+ changes.forEachMove ((move) {
191+ var previousIndex = move.previousIndex;
192+ var value = move.item;
193+ changeFunctions[move.currentIndex] = (index, previousView) {
194+ var previousRow = _rows[previousIndex];
195+ var childScope = previousRow.scope;
196+ var childContext = _updateContext (childScope.context, index, length);
197+ if (! identical (childScope.context[_valueIdentifier], value)) {
198+ childContext[_valueIdentifier] = value;
199+ }
200+ rows[index] = _rows[previousIndex];
201+ // Only move the DOM node when required
202+ if (domIndex < 0 || leftInDom[domIndex] != previousIndex) {
203+ _viewPort.move (previousRow.view, moveAfter: previousView);
204+ leftInDom.remove (previousIndex);
205+ }
206+ domIndex-- ;
207+ };
208+ });
172209 }
173- // remove existing items
174- _rows.forEach ((key, row) {
175- _viewPort.remove (row.view);
176- row.scope.destroy ();
177- });
178- _rows = newRows;
179- return newRowOrder;
180- }
181210
182- void _onCollectionChange (Iterable collection) {
183- // current position of the node
184- dom.Node previousNode = _viewPort.placeholder;
185- dom.Node nextNode;
186- Scope childScope;
187- Map childContext;
188- Scope trackById;
189- View cursor;
190-
191- List <_Row > newRowOrder = _computeNewRows (collection, trackById);
192-
193- for (var index = 0 ; index < collection.length; index++ ) {
194- var value = collection.elementAt (index);
195- _Row row = newRowOrder[index];
196-
197- if (row.startNode != null ) {
198- // if we have already seen this object, then we need to reuse the
199- // associated scope/element
200- childScope = row.scope;
201- childContext = childScope.context as Map ;
202-
203- nextNode = previousNode;
204- do {
205- nextNode = nextNode.nextNode;
206- } while (nextNode != null );
207-
208- if (row.startNode != nextNode) {
209- // existing item which got moved
210- _viewPort.move (row.view, moveAfter: cursor);
211- }
212- previousNode = row.endNode;
211+ var previousView = null ;
212+ domIndex = leftInDom.length - 1 ;
213+ for (var targetIndex = 0 ; targetIndex < length; targetIndex++ ) {
214+ var changeFn = changeFunctions[targetIndex];
215+ if (changeFn == null ) {
216+ rows[targetIndex] = _rows[targetIndex];
217+ domIndex-- ;
218+ // The element has not moved but `$last` and `$middle` might still need
219+ // to be updated
220+ _updateContext (rows[targetIndex].scope.context, targetIndex, length);
213221 } else {
214- // new item which we don't know about
215- childScope = _scope.createChild (childContext =
216- new PrototypeMap (_scope.context));
217- }
218-
219- if (! identical (childScope.context[_valueIdentifier], value)) {
220- childContext[_valueIdentifier] = value;
221- }
222- var first = (index == 0 );
223- var last = (index == collection.length - 1 );
224- childContext..[r'$index' ] = index
225- ..[r'$first' ] = first
226- ..[r'$last' ] = last
227- ..[r'$middle' ] = ! first && ! last
228- ..[r'$odd' ] = index & 1 == 1
229- ..[r'$even' ] = index & 1 == 0 ;
230-
231- if (row.startNode == null ) {
232- var view = _boundViewFactory (childScope);
233- _rows[row.id] = row
234- ..view = view
235- ..scope = childScope
236- ..nodes = view.nodes
237- ..startNode = row.nodes.first
238- ..endNode = row.nodes.last;
239- _viewPort.insert (view, insertAfter: cursor);
222+ changeFn (targetIndex, previousView);
240223 }
241- cursor = row .view;
224+ previousView = rows[targetIndex] .view;
242225 }
226+
227+ _rows = rows;
228+ }
229+
230+ PrototypeMap _updateContext (PrototypeMap context, int index, int length) {
231+ var first = (index == 0 );
232+ var last = (index == length - 1 );
233+ return context
234+ ..[r'$index' ] = index
235+ ..[r'$first' ] = first
236+ ..[r'$last' ] = last
237+ ..[r'$middle' ] = ! (first || last)
238+ ..[r'$odd' ] = index.isOdd
239+ ..[r'$even' ] = index.isEven;
243240 }
244241}
245242
0 commit comments