Index: template_binding/lib/src/template_iterator.dart |
diff --git a/template_binding/lib/src/template_iterator.dart b/template_binding/lib/src/template_iterator.dart |
deleted file mode 100644 |
index 71004867b9f16fbf768fb95412d475dfe0a52348..0000000000000000000000000000000000000000 |
--- a/template_binding/lib/src/template_iterator.dart |
+++ /dev/null |
@@ -1,556 +0,0 @@ |
-// 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); |
-} |