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