Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(163)

Unified Diff: observatory_pub_packages/template_binding/src/template.dart

Issue 816693004: Add observatory_pub_packages snapshot to third_party (Closed) Base URL: http://dart.googlecode.com/svn/third_party/
Patch Set: Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: observatory_pub_packages/template_binding/src/template.dart
===================================================================
--- observatory_pub_packages/template_binding/src/template.dart (revision 0)
+++ observatory_pub_packages/template_binding/src/template.dart (working copy)
@@ -0,0 +1,536 @@
+// 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();

Powered by Google App Engine
This is Rietveld 408576698