| 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
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..ee344f4af7e1f398618cad26edcdb44b7bfd8179
|
| --- /dev/null
|
| +++ b/pkg/template_binding/lib/src/template.dart
|
| @@ -0,0 +1,325 @@
|
| +// 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;
|
| +
|
| +/** Extensions to [Element]s that behave as templates. */
|
| +class TemplateBindExtension extends _ElementExtension {
|
| + var _model;
|
| + BindingDelegate _bindingDelegate;
|
| + _TemplateIterator _templateIterator;
|
| + bool _scheduled = false;
|
| +
|
| + Element _templateInstanceRef;
|
| +
|
| + // Note: only used if `this is! TemplateElement`
|
| + DocumentFragment _content;
|
| + bool _templateIsDecorated;
|
| +
|
| + TemplateBindExtension._(Element node) : super(node);
|
| +
|
| + Element get _node => super._node;
|
| +
|
| + NodeBinding createBinding(String name, model, String path) {
|
| + switch (name) {
|
| + case 'bind':
|
| + case 'repeat':
|
| + case 'if':
|
| + if (_templateIterator == null) {
|
| + _templateIterator = new _TemplateIterator(_node);
|
| + }
|
| + // TODO(jmesserly): why do we do this here and nowhere else?
|
| + if (path == null) path = '';
|
| + return new _TemplateBinding(this, name, model, path);
|
| + default:
|
| + return super.createBinding(name, model, path);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * 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);
|
| +
|
| + _addBindings(instance, model, delegate);
|
| + _addTemplateInstanceRecord(instance, model);
|
| + return instance;
|
| + }
|
| +
|
| + /** The data model which is inherited through the tree. */
|
| + get model => _model;
|
| +
|
| + void set model(value) {
|
| + _model = value;
|
| + _ensureSetModelScheduled();
|
| + }
|
| +
|
| + /**
|
| + * The binding delegate which is inherited through the tree. It can be used
|
| + * to configure custom syntax for `{{bindings}}` inside this template.
|
| + */
|
| + BindingDelegate get bindingDelegate => _bindingDelegate;
|
| +
|
| + void set bindingDelegate(BindingDelegate value) {
|
| + _bindingDelegate = value;
|
| + _ensureSetModelScheduled();
|
| + }
|
| +
|
| + _ensureSetModelScheduled() {
|
| + if (_scheduled) return;
|
| + _decorate();
|
| + _scheduled = true;
|
| + scheduleMicrotask(_setModel);
|
| + }
|
| +
|
| + void _setModel() {
|
| + _scheduled = false;
|
| + _addBindings(_node, _model, _bindingDelegate);
|
| + }
|
| +
|
| + /** Gets the template this node refers to. */
|
| + Element get ref {
|
| + _decorate();
|
| +
|
| + Element result = null;
|
| + var refId = _node.attributes['ref'];
|
| + if (refId != null) {
|
| + var treeScope = _node;
|
| + while (treeScope.parentNode != null) {
|
| + treeScope = treeScope.parentNode;
|
| + }
|
| +
|
| + // Note: JS code tests that getElementById is present. We can't do that
|
| + // easily, so instead check for the types known to implement it.
|
| + if (treeScope is Document ||
|
| + treeScope is ShadowRoot ||
|
| + treeScope is SvgSvgElement) {
|
| +
|
| + result = treeScope.getElementById(refId);
|
| + }
|
| + }
|
| +
|
| + if (result == null) {
|
| + result = _templateInstanceRef;
|
| + if (result == null) return _node;
|
| + }
|
| +
|
| + var nextRef = templateBind(result).ref;
|
| + return nextRef != null ? nextRef : result;
|
| + }
|
| +
|
| + /**
|
| + * Gets the content of this template.
|
| + */
|
| + DocumentFragment get content {
|
| + _decorate();
|
| + return _content != null ? _content : (_node as TemplateElement).content;
|
| + }
|
| +
|
| + /**
|
| + * Ensures proper API and content model for template elements.
|
| + *
|
| + * [instanceRef] can be used to set the [Element.ref] property of [template],
|
| + * and use the ref's content will be used as source when createInstance() is
|
| + * invoked.
|
| + *
|
| + * Returns true if this template was just decorated, or false if it was
|
| + * already decorated.
|
| + */
|
| + static bool decorate(Element template, [Element instanceRef]) =>
|
| + templateBindFallback(template)._decorate(instanceRef);
|
| +
|
| + bool _decorate([Element instanceRef]) {
|
| + // == true check because it starts as a null field.
|
| + if (_templateIsDecorated == true) return false;
|
| +
|
| + _injectStylesheet();
|
| +
|
| + var templateElementExt = this;
|
| + _templateIsDecorated = true;
|
| + var isNative = _node is TemplateElement;
|
| + var bootstrapContents = isNative;
|
| + var liftContents = !isNative;
|
| + var liftRoot = false;
|
| +
|
| + if (!isNative && _isAttributeTemplate(_node)) {
|
| + if (instanceRef != null) {
|
| + // TODO(jmesserly): this is just an assert in MDV.
|
| + throw new ArgumentError('instanceRef should not be supplied for '
|
| + 'attribute templates.');
|
| + }
|
| + templateElementExt = templateBind(
|
| + _extractTemplateFromAttributeTemplate(_node));
|
| + templateElementExt._templateIsDecorated = true;
|
| + isNative = templateElementExt._node is TemplateElement;
|
| + liftRoot = true;
|
| + }
|
| +
|
| + if (!isNative) {
|
| + var doc = _getTemplateContentsOwner(
|
| + templateElementExt._node.ownerDocument);
|
| + templateElementExt._content = doc.createDocumentFragment();
|
| + }
|
| +
|
| + if (instanceRef != null) {
|
| + // template is contained within an instance, its direct content must be
|
| + // empty
|
| + templateElementExt._templateInstanceRef = instanceRef;
|
| + } else if (liftContents) {
|
| + _liftNonNativeChildrenIntoContent(templateElementExt, _node, liftRoot);
|
| + } else if (bootstrapContents) {
|
| + bootstrap(templateElementExt.content);
|
| + }
|
| +
|
| + return true;
|
| + }
|
| +
|
| + static final _contentsOwner = new Expando();
|
| +
|
| + // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#dfn-template-contents-owner
|
| + static Document _getTemplateContentsOwner(HtmlDocument doc) {
|
| + if (doc.window == null) {
|
| + return doc;
|
| + }
|
| + var d = _contentsOwner[doc];
|
| + if (d == null) {
|
| + // TODO(arv): This should either be a Document or HTMLDocument depending
|
| + // on doc.
|
| + d = doc.implementation.createHtmlDocument('');
|
| + while (d.lastChild != null) {
|
| + d.lastChild.remove();
|
| + }
|
| + _contentsOwner[doc] = d;
|
| + }
|
| + return d;
|
| + }
|
| +
|
| + // For non-template browsers, the parser will disallow <template> in certain
|
| + // locations, so we allow "attribute templates" which combine the template
|
| + // element with the top-level container node of the content, e.g.
|
| + //
|
| + // <tr template repeat="{{ foo }}"" class="bar"><td>Bar</td></tr>
|
| + //
|
| + // becomes
|
| + //
|
| + // <template repeat="{{ foo }}">
|
| + // + #document-fragment
|
| + // + <tr class="bar">
|
| + // + <td>Bar</td>
|
| + //
|
| + static Element _extractTemplateFromAttributeTemplate(Element el) {
|
| + var template = el.ownerDocument.createElement('template');
|
| + el.parentNode.insertBefore(template, el);
|
| +
|
| + for (var name in el.attributes.keys.toList()) {
|
| + switch (name) {
|
| + case 'template':
|
| + el.attributes.remove(name);
|
| + break;
|
| + case 'repeat':
|
| + case 'bind':
|
| + case 'ref':
|
| + template.attributes[name] = el.attributes.remove(name);
|
| + break;
|
| + }
|
| + }
|
| +
|
| + return template;
|
| + }
|
| +
|
| + static void _liftNonNativeChildrenIntoContent(TemplateBindExtension template,
|
| + Element el, bool useRoot) {
|
| +
|
| + var content = template.content;
|
| + if (useRoot) {
|
| + content.append(el);
|
| + return;
|
| + }
|
| +
|
| + var child;
|
| + while ((child = el.firstChild) != null) {
|
| + content.append(child);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * This used to decorate recursively all templates from a given node.
|
| + *
|
| + * By default [decorate] will be called on templates lazily when certain
|
| + * properties such as [model] are accessed, but it can be run eagerly to
|
| + * decorate an entire tree recursively.
|
| + */
|
| + // TODO(rafaelw): Review whether this is the right public API.
|
| + static void bootstrap(Node content) {
|
| + void _bootstrap(template) {
|
| + if (!TemplateBindExtension.decorate(template)) {
|
| + bootstrap(templateBind(template).content);
|
| + }
|
| + }
|
| +
|
| + // Need to do this first as the contents may get lifted if |node| is
|
| + // template.
|
| + // TODO(jmesserly): content is DocumentFragment or Element
|
| + var descendents =
|
| + (content as dynamic).querySelectorAll(_allTemplatesSelectors);
|
| + if (isSemanticTemplate(content)) {
|
| + _bootstrap(content);
|
| + }
|
| +
|
| + descendents.forEach(_bootstrap);
|
| + }
|
| +
|
| + static final String _allTemplatesSelectors =
|
| + 'template, ' +
|
| + _SEMANTIC_TEMPLATE_TAGS.keys.map((k) => "$k[template]").join(", ");
|
| +
|
| + static bool _initStyles;
|
| +
|
| + 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;
|
| +}''';
|
| + document.head.append(style);
|
| + }
|
| +}
|
| +
|
| +class _TemplateBinding extends NodeBinding {
|
| + TemplateBindExtension _ext;
|
| +
|
| + // 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, path);
|
| + }
|
| +
|
| + // These are no-ops because we don't use the underlying PathObserver.
|
| + void _observePath() {}
|
| + void boundValueChanged(newValue) {}
|
| +
|
| + void close() {
|
| + if (closed) return;
|
| + var templateIterator = _ext._templateIterator;
|
| + if (templateIterator != null) templateIterator.inputs.unbind(property);
|
| + super.close();
|
| + }
|
| +}
|
|
|