Index: template_binding/lib/src/template.dart |
diff --git a/template_binding/lib/src/template.dart b/template_binding/lib/src/template.dart |
deleted file mode 100644 |
index 2dc7ac5613075f119c77feec0344f2e923027764..0000000000000000000000000000000000000000 |
--- a/template_binding/lib/src/template.dart |
+++ /dev/null |
@@ -1,536 +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; |
- |
-/** Extensions to [Element]s that behave as templates. */ |
-class TemplateBindExtension extends NodeBindExtension { |
- var _model; |
- BindingDelegate _bindingDelegate; |
- _TemplateIterator _iterator; |
- bool _setModelScheduled = false; |
- |
- Element _templateInstanceRef; |
- |
- // Note: only used if `this is! TemplateElement` |
- DocumentFragment _content; |
- bool _templateIsDecorated; |
- |
- HtmlDocument _stagingDocument; |
- |
- _InstanceBindingMap _bindingMap; |
- |
- Node _refContent; |
- |
- TemplateBindExtension._(Element node) : super._(node); |
- |
- Element get _node => super._node; |
- |
- TemplateBindExtension get _self => _node is TemplateBindExtension |
- ? _node : this; |
- |
- Bindable bind(String name, value, {bool oneTime: false}) { |
- if (name != 'ref') return super.bind(name, value, oneTime: oneTime); |
- |
- var ref = oneTime ? value : value.open((ref) { |
- _node.attributes['ref'] = ref; |
- _refChanged(); |
- }); |
- |
- _node.attributes['ref'] = ref; |
- _refChanged(); |
- if (oneTime) return null; |
- |
- if (bindings == null) bindings = {}; |
- return bindings['ref'] = value; |
- } |
- |
- _TemplateIterator _processBindingDirectives(_TemplateBindingMap directives) { |
- if (_iterator != null) _iterator._closeDependencies(); |
- |
- if (directives._if == null && |
- directives._bind == null && |
- directives._repeat == null) { |
- |
- if (_iterator != null) { |
- _iterator.close(); |
- _iterator = null; |
- } |
- return null; |
- } |
- |
- if (_iterator == null) { |
- _iterator = new _TemplateIterator(this); |
- } |
- |
- _iterator._updateDependencies(directives, model); |
- |
- _templateObserver.observe(_node, |
- attributes: true, attributeFilter: ['ref']); |
- |
- return _iterator; |
- } |
- |
- /** |
- * 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 necessary, but is |
- * used internally by the system. |
- */ |
- DocumentFragment createInstance([model, BindingDelegate delegate]) { |
- if (delegate == null) delegate = _bindingDelegate; |
- if (_refContent == null) _refContent = templateBind(_ref).content; |
- |
- var content = _refContent; |
- if (content.firstChild == null) return _emptyInstance; |
- |
- final map = _getInstanceBindingMap(content, delegate); |
- final staging = _getTemplateStagingDocument(); |
- final instance = _stagingDocument.createDocumentFragment(); |
- |
- final instanceExt = new _InstanceExtension(); |
- _instanceExtension[instance] = instanceExt |
- .._templateCreator = _node |
- .._protoContent = content; |
- |
- final instanceRecord = new TemplateInstance(model); |
- nodeBindFallback(instance)._templateInstance = instanceRecord; |
- |
- var i = 0; |
- bool collectTerminator = false; |
- for (var c = content.firstChild; c != null; c = c.nextNode, i++) { |
- // The terminator of the instance is the clone of the last child of the |
- // content. If the last child is an active template, it may produce |
- // instances as a result of production, so simply collecting the last |
- // child of the instance after it has finished producing may be wrong. |
- if (c.nextNode == null) collectTerminator = true; |
- |
- final childMap = map != null ? map.getChild(i) : null; |
- var clone = _cloneAndBindInstance(c, instance, _stagingDocument, |
- childMap, model, delegate, instanceExt._bindings); |
- |
- nodeBindFallback(clone)._templateInstance = instanceRecord; |
- if (collectTerminator) instanceExt._terminator = clone; |
- } |
- |
- instanceRecord._firstNode = instance.firstChild; |
- instanceRecord._lastNode = instance.lastChild; |
- |
- instanceExt._protoContent = null; |
- instanceExt._templateCreator = null; |
- return instance; |
- } |
- |
- /** The data model which is inherited through the tree. */ |
- get model => _model; |
- |
- void set model(value) { |
- _model = value; |
- _ensureSetModelScheduled(); |
- } |
- |
- static Node _deepCloneIgnoreTemplateContent(Node node, stagingDocument) { |
- var clone = stagingDocument.importNode(node, false); |
- if (isSemanticTemplate(clone)) return clone; |
- |
- for (var c = node.firstChild; c != null; c = c.nextNode) { |
- clone.append(_deepCloneIgnoreTemplateContent(c, stagingDocument)); |
- } |
- return clone; |
- } |
- |
- /** |
- * 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) { |
- if (_bindingDelegate != null) { |
- throw new StateError('Template must be cleared before a new ' |
- 'bindingDelegate can be assigned'); |
- } |
- _bindingDelegate = value; |
- |
- // Clear cached state based on the binding delegate. |
- _bindingMap = null; |
- if (_iterator != null) { |
- _iterator._initPrepareFunctions = false; |
- _iterator._instanceModelFn = null; |
- _iterator._instancePositionChangedFn = null; |
- } |
- } |
- |
- _ensureSetModelScheduled() { |
- if (_setModelScheduled) return; |
- _decorate(); |
- _setModelScheduled = true; |
- scheduleMicrotask(_setModel); |
- } |
- |
- void _setModel() { |
- _setModelScheduled = false; |
- var map = _getBindings(_node, _bindingDelegate); |
- _processBindings(_node, map, _model); |
- } |
- |
- _refChanged() { |
- if (_iterator == null || _refContent == templateBind(_ref).content) return; |
- |
- _refContent = null; |
- _iterator._valueChanged(null); |
- _iterator._updateIteratedValue(this._iterator._getUpdatedValue()); |
- } |
- |
- void clear() { |
- _model = null; |
- _bindingDelegate = null; |
- if (bindings != null) { |
- var ref = bindings.remove('ref'); |
- if (ref != null) ref.close(); |
- } |
- _refContent = null; |
- if (_iterator == null) return; |
- _iterator._valueChanged(null); |
- _iterator.close(); |
- _iterator = null; |
- } |
- |
- /** Gets the template this node refers to. */ |
- Element get _ref { |
- _decorate(); |
- |
- var ref = _searchRefId(_node, _node.attributes['ref']); |
- if (ref == null) { |
- ref = _templateInstanceRef; |
- if (ref == null) return _node; |
- } |
- |
- var nextRef = templateBindFallback(ref)._ref; |
- return nextRef != null ? nextRef : ref; |
- } |
- |
- /** |
- * 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(); |
- _globalBaseUriWorkaround(); |
- |
- var templateElementExt = this; |
- _templateIsDecorated = true; |
- var isNativeHtmlTemplate = _node is TemplateElement; |
- final bootstrapContents = isNativeHtmlTemplate; |
- final liftContents = !isNativeHtmlTemplate; |
- var liftRoot = false; |
- |
- 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; |
- } |
- } |
- |
- if (!isNativeHtmlTemplate) { |
- var doc = _getOrCreateTemplateContentsOwner(templateElementExt._node); |
- 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(); |
- static final _ownerStagingDocument = new Expando(); |
- |
- // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#dfn-template-contents-owner |
- static HtmlDocument _getOrCreateTemplateContentsOwner(Element template) { |
- var doc = template.ownerDocument; |
- 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; |
- } |
- |
- HtmlDocument _getTemplateStagingDocument() { |
- if (_stagingDocument == null) { |
- var owner = _node.ownerDocument; |
- var doc = _ownerStagingDocument[owner]; |
- if (doc == null) { |
- doc = owner.implementation.createHtmlDocument(''); |
- _isStagingDocument[doc] = true; |
- _baseUriWorkaround(doc); |
- _ownerStagingDocument[owner] = doc; |
- } |
- _stagingDocument = doc; |
- } |
- return _stagingDocument; |
- } |
- |
- // 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 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) { |
- |
- 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; |
- |
- // 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() |
- ..text = '$_allTemplatesSelectors { display: none; }'; |
- document.head.append(style); |
- } |
- |
- static bool _initBaseUriWorkaround; |
- |
- static void _globalBaseUriWorkaround() { |
- if (_initBaseUriWorkaround == true) return; |
- _initBaseUriWorkaround = true; |
- |
- var t = document.createElement('template'); |
- if (t is TemplateElement) { |
- var d = t.content.ownerDocument; |
- if (d.documentElement == null) { |
- d.append(d.createElement('html')).append(d.createElement('head')); |
- } |
- // don't patch this if TemplateBinding.js already has. |
- if (d.head.querySelector('base') == null) { |
- _baseUriWorkaround(d); |
- } |
- } |
- } |
- |
- // TODO(rafaelw): Remove when fix for |
- // https://codereview.chromium.org/164803002/ |
- // makes it to Chrome release. |
- static void _baseUriWorkaround(HtmlDocument doc) { |
- BaseElement base = doc.createElement('base'); |
- base.href = document.baseUri; |
- doc.head.append(base); |
- } |
- |
- static final _templateObserver = new MutationObserver((records, _) { |
- for (MutationRecord record in records) { |
- templateBindFallback(record.target)._refChanged(); |
- } |
- }); |
- |
-} |
- |
-final DocumentFragment _emptyInstance = () { |
- var empty = new DocumentFragment(); |
- _instanceExtension[empty] = new _InstanceExtension(); |
- return empty; |
-}(); |
- |
-// TODO(jmesserly): if we merged with wtih TemplateInstance, it seems like it |
-// would speed up some operations (e.g. _getInstanceRoot wouldn't need to walk |
-// the parent chain). |
-class _InstanceExtension { |
- final List _bindings = []; |
- Node _terminator; |
- Element _templateCreator; |
- DocumentFragment _protoContent; |
-} |
- |
-// TODO(jmesserly): this is private in JS but public for us because pkg:polymer |
-// uses it. |
-List getTemplateInstanceBindings(DocumentFragment fragment) { |
- var ext = _instanceExtension[fragment]; |
- return ext != null ? ext._bindings : ext; |
-} |
- |
-/// Gets the root of the current node's parent chain |
-_getFragmentRoot(Node node) { |
- var p; |
- while ((p = node.parentNode) != null) { |
- node = p; |
- } |
- return node; |
-} |
- |
-Node _searchRefId(Node node, String id) { |
- if (id == null || id == '') return null; |
- |
- final selector = '#$id'; |
- while (true) { |
- node = _getFragmentRoot(node); |
- |
- Node ref = null; |
- |
- _InstanceExtension instance = _instanceExtension[node]; |
- if (instance != null && instance._protoContent != null) { |
- ref = instance._protoContent.querySelector(selector); |
- } else if (_hasGetElementById(node)) { |
- ref = (node as dynamic).getElementById(id); |
- } |
- |
- if (ref != null) return ref; |
- |
- if (instance == null) return null; |
- node = instance._templateCreator; |
- if (node == null) return null; |
- } |
-} |
- |
-_getInstanceRoot(node) { |
- while (node.parentNode != null) { |
- node = node.parentNode; |
- } |
- _InstanceExtension instance = _instanceExtension[node]; |
- return instance != null && instance._templateCreator != null ? node : null; |
-} |
- |
-// Note: JS code tests that getElementById is present. We can't do that |
-// easily, so instead check for the types known to implement it. |
-bool _hasGetElementById(Node node) => |
- node is Document || node is ShadowRoot || node is SvgSvgElement; |
- |
-final Expando<_InstanceExtension> _instanceExtension = new Expando(); |
- |
-final _isStagingDocument = new Expando(); |