| 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 mdv; | |
| 6 | |
| 7 /** Extensions to the [Node] API. */ | |
| 8 class _NodeExtension { | |
| 9 final Node node; | |
| 10 Map<String, NodeBinding> _bindings; | |
| 11 | |
| 12 _NodeExtension(this.node); | |
| 13 | |
| 14 NodeBinding createBinding(String name, model, String path) => null; | |
| 15 | |
| 16 /** | |
| 17 * Binds the attribute [name] to the [path] of the [model]. | |
| 18 * Path is a String of accessors such as `foo.bar.baz`. | |
| 19 */ | |
| 20 NodeBinding bind(String name, model, String path) { | |
| 21 var binding = bindings[name]; | |
| 22 if (binding != null) binding.close(); | |
| 23 | |
| 24 // Note: dispatch through the xtag so a custom element can override it. | |
| 25 binding = (node is Element ? (node as Element).xtag : node) | |
| 26 .createBinding(name, model, path); | |
| 27 | |
| 28 bindings[name] = binding; | |
| 29 if (binding == null) { | |
| 30 window.console.error('Unhandled binding to Node: ' | |
| 31 '$this $name $model $path'); | |
| 32 } | |
| 33 return binding; | |
| 34 } | |
| 35 | |
| 36 /** Unbinds the attribute [name]. */ | |
| 37 void unbind(String name) { | |
| 38 if (_bindings == null) return; | |
| 39 var binding = bindings.remove(name); | |
| 40 if (binding != null) binding.close(); | |
| 41 } | |
| 42 | |
| 43 /** Unbinds all bound attributes. */ | |
| 44 void unbindAll() { | |
| 45 if (_bindings == null) return; | |
| 46 for (var binding in bindings.values) { | |
| 47 if (binding != null) binding.close(); | |
| 48 } | |
| 49 _bindings = null; | |
| 50 } | |
| 51 | |
| 52 // TODO(jmesserly): we should return a read-only wrapper here. | |
| 53 Map<String, NodeBinding> get bindings { | |
| 54 if (_bindings == null) _bindings = new LinkedHashMap<String, NodeBinding>(); | |
| 55 return _bindings; | |
| 56 } | |
| 57 | |
| 58 TemplateInstance _templateInstance; | |
| 59 | |
| 60 /** Gets the template instance that instantiated this node, if any. */ | |
| 61 TemplateInstance get templateInstance => | |
| 62 _templateInstance != null ? _templateInstance : | |
| 63 (node.parent != null ? node.parent.templateInstance : null); | |
| 64 } | |
| 65 | |
| 66 /** A data binding on a [Node]. See [Node.bindings] and [Node.bind]. */ | |
| 67 abstract class NodeBinding { | |
| 68 Node _node; | |
| 69 var _model; | |
| 70 PathObserver _observer; | |
| 71 StreamSubscription _pathSub; | |
| 72 | |
| 73 /** The property of [node] which will be data bound. */ | |
| 74 final String property; | |
| 75 | |
| 76 /** The property of [node] which will be data bound. */ | |
| 77 final String path; | |
| 78 | |
| 79 /** The node that has [property] which will be data bound. */ | |
| 80 Node get node => _node; | |
| 81 | |
| 82 /** | |
| 83 * The bound data model. | |
| 84 */ | |
| 85 get model => _model; | |
| 86 | |
| 87 /** True if this binding has been [closed]. */ | |
| 88 bool get closed => _observer == null; | |
| 89 | |
| 90 /** The value at the [path] on [model]. */ | |
| 91 get value => _observer.value; | |
| 92 | |
| 93 set value(newValue) { | |
| 94 _observer.value = newValue; | |
| 95 } | |
| 96 | |
| 97 NodeBinding(this._node, this.property, this._model, this.path) { | |
| 98 // Create the path observer | |
| 99 _observer = new PathObserver(model, path); | |
| 100 _observePath(); | |
| 101 } | |
| 102 | |
| 103 void _observePath() { | |
| 104 _pathSub = _observer.bindSync(boundValueChanged); | |
| 105 } | |
| 106 | |
| 107 /** Called when [value] changes to update the [node]. */ | |
| 108 // TODO(jmesserly): the impl in MDV uses mirrors to set the property, | |
| 109 // but that isn't used except for specific known fields like "textContent", | |
| 110 // so I'm overridding this in the subclasses instead. | |
| 111 void boundValueChanged(newValue); | |
| 112 | |
| 113 /** Called to sanitize the value before it is assigned into the property. */ | |
| 114 sanitizeBoundValue(value) => value == null ? '' : '$value'; | |
| 115 | |
| 116 /** | |
| 117 * Called by [Node.unbind] to close this binding and unobserve the [path]. | |
| 118 * | |
| 119 * This can be overridden in subclasses, but they must call `super.close()` | |
| 120 * to free associated resources. They must also check [closed] and return | |
| 121 * immediately if already closed. | |
| 122 */ | |
| 123 void close() { | |
| 124 if (closed) return; | |
| 125 | |
| 126 if (_pathSub != null) _pathSub.cancel(); | |
| 127 _pathSub = null; | |
| 128 _observer = null; | |
| 129 _node = null; | |
| 130 _model = null; | |
| 131 } | |
| 132 } | |
| OLD | NEW |