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 be6a6fd436db754ffb1dbc772cd520ef5cad0e66..ad35eb816beac4368f76a302765389a99d889966 100644 |
--- a/pkg/template_binding/lib/src/template.dart |
+++ b/pkg/template_binding/lib/src/template.dart |
@@ -8,7 +8,7 @@ part of template_binding; |
class TemplateBindExtension extends _ElementExtension { |
var _model; |
BindingDelegate _bindingDelegate; |
- _TemplateIterator _templateIterator; |
+ _TemplateIterator _iterator; |
bool _scheduled = false; |
Element _templateInstanceRef; |
@@ -17,34 +17,115 @@ class TemplateBindExtension extends _ElementExtension { |
DocumentFragment _content; |
bool _templateIsDecorated; |
+ var _bindingMap; |
+ |
TemplateBindExtension._(Element node) : super(node); |
Element get _node => super._node; |
+ TemplateBindExtension get _self => super._node is TemplateBindExtension |
+ ? _node : this; |
+ |
NodeBinding bind(String name, model, [String path]) { |
+ path = path != null ? path : ''; |
+ |
+ if (_iterator == null) { |
+ // TODO(jmesserly): since there's only one iterator, we could just |
+ // inline it into this object. |
+ _iterator = new _TemplateIterator(this); |
+ } |
+ |
+ // 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': |
- _self.unbind(name); |
- if (_templateIterator == null) { |
- _templateIterator = new _TemplateIterator(_node); |
- } |
+ _iterator..hasIf = true |
+ ..ifModel = model |
+ ..ifPath = path; |
+ _scheduleIterator(); |
return bindings[name] = new _TemplateBinding(this, name, model, path); |
default: |
return super.bind(name, model, path); |
} |
} |
+ 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; |
+ } |
+ } |
+ |
+ void _scheduleIterator() { |
+ if (!_iterator.depsChanging) { |
+ _iterator.depsChanging = true; |
+ scheduleMicrotask(_iterator.resolve); |
+ } |
+ } |
+ |
/** |
* Creates an instance of the template, using the provided model and optional |
* binding delegate. |
*/ |
- DocumentFragment createInstance([model, BindingDelegate delegate]) { |
- var instance = _createDeepCloneAndDecorateTemplates( |
- templateBind(ref).content, delegate); |
+ DocumentFragment createInstance([model, BindingDelegate delegate, |
+ List<NodeBinding> bound]) { |
+ var ref = templateBind(this.ref); |
+ var content = 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) { |
+ // TODO(rafaelw): Setup a MutationObserver on content to detect |
+ // when the instanceMap is invalid. |
+ map = new _InstanceBindingMap(content, delegate); |
+ ref._bindingMap = map; |
+ } |
+ |
+ var instance = map.hasSubTemplate |
+ ? _deepCloneIgnoreTemplateContent(content) |
+ : content.clone(true); |
- _addBindings(instance, model, delegate); |
+ _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; |
} |
@@ -57,6 +138,16 @@ class TemplateBindExtension extends _ElementExtension { |
_ensureSetModelScheduled(); |
} |
+ static Node _deepCloneIgnoreTemplateContent(Node node) { |
+ var clone = node.clone(false); // Shallow clone. |
+ if (isSemanticTemplate(clone)) return clone; |
+ |
+ for (var c = node.firstChild; c != null; c = c.nextNode) { |
+ clone.append(_deepCloneIgnoreTemplateContent(c)); |
+ } |
+ return clone; |
+ } |
+ |
/** |
* The binding delegate which is inherited through the tree. It can be used |
* to configure custom syntax for `{{bindings}}` inside this template. |
@@ -138,7 +229,7 @@ class TemplateBindExtension extends _ElementExtension { |
if (!isNative && _isAttributeTemplate(_node)) { |
if (instanceRef != null) { |
- // TODO(jmesserly): this is just an assert in MDV. |
+ // TODO(jmesserly): this is just an assert in TemplateBinding. |
throw new ArgumentError('instanceRef should not be supplied for ' |
'attribute templates.'); |
} |
@@ -269,48 +360,64 @@ class TemplateBindExtension extends _ElementExtension { |
static bool _initStyles; |
+ // This is to replicate template_element.css |
+ // TODO(jmesserly): move this to an opt-in CSS file? |
static void _injectStylesheet() { |
if (_initStyles == true) return; |
_initStyles = true; |
- var style = new StyleElement(); |
- style.text = r''' |
-template, |
-thead[template], |
-tbody[template], |
-tfoot[template], |
-th[template], |
-tr[template], |
-td[template], |
-caption[template], |
-colgroup[template], |
-col[template], |
-option[template] { |
- display: none; |
-}'''; |
+ var style = new StyleElement() |
+ ..text = '$_allTemplatesSelectors { display: none; }'; |
document.head.append(style); |
} |
} |
-class _TemplateBinding extends NodeBinding { |
+// 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; |
- // TODO(jmesserly): MDV uses TemplateIterator as the node, see: |
- // https://github.com/Polymer/mdv/issues/127 |
- _TemplateBinding(ext, name, model, path) |
- : _ext = ext, super(ext._node, name, model, path) { |
- _ext._templateIterator.inputs.bind(property, model, this.path); |
+ 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); |
} |
- // These are no-ops because we don't use the underlying PathObserver. |
- void _observePath() {} |
+ _TemplateBinding(this._ext, this.property, this._model, this.path); |
+ |
void valueChanged(newValue) {} |
+ sanitizeBoundValue(value) => value == null ? '' : '$value'; |
+ |
void close() { |
if (closed) return; |
- var templateIterator = _ext._templateIterator; |
- if (templateIterator != null) templateIterator.inputs.unbind(property); |
- super.close(); |
+ |
+ // 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; |
} |
} |