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

Side by Side Diff: pkg/template_binding/lib/src/node.dart

Issue 50203004: port TemplateBinding to ed3266266e751b5ab1f75f8e0509d0d5f0ef35d8 (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 1 month 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 part of template_binding; 5 part of template_binding;
6 6
7 /** Extensions to the [Node] API. */ 7 /** Extensions to the [Node] API. */
8 class NodeBindExtension { 8 class NodeBindExtension {
9 final Node _node; 9 final Node _node;
10 Map<String, NodeBinding> _bindings; 10 Map<String, NodeBinding> _bindings;
(...skipping 13 matching lines...) Expand all
24 /** Unbinds the attribute [name]. */ 24 /** Unbinds the attribute [name]. */
25 void unbind(String name) { 25 void unbind(String name) {
26 if (_bindings == null) return; 26 if (_bindings == null) return;
27 var binding = bindings.remove(name); 27 var binding = bindings.remove(name);
28 if (binding != null) binding.close(); 28 if (binding != null) binding.close();
29 } 29 }
30 30
31 /** Unbinds all bound attributes. */ 31 /** Unbinds all bound attributes. */
32 void unbindAll() { 32 void unbindAll() {
33 if (_bindings == null) return; 33 if (_bindings == null) return;
34 for (var binding in bindings.values) { 34 for (var binding in bindings.values.toList()) {
35 if (binding != null) binding.close(); 35 if (binding != null) binding.close();
36 } 36 }
37 _bindings = null; 37 _bindings = null;
38 } 38 }
39 39
40 // TODO(jmesserly): we should return a read-only wrapper here. 40 // TODO(jmesserly): we should return a read-only wrapper here.
41 /** Gets the data bindings that are associated with this node. */ 41 /** Gets the data bindings that are associated with this node. */
42 Map<String, NodeBinding> get bindings { 42 Map<String, NodeBinding> get bindings {
43 if (_bindings == null) _bindings = new LinkedHashMap<String, NodeBinding>(); 43 if (_bindings == null) _bindings = new LinkedHashMap<String, NodeBinding>();
44 return _bindings; 44 return _bindings;
45 } 45 }
46 46
47 /** 47 /**
48 * Dispatch support so custom HtmlElement's can override these methods. 48 * Dispatch support so custom HtmlElement's can override these methods.
49 * A public method like [this.bind] should not call another public method such 49 * A public method like [this.bind] should not call another public method such
50 * as [this.unbind]. Instead it should dispatch through [_self.unbind]. 50 * as [this.unbind]. Instead it should dispatch through [_self.unbind].
51 */ 51 */
52 NodeBindExtension get _self => _node is NodeBindExtension ? _node : this; 52 NodeBindExtension get _self => _node is NodeBindExtension ? _node : this;
53 53
54 TemplateInstance _templateInstance; 54 TemplateInstance _templateInstance;
55 55
56 /** Gets the template instance that instantiated this node, if any. */ 56 /** Gets the template instance that instantiated this node, if any. */
57 TemplateInstance get templateInstance => 57 TemplateInstance get templateInstance =>
58 _templateInstance != null ? _templateInstance : 58 _templateInstance != null ? _templateInstance :
59 (_node.parent != null ? _node.parent.templateInstance : null); 59 (_node.parent != null ? nodeBind(_node.parent).templateInstance : null);
60 } 60 }
61 61
62 62
63 /** Information about the instantiated template. */ 63 /** Information about the instantiated template. */
64 class TemplateInstance { 64 class TemplateInstance {
65 // TODO(rafaelw): firstNode & lastNode should be read-synchronous 65 // TODO(rafaelw): firstNode & lastNode should be read-synchronous
66 // in cases where script has modified the template instance boundary. 66 // in cases where script has modified the template instance boundary.
67 67
68 /** The first node of this template instantiation. */ 68 /** The first node of this template instantiation. */
69 final Node firstNode; 69 final Node firstNode;
70 70
71 /** 71 /**
72 * The last node of this template instantiation. 72 * The last node of this template instantiation.
73 * This could be identical to [firstNode] if the template only expanded to a 73 * This could be identical to [firstNode] if the template only expanded to a
74 * single node. 74 * single node.
75 */ 75 */
76 final Node lastNode; 76 final Node lastNode;
77 77
78 /** The model used to instantiate the template. */ 78 /** The model used to instantiate the template. */
79 final model; 79 final model;
80 80
81 TemplateInstance(this.firstNode, this.lastNode, this.model); 81 TemplateInstance(this.firstNode, this.lastNode, this.model);
82 } 82 }
83
84
85 /**
86 * Template Bindings native features enables a wide-range of use cases,
87 * but (by design) don't attempt to implement a wide array of specialized
88 * behaviors.
89 *
90 * Enabling these features is a matter of implementing and registering a
91 * BindingDelegate. A binding delegate is an object which contains one or more
92 * delegation functions which implement specialized behavior. This object is
93 * registered via [TemplateBindExtension.bindingDelegate]:
94 *
95 * HTML:
96 * <template bind>
97 * {{ What!Ever('crazy')->thing^^^I+Want(data) }}
98 * </template>
99 *
100 * Dart:
101 * class MySyntax extends BindingDelegate {
102 * getBinding(model, path, name, node) {
103 * // The magic happens here!
104 * }
105 * }
106 * ...
107 * templateBind(query('template'))
108 * ..bindingDelegate = new MySyntax()
109 * ..model = new MyModel();
110 *
111 * See <https://github.com/polymer-project/mdv/blob/master/docs/syntax.md> for
112 * more information about Custom Syntax.
113 */
114 abstract class BindingDelegate {
115 /**
116 * This syntax method allows for a custom interpretation of the contents of
117 * mustaches (`{{` ... `}}`).
118 *
119 * When a template is inserting an instance, it will invoke this method for
120 * each mustache which is encountered. The function is invoked with four
121 * arguments:
122 *
123 * - [model]: The data context for which this instance is being created.
124 * - [path]: The text contents (trimmed of outer whitespace) of the mustache.
125 * - [name]: The context in which the mustache occurs. Within element
126 * attributes, this will be the name of the attribute. Within text,
127 * this will be 'text'.
128 * - [node]: A reference to the node to which this binding will be created.
129 *
130 * If the method wishes to handle binding, it is required to return an object
131 * which has at least a `value` property that can be observed. If it does,
132 * then MDV will call [NodeBindExtension.bind] on the node:
133 *
134 * nodeBind(node).bind(name, retval, 'value');
135 *
136 * If the 'getBinding' does not wish to override the binding, it should return
137 * null.
138 */
139 // TODO(jmesserly): I had to remove type annotations from "name" and "node"
140 // Normally they are String and Node respectively. But sometimes it will pass
141 // (int name, CompoundBinding node). That seems very confusing; we may want
142 // to change this API.
143 getBinding(model, String path, name, node) => null;
144
145 /**
146 * This syntax method allows a syntax to provide an alterate model than the
147 * one the template would otherwise use when producing an instance.
148 *
149 * When a template is about to create an instance, it will invoke this method
150 * The function is invoked with two arguments:
151 *
152 * - [template]: The template element which is about to create and insert an
153 * instance.
154 * - [model]: The data context for which this instance is being created.
155 *
156 * The template element will always use the return value of `getInstanceModel`
157 * as the model for the new instance. If the syntax does not wish to override
158 * the value, it should simply return the `model` value it was passed.
159 */
160 getInstanceModel(Element template, model) => model;
161 }
162
163 /**
164 * A data binding on a [Node].
165 * See [NodeBindExtension.bindings] and [NodeBindExtension.bind].
166 */
167 abstract class NodeBinding {
168 Node _node;
169 var _model;
170 PathObserver _observer;
171 StreamSubscription _pathSub;
172
173 /** The property of [node] which will be data bound. */
174 final String property;
175
176 /** The property of [node] which will be data bound. */
177 final String path;
178
179 /** The node that has [property] which will be data bound. */
180 Node get node => _node;
181
182 /**
183 * The bound data model.
184 */
185 get model => _model;
186
187 /** True if this binding has been [closed]. */
188 bool get closed => _node == null;
189
190 /** The value at the [path] on [model]. */
191 get value => _observer.value;
192
193 set value(newValue) {
194 _observer.value = newValue;
195 }
196
197 NodeBinding(this._node, this.property, this._model, [String path])
198 : path = path != null ? path : '' {
199 _observePath();
200 }
201
202 _observePath() {
203 // Fast path if we're observing PathObserver.value
204 if (model is PathObserver && path == 'value') {
205 _observer = model;
206 } else {
207 // Create the path observer
208 _observer = new PathObserver(model, path);
209 }
210 _pathSub = _observer.bindSync(valueChanged);
211 }
212
213 /** Called when [value] changes to update the [node]. */
214 // TODO(jmesserly): the impl in template_binding uses reflection to set the
215 // property, but that isn't used except for specific known fields like
216 // "textContent", so I'm overridding this in the subclasses instead.
217 void valueChanged(newValue);
218
219 /** Called to sanitize the value before it is assigned into the property. */
220 sanitizeBoundValue(value) => value == null ? '' : '$value';
221
222 /**
223 * Called by [NodeBindExtension.unbind] to close this binding and unobserve
224 * the [path].
225 *
226 * This can be overridden in subclasses, but they must call `super.close()`
227 * to free associated resources. They must also check [closed] and return
228 * immediately if already closed.
229 */
230 void close() {
231 if (closed) return;
232
233 if (_pathSub != null) _pathSub.cancel();
234 _pathSub = null;
235 _observer = null;
236 _node = null;
237 _model = null;
238 }
239 }
OLDNEW
« no previous file with comments | « pkg/template_binding/lib/src/list_diff.dart ('k') | pkg/template_binding/lib/src/node_binding.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698