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; |
+} |