| Index: third_party/pkg/angular/lib/directive/ng_repeat.dart
|
| diff --git a/third_party/pkg/angular/lib/directive/ng_repeat.dart b/third_party/pkg/angular/lib/directive/ng_repeat.dart
|
| index 60885e615dd697abc41f583790d1ad8777f7af27..603b413c1f6a89e02da5c4e8173971523ebe5734 100644
|
| --- a/third_party/pkg/angular/lib/directive/ng_repeat.dart
|
| +++ b/third_party/pkg/angular/lib/directive/ng_repeat.dart
|
| @@ -12,11 +12,13 @@ class _Row {
|
| }
|
|
|
| /**
|
| - * The `ngRepeat` directive instantiates a template once per item from a collection. Each template
|
| - * 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.
|
| + * The `ngRepeat` directive instantiates a template once per item from a
|
| + * collection. Each template 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:
|
| + * Special properties are exposed on the local scope of each template instance,
|
| + * including:
|
| *
|
| * <table>
|
| * <tr><th> Variable </th><th> Type </th><th> Details <th></tr>
|
| @@ -29,43 +31,48 @@ class _Row {
|
| * </table>
|
| *
|
| *
|
| - * [repeat_expression] ngRepeat The expression indicating how to enumerate a collection. These
|
| - * formats are currently supported:
|
| + * [repeat_expression] ngRepeat The expression indicating how to enumerate a
|
| + * collection. These 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.
|
| + * * `variable in expression` – where variable is the user defined loop
|
| + * variable and `expression` is a scope expression giving the collection to
|
| + * enumerate.
|
| *
|
| * For example: `album in artist.albums`.
|
| *
|
| - * * `variable in expression track by tracking_expression` – You can also provide an optional
|
| - * tracking function which can be used to associate the objects in the collection with the DOM
|
| - * elements. If no tracking function is specified the ng-repeat associates elements by identity
|
| - * in the collection. It is an error to have more than one tracking function to resolve to the
|
| - * same key. (This would mean that two distinct objects are mapped to the same DOM element,
|
| - * which is not possible.) Filters should be applied to the expression, before specifying a
|
| - * tracking expression.
|
| + * * `variable in expression track by tracking_expression` – You can also
|
| + * provide an optional tracking function which can be used to associate the
|
| + * objects in the collection with the DOM elements. If no tracking function is
|
| + * specified the ng-repeat associates elements by identity in the collection.
|
| + * It is an error to have more than one tracking function to resolve to the
|
| + * same key. (This would mean that two distinct objects are mapped to the same
|
| + * DOM element, which is not possible.) Filters should be applied to the
|
| + * expression, before specifying a tracking expression.
|
| *
|
| - * 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` 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 element in the same way ian the
|
| + * 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 element in the same way ian the
|
| * DOM.
|
| *
|
| - * For example: `item in items track by item.id` is a typical pattern when the items come from
|
| - * the database. In this case the object identity does not matter. Two objects are considered
|
| - * equivalent as long as their `id` property is same.
|
| - *
|
| - * 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.
|
| + * For example: `item in items track by item.id` is a typical pattern when
|
| + * the items come from the database. In this case the object identity does
|
| + * not matter. Two objects are considered equivalent as long as their `id`
|
| + * property is same.
|
| *
|
| + * 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.
|
| *
|
| * # Example:
|
| *
|
| - * <ul ng-repeat="item in ['foo', 'bar', 'baz']">
|
| - * <li>{{$item}}</li>
|
| + * <ul>
|
| + * <li ng-repeat="item in ['foo', 'bar', 'baz']">{{item}}</li>
|
| * </ul>
|
| */
|
|
|
| @@ -76,90 +83,119 @@ class _Row {
|
| class NgRepeatDirective extends AbstractNgRepeatDirective {
|
| NgRepeatDirective(BlockHole blockHole,
|
| BoundBlockFactory boundBlockFactory,
|
| - Scope scope): super(blockHole, boundBlockFactory, scope);
|
| - get _shalow => false;
|
| + Scope scope,
|
| + Parser parser,
|
| + AstParser astParser)
|
| + : super(blockHole, boundBlockFactory, scope, parser, astParser);
|
| }
|
|
|
| /**
|
| - * *EXPERIMENTAL:* This feature is experimental. We reserve the right to change or delete it.
|
| + * *EXPERIMENTAL:* This feature is experimental. We reserve the right to change
|
| + * or delete it.
|
| *
|
| - * [ng-shallow-repeat] is same as [ng-repeat] with some tradeoffs designed for speed. Use
|
| - * [ng-shollow-repeat] when you expect that your items you are repeating over do not change
|
| - * during the repeater lifetime.
|
| + * [ng-shallow-repeat] is same as [ng-repeat] with some tradeoffs designed for
|
| + * speed. Use [ng-shallow-repeat] when you expect that your items you are
|
| + * repeating over do not change during the repeater lifetime.
|
| *
|
| * The shallow repeater introduces these changes:
|
| *
|
| - * * The repeater only fires if the identity of the list changes or if the list [length] property
|
| - * changes. This means that the repeater will still see additions and deletions but not changes
|
| - * to the array.
|
| - * * The child scopes for each item are created in the lazy mode (see [Scope.$new]). This means
|
| - * the scopes are effectivly taken out of the digest cycle and will not update on changes
|
| - * to the model.
|
| + * * The repeater only fires if the identity of the list changes or if the list
|
| + * [length] property changes. This means that the repeater will still see
|
| + * additions and deletions but not changes to the array.
|
| + * * The child scopes for each item are created in the lazy mode
|
| + * (see [Scope.$new]). This means the scopes are effectively taken out of the
|
| + * digest cycle and will not update on changes to the model.
|
| *
|
| */
|
| +@deprecated
|
| @NgDirective(
|
| children: NgAnnotation.TRANSCLUDE_CHILDREN,
|
| selector: '[ng-shallow-repeat]',
|
| map: const {'.': '@expression'})
|
| -class NgShalowRepeatDirective extends AbstractNgRepeatDirective {
|
| - NgShalowRepeatDirective(BlockHole blockHole,
|
| +//TODO(misko): delete me, since we can no longer do shallow digest.
|
| +class NgShallowRepeatDirective extends AbstractNgRepeatDirective {
|
| + NgShallowRepeatDirective(BlockHole blockHole,
|
| BoundBlockFactory boundBlockFactory,
|
| - Scope scope): super(blockHole, boundBlockFactory, scope);
|
| - get _shalow => true;
|
| + Scope scope,
|
| + Parser parser,
|
| + AstParser astParser)
|
| + : super(blockHole, boundBlockFactory, scope, parser, astParser)
|
| + {
|
| + print('DEPRECATED: [ng-shallow-repeat] use [ng-repeat]');
|
| + }
|
| }
|
|
|
| abstract class AbstractNgRepeatDirective {
|
| static RegExp _SYNTAX = new RegExp(r'^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?(\s+lazily\s*)?$');
|
| static RegExp _LHS_SYNTAX = new RegExp(r'^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$');
|
|
|
| - BlockHole _blockHole;
|
| - BoundBlockFactory _boundBlockFactory;
|
| - Scope _scope;
|
| + final BlockHole _blockHole;
|
| + final BoundBlockFactory _boundBlockFactory;
|
| + final Scope _scope;
|
| + final Parser _parser;
|
| + final AstParser _astParser;
|
|
|
| String _expression;
|
| String _valueIdentifier;
|
| String _keyIdentifier;
|
| String _listExpr;
|
| - Map<Object, _Row> _rows = new Map<dynamic, _Row>();
|
| + Map<dynamic, _Row> _rows = {};
|
| Function _trackByIdFn = (key, value, index) => value;
|
| - Function _removeWatch = () => null;
|
| + Watch _watch = null;
|
| Iterable _lastCollection;
|
|
|
| - AbstractNgRepeatDirective(BlockHole this._blockHole,
|
| - BoundBlockFactory this._boundBlockFactory,
|
| - Scope this._scope);
|
| -
|
| - get _shalow;
|
| + AbstractNgRepeatDirective(this._blockHole, this._boundBlockFactory,
|
| + this._scope, this._parser, this._astParser);
|
|
|
| set expression(value) {
|
| _expression = value;
|
| - _removeWatch();
|
| + if (_watch != null) _watch.remove();
|
| Match match = _SYNTAX.firstMatch(_expression);
|
| if (match == null) {
|
| - throw "[NgErr7] ngRepeat error! Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '$_expression'.";
|
| + throw "[NgErr7] ngRepeat error! Expected expression in form of '_item_ "
|
| + "in _collection_[ track by _id_]' but got '$_expression'.";
|
| }
|
| _listExpr = match.group(2);
|
| + var trackByExpr = match.group(4);
|
| + if (trackByExpr != null) {
|
| + Expression trackBy = _parser(trackByExpr);
|
| + _trackByIdFn = ((key, value, index) {
|
| + final trackByLocals = <String, Object>{};
|
| + if (_keyIdentifier != null) trackByLocals[_keyIdentifier] = key;
|
| + trackByLocals
|
| + ..[_valueIdentifier] = value
|
| + ..[r'$index'] = index
|
| + ..[r'$id'] = (obj) => obj;
|
| + return relaxFnArgs(trackBy.eval)(new ScopeLocals(_scope.context, trackByLocals));
|
| + });
|
| + }
|
| var assignExpr = match.group(1);
|
| match = _LHS_SYNTAX.firstMatch(assignExpr);
|
| if (match == null) {
|
| - throw "[NgErr8] ngRepeat error! '_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '$assignExpr'.";
|
| + throw "[NgErr8] ngRepeat error! '_item_' in '_item_ in _collection_' "
|
| + "should be an identifier or '(_key_, _value_)' expression, but got "
|
| + "'$assignExpr'.";
|
| }
|
| _valueIdentifier = match.group(3);
|
| if (_valueIdentifier == null) _valueIdentifier = match.group(1);
|
| _keyIdentifier = match.group(2);
|
|
|
| - _removeWatch = _scope.$watchCollection(_listExpr, _onCollectionChange, value, _shalow);
|
| + _watch = _scope.watch(
|
| + _astParser(_listExpr, collection: true),
|
| + (CollectionChangeRecord collection, _) {
|
| + //TODO(misko): we should take advantage of the CollectionChangeRecord!
|
| + _onCollectionChange(collection == null ? [] : collection.iterable);
|
| + }
|
| + );
|
| }
|
|
|
| List<_Row> _computeNewRows(Iterable collection, trackById) {
|
| - List<_Row> newRowOrder = [];
|
| + final newRowOrder = new List<_Row>(collection.length);
|
| // Same as lastBlockMap but it has the current state. It will become the
|
| // lastBlockMap on the next iteration.
|
| - Map<dynamic, _Row> newRows = new Map<dynamic, _Row>();
|
| - var arrayLength = collection.length;
|
| + final newRows = <dynamic, _Row>{};
|
| // locate existing items
|
| - var length = newRowOrder.length = collection.length;
|
| - for (var index = 0; index < length; index++) {
|
| + for (var index = 0; index < newRowOrder.length; index++) {
|
| var value = collection.elementAt(index);
|
| trackById = _trackByIdFn(index, value, index);
|
| if (_rows.containsKey(trackById)) {
|
| @@ -170,12 +206,12 @@ abstract class AbstractNgRepeatDirective {
|
| } else if (newRows.containsKey(trackById)) {
|
| // restore lastBlockMap
|
| newRowOrder.forEach((row) {
|
| - if (row != null && row.startNode != null) {
|
| - _rows[row.id] = row;
|
| - }
|
| + if (row != null && row.startNode != null) _rows[row.id] = row;
|
| });
|
| // This is a duplicate and we need to throw an error
|
| - throw "[NgErr50] ngRepeat error! Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: $_expression, Duplicate key: $trackById";
|
| + throw "[NgErr50] ngRepeat error! Duplicates in a repeater are not "
|
| + "allowed. Use 'track by' expression to specify unique keys. "
|
| + "Repeater: $_expression, Duplicate key: $trackById";
|
| } else {
|
| // new never before seen row
|
| newRowOrder[index] = new _Row(trackById);
|
| @@ -183,31 +219,25 @@ abstract class AbstractNgRepeatDirective {
|
| }
|
| }
|
| // remove existing items
|
| - _rows.forEach((key, row){
|
| + _rows.forEach((key, row) {
|
| row.block.remove();
|
| - row.scope.$destroy();
|
| + row.scope.destroy();
|
| });
|
| _rows = newRows;
|
| return newRowOrder;
|
| }
|
|
|
| _onCollectionChange(Iterable collection) {
|
| - var previousNode = _blockHole.elements[0], // current position of the node
|
| - nextNode,
|
| - childScope,
|
| - trackById,
|
| - cursor = _blockHole,
|
| - arrayChange = _lastCollection != collection;
|
| -
|
| - if (arrayChange) { _lastCollection = collection; }
|
| - if (collection is! Iterable) {
|
| - collection = [];
|
| - }
|
| + dom.Node previousNode = _blockHole.elements[0]; // current position of the node
|
| + dom.Node nextNode;
|
| + Scope childScope;
|
| + Map childContext;
|
| + Scope trackById;
|
| + ElementWrapper cursor = _blockHole;
|
|
|
| List<_Row> newRowOrder = _computeNewRows(collection, trackById);
|
|
|
| - for (var index = 0, length = collection.length; index < length; index++) {
|
| - var key = index;
|
| + for (var index = 0; index < collection.length; index++) {
|
| var value = collection.elementAt(index);
|
| _Row row = newRowOrder[index];
|
|
|
| @@ -215,46 +245,42 @@ abstract class AbstractNgRepeatDirective {
|
| // if we have already seen this object, then we need to reuse the
|
| // associated scope/element
|
| childScope = row.scope;
|
| + childContext = childScope.context as Map;
|
|
|
| nextNode = previousNode;
|
| do {
|
| nextNode = nextNode.nextNode;
|
| } while(nextNode != null);
|
|
|
| - if (row.startNode == nextNode) {
|
| - // do nothing
|
| - } else {
|
| - // existing item which got moved
|
| - row.block.moveAfter(cursor);
|
| - }
|
| + // existing item which got moved
|
| + if (row.startNode != nextNode) row.block.moveAfter(cursor);
|
| previousNode = row.endNode;
|
| } else {
|
| // new item which we don't know about
|
| - childScope = _scope.$new(lazy:_shalow);
|
| + childScope = _scope.createChild(childContext = new PrototypeMap(_scope.context));
|
| }
|
|
|
| - if (!identical(childScope[_valueIdentifier], value)) {
|
| - childScope[_valueIdentifier] = value;
|
| - childScope.$dirty();
|
| - }
|
| - childScope[r'$index'] = index;
|
| - childScope[r'$first'] = (index == 0);
|
| - childScope[r'$last'] = (index == (collection.length - 1));
|
| - childScope[r'$middle'] = !(childScope.$first || childScope.$last);
|
| - childScope[r'$odd'] = index & 1 == 1;
|
| - childScope[r'$even'] = index & 1 == 0;
|
| - if (arrayChange && _shalow) {
|
| - childScope.$dirty();
|
| + if (!identical(childScope.context[_valueIdentifier], value)) {
|
| + childContext[_valueIdentifier] = value;
|
| }
|
| + var first = (index == 0);
|
| + var last = (index == collection.length - 1);
|
| + childContext
|
| + ..[r'$index'] = index
|
| + ..[r'$first'] = first
|
| + ..[r'$last'] = last
|
| + ..[r'$middle'] = !first && !last
|
| + ..[r'$odd'] = index & 1 == 1
|
| + ..[r'$even'] = index & 1 == 0;
|
|
|
| if (row.startNode == null) {
|
| - _rows[row.id] = row;
|
| var block = _boundBlockFactory(childScope);
|
| - row..block = block
|
| - ..scope = childScope
|
| - ..elements = block.elements
|
| - ..startNode = row.elements[0]
|
| - ..endNode = row.elements[row.elements.length - 1];
|
| + _rows[row.id] = row
|
| + ..block = block
|
| + ..scope = childScope
|
| + ..elements = block.elements
|
| + ..startNode = row.elements[0]
|
| + ..endNode = row.elements[row.elements.length - 1];
|
| block.insertAfter(cursor);
|
| }
|
| cursor = row.block;
|
|
|