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 final JsObject _js; |
| 11 |
| 12 NodeBindExtension._(node) |
| 13 : _node = node, |
| 14 _js = new JsObject.fromBrowserObject(node); |
| 15 |
| 16 /** |
| 17 * Gets the data bindings that are associated with this node, if any. |
| 18 * |
| 19 * This starts out null, and if [enableBindingsReflection] is enabled, calls |
| 20 * to [bind] will initialize this field and the binding. |
| 21 */ |
| 22 // Dart note: in JS this has a trailing underscore, meaning "private". |
| 23 // But in dart if we made it _bindings, it wouldn't be accessible at all. |
| 24 // It is unfortunately needed to implement Node.bind correctly. |
| 25 Map<String, Bindable> get bindings { |
| 26 var b = _js['bindings_']; |
| 27 if (b == null) return null; |
| 28 // TODO(jmesserly): should cache this for identity. |
| 29 return new _NodeBindingsMap(_node, b); |
| 30 } |
| 31 |
| 32 set bindings(Map<String, Bindable> value) { |
| 33 if (value == null) { |
| 34 _js.deleteProperty('bindings_'); |
| 35 return; |
| 36 } |
| 37 var b = bindings; |
| 38 if (b == null) { |
| 39 _js['bindings_'] = new JsObject.jsify({}); |
| 40 b = bindings; |
| 41 } |
| 42 b.addAll(value); |
| 43 } |
| 44 |
| 45 /** |
| 46 * Binds the attribute [name] to [value]. [value] can be a simple value when |
| 47 * [oneTime] is true, or a [Bindable] like [PathObserver]. |
| 48 * Returns the [Bindable] instance. |
| 49 */ |
| 50 Bindable bind(String name, value, {bool oneTime: false}) { |
| 51 name = _dartToJsName(_node, name); |
| 52 |
| 53 if (!oneTime && value is Bindable) { |
| 54 value = bindableToJsObject(value); |
| 55 } |
| 56 return jsObjectToBindable(_js.callMethod('bind', [name, value, oneTime])); |
| 57 } |
| 58 |
| 59 /** |
| 60 * Called when all [bind] calls are finished for a given template expansion. |
| 61 */ |
| 62 bindFinished() => _js.callMethod('bindFinished'); |
| 63 |
| 64 // Note: confusingly this is on NodeBindExtension because it can be on any |
| 65 // Node. It's really an API added by TemplateBinding. Therefore it stays |
| 66 // implemented in Dart because TemplateBinding still is. |
| 67 TemplateInstance _templateInstance; |
| 68 |
| 69 /** Gets the template instance that instantiated this node, if any. */ |
| 70 TemplateInstance get templateInstance => |
| 71 _templateInstance != null ? _templateInstance : |
| 72 (_node.parent != null ? nodeBind(_node.parent).templateInstance : null); |
| 73 } |
| 74 |
| 75 class _NodeBindingsMap extends MapBase<String, Bindable> { |
| 76 final Node _node; |
| 77 final JsObject _bindings; |
| 78 |
| 79 _NodeBindingsMap(this._node, this._bindings); |
| 80 |
| 81 // TODO(jmesserly): this should be lazy |
| 82 Iterable<String> get keys => |
| 83 js.context['Object'].callMethod('keys', [_bindings]).map( |
| 84 (name) => _jsToDartName(_node, name)); |
| 85 |
| 86 Bindable operator[](String name) => |
| 87 jsObjectToBindable(_bindings[_dartToJsName(_node, name)]); |
| 88 |
| 89 operator[]=(String name, Bindable value) { |
| 90 _bindings[_dartToJsName(_node, name)] = bindableToJsObject(value); |
| 91 } |
| 92 |
| 93 @override Bindable remove(String name) { |
| 94 name = _dartToJsName(_node, name); |
| 95 var old = this[name]; |
| 96 _bindings.deleteProperty(name); |
| 97 return old; |
| 98 } |
| 99 |
| 100 @override void clear() { |
| 101 // Notes: this implementation only works because our "keys" returns a copy. |
| 102 // We could also make it O(1) by assigning a new JS object to the bindings_ |
| 103 // property, if performance is an issue. |
| 104 keys.forEach(remove); |
| 105 } |
| 106 } |
| 107 |
| 108 // TODO(jmesserly): perhaps we should switch Dart's Node.bind API back to |
| 109 // 'textContent' for consistency? This only affects the raw Node.bind API when |
| 110 // called on Text nodes, which is unlikely to be used except by TemplateBinding. |
| 111 // Seems like a lot of magic to support it. I don't think Node.bind promises any |
| 112 // strong relationship between properties and [name], so textContent seems fine. |
| 113 String _dartToJsName(Node node, String name) { |
| 114 if (node is Text && name == 'text') name = 'textContent'; |
| 115 return name; |
| 116 } |
| 117 |
| 118 |
| 119 String _jsToDartName(Node node, String name) { |
| 120 if (node is Text && name == 'textContent') name = 'text'; |
| 121 return name; |
| 122 } |
| 123 |
| 124 |
| 125 /// Given a bindable [JsObject], wraps it in a Dart [Bindable]. |
| 126 /// See [bindableToJsObject] to go in the other direction. |
| 127 Bindable jsObjectToBindable(JsObject obj) { |
| 128 if (obj == null) return null; |
| 129 var b = obj['__dartBindable']; |
| 130 // For performance, unwrap the Dart bindable if we find one. |
| 131 // Note: in the unlikely event some code messes with our __dartBindable |
| 132 // property we can simply fallback to a _JsBindable wrapper. |
| 133 return b is Bindable ? b : new _JsBindable(obj); |
| 134 } |
| 135 |
| 136 class _JsBindable extends Bindable { |
| 137 final JsObject _js; |
| 138 _JsBindable(JsObject obj) : _js = obj; |
| 139 |
| 140 open(callback) => _js.callMethod('open', |
| 141 [Zone.current.bindUnaryCallback(callback)]); |
| 142 |
| 143 close() => _js.callMethod('close'); |
| 144 |
| 145 get value => _js.callMethod('discardChanges'); |
| 146 |
| 147 set value(newValue) { |
| 148 _js.callMethod('setValue', [newValue]); |
| 149 } |
| 150 |
| 151 deliver() => _js.callMethod('deliver'); |
| 152 } |
| 153 |
| 154 /// Given a [bindable], create a JS object proxy for it. |
| 155 /// This is the inverse of [jsObjectToBindable]. |
| 156 JsObject bindableToJsObject(Bindable bindable) { |
| 157 if (bindable is _JsBindable) return bindable._js; |
| 158 |
| 159 var zone = Zone.current; |
| 160 inZone(f) => zone.bindCallback(f, runGuarded: false); |
| 161 inZoneUnary(f) => zone.bindUnaryCallback(f, runGuarded: false); |
| 162 |
| 163 return new JsObject.jsify({ |
| 164 'open': inZoneUnary( |
| 165 (callback) => bindable.open((x) => callback.apply([x]))), |
| 166 'close': inZone(() => bindable.close()), |
| 167 'discardChanges': inZone(() => bindable.value), |
| 168 'setValue': inZoneUnary((x) => bindable.value = x), |
| 169 // NOTE: this is not used by Node.bind, but it's used by Polymer: |
| 170 // https://github.com/Polymer/polymer-dev/blob/ba2b68fe5a5721f60b5994135f327
0e63588809a/src/declaration/properties.js#L130 |
| 171 // Technically this works because 'deliver' is on PathObserver and |
| 172 // CompoundObserver. But ideally Polymer-JS would not assume that. |
| 173 'deliver': inZone(() => bindable.deliver()), |
| 174 // Save this so we can return it from [jsObjectToBindable] |
| 175 '__dartBindable': bindable |
| 176 }); |
| 177 } |
| 178 |
| 179 /** Information about the instantiated template. */ |
| 180 class TemplateInstance { |
| 181 // TODO(rafaelw): firstNode & lastNode should be read-synchronous |
| 182 // in cases where script has modified the template instance boundary. |
| 183 |
| 184 /** The first node of this template instantiation. */ |
| 185 Node get firstNode => _firstNode; |
| 186 |
| 187 /** |
| 188 * The last node of this template instantiation. |
| 189 * This could be identical to [firstNode] if the template only expanded to a |
| 190 * single node. |
| 191 */ |
| 192 Node get lastNode => _lastNode; |
| 193 |
| 194 /** The model used to instantiate the template. */ |
| 195 final model; |
| 196 |
| 197 Node _firstNode, _lastNode; |
| 198 |
| 199 TemplateInstance(this.model); |
| 200 } |
OLD | NEW |