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

Unified Diff: observatory_pub_packages/template_binding/src/template_iterator.dart

Issue 816693004: Add observatory_pub_packages snapshot to third_party (Closed) Base URL: http://dart.googlecode.com/svn/third_party/
Patch Set: Created 6 years 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 side-by-side diff with in-line comments
Download patch
Index: observatory_pub_packages/template_binding/src/template_iterator.dart
===================================================================
--- observatory_pub_packages/template_binding/src/template_iterator.dart (revision 0)
+++ observatory_pub_packages/template_binding/src/template_iterator.dart (working copy)
@@ -0,0 +1,556 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of template_binding;
+
+// This code is a port of what was formerly known as Model-Driven-Views, now
+// located at:
+// https://github.com/polymer/TemplateBinding
+// https://github.com/polymer/NodeBind
+
+// TODO(jmesserly): not sure what kind of boolean conversion rules to
+// apply for template data-binding. HTML attributes are true if they're
+// present. However Dart only treats "true" as true. Since this is HTML we'll
+// use something closer to the HTML rules: null (missing) and false are false,
+// everything else is true.
+// See: https://github.com/polymer/TemplateBinding/issues/59
+bool _toBoolean(value) => null != value && false != value;
+
+// Dart note: this was added to decouple the MustacheTokens.parse function from
+// the rest of template_binding.
+_getDelegateFactory(name, node, delegate) {
+ if (delegate == null) return null;
+ return (pathString) => delegate.prepareBinding(pathString, name, node);
+}
+
+_InstanceBindingMap _getBindings(Node node, BindingDelegate delegate) {
+ if (node is Element) {
+ return _parseAttributeBindings(node, delegate);
+ }
+
+ if (node is Text) {
+ var tokens = MustacheTokens.parse(node.text,
+ _getDelegateFactory('text', node, delegate));
+ if (tokens != null) return new _InstanceBindingMap(['text', tokens]);
+ }
+
+ return null;
+}
+
+void _addBindings(Node node, model, [BindingDelegate delegate]) {
+ final bindings = _getBindings(node, delegate);
+ if (bindings != null) {
+ _processBindings(node, bindings, model);
+ }
+
+ for (var c = node.firstChild; c != null; c = c.nextNode) {
+ _addBindings(c, model, delegate);
+ }
+}
+
+MustacheTokens _parseWithDefault(Element element, String name,
+ BindingDelegate delegate) {
+
+ var v = element.attributes[name];
+ if (v == '') v = '{{}}';
+ return MustacheTokens.parse(v, _getDelegateFactory(name, element, delegate));
+}
+
+_InstanceBindingMap _parseAttributeBindings(Element element,
+ BindingDelegate delegate) {
+
+ var bindings = null;
+ var ifFound = false;
+ var bindFound = false;
+ var isTemplateNode = isSemanticTemplate(element);
+
+ element.attributes.forEach((name, value) {
+ // Allow bindings expressed in attributes to be prefixed with underbars.
+ // We do this to allow correct semantics for browsers that don't implement
+ // <template> where certain attributes might trigger side-effects -- and
+ // for IE which sanitizes certain attributes, disallowing mustache
+ // replacements in their text.
+ while (name[0] == '_') {
+ name = name.substring(1);
+ }
+
+ if (isTemplateNode &&
+ (name == 'bind' || name == 'if' || name == 'repeat')) {
+ return;
+ }
+
+ var tokens = MustacheTokens.parse(value,
+ _getDelegateFactory(name, element, delegate));
+ if (tokens != null) {
+ if (bindings == null) bindings = [];
+ bindings..add(name)..add(tokens);
+ }
+ });
+
+ if (isTemplateNode) {
+ if (bindings == null) bindings = [];
+ var result = new _TemplateBindingMap(bindings)
+ .._if = _parseWithDefault(element, 'if', delegate)
+ .._bind = _parseWithDefault(element, 'bind', delegate)
+ .._repeat = _parseWithDefault(element, 'repeat', delegate);
+
+ // Treat <template if> as <template bind if>
+ if (result._if != null && result._bind == null && result._repeat == null) {
+ result._bind = MustacheTokens.parse('{{}}',
+ _getDelegateFactory('bind', element, delegate));
+ }
+
+ return result;
+ }
+
+ return bindings == null ? null : new _InstanceBindingMap(bindings);
+}
+
+_processOneTimeBinding(String name, MustacheTokens tokens, Node node, model) {
+
+ if (tokens.hasOnePath) {
+ var delegateFn = tokens.getPrepareBinding(0);
+ var value = delegateFn != null ? delegateFn(model, node, true) :
+ tokens.getPath(0).getValueFrom(model);
+ return tokens.isSimplePath ? value : tokens.combinator(value);
+ }
+
+ // Tokens uses a striding scheme to essentially store a sequence of structs in
+ // the list. See _MustacheTokens for more information.
+ var values = new List(tokens.length);
+ for (int i = 0; i < tokens.length; i++) {
+ Function delegateFn = tokens.getPrepareBinding(i);
+ values[i] = delegateFn != null ?
+ delegateFn(model, node, false) :
+ tokens.getPath(i).getValueFrom(model);
+ }
+ return tokens.combinator(values);
+}
+
+_processSinglePathBinding(String name, MustacheTokens tokens, Node node,
+ model) {
+ Function delegateFn = tokens.getPrepareBinding(0);
+ var observer = delegateFn != null ?
+ delegateFn(model, node, false) :
+ new PathObserver(model, tokens.getPath(0));
+
+ return tokens.isSimplePath ? observer :
+ new ObserverTransform(observer, tokens.combinator);
+}
+
+_processBinding(String name, MustacheTokens tokens, Node node, model) {
+ if (tokens.onlyOneTime) {
+ return _processOneTimeBinding(name, tokens, node, model);
+ }
+ if (tokens.hasOnePath) {
+ return _processSinglePathBinding(name, tokens, node, model);
+ }
+
+ var observer = new CompoundObserver();
+
+ for (int i = 0; i < tokens.length; i++) {
+ bool oneTime = tokens.getOneTime(i);
+ Function delegateFn = tokens.getPrepareBinding(i);
+
+ if (delegateFn != null) {
+ var value = delegateFn(model, node, oneTime);
+ if (oneTime) {
+ observer.addPath(value);
+ } else {
+ observer.addObserver(value);
+ }
+ continue;
+ }
+
+ PropertyPath path = tokens.getPath(i);
+ if (oneTime) {
+ observer.addPath(path.getValueFrom(model));
+ } else {
+ observer.addPath(model, path);
+ }
+ }
+
+ return new ObserverTransform(observer, tokens.combinator);
+}
+
+void _processBindings(Node node, _InstanceBindingMap map, model,
+ [List<Bindable> instanceBindings]) {
+
+ final bindings = map.bindings;
+ final nodeExt = nodeBind(node);
+ for (var i = 0; i < bindings.length; i += 2) {
+ var name = bindings[i];
+ var tokens = bindings[i + 1];
+
+ var value = _processBinding(name, tokens, node, model);
+ var binding = nodeExt.bind(name, value, oneTime: tokens.onlyOneTime);
+ if (binding != null && instanceBindings != null) {
+ instanceBindings.add(binding);
+ }
+ }
+
+ nodeExt.bindFinished();
+ if (map is! _TemplateBindingMap) return;
+
+ final templateExt = nodeBindFallback(node);
+ templateExt._model = model;
+
+ var iter = templateExt._processBindingDirectives(map);
+ if (iter != null && instanceBindings != null) {
+ instanceBindings.add(iter);
+ }
+}
+
+
+// Note: this doesn't really implement most of Bindable. See:
+// https://github.com/Polymer/TemplateBinding/issues/147
+class _TemplateIterator extends Bindable {
+ final TemplateBindExtension _templateExt;
+
+ final List<DocumentFragment> _instances = [];
+
+ /** A copy of the last rendered [_presentValue] list state. */
+ final List _iteratedValue = [];
+
+ List _presentValue;
+
+ bool _closed = false;
+
+ // Dart note: instead of storing these in a Map like JS, or using a separate
+ // object (extra memory overhead) we just inline the fields.
+ var _ifValue, _value;
+
+ // TODO(jmesserly): lots of booleans in this object. Bitmask?
+ bool _hasIf, _hasRepeat;
+ bool _ifOneTime, _oneTime;
+
+ StreamSubscription _listSub;
+
+ bool _initPrepareFunctions = false;
+ PrepareInstanceModelFunction _instanceModelFn;
+ PrepareInstancePositionChangedFunction _instancePositionChangedFn;
+
+ _TemplateIterator(this._templateExt);
+
+ open(callback) => throw new StateError('binding already opened');
+ get value => _value;
+
+ Element get _templateElement => _templateExt._node;
+
+ void _closeDependencies() {
+ if (_ifValue is Bindable) {
+ _ifValue.close();
+ _ifValue = null;
+ }
+ if (_value is Bindable) {
+ _value.close();
+ _value = null;
+ }
+ }
+
+ void _updateDependencies(_TemplateBindingMap directives, model) {
+ _closeDependencies();
+
+ final template = _templateElement;
+
+ _hasIf = directives._if != null;
+ _hasRepeat = directives._repeat != null;
+
+ var ifValue = true;
+ if (_hasIf) {
+ _ifOneTime = directives._if.onlyOneTime;
+ _ifValue = _processBinding('if', directives._if, template, model);
+ ifValue = _ifValue;
+
+ // oneTime if & predicate is false. nothing else to do.
+ if (_ifOneTime && !_toBoolean(ifValue)) {
+ _valueChanged(null);
+ return;
+ }
+
+ if (!_ifOneTime) {
+ ifValue = (ifValue as Bindable).open(_updateIfValue);
+ }
+ }
+
+ if (_hasRepeat) {
+ _oneTime = directives._repeat.onlyOneTime;
+ _value = _processBinding('repeat', directives._repeat, template, model);
+ } else {
+ _oneTime = directives._bind.onlyOneTime;
+ _value = _processBinding('bind', directives._bind, template, model);
+ }
+
+ var value = _value;
+ if (!_oneTime) {
+ value = _value.open(_updateIteratedValue);
+ }
+
+ if (!_toBoolean(ifValue)) {
+ _valueChanged(null);
+ return;
+ }
+
+ _updateValue(value);
+ }
+
+ /// Gets the updated value of the bind/repeat. This can potentially call
+ /// user code (if a bindingDelegate is set up) so we try to avoid it if we
+ /// already have the value in hand (from Observer.open).
+ Object _getUpdatedValue() {
+ var value = _value;
+ // Dart note: x.discardChanges() is x.value in Dart.
+ if (!_toBoolean(_oneTime)) value = value.value;
+ return value;
+ }
+
+ void _updateIfValue(ifValue) {
+ if (!_toBoolean(ifValue)) {
+ _valueChanged(null);
+ return;
+ }
+ _updateValue(_getUpdatedValue());
+ }
+
+ void _updateIteratedValue(value) {
+ if (_hasIf) {
+ var ifValue = _ifValue;
+ if (!_ifOneTime) ifValue = (ifValue as Bindable).value;
+ if (!_toBoolean(ifValue)) {
+ _valueChanged([]);
+ return;
+ }
+ }
+
+ _updateValue(value);
+ }
+
+ void _updateValue(Object value) {
+ if (!_hasRepeat) value = [value];
+ _valueChanged(value);
+ }
+
+ void _valueChanged(Object value) {
+ if (value is! List) {
+ if (value is Iterable) {
+ // Dart note: we support Iterable by calling toList.
+ // But we need to be careful to observe the original iterator if it
+ // supports that.
+ value = (value as Iterable).toList();
+ } else {
+ value = [];
+ }
+ }
+
+ if (identical(value, _iteratedValue)) return;
+
+ _unobserve();
+ _presentValue = value;
+
+ if (value is ObservableList && _hasRepeat && !_oneTime) {
+ // Make sure any pending changes aren't delivered, since we're getting
+ // a snapshot at this point in time.
+ value.discardListChages();
+ _listSub = value.listChanges.listen(_handleSplices);
+ }
+
+ _handleSplices(ObservableList.calculateChangeRecords(
+ _iteratedValue != null ? _iteratedValue : [],
+ _presentValue != null ? _presentValue : []));
+ }
+
+ Node _getLastInstanceNode(int index) {
+ if (index == -1) return _templateElement;
+ // TODO(jmesserly): we could avoid this expando lookup by caching the
+ // instance extension instead of the instance.
+ var instance = _instanceExtension[_instances[index]];
+ var terminator = instance._terminator;
+ if (terminator == null) return _getLastInstanceNode(index - 1);
+
+ if (!isSemanticTemplate(terminator) ||
+ identical(terminator, _templateElement)) {
+ return terminator;
+ }
+
+ var subtemplateIterator = templateBindFallback(terminator)._iterator;
+ if (subtemplateIterator == null) return terminator;
+
+ return subtemplateIterator._getLastTemplateNode();
+ }
+
+ Node _getLastTemplateNode() => _getLastInstanceNode(_instances.length - 1);
+
+ void _insertInstanceAt(int index, DocumentFragment fragment) {
+ var previousInstanceLast = _getLastInstanceNode(index - 1);
+ var parent = _templateElement.parentNode;
+
+ _instances.insert(index, fragment);
+ parent.insertBefore(fragment, previousInstanceLast.nextNode);
+ }
+
+ DocumentFragment _extractInstanceAt(int index) {
+ var previousInstanceLast = _getLastInstanceNode(index - 1);
+ var lastNode = _getLastInstanceNode(index);
+ var parent = _templateElement.parentNode;
+ var instance = _instances.removeAt(index);
+
+ while (lastNode != previousInstanceLast) {
+ var node = previousInstanceLast.nextNode;
+ if (node == lastNode) lastNode = previousInstanceLast;
+
+ instance.append(node..remove());
+ }
+
+ return instance;
+ }
+
+ void _handleSplices(List<ListChangeRecord> splices) {
+ if (_closed || splices.isEmpty) return;
+
+ final template = _templateElement;
+
+ if (template.parentNode == null) {
+ close();
+ return;
+ }
+
+ ObservableList.applyChangeRecords(_iteratedValue, _presentValue, splices);
+
+ final delegate = _templateExt.bindingDelegate;
+
+ // Dart note: the JavaScript code relies on the distinction between null
+ // and undefined to track whether the functions are prepared. We use a bool.
+ if (!_initPrepareFunctions) {
+ _initPrepareFunctions = true;
+ final delegate = _templateExt._self.bindingDelegate;
+ if (delegate != null) {
+ _instanceModelFn = delegate.prepareInstanceModel(template);
+ _instancePositionChangedFn =
+ delegate.prepareInstancePositionChanged(template);
+ }
+ }
+
+ // Instance Removals.
+ var instanceCache = new HashMap(equals: identical);
+ var removeDelta = 0;
+ for (var splice in splices) {
+ for (var model in splice.removed) {
+ var instance = _extractInstanceAt(splice.index + removeDelta);
+ if (instance != _emptyInstance) {
+ instanceCache[model] = instance;
+ }
+ }
+
+ removeDelta -= splice.addedCount;
+ }
+
+ for (var splice in splices) {
+ for (var addIndex = splice.index;
+ addIndex < splice.index + splice.addedCount;
+ addIndex++) {
+
+ var model = _iteratedValue[addIndex];
+ DocumentFragment instance = instanceCache.remove(model);
+ if (instance == null) {
+ try {
+ if (_instanceModelFn != null) {
+ model = _instanceModelFn(model);
+ }
+ if (model == null) {
+ instance = _emptyInstance;
+ } else {
+ instance = _templateExt.createInstance(model, delegate);
+ }
+ } catch (e, s) {
+ // Dart note: we propagate errors asynchronously here to avoid
+ // disrupting the rendering flow. This is different than in the JS
+ // implementation but it should probably be fixed there too. Dart
+ // hits this case more because non-existing properties in
+ // [PropertyPath] are treated as errors, while JS treats them as
+ // null/undefined.
+ // TODO(sigmund): this should be a synchronous throw when this is
+ // called from createInstance, but that requires enough refactoring
+ // that it should be done upstream first. See dartbug.com/17789.
+ new Completer().completeError(e, s);
+ instance = _emptyInstance;
+ }
+ }
+
+ _insertInstanceAt(addIndex, instance);
+ }
+ }
+
+ for (var instance in instanceCache.values) {
+ _closeInstanceBindings(instance);
+ }
+
+ if (_instancePositionChangedFn != null) _reportInstancesMoved(splices);
+ }
+
+ void _reportInstanceMoved(int index) {
+ var instance = _instances[index];
+ if (instance == _emptyInstance) return;
+
+ _instancePositionChangedFn(nodeBind(instance).templateInstance, index);
+ }
+
+ void _reportInstancesMoved(List<ListChangeRecord> splices) {
+ var index = 0;
+ var offset = 0;
+ for (var splice in splices) {
+ if (offset != 0) {
+ while (index < splice.index) {
+ _reportInstanceMoved(index);
+ index++;
+ }
+ } else {
+ index = splice.index;
+ }
+
+ while (index < splice.index + splice.addedCount) {
+ _reportInstanceMoved(index);
+ index++;
+ }
+
+ offset += splice.addedCount - splice.removed.length;
+ }
+
+ if (offset == 0) return;
+
+ var length = _instances.length;
+ while (index < length) {
+ _reportInstanceMoved(index);
+ index++;
+ }
+ }
+
+ void _closeInstanceBindings(DocumentFragment instance) {
+ var bindings = _instanceExtension[instance]._bindings;
+ for (var binding in bindings) binding.close();
+ }
+
+ void _unobserve() {
+ if (_listSub == null) return;
+ _listSub.cancel();
+ _listSub = null;
+ }
+
+ void close() {
+ if (_closed) return;
+
+ _unobserve();
+ _instances.forEach(_closeInstanceBindings);
+ _instances.clear();
+ _closeDependencies();
+ _templateExt._iterator = null;
+ _closed = true;
+ }
+}
+
+// Dart note: the JavaScript version just puts an expando on the array.
+class _BoundNodes {
+ final List<Node> nodes;
+ final List<Bindable> instanceBindings;
+ _BoundNodes(this.nodes, this.instanceBindings);
+}

Powered by Google App Engine
This is Rietveld 408576698