| Index: pkg/template_binding/lib/src/node.dart
|
| diff --git a/pkg/template_binding/lib/src/node.dart b/pkg/template_binding/lib/src/node.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..a1bc29c99422d7df5b29d8827cdc48a4020f5832
|
| --- /dev/null
|
| +++ b/pkg/template_binding/lib/src/node.dart
|
| @@ -0,0 +1,247 @@
|
| +// 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 the [Node] API. */
|
| +class NodeBindExtension {
|
| + final Node _node;
|
| + Map<String, NodeBinding> _bindings;
|
| +
|
| + NodeBindExtension._(this._node);
|
| +
|
| + /**
|
| + * Creates a binding to the attribute [name] to the [path] of the [model].
|
| + *
|
| + * This can be overridden by custom elements to provide the binding used in
|
| + * [bind]. This will only create the binding; it will not add
|
| + * it to [bindings].
|
| + *
|
| + * You should not need to call this directly except from [bind].
|
| + */
|
| + NodeBinding createBinding(String name, model, String path) => null;
|
| +
|
| + /**
|
| + * Binds the attribute [name] to the [path] of the [model].
|
| + * Path is a String of accessors such as `foo.bar.baz`.
|
| + * Returns the `NodeBinding` instance.
|
| + */
|
| + NodeBinding bind(String name, model, String path) {
|
| + var binding = bindings[name];
|
| + if (binding != null) binding.close();
|
| +
|
| + // Note: dispatch through the node so it can override this.
|
| + binding = nodeBind(_node).createBinding(name, model, path);
|
| +
|
| + bindings[name] = binding;
|
| + if (binding == null) {
|
| + window.console.error('Unhandled binding to Node: '
|
| + '$this $name $model $path');
|
| + }
|
| + return binding;
|
| + }
|
| +
|
| + /** Unbinds the attribute [name]. */
|
| + void unbind(String name) {
|
| + if (_bindings == null) return;
|
| + var binding = bindings.remove(name);
|
| + if (binding != null) binding.close();
|
| + }
|
| +
|
| + /** Unbinds all bound attributes. */
|
| + void unbindAll() {
|
| + if (_bindings == null) return;
|
| + for (var binding in bindings.values) {
|
| + if (binding != null) binding.close();
|
| + }
|
| + _bindings = null;
|
| + }
|
| +
|
| + // TODO(jmesserly): we should return a read-only wrapper here.
|
| + /** Gets the data bindings that are associated with this node. */
|
| + Map<String, NodeBinding> get bindings {
|
| + if (_bindings == null) _bindings = new LinkedHashMap<String, NodeBinding>();
|
| + return _bindings;
|
| + }
|
| +
|
| + TemplateInstance _templateInstance;
|
| +
|
| + /** Gets the template instance that instantiated this node, if any. */
|
| + TemplateInstance get templateInstance =>
|
| + _templateInstance != null ? _templateInstance :
|
| + (_node.parent != null ? _node.parent.templateInstance : null);
|
| +}
|
| +
|
| +
|
| +/** 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);
|
| +}
|
| +
|
| +
|
| +/**
|
| + * Template Bindings 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 is a matter of implementing and registering a
|
| + * BindingDelegate. A binding delegate is an object which contains one or more
|
| + * delegation functions which implement specialized behavior. This object is
|
| + * registered via [TemplateBindExtension.bindingDelegate]:
|
| + *
|
| + * HTML:
|
| + * <template bind>
|
| + * {{ What!Ever('crazy')->thing^^^I+Want(data) }}
|
| + * </template>
|
| + *
|
| + * Dart:
|
| + * class MySyntax extends BindingDelegate {
|
| + * getBinding(model, path, name, node) {
|
| + * // The magic happens here!
|
| + * }
|
| + * }
|
| + * ...
|
| + * templateBind(query('template'))
|
| + * ..bindingDelegate = new MySyntax()
|
| + * ..model = new MyModel();
|
| + *
|
| + * See <https://github.com/polymer-project/mdv/blob/master/docs/syntax.md> for
|
| + * more information about Custom Syntax.
|
| + */
|
| +abstract class BindingDelegate {
|
| + /**
|
| + * 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 [NodeBindExtension.bind] on the node:
|
| + *
|
| + * nodeBind(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;
|
| +}
|
| +
|
| +/**
|
| + * A data binding on a [Node].
|
| + * See [NodeBindExtension.bindings] and [NodeBindExtension.bind].
|
| + */
|
| +abstract class NodeBinding {
|
| + Node _node;
|
| + var _model;
|
| + PathObserver _observer;
|
| + StreamSubscription _pathSub;
|
| +
|
| + /** The property of [node] which will be data bound. */
|
| + final String property;
|
| +
|
| + /** The property of [node] which will be data bound. */
|
| + final String path;
|
| +
|
| + /** The node that has [property] which will be data bound. */
|
| + Node get node => _node;
|
| +
|
| + /**
|
| + * The bound data model.
|
| + */
|
| + get model => _model;
|
| +
|
| + /** True if this binding has been [closed]. */
|
| + bool get closed => _observer == null;
|
| +
|
| + /** The value at the [path] on [model]. */
|
| + get value => _observer.value;
|
| +
|
| + set value(newValue) {
|
| + _observer.value = newValue;
|
| + }
|
| +
|
| + NodeBinding(this._node, this.property, this._model, this.path) {
|
| + // Create the path observer
|
| + _observer = new PathObserver(model, path);
|
| + _observePath();
|
| + }
|
| +
|
| + void _observePath() {
|
| + _pathSub = _observer.bindSync(boundValueChanged);
|
| + }
|
| +
|
| + /** Called when [value] changes to update the [node]. */
|
| + // TODO(jmesserly): the impl in MDV uses mirrors to set the property,
|
| + // but that isn't used except for specific known fields like "textContent",
|
| + // so I'm overridding this in the subclasses instead.
|
| + void boundValueChanged(newValue);
|
| +
|
| + /** Called to sanitize the value before it is assigned into the property. */
|
| + sanitizeBoundValue(value) => value == null ? '' : '$value';
|
| +
|
| + /**
|
| + * Called by [NodeBindExtension.unbind] to close this binding and unobserve
|
| + * the [path].
|
| + *
|
| + * This can be overridden in subclasses, but they must call `super.close()`
|
| + * to free associated resources. They must also check [closed] and return
|
| + * immediately if already closed.
|
| + */
|
| + void close() {
|
| + if (closed) return;
|
| +
|
| + if (_pathSub != null) _pathSub.cancel();
|
| + _pathSub = null;
|
| + _observer = null;
|
| + _node = null;
|
| + _model = null;
|
| + }
|
| +}
|
|
|