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

Unified Diff: sdk/lib/html/dartium/html_dartium.dart

Issue 17552019: Reorganize mdv and observe packages (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: merged Created 7 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
Download patch
« no previous file with comments | « sdk/lib/html/dart2js/html_dart2js.dart ('k') | sdk/lib/mdv_observe_impl/mdv_observe_impl.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: sdk/lib/html/dartium/html_dartium.dart
diff --git a/sdk/lib/html/dartium/html_dartium.dart b/sdk/lib/html/dartium/html_dartium.dart
index 58e78985ed0b6224bba1bdce66cfc79f4e049d91..476283e41a0bf6b78a1f016887f14d3d0b69f5cc 100644
--- a/sdk/lib/html/dartium/html_dartium.dart
+++ b/sdk/lib/html/dartium/html_dartium.dart
@@ -14,7 +14,6 @@ import 'dart:isolate';
import 'dart:json' as json;
import 'dart:math';
import 'dart:nativewrappers';
-import 'dart:mdv_observe_impl';
import 'dart:typed_data';
import 'dart:web_gl' as gl;
import 'dart:web_sql';
@@ -8698,18 +8697,6 @@ abstract class Element extends Node implements ElementTraversal {
}
- Map<String, StreamSubscription> _attributeBindings;
-
- // TODO(jmesserly): I'm concerned about adding these to every element.
- // Conceptually all of these belong on TemplateElement. They are here to
- // support browsers that don't have <template> yet.
- // However even in the polyfill they're restricted to certain tags
- // (see [isTemplate]). So we can probably convert it to a (public) mixin, and
- // only mix it in to the elements that need it.
- var _model;
-
- _TemplateIterator _templateIterator;
-
Element _templateInstanceRef;
// Note: only used if `this is! TemplateElement`
@@ -8717,125 +8704,6 @@ abstract class Element extends Node implements ElementTraversal {
bool _templateIsDecorated;
- // TODO(jmesserly): should path be optional, and default to empty path?
- // It is used that way in at least one path in JS TemplateElement tests
- // (see "BindImperative" test in original JS code).
- @Experimental
- void bind(String name, model, String path) {
- _bindElement(this, name, model, path);
- }
-
- // TODO(jmesserly): this is static to work around http://dartbug.com/10166
- // Similar issue for unbind/unbindAll below.
- static void _bindElement(Element self, String name, model, String path) {
- if (self._bindTemplate(name, model, path)) return;
-
- if (self._attributeBindings == null) {
- self._attributeBindings = new Map<String, StreamSubscription>();
- }
-
- self.xtag.attributes.remove(name);
-
- var changed;
- if (name.endsWith('?')) {
- name = name.substring(0, name.length - 1);
-
- changed = (value) {
- if (_Bindings._toBoolean(value)) {
- self.xtag.attributes[name] = '';
- } else {
- self.xtag.attributes.remove(name);
- }
- };
- } else {
- changed = (value) {
- // TODO(jmesserly): escape value if needed to protect against XSS.
- // See https://github.com/polymer-project/mdv/issues/58
- self.xtag.attributes[name] = value == null ? '' : '$value';
- };
- }
-
- self.unbind(name);
-
- self._attributeBindings[name] =
- new PathObserver(model, path).bindSync(changed);
- }
-
- @Experimental
- void unbind(String name) {
- _unbindElement(this, name);
- }
-
- static _unbindElement(Element self, String name) {
- if (self._unbindTemplate(name)) return;
- if (self._attributeBindings != null) {
- var binding = self._attributeBindings.remove(name);
- if (binding != null) binding.cancel();
- }
- }
-
- @Experimental
- void unbindAll() {
- _unbindAllElement(this);
- }
-
- static void _unbindAllElement(Element self) {
- self._unbindAllTemplate();
-
- if (self._attributeBindings != null) {
- for (var binding in self._attributeBindings.values) {
- binding.cancel();
- }
- self._attributeBindings = null;
- }
- }
-
- // TODO(jmesserly): unlike the JS polyfill, we can't mixin
- // HTMLTemplateElement at runtime into things that are semantically template
- // elements. So instead we implement it here with a runtime check.
- // If the bind succeeds, we return true, otherwise we return false and let
- // the normal Element.bind logic kick in.
- bool _bindTemplate(String name, model, String path) {
- if (isTemplate) {
- switch (name) {
- case 'bind':
- case 'repeat':
- case 'if':
- _ensureTemplate();
- if (_templateIterator == null) {
- _templateIterator = new _TemplateIterator(this);
- }
- _templateIterator.inputs.bind(name, model, path);
- return true;
- }
- }
- return false;
- }
-
- bool _unbindTemplate(String name) {
- if (isTemplate) {
- switch (name) {
- case 'bind':
- case 'repeat':
- case 'if':
- _ensureTemplate();
- if (_templateIterator != null) {
- _templateIterator.inputs.unbind(name);
- }
- return true;
- }
- }
- return false;
- }
-
- void _unbindAllTemplate() {
- if (isTemplate) {
- unbind('bind');
- unbind('repeat');
- unbind('if');
- }
- }
-
/**
* Gets the template this node refers to.
* This is only supported if [isTemplate] is true.
@@ -8870,38 +8738,20 @@ abstract class Element extends Node implements ElementTraversal {
@Experimental
DocumentFragment createInstance() {
_ensureTemplate();
-
- var template = ref;
- if (template == null) template = this;
-
- var instance = _Bindings._createDeepCloneAndDecorateTemplates(
- template.content, attributes['syntax']);
-
- if (TemplateElement._instanceCreated != null) {
- TemplateElement._instanceCreated.add(instance);
- }
- return instance;
+ return TemplateElement.mdvPackage(this).createInstance();
}
/**
* The data model which is inherited through the tree.
* This is only supported if [isTemplate] is true.
- *
- * Setting this will destructive propagate the value to all descendant nodes,
- * and reinstantiate all of the nodes expanded by this template.
- *
- * Currently this does not support propagation through Shadow DOMs.
*/
@Experimental
- get model => _model;
+ get model => TemplateElement.mdvPackage(this).model;
@Experimental
void set model(value) {
_ensureTemplate();
-
- var syntax = TemplateElement.syntax[attributes['syntax']];
- _model = value;
- _Bindings._addBindings(this, model, syntax);
+ TemplateElement.mdvPackage(this).model = value;
}
// TODO(jmesserly): const set would be better
@@ -13170,59 +13020,6 @@ class InputElement extends _HTMLElement implements
return e;
}
- _ValueBinding _valueBinding;
-
- _CheckedBinding _checkedBinding;
-
- @Experimental
- void bind(String name, model, String path) {
- switch (name) {
- case 'value':
- unbind('value');
- attributes.remove('value');
- _valueBinding = new _ValueBinding(this, model, path);
- break;
- case 'checked':
- unbind('checked');
- attributes.remove('checked');
- _checkedBinding = new _CheckedBinding(this, model, path);
- break;
- default:
- // TODO(jmesserly): this should be "super" (http://dartbug.com/10166).
- // Similar issue for unbind/unbindAll below.
- Element._bindElement(this, name, model, path);
- break;
- }
- }
-
- @Experimental
- void unbind(String name) {
- switch (name) {
- case 'value':
- if (_valueBinding != null) {
- _valueBinding.unbind();
- _valueBinding = null;
- }
- break;
- case 'checked':
- if (_checkedBinding != null) {
- _checkedBinding.unbind();
- _checkedBinding = null;
- }
- break;
- default:
- Element._unbindElement(this, name);
- break;
- }
- }
-
- @Experimental
- void unbindAll() {
- unbind('value');
- unbind('checked');
- Element._unbindAllElement(this);
- }
-
InputElement.internal() : super.internal();
@DomName('HTMLInputElement.webkitSpeechChangeEvent')
@@ -17670,6 +17467,27 @@ class _ChildNodeListLazy extends ListBase<Node> {
Node operator[](int index) => _this.$dom_childNodes[index];
}
+/** Information about the instantiated template. */
+class TemplateInstance {
+ // TODO(rafaelw): firstNode & lastNode should be read-synchronous
+ // in cases where script has modified the template instance boundary.
+
+ /** The first node of this template instantiation. */
+ final Node firstNode;
+
+ /**
+ * The last node of this template instantiation.
+ * This could be identical to [firstNode] if the template only expanded to a
+ * single node.
+ */
+ final Node lastNode;
+
+ /** The model used to instantiate the template. */
+ final model;
+
+ TemplateInstance(this.firstNode, this.lastNode, this.model);
+}
+
@DomName('Node')
class Node extends EventTarget {
List<Node> get nodes {
@@ -17749,26 +17567,24 @@ class Node extends EventTarget {
*/
@Experimental
void bind(String name, model, String path) {
- // TODO(jmesserly): should we throw instead?
- window.console.error('Unhandled binding to Node: '
- '$this $name $model $path');
+ TemplateElement.mdvPackage(this).bind(name, model, path);
}
/** Unbinds the attribute [name]. */
@Experimental
- void unbind(String name) {}
+ void unbind(String name) {
+ TemplateElement.mdvPackage(this).unbind(name);
+ }
/** Unbinds all bound attributes. */
@Experimental
- void unbindAll() {}
-
- TemplateInstance _templateInstance;
+ void unbindAll() {
+ TemplateElement.mdvPackage(this).unbindAll();
+ }
/** Gets the template instance that instantiated this node, if any. */
@Experimental
- TemplateInstance get templateInstance =>
- _templateInstance != null ? _templateInstance :
- (parent != null ? parent.templateInstance : null);
+ TemplateInstance get templateInstance => mdvPackage(this).templateInstance;
Node.internal() : super.internal();
@@ -22725,6 +22541,99 @@ class TableSectionElement extends _HTMLElement {
// WARNING: Do not edit - generated code.
+
+/**
+ * Model-Driven Views (MDV)'s native features enables a wide-range of use cases,
+ * but (by design) don't attempt to implement a wide array of specialized
+ * behaviors.
+ *
+ * Enabling these features in MDV is a matter of implementing and registering an
+ * MDV Custom Syntax. A Custom Syntax is an object which contains one or more
+ * delegation functions which implement specialized behavior. This object is
+ * registered with MDV via [TemplateElement.syntax]:
+ *
+ *
+ * HTML:
+ * <template bind syntax="MySyntax">
+ * {{ What!Ever('crazy')->thing^^^I+Want(data) }}
+ * </template>
+ *
+ * Dart:
+ * class MySyntax extends CustomBindingSyntax {
+ * getBinding(model, path, name, node) {
+ * // The magic happens here!
+ * }
+ * }
+ *
+ * ...
+ *
+ * TemplateElement.syntax['MySyntax'] = new MySyntax();
+ *
+ * See <https://github.com/polymer-project/mdv/blob/master/docs/syntax.md> for
+ * more information about Custom Syntax.
+ */
+// TODO(jmesserly): if this is just one method, a function type would make it
+// more Dart-friendly.
+@Experimental
+abstract class CustomBindingSyntax {
+ /**
+ * This syntax method allows for a custom interpretation of the contents of
+ * mustaches (`{{` ... `}}`).
+ *
+ * When a template is inserting an instance, it will invoke this method for
+ * each mustache which is encountered. The function is invoked with four
+ * arguments:
+ *
+ * - [model]: The data context for which this instance is being created.
+ * - [path]: The text contents (trimmed of outer whitespace) of the mustache.
+ * - [name]: The context in which the mustache occurs. Within element
+ * attributes, this will be the name of the attribute. Within text,
+ * this will be 'text'.
+ * - [node]: A reference to the node to which this binding will be created.
+ *
+ * If the method wishes to handle binding, it is required to return an object
+ * which has at least a `value` property that can be observed. If it does,
+ * then MDV will call [Node.bind on the node:
+ *
+ * node.bind(name, retval, 'value');
+ *
+ * If the 'getBinding' does not wish to override the binding, it should return
+ * null.
+ */
+ // TODO(jmesserly): I had to remove type annotations from "name" and "node"
+ // Normally they are String and Node respectively. But sometimes it will pass
+ // (int name, CompoundBinding node). That seems very confusing; we may want
+ // to change this API.
+ getBinding(model, String path, name, node) => null;
+
+ /**
+ * This syntax method allows a syntax to provide an alterate model than the
+ * one the template would otherwise use when producing an instance.
+ *
+ * When a template is about to create an instance, it will invoke this method
+ * The function is invoked with two arguments:
+ *
+ * - [template]: The template element which is about to create and insert an
+ * instance.
+ * - [model]: The data context for which this instance is being created.
+ *
+ * The template element will always use the return value of `getInstanceModel`
+ * as the model for the new instance. If the syntax does not wish to override
+ * the value, it should simply return the `model` value it was passed.
+ */
+ getInstanceModel(Element template, model) => model;
+
+ /**
+ * This syntax method allows a syntax to provide an alterate expansion of
+ * the [template] contents. When the template wants to create an instance,
+ * it will call this method with the template element.
+ *
+ * By default this will call `template.createInstance()`.
+ */
+ getInstanceFragment(Element template) => template.createInstance();
+}
+
+
@Experimental
@DomName('HTMLTemplateElement')
@SupportedBrowser(SupportedBrowser.CHROME)
@@ -22750,24 +22659,23 @@ class TemplateElement extends _HTMLElement {
@Experimental
DocumentFragment get content => $dom_content;
- static StreamController<DocumentFragment> _instanceCreated;
/**
- * *Warning*: This is an implementation helper for Model-Driven Views and
- * should not be used in your code.
+ * The MDV package, if available.
*
- * This event is fired whenever a template is instantiated via
- * [createInstance].
+ * This can be used to initialize MDV support via:
+ *
+ * import 'dart:html';
+ * import 'package:mdv/mdv.dart' as mdv;
+ * main() {
+ * mdv.initialize();
+ * }
*/
- // TODO(rafaelw): This is a hack, and is neccesary for the polyfill
- // because custom elements are not upgraded during clone()
- @Experimental
- static Stream<DocumentFragment> get instanceCreated {
- if (_instanceCreated == null) {
- _instanceCreated = new StreamController<DocumentFragment>(sync: true);
- }
- return _instanceCreated.stream;
- }
+ static Function mdvPackage = (node) {
+ throw new UnsupportedError("The MDV package is not available. "
+ "You can enable it with `import 'package:mdv/mdv.dart' as mdv;` and "
+ "`mdv.initialize()`");
+ };
/**
* Ensures proper API and content model for template elements.
@@ -22790,7 +22698,7 @@ class TemplateElement extends _HTMLElement {
// Create content
if (template is! TemplateElement) {
- var doc = _Bindings._getTemplateContentsOwner(template.document);
+ var doc = _getTemplateContentsOwner(template.document);
template._templateContent = doc.createDocumentFragment();
}
@@ -22802,55 +22710,124 @@ class TemplateElement extends _HTMLElement {
if (template is TemplateElement) {
bootstrap(template.content);
} else {
- _Bindings._liftNonNativeChildrenIntoContent(template);
+ _liftNonNativeChildrenIntoContent(template);
}
return true;
}
- /**
- * 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.
- @Experimental
- static void bootstrap(Node content) {
- _Bindings._bootstrapTemplatesRecursivelyFrom(content);
- }
-
- /**
- * Binds all mustaches recursively starting from the [root] node.
- *
- * Note: this is not an official Model-Driven-Views API; it is intended to
- * support binding the [ShadowRoot]'s content to a model.
- */
- // TODO(jmesserly): this is needed to avoid two <template> nodes when using
- // bindings in a custom element's template. See also:
- // https://github.com/polymer-project/polymer/blob/master/src/bindMDV.js#L68
- // Called from:
- // https://github.com/polymer-project/polymer/blob/master/src/register.js#L99
- @Experimental
- static void bindModel(Node root, model, [CustomBindingSyntax syntax]) {
- _Bindings._addBindings(root, model, syntax);
+ // 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 = doc._templateContentsOwner;
+ 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();
+ }
+ doc._templateContentsOwner = d;
+ }
+ return d;
}
- 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],
+ static Element _cloneAndSeperateAttributeTemplate(Element templateElement) {
+ var clone = templateElement.clone(false);
+ var attributes = templateElement.attributes;
+ for (var name in attributes.keys.toList()) {
+ switch (name) {
+ case 'template':
+ case 'repeat':
+ case 'bind':
+ case 'ref':
+ clone.attributes.remove(name);
+ break;
+ default:
+ attributes.remove(name);
+ break;
+ }
+ }
+
+ return clone;
+ }
+
+ static void _liftNonNativeChildrenIntoContent(Element templateElement) {
+ var content = templateElement.content;
+
+ if (!templateElement._isAttributeTemplate) {
+ var child;
+ while ((child = templateElement.firstChild) != null) {
+ content.append(child);
+ }
+ return;
+ }
+
+ // For attribute templates we copy the whole thing into the content and
+ // we move the non template attributes into the content.
+ //
+ // <tr foo template>
+ //
+ // becomes
+ //
+ // <tr template>
+ // + #document-fragment
+ // + <tr foo>
+ //
+ var newRoot = _cloneAndSeperateAttributeTemplate(templateElement);
+ var child;
+ while ((child = templateElement.firstChild) != null) {
+ newRoot.append(child);
+ }
+ content.append(newRoot);
+ }
+
+ /**
+ * 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.
+ @Experimental
+ static void bootstrap(Node content) {
+ void _bootstrap(template) {
+ if (!TemplateElement.decorate(template)) {
+ bootstrap(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).queryAll(_allTemplatesSelectors);
+ if (content is Element && (content as Element).isTemplate) {
+ _bootstrap(content);
+ }
+
+ descendents.forEach(_bootstrap);
+ }
+
+ static final String _allTemplatesSelectors = 'template, option[template], ' +
+ Element._TABLE_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],
@@ -22894,41 +22871,6 @@ class Text extends CharacterData {
@DocsEditable
Text splitText(int offset) native "Text_splitText_Callback";
-
- StreamSubscription _textBinding;
-
- @Experimental
- void bind(String name, model, String path) {
- if (name != 'text') {
- super.bind(name, model, path);
- return;
- }
-
- unbind('text');
-
- _textBinding = new PathObserver(model, path).bindSync((value) {
- text = value == null ? '' : '$value';
- });
- }
-
- @Experimental
- void unbind(String name) {
- if (name != 'text') {
- super.unbind(name);
- return;
- }
-
- if (_textBinding == null) return;
-
- _textBinding.cancel();
- _textBinding = null;
- }
-
- @Experimental
- void unbindAll() {
- unbind('text');
- super.unbindAll();
- }
}
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
@@ -29703,794 +29645,6 @@ class Rect {
// BSD-style license that can be found in the LICENSE file.
-// This code is a port of Model-Driven-Views:
-// https://github.com/polymer-project/mdv
-// The code mostly comes from src/template_element.js
-
-typedef void _ChangeHandler(value);
-
-/**
- * Model-Driven Views (MDV)'s native features enables a wide-range of use cases,
- * but (by design) don't attempt to implement a wide array of specialized
- * behaviors.
- *
- * Enabling these features in MDV is a matter of implementing and registering an
- * MDV Custom Syntax. A Custom Syntax is an object which contains one or more
- * delegation functions which implement specialized behavior. This object is
- * registered with MDV via [TemplateElement.syntax]:
- *
- *
- * HTML:
- * <template bind syntax="MySyntax">
- * {{ What!Ever('crazy')->thing^^^I+Want(data) }}
- * </template>
- *
- * Dart:
- * class MySyntax extends CustomBindingSyntax {
- * getBinding(model, path, name, node) {
- * // The magic happens here!
- * }
- * }
- *
- * ...
- *
- * TemplateElement.syntax['MySyntax'] = new MySyntax();
- *
- * See <https://github.com/polymer-project/mdv/blob/master/docs/syntax.md> for more
- * information about Custom Syntax.
- */
-// TODO(jmesserly): if this is just one method, a function type would make it
-// more Dart-friendly.
-@Experimental
-abstract class CustomBindingSyntax {
- /**
- * This syntax method allows for a custom interpretation of the contents of
- * mustaches (`{{` ... `}}`).
- *
- * When a template is inserting an instance, it will invoke this method for
- * each mustache which is encountered. The function is invoked with four
- * arguments:
- *
- * - [model]: The data context for which this instance is being created.
- * - [path]: The text contents (trimmed of outer whitespace) of the mustache.
- * - [name]: The context in which the mustache occurs. Within element
- * attributes, this will be the name of the attribute. Within text,
- * this will be 'text'.
- * - [node]: A reference to the node to which this binding will be created.
- *
- * If the method wishes to handle binding, it is required to return an object
- * which has at least a `value` property that can be observed. If it does,
- * then MDV will call [Node.bind on the node:
- *
- * node.bind(name, retval, 'value');
- *
- * If the 'getBinding' does not wish to override the binding, it should return
- * null.
- */
- // TODO(jmesserly): I had to remove type annotations from "name" and "node"
- // Normally they are String and Node respectively. But sometimes it will pass
- // (int name, CompoundBinding node). That seems very confusing; we may want
- // to change this API.
- getBinding(model, String path, name, node) => null;
-
- /**
- * This syntax method allows a syntax to provide an alterate model than the
- * one the template would otherwise use when producing an instance.
- *
- * When a template is about to create an instance, it will invoke this method
- * The function is invoked with two arguments:
- *
- * - [template]: The template element which is about to create and insert an
- * instance.
- * - [model]: The data context for which this instance is being created.
- *
- * The template element will always use the return value of `getInstanceModel`
- * as the model for the new instance. If the syntax does not wish to override
- * the value, it should simply return the `model` value it was passed.
- */
- getInstanceModel(Element template, model) => model;
-
- /**
- * This syntax method allows a syntax to provide an alterate expansion of
- * the [template] contents. When the template wants to create an instance,
- * it will call this method with the template element.
- *
- * By default this will call `template.createInstance()`.
- */
- getInstanceFragment(Element template) => template.createInstance();
-}
-
-/** The callback used in the [CompoundBinding.combinator] field. */
-@Experimental
-typedef Object CompoundBindingCombinator(Map objects);
-
-/** Information about the instantiated template. */
-@Experimental
-class TemplateInstance {
- // TODO(rafaelw): firstNode & lastNode should be read-synchronous
- // in cases where script has modified the template instance boundary.
-
- /** The first node of this template instantiation. */
- final Node firstNode;
-
- /**
- * The last node of this template instantiation.
- * This could be identical to [firstNode] if the template only expanded to a
- * single node.
- */
- final Node lastNode;
-
- /** The model used to instantiate the template. */
- final model;
-
- TemplateInstance(this.firstNode, this.lastNode, this.model);
-}
-
-/**
- * Model-Driven Views contains a helper object which is useful for the
- * implementation of a Custom Syntax.
- *
- * var binding = new CompoundBinding((values) {
- * var combinedValue;
- * // compute combinedValue based on the current values which are provided
- * return combinedValue;
- * });
- * binding.bind('name1', obj1, path1);
- * binding.bind('name2', obj2, path2);
- * //...
- * binding.bind('nameN', objN, pathN);
- *
- * CompoundBinding is an object which knows how to listen to multiple path
- * values (registered via [bind]) and invoke its [combinator] when one or more
- * of the values have changed and set its [value] property to the return value
- * of the function. When any value has changed, all current values are provided
- * to the [combinator] in the single `values` argument.
- *
- * See [CustomBindingSyntax] for more information.
- */
-// TODO(jmesserly): what is the public API surface here? I just guessed;
-// most of it seemed non-public.
-@Experimental
-class CompoundBinding extends ObservableBase {
- CompoundBindingCombinator _combinator;
-
- // TODO(jmesserly): ideally these would be String keys, but sometimes we
- // use integers.
- Map<dynamic, StreamSubscription> _bindings = new Map();
- Map _values = new Map();
- bool _scheduled = false;
- bool _disposed = false;
- Object _value;
-
- CompoundBinding([CompoundBindingCombinator combinator]) {
- // TODO(jmesserly): this is a tweak to the original code, it seemed to me
- // that passing the combinator to the constructor should be equivalent to
- // setting it via the property.
- // I also added a null check to the combinator setter.
- this.combinator = combinator;
- }
-
- CompoundBindingCombinator get combinator => _combinator;
-
- set combinator(CompoundBindingCombinator combinator) {
- _combinator = combinator;
- if (combinator != null) _scheduleResolve();
- }
-
- static const _VALUE = const Symbol('value');
-
- get value => _value;
-
- void set value(newValue) {
- _value = notifyPropertyChange(_VALUE, _value, newValue);
- }
-
- // TODO(jmesserly): remove these workarounds when dart2js supports mirrors!
- getValueWorkaround(key) {
- if (key == _VALUE) return value;
- return null;
- }
- setValueWorkaround(key, val) {
- if (key == _VALUE) value = val;
- }
-
- void bind(name, model, String path) {
- unbind(name);
-
- _bindings[name] = new PathObserver(model, path).bindSync((value) {
- _values[name] = value;
- _scheduleResolve();
- });
- }
-
- void unbind(name, {bool suppressResolve: false}) {
- var binding = _bindings.remove(name);
- if (binding == null) return;
-
- binding.cancel();
- _values.remove(name);
- if (!suppressResolve) _scheduleResolve();
- }
-
- // TODO(rafaelw): Is this the right processing model?
- // TODO(rafaelw): Consider having a seperate ChangeSummary for
- // CompoundBindings so to excess dirtyChecks.
- void _scheduleResolve() {
- if (_scheduled) return;
- _scheduled = true;
- queueChangeRecords(resolve);
- }
-
- void resolve() {
- if (_disposed) return;
- _scheduled = false;
-
- if (_combinator == null) {
- throw new StateError(
- 'CompoundBinding attempted to resolve without a combinator');
- }
-
- value = _combinator(_values);
- }
-
- void dispose() {
- for (var binding in _bindings.values) {
- binding.cancel();
- }
- _bindings.clear();
- _values.clear();
-
- _disposed = true;
- value = null;
- }
-}
-
-abstract class _InputBinding {
- final InputElement element;
- PathObserver binding;
- StreamSubscription _pathSub;
- StreamSubscription _eventSub;
-
- _InputBinding(this.element, model, String path) {
- binding = new PathObserver(model, path);
- _pathSub = binding.bindSync(valueChanged);
- _eventSub = _getStreamForInputType(element).listen(updateBinding);
- }
-
- void valueChanged(newValue);
-
- void updateBinding(e);
-
- void unbind() {
- binding = null;
- _pathSub.cancel();
- _eventSub.cancel();
- }
-
-
- static Stream<Event> _getStreamForInputType(InputElement element) {
- switch (element.type) {
- case 'checkbox':
- return element.onClick;
- case 'radio':
- case 'select-multiple':
- case 'select-one':
- return element.onChange;
- default:
- return element.onInput;
- }
- }
-}
-
-class _ValueBinding extends _InputBinding {
- _ValueBinding(element, model, path) : super(element, model, path);
-
- void valueChanged(value) {
- element.value = value == null ? '' : '$value';
- }
-
- void updateBinding(e) {
- binding.value = element.value;
- }
-}
-
-class _CheckedBinding extends _InputBinding {
- _CheckedBinding(element, model, path) : super(element, model, path);
-
- void valueChanged(value) {
- element.checked = _Bindings._toBoolean(value);
- }
-
- void updateBinding(e) {
- binding.value = element.checked;
-
- // Only the radio button that is getting checked gets an event. We
- // therefore find all the associated radio buttons and update their
- // CheckedBinding manually.
- if (element is InputElement && element.type == 'radio') {
- for (var r in _getAssociatedRadioButtons(element)) {
- var checkedBinding = r._checkedBinding;
- if (checkedBinding != null) {
- // Set the value directly to avoid an infinite call stack.
- checkedBinding.binding.value = false;
- }
- }
- }
- }
-
- // |element| is assumed to be an HTMLInputElement with |type| == 'radio'.
- // Returns an array containing all radio buttons other than |element| that
- // have the same |name|, either in the form that |element| belongs to or,
- // if no form, in the document tree to which |element| belongs.
- //
- // This implementation is based upon the HTML spec definition of a
- // "radio button group":
- // http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state.html#radio-button-group
- //
- static Iterable _getAssociatedRadioButtons(element) {
- if (!_isNodeInDocument(element)) return [];
- if (element.form != null) {
- return element.form.nodes.where((el) {
- return el != element &&
- el is InputElement &&
- el.type == 'radio' &&
- el.name == element.name;
- });
- } else {
- var radios = element.document.queryAll(
- 'input[type="radio"][name="${element.name}"]');
- return radios.where((el) => el != element && el.form == null);
- }
- }
-
- // TODO(jmesserly): polyfill document.contains API instead of doing it here
- static bool _isNodeInDocument(Node node) {
- // On non-IE this works:
- // return node.document.contains(node);
- var document = node.document;
- if (node == document || node.parentNode == document) return true;
- return document.documentElement.contains(node);
- }
-}
-
-class _Bindings {
- // TODO(jmesserly): not sure what kind of boolean conversion rules to
- // apply for template data-binding. HTML attributes are true if they're
- // present. However Dart only treats "true" as true. Since this is HTML we'll
- // use something closer to the HTML rules: null (missing) and false are false,
- // everything else is true. See: https://github.com/polymer-project/mdv/issues/59
- static bool _toBoolean(value) => null != value && false != value;
-
- static Node _createDeepCloneAndDecorateTemplates(Node node, String syntax) {
- var clone = node.clone(false); // Shallow clone.
- if (clone is Element && clone.isTemplate) {
- TemplateElement.decorate(clone, node);
- if (syntax != null) {
- clone.attributes.putIfAbsent('syntax', () => syntax);
- }
- }
-
- for (var c = node.firstChild; c != null; c = c.nextNode) {
- clone.append(_createDeepCloneAndDecorateTemplates(c, syntax));
- }
- return clone;
- }
-
- // 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 = doc._templateContentsOwner;
- 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();
- }
- doc._templateContentsOwner = d;
- }
- return d;
- }
-
- static Element _cloneAndSeperateAttributeTemplate(Element templateElement) {
- var clone = templateElement.clone(false);
- var attributes = templateElement.attributes;
- for (var name in attributes.keys.toList()) {
- switch (name) {
- case 'template':
- case 'repeat':
- case 'bind':
- case 'ref':
- clone.attributes.remove(name);
- break;
- default:
- attributes.remove(name);
- break;
- }
- }
-
- return clone;
- }
-
- static void _liftNonNativeChildrenIntoContent(Element templateElement) {
- var content = templateElement.content;
-
- if (!templateElement._isAttributeTemplate) {
- var child;
- while ((child = templateElement.firstChild) != null) {
- content.append(child);
- }
- return;
- }
-
- // For attribute templates we copy the whole thing into the content and
- // we move the non template attributes into the content.
- //
- // <tr foo template>
- //
- // becomes
- //
- // <tr template>
- // + #document-fragment
- // + <tr foo>
- //
- var newRoot = _cloneAndSeperateAttributeTemplate(templateElement);
- var child;
- while ((child = templateElement.firstChild) != null) {
- newRoot.append(child);
- }
- content.append(newRoot);
- }
-
- static void _bootstrapTemplatesRecursivelyFrom(Node node) {
- void bootstrap(template) {
- if (!TemplateElement.decorate(template)) {
- _bootstrapTemplatesRecursivelyFrom(template.content);
- }
- }
-
- // Need to do this first as the contents may get lifted if |node| is
- // template.
- // TODO(jmesserly): node is DocumentFragment or Element
- var descendents = (node as dynamic).queryAll(_allTemplatesSelectors);
- if (node is Element && (node as Element).isTemplate) bootstrap(node);
-
- descendents.forEach(bootstrap);
- }
-
- static final String _allTemplatesSelectors = 'template, option[template], ' +
- Element._TABLE_TAGS.keys.map((k) => "$k[template]").join(", ");
-
- static void _addBindings(Node node, model, [CustomBindingSyntax syntax]) {
- if (node is Element) {
- _addAttributeBindings(node, model, syntax);
- } else if (node is Text) {
- _parseAndBind(node, 'text', node.text, model, syntax);
- }
-
- for (var c = node.firstChild; c != null; c = c.nextNode) {
- _addBindings(c, model, syntax);
- }
- }
-
- static void _addAttributeBindings(Element element, model, syntax) {
- element.attributes.forEach((name, value) {
- if (value == '' && (name == 'bind' || name == 'repeat')) {
- value = '{{}}';
- }
- _parseAndBind(element, name, value, model, syntax);
- });
- }
-
- static void _parseAndBind(Node node, String name, String text, model,
- CustomBindingSyntax syntax) {
-
- var tokens = _parseMustacheTokens(text);
- if (tokens.length == 0 || (tokens.length == 1 && tokens[0].isText)) {
- return;
- }
-
- // If this is a custom element, give the .xtag a change to bind.
- node = _nodeOrCustom(node);
-
- if (tokens.length == 1 && tokens[0].isBinding) {
- _bindOrDelegate(node, name, model, tokens[0].value, syntax);
- return;
- }
-
- var replacementBinding = new CompoundBinding();
- for (var i = 0; i < tokens.length; i++) {
- var token = tokens[i];
- if (token.isBinding) {
- _bindOrDelegate(replacementBinding, i, model, token.value, syntax);
- }
- }
-
- replacementBinding.combinator = (values) {
- var newValue = new StringBuffer();
-
- for (var i = 0; i < tokens.length; i++) {
- var token = tokens[i];
- if (token.isText) {
- newValue.write(token.value);
- } else {
- var value = values[i];
- if (value != null) {
- newValue.write(value);
- }
- }
- }
-
- return newValue.toString();
- };
-
- node.bind(name, replacementBinding, 'value');
- }
-
- static void _bindOrDelegate(node, name, model, String path,
- CustomBindingSyntax syntax) {
-
- if (syntax != null) {
- var delegateBinding = syntax.getBinding(model, path, name, node);
- if (delegateBinding != null) {
- model = delegateBinding;
- path = 'value';
- }
- }
-
- node.bind(name, model, path);
- }
-
- /**
- * Gets the [node]'s custom [Element.xtag] if present, otherwise returns
- * the node. This is used so nodes can override [Node.bind], [Node.unbind],
- * and [Node.unbindAll] like InputElement does.
- */
- // TODO(jmesserly): remove this when we can extend Element for real.
- static _nodeOrCustom(node) => node is Element ? node.xtag : node;
-
- static List<_BindingToken> _parseMustacheTokens(String s) {
- var result = [];
- var length = s.length;
- var index = 0, lastIndex = 0;
- while (lastIndex < length) {
- index = s.indexOf('{{', lastIndex);
- if (index < 0) {
- result.add(new _BindingToken(s.substring(lastIndex)));
- break;
- } else {
- // There is a non-empty text run before the next path token.
- if (index > 0 && lastIndex < index) {
- result.add(new _BindingToken(s.substring(lastIndex, index)));
- }
- lastIndex = index + 2;
- index = s.indexOf('}}', lastIndex);
- if (index < 0) {
- var text = s.substring(lastIndex - 2);
- if (result.length > 0 && result.last.isText) {
- result.last.value += text;
- } else {
- result.add(new _BindingToken(text));
- }
- break;
- }
-
- var value = s.substring(lastIndex, index).trim();
- result.add(new _BindingToken(value, isBinding: true));
- lastIndex = index + 2;
- }
- }
- return result;
- }
-
- static void _addTemplateInstanceRecord(fragment, model) {
- if (fragment.firstChild == null) {
- return;
- }
-
- var instanceRecord = new TemplateInstance(
- fragment.firstChild, fragment.lastChild, model);
-
- var node = instanceRecord.firstNode;
- while (node != null) {
- node._templateInstance = instanceRecord;
- node = node.nextNode;
- }
- }
-
- static void _removeAllBindingsRecursively(Node node) {
- _nodeOrCustom(node).unbindAll();
- for (var c = node.firstChild; c != null; c = c.nextNode) {
- _removeAllBindingsRecursively(c);
- }
- }
-
- static void _removeChild(Node parent, Node child) {
- child._templateInstance = null;
- if (child is Element && (child as Element).isTemplate) {
- Element childElement = child;
- // Make sure we stop observing when we remove an element.
- var templateIterator = childElement._templateIterator;
- if (templateIterator != null) {
- templateIterator.abandon();
- childElement._templateIterator = null;
- }
- }
- child.remove();
- _removeAllBindingsRecursively(child);
- }
-}
-
-class _BindingToken {
- final String value;
- final bool isBinding;
-
- _BindingToken(this.value, {this.isBinding: false});
-
- bool get isText => !isBinding;
-}
-
-class _TemplateIterator {
- final Element _templateElement;
- final List<Node> terminators = [];
- final CompoundBinding inputs;
- List iteratedValue;
-
- StreamSubscription _sub;
- StreamSubscription _valueBinding;
-
- _TemplateIterator(this._templateElement)
- : inputs = new CompoundBinding(resolveInputs) {
-
- _valueBinding = new PathObserver(inputs, 'value').bindSync(valueChanged);
- }
-
- static Object resolveInputs(Map values) {
- if (values.containsKey('if') && !_Bindings._toBoolean(values['if'])) {
- return null;
- }
-
- if (values.containsKey('repeat')) {
- return values['repeat'];
- }
-
- if (values.containsKey('bind')) {
- return [values['bind']];
- }
-
- return null;
- }
-
- void valueChanged(value) {
- clear();
- if (value is! List) return;
-
- iteratedValue = value;
-
- if (value is Observable) {
- _sub = value.changes.listen(_handleChanges);
- }
-
- int len = iteratedValue.length;
- if (len > 0) {
- _handleChanges([new ListChangeRecord(0, addedCount: len)]);
- }
- }
-
- Node getTerminatorAt(int index) {
- if (index == -1) return _templateElement;
- var terminator = terminators[index];
- if (terminator is! Element) return terminator;
-
- var subIterator = terminator._templateIterator;
- if (subIterator == null) return terminator;
-
- return subIterator.getTerminatorAt(subIterator.terminators.length - 1);
- }
-
- void insertInstanceAt(int index, Node fragment) {
- var previousTerminator = getTerminatorAt(index - 1);
- var terminator = fragment.lastChild;
- if (terminator == null) terminator = previousTerminator;
-
- terminators.insert(index, terminator);
- var parent = _templateElement.parentNode;
- parent.insertBefore(fragment, previousTerminator.nextNode);
- }
-
- void removeInstanceAt(int index) {
- var previousTerminator = getTerminatorAt(index - 1);
- var terminator = getTerminatorAt(index);
- terminators.removeAt(index);
-
- var parent = _templateElement.parentNode;
- while (terminator != previousTerminator) {
- var node = terminator;
- terminator = node.previousNode;
- _Bindings._removeChild(parent, node);
- }
- }
-
- void removeAllInstances() {
- if (terminators.length == 0) return;
-
- var previousTerminator = _templateElement;
- var terminator = getTerminatorAt(terminators.length - 1);
- terminators.length = 0;
-
- var parent = _templateElement.parentNode;
- while (terminator != previousTerminator) {
- var node = terminator;
- terminator = node.previousNode;
- _Bindings._removeChild(parent, node);
- }
- }
-
- void clear() {
- unobserve();
- removeAllInstances();
- iteratedValue = null;
- }
-
- getInstanceModel(model, syntax) {
- if (syntax != null) {
- return syntax.getInstanceModel(_templateElement, model);
- }
- return model;
- }
-
- getInstanceFragment(syntax) {
- if (syntax != null) {
- return syntax.getInstanceFragment(_templateElement);
- }
- return _templateElement.createInstance();
- }
-
- void _handleChanges(List<ListChangeRecord> splices) {
- var syntax = TemplateElement.syntax[_templateElement.attributes['syntax']];
-
- for (var splice in splices) {
- if (splice is! ListChangeRecord) continue;
-
- for (int i = 0; i < splice.removedCount; i++) {
- removeInstanceAt(splice.index);
- }
-
- for (var addIndex = splice.index;
- addIndex < splice.index + splice.addedCount;
- addIndex++) {
-
- var model = getInstanceModel(iteratedValue[addIndex], syntax);
-
- var fragment = getInstanceFragment(syntax);
-
- _Bindings._addBindings(fragment, model, syntax);
- _Bindings._addTemplateInstanceRecord(fragment, model);
-
- insertInstanceAt(addIndex, fragment);
- }
- }
- }
-
- void unobserve() {
- if (_sub == null) return;
- _sub.cancel();
- _sub = null;
- }
-
- void abandon() {
- unobserve();
- _valueBinding.cancel();
- inputs.dispose();
- }
-}
-// 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.
-
-
/**
* Helper class to implement custom events which wrap DOM events.
*/
« no previous file with comments | « sdk/lib/html/dart2js/html_dart2js.dart ('k') | sdk/lib/mdv_observe_impl/mdv_observe_impl.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698