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 final JsObject _js; | 10 final JsObject _js; |
11 | 11 |
12 NodeBindExtension._(node) | 12 NodeBindExtension._(node) |
13 : _node = node, | 13 : _node = node, |
14 _js = new JsObject.fromBrowserObject(node); | 14 _js = new JsObject.fromBrowserObject(node); |
15 | 15 |
16 /** | 16 /** |
17 * Gets the data bindings that are associated with this node, if any. | 17 * Gets the data bindings that are associated with this node, if any. |
18 * | 18 * |
19 * This starts out null, and if [enableBindingsReflection] is enabled, calls | 19 * This starts out null, and if [enableBindingsReflection] is enabled, calls |
20 * to [bind] will initialize this field and the binding. | 20 * to [bind] will initialize this field and the binding. |
21 */ | 21 */ |
22 // Dart note: in JS this has a trailing underscore, meaning "private". | 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. | 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. | 24 // It is unfortunately needed to implement Node.bind correctly. |
25 Map<String, Bindable> get bindings { | 25 Map<String, Bindable> get bindings { |
26 var b = _js['bindings_']; | 26 var b = _js['bindings_']; |
27 if (b == null) return null; | 27 if (b == null) return null; |
28 // TODO(jmesserly): should cache this for identity. | 28 // TODO(jmesserly): should cache this for identity. |
29 return new _NodeBindingsMap(b); | 29 return new _NodeBindingsMap(_node, b); |
30 } | 30 } |
31 | 31 |
32 set bindings(Map<String, Bindable> value) { | 32 set bindings(Map<String, Bindable> value) { |
33 if (value == null) { | 33 if (value == null) { |
34 _js.deleteProperty('bindings_'); | 34 _js.deleteProperty('bindings_'); |
35 return; | 35 return; |
36 } | 36 } |
37 var b = bindings; | 37 var b = bindings; |
38 if (b == null) { | 38 if (b == null) { |
39 _js['bindings_'] = new JsObject.jsify({}); | 39 _js['bindings_'] = new JsObject.jsify({}); |
40 b = bindings; | 40 b = bindings; |
41 } | 41 } |
42 b.addAll(value); | 42 b.addAll(value); |
43 } | 43 } |
44 | 44 |
45 /** | 45 /** |
46 * Binds the attribute [name] to [value]. [value] can be a simple value when | 46 * Binds the attribute [name] to [value]. [value] can be a simple value when |
47 * [oneTime] is true, or a [Bindable] like [PathObserver]. | 47 * [oneTime] is true, or a [Bindable] like [PathObserver]. |
48 * Returns the [Bindable] instance. | 48 * Returns the [Bindable] instance. |
49 */ | 49 */ |
50 Bindable bind(String name, value, {bool oneTime: false}) { | 50 Bindable bind(String name, value, {bool oneTime: false}) { |
51 name = _dartToJsName(name); | 51 name = _dartToJsName(_node, name); |
52 | 52 |
53 if (!oneTime && value is Bindable) { | 53 if (!oneTime && value is Bindable) { |
54 value = bindableToJsObject(value); | 54 value = bindableToJsObject(value); |
55 } | 55 } |
56 return jsObjectToBindable(_js.callMethod('bind', [name, value, oneTime])); | 56 return jsObjectToBindable(_js.callMethod('bind', [name, value, oneTime])); |
57 } | 57 } |
58 | 58 |
59 /** | 59 /** |
60 * Called when all [bind] calls are finished for a given template expansion. | 60 * Called when all [bind] calls are finished for a given template expansion. |
61 */ | 61 */ |
62 bindFinished() => _js.callMethod('bindFinished'); | 62 bindFinished() => _js.callMethod('bindFinished'); |
63 | 63 |
64 // Note: confusingly this is on NodeBindExtension because it can be on any | 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 | 65 // Node. It's really an API added by TemplateBinding. Therefore it stays |
66 // implemented in Dart because TemplateBinding still is. | 66 // implemented in Dart because TemplateBinding still is. |
67 TemplateInstance _templateInstance; | 67 TemplateInstance _templateInstance; |
68 | 68 |
69 /** Gets the template instance that instantiated this node, if any. */ | 69 /** Gets the template instance that instantiated this node, if any. */ |
70 TemplateInstance get templateInstance => | 70 TemplateInstance get templateInstance => |
71 _templateInstance != null ? _templateInstance : | 71 _templateInstance != null ? _templateInstance : |
72 (_node.parent != null ? nodeBind(_node.parent).templateInstance : null); | 72 (_node.parent != null ? nodeBind(_node.parent).templateInstance : null); |
73 } | 73 } |
74 | 74 |
75 class _NodeBindingsMap extends MapBase<String, Bindable> { | 75 class _NodeBindingsMap extends MapBase<String, Bindable> { |
| 76 final Node _node; |
76 final JsObject _bindings; | 77 final JsObject _bindings; |
77 | 78 |
78 _NodeBindingsMap(this._bindings); | 79 _NodeBindingsMap(this._node, this._bindings); |
79 | 80 |
80 // TODO(jmesserly): this should be lazy | 81 // TODO(jmesserly): this should be lazy |
81 Iterable<String> get keys => | 82 Iterable<String> get keys => |
82 js.context['Object'].callMethod('keys', [_bindings]).map(_jsToDartName); | 83 js.context['Object'].callMethod('keys', [_bindings]).map( |
| 84 (name) => _jsToDartName(_node, name)); |
83 | 85 |
84 Bindable operator[](String name) => | 86 Bindable operator[](String name) => |
85 jsObjectToBindable(_bindings[_dartToJsName(name)]); | 87 jsObjectToBindable(_bindings[_dartToJsName(_node, name)]); |
86 | 88 |
87 operator[]=(String name, Bindable value) { | 89 operator[]=(String name, Bindable value) { |
88 _bindings[_dartToJsName(name)] = bindableToJsObject(value); | 90 _bindings[_dartToJsName(_node, name)] = bindableToJsObject(value); |
89 } | 91 } |
90 | 92 |
91 @override Bindable remove(String name) { | 93 @override Bindable remove(String name) { |
92 name = _dartToJsName(name); | 94 name = _dartToJsName(_node, name); |
93 var old = this[name]; | 95 var old = this[name]; |
94 _bindings.deleteProperty(name); | 96 _bindings.deleteProperty(name); |
95 return old; | 97 return old; |
96 } | 98 } |
97 | 99 |
98 @override void clear() { | 100 @override void clear() { |
99 // Notes: this implementation only works because our "keys" returns a copy. | 101 // Notes: this implementation only works because our "keys" returns a copy. |
100 // We could also make it O(1) by assigning a new JS object to the bindings_ | 102 // We could also make it O(1) by assigning a new JS object to the bindings_ |
101 // property, if performance is an issue. | 103 // property, if performance is an issue. |
102 keys.forEach(remove); | 104 keys.forEach(remove); |
103 } | 105 } |
104 } | 106 } |
105 | 107 |
106 // TODO(jmesserly): perhaps we should switch Dart's Node.bind API back to | 108 // TODO(jmesserly): perhaps we should switch Dart's Node.bind API back to |
107 // 'textContent' for consistency? This only affects the raw Node.bind API when | 109 // 'textContent' for consistency? This only affects the raw Node.bind API when |
108 // called on Text nodes, which is unlikely to be used except by TemplateBinding. | 110 // called on Text nodes, which is unlikely to be used except by TemplateBinding. |
109 // Seems like a lot of magic to support it. I don't think Node.bind promises any | 111 // Seems like a lot of magic to support it. I don't think Node.bind promises any |
110 // strong relationship between properties and [name], so textContent seems fine. | 112 // strong relationship between properties and [name], so textContent seems fine. |
111 String _dartToJsName(String name) { | 113 String _dartToJsName(Node node, String name) { |
112 if (name == 'text') name = 'textContent'; | 114 if (node is Text && name == 'text') name = 'textContent'; |
113 return name; | 115 return name; |
114 } | 116 } |
115 | 117 |
116 | 118 |
117 String _jsToDartName(String name) { | 119 String _jsToDartName(Node node, String name) { |
118 if (name == 'textContent') name = 'text'; | 120 if (node is Text && name == 'textContent') name = 'text'; |
119 return name; | 121 return name; |
120 } | 122 } |
121 | 123 |
122 | 124 |
123 /// Given a bindable [JsObject], wraps it in a Dart [Bindable]. | 125 /// Given a bindable [JsObject], wraps it in a Dart [Bindable]. |
124 /// See [bindableToJsObject] to go in the other direction. | 126 /// See [bindableToJsObject] to go in the other direction. |
125 Bindable jsObjectToBindable(JsObject obj) { | 127 Bindable jsObjectToBindable(JsObject obj) { |
126 if (obj == null) return null; | 128 if (obj == null) return null; |
127 var b = obj['__dartBindable']; | 129 var b = obj['__dartBindable']; |
128 // For performance, unwrap the Dart bindable if we find one. | 130 // For performance, unwrap the Dart bindable if we find one. |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
183 */ | 185 */ |
184 Node get lastNode => _lastNode; | 186 Node get lastNode => _lastNode; |
185 | 187 |
186 /** The model used to instantiate the template. */ | 188 /** The model used to instantiate the template. */ |
187 final model; | 189 final model; |
188 | 190 |
189 Node _firstNode, _lastNode; | 191 Node _firstNode, _lastNode; |
190 | 192 |
191 TemplateInstance(this.model); | 193 TemplateInstance(this.model); |
192 } | 194 } |
OLD | NEW |