| Index: pkg/template_binding/lib/src/template.dart
|
| diff --git a/pkg/template_binding/lib/src/template.dart b/pkg/template_binding/lib/src/template.dart
|
| index 8df84d1bbd28ec5ac621f7a39d59d9fc1ed47957..d085a8962a3f716c397a1bcfc8da35f20aa72d83 100644
|
| --- a/pkg/template_binding/lib/src/template.dart
|
| +++ b/pkg/template_binding/lib/src/template.dart
|
| @@ -9,7 +9,7 @@ class TemplateBindExtension extends _ElementExtension {
|
| var _model;
|
| BindingDelegate _bindingDelegate;
|
| _TemplateIterator _iterator;
|
| - bool _scheduled = false;
|
| + bool _setModelScheduled = false;
|
|
|
| Element _templateInstanceRef;
|
|
|
| @@ -19,7 +19,7 @@ class TemplateBindExtension extends _ElementExtension {
|
|
|
| HtmlDocument _stagingDocument;
|
|
|
| - var _bindingMap;
|
| + _InstanceBindingMap _bindingMap;
|
|
|
| TemplateBindExtension._(Element node) : super(node);
|
|
|
| @@ -28,106 +28,70 @@ class TemplateBindExtension extends _ElementExtension {
|
| TemplateBindExtension get _self => super._node is TemplateBindExtension
|
| ? _node : this;
|
|
|
| - NodeBinding bind(String name, model, [String path]) {
|
| - path = path != null ? path : '';
|
| + _TemplateIterator _processBindingDirectives(_TemplateBindingMap directives) {
|
| + if (_iterator != null) _iterator._closeDependencies();
|
|
|
| - if (_iterator == null) {
|
| - // TODO(jmesserly): since there's only one iterator, we could just
|
| - // inline it into this object.
|
| - _iterator = new _TemplateIterator(this);
|
| - }
|
| + if (directives._if == null &&
|
| + directives._bind == null &&
|
| + directives._repeat == null) {
|
|
|
| - // Dart note: we return _TemplateBinding instead of _iterator.
|
| - // See comment on _TemplateBinding class.
|
| - switch (name) {
|
| - case 'bind':
|
| - _iterator..hasBind = true
|
| - ..bindModel = model
|
| - ..bindPath = path;
|
| - _scheduleIterator();
|
| - return bindings[name] = new _TemplateBinding(this, name, model, path);
|
| - case 'repeat':
|
| - _iterator..hasRepeat = true
|
| - ..repeatModel = model
|
| - ..repeatPath = path;
|
| - _scheduleIterator();
|
| - return bindings[name] = new _TemplateBinding(this, name, model, path);
|
| - case 'if':
|
| - _iterator..hasIf = true
|
| - ..ifModel = model
|
| - ..ifPath = path;
|
| - _scheduleIterator();
|
| - return bindings[name] = new _TemplateBinding(this, name, model, path);
|
| - default:
|
| - return super.bind(name, model, path);
|
| + if (_iterator != null) {
|
| + _iterator.close();
|
| + _iterator = null;
|
| + bindings.remove('iterator');
|
| + }
|
| + return null;
|
| }
|
| - }
|
|
|
| - void unbind(String name) {
|
| - switch (name) {
|
| - case 'bind':
|
| - if (_iterator == null) return;
|
| - _iterator..hasBind = false
|
| - ..bindModel = null
|
| - ..bindPath = null;
|
| - _scheduleIterator();
|
| - bindings.remove(name);
|
| - return;
|
| - case 'repeat':
|
| - if (_iterator == null) return;
|
| - _iterator..hasRepeat = false
|
| - ..repeatModel = null
|
| - ..repeatPath = null;
|
| - _scheduleIterator();
|
| - bindings.remove(name);
|
| - return;
|
| - case 'if':
|
| - if (_iterator == null) return;
|
| - _iterator..hasIf = false
|
| - ..ifModel = null
|
| - ..ifPath = null;
|
| - _scheduleIterator();
|
| - bindings.remove(name);
|
| - return;
|
| - default:
|
| - super.unbind(name);
|
| - return;
|
| + if (_iterator == null) {
|
| + bindings['iterator'] = _iterator = new _TemplateIterator(this);
|
| }
|
| - }
|
|
|
| - void _scheduleIterator() {
|
| - if (!_iterator.depsChanging) {
|
| - _iterator.depsChanging = true;
|
| - scheduleMicrotask(_iterator.resolve);
|
| - }
|
| + _iterator._updateDependencies(directives, model);
|
| + return _iterator;
|
| }
|
|
|
| /**
|
| - * Creates an instance of the template, using the provided model and optional
|
| - * binding delegate.
|
| + * Creates an instance of the template, using the provided [model] and
|
| + * optional binding [delegate].
|
| + *
|
| + * If [instanceBindings] is supplied, each [Bindable] in the returned
|
| + * instance will be added to the list. This makes it easy to close all of the
|
| + * bindings without walking the tree. This is not normally necesssary, but is
|
| + * used internally by the system.
|
| */
|
| DocumentFragment createInstance([model, BindingDelegate delegate,
|
| - List<NodeBinding> bound]) {
|
| - var ref = templateBind(this.ref);
|
| - var content = ref.content;
|
| + List<Bindable> instanceBindings]) {
|
| +
|
| + final content = templateBind(ref).content;
|
| // Dart note: we store _bindingMap on the TemplateBindExtension instead of
|
| // the "content" because we already have an expando for it.
|
| - var map = ref._bindingMap;
|
| - if (map == null) {
|
| + var map = _bindingMap;
|
| + if (map == null || !identical(map.content, content)) {
|
| // TODO(rafaelw): Setup a MutationObserver on content to detect
|
| // when the instanceMap is invalid.
|
| map = _createInstanceBindingMap(content, delegate);
|
| - ref._bindingMap = map;
|
| + map.content = content;
|
| + _bindingMap = map;
|
| + }
|
| +
|
| + final staging = _getTemplateStagingDocument();
|
| + final instance = _stagingDocument.createDocumentFragment();
|
| + _templateCreator[instance] = _node;
|
| +
|
| + final instanceRecord = new TemplateInstance(model);
|
| +
|
| + var i = 0;
|
| + for (var c = content.firstChild; c != null; c = c.nextNode, i++) {
|
| + final childMap = map != null ? map.getChild(i) : null;
|
| + var clone = _cloneAndBindInstance(c, instance, _stagingDocument,
|
| + childMap, model, delegate, instanceBindings);
|
| + nodeBindFallback(clone)._templateInstance = instanceRecord;
|
| }
|
|
|
| - var staging = _getTemplateStagingDocument();
|
| - var instance = _deepCloneIgnoreTemplateContent(content, staging);
|
| + instanceRecord._firstNode = instance.firstChild;
|
| + instanceRecord._lastNode = instance.lastChild;
|
|
|
| - _addMapBindings(instance, map, model, delegate, bound);
|
| - // TODO(rafaelw): We can do this more lazily, but setting a sentinel
|
| - // in the parent of the template element, and creating it when it's
|
| - // asked for by walking back to find the iterating template.
|
| - _addTemplateInstanceRecord(instance, model);
|
| return instance;
|
| }
|
|
|
| @@ -157,19 +121,27 @@ class TemplateBindExtension extends _ElementExtension {
|
|
|
| void set bindingDelegate(BindingDelegate value) {
|
| _bindingDelegate = value;
|
| - _ensureSetModelScheduled();
|
| +
|
| + // Clear cached state based on the binding delegate.
|
| + _bindingMap = null;
|
| + if (_iterator != null) {
|
| + _iterator._initPrepareFunctions = false;
|
| + _iterator._instanceModelFn = null;
|
| + _iterator._instancePositionChangedFn = null;
|
| + }
|
| }
|
|
|
| _ensureSetModelScheduled() {
|
| - if (_scheduled) return;
|
| + if (_setModelScheduled) return;
|
| _decorate();
|
| - _scheduled = true;
|
| + _setModelScheduled = true;
|
| scheduleMicrotask(_setModel);
|
| }
|
|
|
| void _setModel() {
|
| - _scheduled = false;
|
| - _addBindings(_node, _model, _bindingDelegate);
|
| + _setModelScheduled = false;
|
| + var map = _getBindings(_node, _bindingDelegate);
|
| + _processBindings(_node, map, _model);
|
| }
|
|
|
| /** Gets the template this node refers to. */
|
| @@ -183,6 +155,15 @@ class TemplateBindExtension extends _ElementExtension {
|
| if (treeScope != null) {
|
| result = treeScope.getElementById(refId);
|
| }
|
| + if (result == null) {
|
| + var instanceRoot = _getInstanceRoot(_node);
|
| +
|
| + // TODO(jmesserly): this won't work if refId is a number
|
| + // Similar to bug: https://github.com/Polymer/ShadowDOM/issues/340
|
| + if (instanceRoot != null) {
|
| + result = instanceRoot.querySelector('#$refId');
|
| + }
|
| + }
|
| }
|
|
|
| if (result == null) {
|
| @@ -223,25 +204,32 @@ class TemplateBindExtension extends _ElementExtension {
|
|
|
| var templateElementExt = this;
|
| _templateIsDecorated = true;
|
| - var isNative = _node is TemplateElement;
|
| - var bootstrapContents = isNative;
|
| - var liftContents = !isNative;
|
| + var isNativeHtmlTemplate = _node is TemplateElement;
|
| + final bootstrapContents = isNativeHtmlTemplate;
|
| + final liftContents = !isNativeHtmlTemplate;
|
| var liftRoot = false;
|
|
|
| - if (!isNative && _isAttributeTemplate(_node)) {
|
| - if (instanceRef != null) {
|
| - // TODO(jmesserly): this is just an assert in TemplateBinding.
|
| - throw new ArgumentError('instanceRef should not be supplied for '
|
| - 'attribute templates.');
|
| + if (!isNativeHtmlTemplate) {
|
| + if (_isAttributeTemplate(_node)) {
|
| + if (instanceRef != null) {
|
| + // Dart note: this is just an assert in JS.
|
| + throw new ArgumentError('instanceRef should not be supplied for '
|
| + 'attribute templates.');
|
| + }
|
| + templateElementExt = templateBind(
|
| + _extractTemplateFromAttributeTemplate(_node));
|
| + templateElementExt._templateIsDecorated = true;
|
| + isNativeHtmlTemplate = templateElementExt._node is TemplateElement;
|
| + liftRoot = true;
|
| + } else if (_isSvgTemplate(_node)) {
|
| + templateElementExt = templateBind(
|
| + _extractTemplateFromSvgTemplate(_node));
|
| + templateElementExt._templateIsDecorated = true;
|
| + isNativeHtmlTemplate = templateElementExt._node is TemplateElement;
|
| }
|
| - templateElementExt = templateBind(
|
| - _extractTemplateFromAttributeTemplate(_node));
|
| - templateElementExt._templateIsDecorated = true;
|
| - isNative = templateElementExt._node is TemplateElement;
|
| - liftRoot = true;
|
| - }
|
| -
|
| - if (!isNative) {
|
| + }
|
| +
|
| + if (!isNativeHtmlTemplate) {
|
| var doc = _getOrCreateTemplateContentsOwner(templateElementExt._node);
|
| templateElementExt._content = doc.createDocumentFragment();
|
| }
|
| @@ -326,6 +314,16 @@ class TemplateBindExtension extends _ElementExtension {
|
| return template;
|
| }
|
|
|
| + static Element _extractTemplateFromSvgTemplate(Element el) {
|
| + var template = el.ownerDocument.createElement('template');
|
| + el.parentNode.insertBefore(template, el);
|
| + template.attributes.addAll(el.attributes);
|
| +
|
| + el.attributes.clear();
|
| + el.remove();
|
| + return template;
|
| + }
|
| +
|
| static void _liftNonNativeChildrenIntoContent(TemplateBindExtension template,
|
| Element el, bool useRoot) {
|
|
|
| @@ -386,58 +384,19 @@ class TemplateBindExtension extends _ElementExtension {
|
| }
|
| }
|
|
|
| -// TODO(jmesserly): https://github.com/polymer/templatebinding uses
|
| -// TemplateIterator as the binding. This is a nice performance optimization,
|
| -// however it means it doesn't share any of the reflective APIs with
|
| -// NodeBinding: https://github.com/Polymer/TemplateBinding/issues/147
|
| -class _TemplateBinding implements NodeBinding {
|
| - TemplateBindExtension _ext;
|
| - Object _model;
|
| - final String property;
|
| - final String path;
|
| -
|
| - Node get node => _ext._node;
|
| -
|
| - get model => _model;
|
| -
|
| - bool get closed => _ext == null;
|
| -
|
| - get value => _observer.value;
|
| -
|
| - set value(newValue) {
|
| - _observer.value = newValue;
|
| - }
|
| -
|
| - // No need to cache this since we only have it to support get/set value.
|
| - get _observer {
|
| - if ((_model is PathObserver || _model is CompoundPathObserver) &&
|
| - path == 'value') {
|
| - return _model;
|
| - }
|
| - return new PathObserver(_model, path);
|
| - }
|
| -
|
| - _TemplateBinding(this._ext, this.property, this._model, this.path);
|
| -
|
| - void valueChanged(newValue) {}
|
| -
|
| - sanitizeBoundValue(value) => value == null ? '' : '$value';
|
| -
|
| - void close() {
|
| - if (closed) return;
|
| -
|
| - // TODO(jmesserly): unlike normal NodeBinding.close methods this will remove
|
| - // the binding from _node.bindings. Is that okay?
|
| - _ext.unbind(property);
|
| -
|
| - _model = null;
|
| - _ext = null;
|
| - }
|
| -}
|
| +final _templateCreator = new Expando();
|
|
|
| _getTreeScope(Node node) {
|
| - while (node.parentNode != null) {
|
| - node = node.parentNode;
|
| + while (true) {
|
| + var parent = node.parentNode;
|
| + if (parent != null) {
|
| + node = parent;
|
| + } else {
|
| + var creator = _templateCreator[node];
|
| + if (creator == null) break;
|
| +
|
| + node = creator;
|
| + }
|
| }
|
|
|
| // Note: JS code tests that getElementById is present. We can't do that
|
| @@ -447,3 +406,10 @@ _getTreeScope(Node node) {
|
| }
|
| return null;
|
| }
|
| +
|
| +_getInstanceRoot(node) {
|
| + while (node.parentNode != null) {
|
| + node = node.parentNode;
|
| + }
|
| + return _templateCreator[node] != null ? node : null;
|
| +}
|
|
|