OLD | NEW |
| (Empty) |
1 // Copyright (c) 2014, 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 /** | |
6 * This library provides access to the Polymer project's | |
7 * [Data Binding](http://www.polymer-project.org/docs/polymer/databinding.html) | |
8 * Find more information at the | |
9 * [Polymer.dart homepage](https://www.dartlang.org/polymer-dart/). | |
10 * | |
11 * Extends the capabilities of the HTML Template Element by enabling it to | |
12 * create, manage, and remove instances of content bound to data defined in | |
13 * Dart. | |
14 * | |
15 * Node.bind() is a new method added to all DOM nodes which instructs them to | |
16 * bind the named property to the data provided. These allows applications to | |
17 * create a data model in Dart or JavaScript that DOM reacts to. | |
18 */ | |
19 library template_binding; | |
20 | |
21 import 'dart:async'; | |
22 import 'dart:collection'; | |
23 import 'dart:html'; | |
24 import 'dart:js' as js show context; | |
25 import 'dart:js' show JsObject; | |
26 import 'dart:svg' show SvgSvgElement; | |
27 import 'package:observe/observe.dart'; | |
28 | |
29 import 'src/mustache_tokens.dart'; | |
30 | |
31 part 'src/binding_delegate.dart'; | |
32 part 'src/instance_binding_map.dart'; | |
33 part 'src/node.dart'; | |
34 part 'src/template.dart'; | |
35 part 'src/template_iterator.dart'; | |
36 | |
37 // TODO(jmesserly): ideally we would split TemplateBinding and Node.bind into | |
38 // two packages, but this is not easy when we are faking extension methods. | |
39 // Since TemplateElement needs to override Node.bind, it seems like the | |
40 // Node.bind layer must have some innate knowledge of TemplateBinding. | |
41 // NOTE: I've heard NodeBind might become an internal API, which is all the more | |
42 // reason to have it in this package. | |
43 | |
44 /** | |
45 * Provides access to the data binding APIs for the [node]. For example: | |
46 * | |
47 * templateBind(node).model = new MyModel(); | |
48 * | |
49 * This is equivalent to [nodeBind], but provides access to | |
50 * [TemplateBindExtension] APIs. [node] should be a [TemplateElement], or | |
51 * equivalent semantic template such as: | |
52 * | |
53 * <table template repeat="{{row in rows}}"> | |
54 * <tr template repeat="{{item in row}}"> | |
55 * <td>{{item}}</td> | |
56 * </tr> | |
57 * </table> | |
58 */ | |
59 TemplateBindExtension templateBind(Element node) => nodeBind(node); | |
60 | |
61 /** | |
62 * Like [templateBind], but intended to be used only within a custom element | |
63 * that implements [TemplateBindExtension]. This method can be used to simulate | |
64 * a super call. For example: | |
65 * | |
66 * class CoolTemplate extends TemplateElement | |
67 * implements TemplateBindExtension { | |
68 * | |
69 * createInstance(model, delegate) { | |
70 * // do something cool... | |
71 * // otherwise, fall back to superclass | |
72 * return templateBindFallback(this).createInstance(model, delegate); | |
73 * } | |
74 * ... | |
75 * } | |
76 */ | |
77 TemplateBindExtension templateBindFallback(Element node) => | |
78 nodeBindFallback(node); | |
79 | |
80 /** | |
81 * Provides access to the data binding APIs for the [node]. For example: | |
82 * | |
83 * nodeBind(node).bind('checked', model, 'path.to.some.value'); | |
84 */ | |
85 NodeBindExtension nodeBind(Node node) { | |
86 return node is NodeBindExtension ? node : nodeBindFallback(node); | |
87 } | |
88 | |
89 /** | |
90 * Like [nodeBind], but intended to be used only within a custom element that | |
91 * implements [NodeBindExtension]. This method can be used to simulate a super | |
92 * call. For example: | |
93 * | |
94 * class FancyButton extends ButtonElement implements NodeBindExtension { | |
95 * bind(name, model, path) { | |
96 * if (name == 'fancy-prop') ... // do fancy binding | |
97 * // otherwise, fall back to superclass | |
98 * return nodeBindFallback(this).bind(name, model, path); | |
99 * } | |
100 * ... | |
101 * } | |
102 */ | |
103 NodeBindExtension nodeBindFallback(Node node) { | |
104 var extension = _expando[node]; | |
105 if (extension != null) return extension; | |
106 | |
107 if (isSemanticTemplate(node)) { | |
108 extension = new TemplateBindExtension._(node); | |
109 } else { | |
110 extension = new NodeBindExtension._(node); | |
111 } | |
112 _expando[node] = extension; | |
113 return extension; | |
114 } | |
115 | |
116 | |
117 bool _isAttributeTemplate(Element n) => n.attributes.containsKey('template') && | |
118 _SEMANTIC_TEMPLATE_TAGS.containsKey(n.localName); | |
119 | |
120 bool _isSvgTemplate(Element el) => el.tagName == 'template' && | |
121 el.namespaceUri == 'http://www.w3.org/2000/svg'; | |
122 | |
123 bool _isHtmlTemplate(Element el) => el.tagName == 'TEMPLATE' && | |
124 el.namespaceUri == 'http://www.w3.org/1999/xhtml'; | |
125 | |
126 /** | |
127 * Returns true if this node is semantically a template. | |
128 * | |
129 * A node is a template if [tagName] is TEMPLATE, or the node has the | |
130 * 'template' attribute and this tag supports attribute form for backwards | |
131 * compatibility with existing HTML parsers. The nodes that can use attribute | |
132 * form are table elements (THEAD, TBODY, TFOOT, TH, TR, TD, CAPTION, COLGROUP | |
133 * and COL), OPTION, and OPTGROUP. | |
134 */ | |
135 bool isSemanticTemplate(Node n) => n is Element && | |
136 (_isHtmlTemplate(n) || _isAttributeTemplate(n) || _isSvgTemplate(n)); | |
137 | |
138 /** Returns true if this is the staging document for a template. */ | |
139 bool isTemplateStagingDocument(Document d) => _isStagingDocument[d] == true; | |
140 | |
141 | |
142 /** | |
143 * True to enable [NodeBindingExtension.bindings]. This can be used by tools | |
144 * such as UI builders to easily inspect live bindings. Defaults to false for | |
145 * performance reasons. | |
146 */ | |
147 bool get enableBindingsReflection => | |
148 js.context['Platform']['enableBindingsReflection'] == true; | |
149 | |
150 set enableBindingsReflection(bool value) { | |
151 js.context['Platform']['enableBindingsReflection'] = value; | |
152 } | |
153 | |
154 // TODO(jmesserly): const set would be better | |
155 const _SEMANTIC_TEMPLATE_TAGS = const { | |
156 'caption': null, | |
157 'col': null, | |
158 'colgroup': null, | |
159 'option': null, | |
160 'optgroup': null, | |
161 'tbody': null, | |
162 'td': null, | |
163 'tfoot': null, | |
164 'th': null, | |
165 'thead': null, | |
166 'tr': null, | |
167 }; | |
168 | |
169 | |
170 // TODO(jmesserly): investigate if expandos give us enough performance. | |
171 | |
172 // The expando for storing our MDV extensions. | |
173 // | |
174 // In general, we need state associated with the nodes. Rather than having a | |
175 // bunch of individual expandos, we keep one per node. | |
176 // | |
177 // Aside from the potentially helping performance, it also keeps things simpler | |
178 // if we decide to integrate MDV into the DOM later, and means less code needs | |
179 // to worry about expandos. | |
180 final Expando _expando = new Expando('template_binding'); | |
OLD | NEW |