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 |