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 |