| OLD | NEW |
| 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; |
| 11 | 11 |
| 12 NodeBindExtension._(this._node); | 12 NodeBindExtension._(this._node); |
| 13 | 13 |
| 14 /** | 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]. | 15 * Binds the attribute [name] to the [path] of the [model]. |
| 27 * Path is a String of accessors such as `foo.bar.baz`. | 16 * Path is a String of accessors such as `foo.bar.baz`. |
| 28 * Returns the `NodeBinding` instance. | 17 * Returns the `NodeBinding` instance. |
| 29 */ | 18 */ |
| 30 NodeBinding bind(String name, model, String path) { | 19 NodeBinding bind(String name, model, [String path]) { |
| 31 var binding = bindings[name]; | 20 window.console.error('Unhandled binding to Node: ' |
| 32 if (binding != null) binding.close(); | 21 '$this $name $model $path'); |
| 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 } | 22 } |
| 44 | 23 |
| 45 /** Unbinds the attribute [name]. */ | 24 /** Unbinds the attribute [name]. */ |
| 46 void unbind(String name) { | 25 void unbind(String name) { |
| 47 if (_bindings == null) return; | 26 if (_bindings == null) return; |
| 48 var binding = bindings.remove(name); | 27 var binding = bindings.remove(name); |
| 49 if (binding != null) binding.close(); | 28 if (binding != null) binding.close(); |
| 50 } | 29 } |
| 51 | 30 |
| 52 /** Unbinds all bound attributes. */ | 31 /** Unbinds all bound attributes. */ |
| 53 void unbindAll() { | 32 void unbindAll() { |
| 54 if (_bindings == null) return; | 33 if (_bindings == null) return; |
| 55 for (var binding in bindings.values) { | 34 for (var binding in bindings.values) { |
| 56 if (binding != null) binding.close(); | 35 if (binding != null) binding.close(); |
| 57 } | 36 } |
| 58 _bindings = null; | 37 _bindings = null; |
| 59 } | 38 } |
| 60 | 39 |
| 61 // TODO(jmesserly): we should return a read-only wrapper here. | 40 // TODO(jmesserly): we should return a read-only wrapper here. |
| 62 /** Gets the data bindings that are associated with this node. */ | 41 /** Gets the data bindings that are associated with this node. */ |
| 63 Map<String, NodeBinding> get bindings { | 42 Map<String, NodeBinding> get bindings { |
| 64 if (_bindings == null) _bindings = new LinkedHashMap<String, NodeBinding>(); | 43 if (_bindings == null) _bindings = new LinkedHashMap<String, NodeBinding>(); |
| 65 return _bindings; | 44 return _bindings; |
| 66 } | 45 } |
| 67 | 46 |
| 47 /** |
| 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 |
| 50 * as [this.unbind]. Instead it should dispatch through [_self.unbind]. |
| 51 */ |
| 52 NodeBindExtension get _self => _node is NodeBindExtension ? _node : this; |
| 53 |
| 68 TemplateInstance _templateInstance; | 54 TemplateInstance _templateInstance; |
| 69 | 55 |
| 70 /** Gets the template instance that instantiated this node, if any. */ | 56 /** Gets the template instance that instantiated this node, if any. */ |
| 71 TemplateInstance get templateInstance => | 57 TemplateInstance get templateInstance => |
| 72 _templateInstance != null ? _templateInstance : | 58 _templateInstance != null ? _templateInstance : |
| 73 (_node.parent != null ? _node.parent.templateInstance : null); | 59 (_node.parent != null ? _node.parent.templateInstance : null); |
| 74 } | 60 } |
| 75 | 61 |
| 76 | 62 |
| 77 /** Information about the instantiated template. */ | 63 /** Information about the instantiated template. */ |
| (...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 192 | 178 |
| 193 /** The node that has [property] which will be data bound. */ | 179 /** The node that has [property] which will be data bound. */ |
| 194 Node get node => _node; | 180 Node get node => _node; |
| 195 | 181 |
| 196 /** | 182 /** |
| 197 * The bound data model. | 183 * The bound data model. |
| 198 */ | 184 */ |
| 199 get model => _model; | 185 get model => _model; |
| 200 | 186 |
| 201 /** True if this binding has been [closed]. */ | 187 /** True if this binding has been [closed]. */ |
| 202 bool get closed => _observer == null; | 188 bool get closed => _node == null; |
| 203 | 189 |
| 204 /** The value at the [path] on [model]. */ | 190 /** The value at the [path] on [model]. */ |
| 205 get value => _observer.value; | 191 get value => _observer.value; |
| 206 | 192 |
| 207 set value(newValue) { | 193 set value(newValue) { |
| 208 _observer.value = newValue; | 194 _observer.value = newValue; |
| 209 } | 195 } |
| 210 | 196 |
| 211 NodeBinding(this._node, this.property, this._model, this.path) { | 197 NodeBinding(this._node, this.property, this._model, [String path]) |
| 212 // Create the path observer | 198 : path = path != null ? path : '' { |
| 213 _observer = new PathObserver(model, path); | |
| 214 _observePath(); | 199 _observePath(); |
| 215 } | 200 } |
| 216 | 201 |
| 217 void _observePath() { | 202 _observePath() { |
| 218 _pathSub = _observer.bindSync(boundValueChanged); | 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); |
| 219 } | 211 } |
| 220 | 212 |
| 221 /** Called when [value] changes to update the [node]. */ | 213 /** Called when [value] changes to update the [node]. */ |
| 222 // TODO(jmesserly): the impl in MDV uses mirrors to set the property, | 214 // TODO(jmesserly): the impl in template_binding uses reflection to set the |
| 223 // but that isn't used except for specific known fields like "textContent", | 215 // property, but that isn't used except for specific known fields like |
| 224 // so I'm overridding this in the subclasses instead. | 216 // "textContent", so I'm overridding this in the subclasses instead. |
| 225 void boundValueChanged(newValue); | 217 void valueChanged(newValue); |
| 226 | 218 |
| 227 /** Called to sanitize the value before it is assigned into the property. */ | 219 /** Called to sanitize the value before it is assigned into the property. */ |
| 228 sanitizeBoundValue(value) => value == null ? '' : '$value'; | 220 sanitizeBoundValue(value) => value == null ? '' : '$value'; |
| 229 | 221 |
| 230 /** | 222 /** |
| 231 * Called by [NodeBindExtension.unbind] to close this binding and unobserve | 223 * Called by [NodeBindExtension.unbind] to close this binding and unobserve |
| 232 * the [path]. | 224 * the [path]. |
| 233 * | 225 * |
| 234 * This can be overridden in subclasses, but they must call `super.close()` | 226 * This can be overridden in subclasses, but they must call `super.close()` |
| 235 * to free associated resources. They must also check [closed] and return | 227 * to free associated resources. They must also check [closed] and return |
| 236 * immediately if already closed. | 228 * immediately if already closed. |
| 237 */ | 229 */ |
| 238 void close() { | 230 void close() { |
| 239 if (closed) return; | 231 if (closed) return; |
| 240 | 232 |
| 241 if (_pathSub != null) _pathSub.cancel(); | 233 if (_pathSub != null) _pathSub.cancel(); |
| 242 _pathSub = null; | 234 _pathSub = null; |
| 243 _observer = null; | 235 _observer = null; |
| 244 _node = null; | 236 _node = null; |
| 245 _model = null; | 237 _model = null; |
| 246 } | 238 } |
| 247 } | 239 } |
| OLD | NEW |