| 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 [Element]s that behave as templates. */ |
| 8 class TemplateBindExtension extends _ElementExtension { |
| 9 var _model; |
| 10 BindingDelegate _bindingDelegate; |
| 11 _TemplateIterator _templateIterator; |
| 12 bool _scheduled = false; |
| 13 |
| 14 Element _templateInstanceRef; |
| 15 |
| 16 // Note: only used if `this is! TemplateElement` |
| 17 DocumentFragment _content; |
| 18 bool _templateIsDecorated; |
| 19 |
| 20 TemplateBindExtension._(Element node) : super(node); |
| 21 |
| 22 Element get _node => super._node; |
| 23 |
| 24 NodeBinding createBinding(String name, model, String path) { |
| 25 switch (name) { |
| 26 case 'bind': |
| 27 case 'repeat': |
| 28 case 'if': |
| 29 if (_templateIterator == null) { |
| 30 _templateIterator = new _TemplateIterator(_node); |
| 31 } |
| 32 // TODO(jmesserly): why do we do this here and nowhere else? |
| 33 if (path == null) path = ''; |
| 34 return new _TemplateBinding(this, name, model, path); |
| 35 default: |
| 36 return super.createBinding(name, model, path); |
| 37 } |
| 38 } |
| 39 |
| 40 /** |
| 41 * Creates an instance of the template, using the provided model and optional |
| 42 * binding delegate. |
| 43 */ |
| 44 DocumentFragment createInstance([model, BindingDelegate delegate]) { |
| 45 var instance = _createDeepCloneAndDecorateTemplates( |
| 46 templateBind(ref).content, delegate); |
| 47 |
| 48 _addBindings(instance, model, delegate); |
| 49 _addTemplateInstanceRecord(instance, model); |
| 50 return instance; |
| 51 } |
| 52 |
| 53 /** The data model which is inherited through the tree. */ |
| 54 get model => _model; |
| 55 |
| 56 void set model(value) { |
| 57 _model = value; |
| 58 _ensureSetModelScheduled(); |
| 59 } |
| 60 |
| 61 /** |
| 62 * The binding delegate which is inherited through the tree. It can be used |
| 63 * to configure custom syntax for `{{bindings}}` inside this template. |
| 64 */ |
| 65 BindingDelegate get bindingDelegate => _bindingDelegate; |
| 66 |
| 67 void set bindingDelegate(BindingDelegate value) { |
| 68 _bindingDelegate = value; |
| 69 _ensureSetModelScheduled(); |
| 70 } |
| 71 |
| 72 _ensureSetModelScheduled() { |
| 73 if (_scheduled) return; |
| 74 _decorate(); |
| 75 _scheduled = true; |
| 76 scheduleMicrotask(_setModel); |
| 77 } |
| 78 |
| 79 void _setModel() { |
| 80 _scheduled = false; |
| 81 _addBindings(_node, _model, _bindingDelegate); |
| 82 } |
| 83 |
| 84 /** Gets the template this node refers to. */ |
| 85 Element get ref { |
| 86 _decorate(); |
| 87 |
| 88 Element result = null; |
| 89 var refId = _node.attributes['ref']; |
| 90 if (refId != null) { |
| 91 var treeScope = _node; |
| 92 while (treeScope.parentNode != null) { |
| 93 treeScope = treeScope.parentNode; |
| 94 } |
| 95 |
| 96 // Note: JS code tests that getElementById is present. We can't do that |
| 97 // easily, so instead check for the types known to implement it. |
| 98 if (treeScope is Document || |
| 99 treeScope is ShadowRoot || |
| 100 treeScope is SvgSvgElement) { |
| 101 |
| 102 result = treeScope.getElementById(refId); |
| 103 } |
| 104 } |
| 105 |
| 106 if (result == null) { |
| 107 result = _templateInstanceRef; |
| 108 if (result == null) return _node; |
| 109 } |
| 110 |
| 111 var nextRef = templateBind(result).ref; |
| 112 return nextRef != null ? nextRef : result; |
| 113 } |
| 114 |
| 115 /** |
| 116 * Gets the content of this template. |
| 117 */ |
| 118 DocumentFragment get content { |
| 119 _decorate(); |
| 120 return _content != null ? _content : (_node as TemplateElement).content; |
| 121 } |
| 122 |
| 123 /** |
| 124 * Ensures proper API and content model for template elements. |
| 125 * |
| 126 * [instanceRef] can be used to set the [Element.ref] property of [template], |
| 127 * and use the ref's content will be used as source when createInstance() is |
| 128 * invoked. |
| 129 * |
| 130 * Returns true if this template was just decorated, or false if it was |
| 131 * already decorated. |
| 132 */ |
| 133 static bool decorate(Element template, [Element instanceRef]) => |
| 134 templateBindFallback(template)._decorate(instanceRef); |
| 135 |
| 136 bool _decorate([Element instanceRef]) { |
| 137 // == true check because it starts as a null field. |
| 138 if (_templateIsDecorated == true) return false; |
| 139 |
| 140 _injectStylesheet(); |
| 141 |
| 142 var templateElementExt = this; |
| 143 _templateIsDecorated = true; |
| 144 var isNative = _node is TemplateElement; |
| 145 var bootstrapContents = isNative; |
| 146 var liftContents = !isNative; |
| 147 var liftRoot = false; |
| 148 |
| 149 if (!isNative && _isAttributeTemplate(_node)) { |
| 150 if (instanceRef != null) { |
| 151 // TODO(jmesserly): this is just an assert in MDV. |
| 152 throw new ArgumentError('instanceRef should not be supplied for ' |
| 153 'attribute templates.'); |
| 154 } |
| 155 templateElementExt = templateBind( |
| 156 _extractTemplateFromAttributeTemplate(_node)); |
| 157 templateElementExt._templateIsDecorated = true; |
| 158 isNative = templateElementExt._node is TemplateElement; |
| 159 liftRoot = true; |
| 160 } |
| 161 |
| 162 if (!isNative) { |
| 163 var doc = _getTemplateContentsOwner( |
| 164 templateElementExt._node.ownerDocument); |
| 165 templateElementExt._content = doc.createDocumentFragment(); |
| 166 } |
| 167 |
| 168 if (instanceRef != null) { |
| 169 // template is contained within an instance, its direct content must be |
| 170 // empty |
| 171 templateElementExt._templateInstanceRef = instanceRef; |
| 172 } else if (liftContents) { |
| 173 _liftNonNativeChildrenIntoContent(templateElementExt, _node, liftRoot); |
| 174 } else if (bootstrapContents) { |
| 175 bootstrap(templateElementExt.content); |
| 176 } |
| 177 |
| 178 return true; |
| 179 } |
| 180 |
| 181 static final _contentsOwner = new Expando(); |
| 182 |
| 183 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#
dfn-template-contents-owner |
| 184 static Document _getTemplateContentsOwner(HtmlDocument doc) { |
| 185 if (doc.window == null) { |
| 186 return doc; |
| 187 } |
| 188 var d = _contentsOwner[doc]; |
| 189 if (d == null) { |
| 190 // TODO(arv): This should either be a Document or HTMLDocument depending |
| 191 // on doc. |
| 192 d = doc.implementation.createHtmlDocument(''); |
| 193 while (d.lastChild != null) { |
| 194 d.lastChild.remove(); |
| 195 } |
| 196 _contentsOwner[doc] = d; |
| 197 } |
| 198 return d; |
| 199 } |
| 200 |
| 201 // For non-template browsers, the parser will disallow <template> in certain |
| 202 // locations, so we allow "attribute templates" which combine the template |
| 203 // element with the top-level container node of the content, e.g. |
| 204 // |
| 205 // <tr template repeat="{{ foo }}"" class="bar"><td>Bar</td></tr> |
| 206 // |
| 207 // becomes |
| 208 // |
| 209 // <template repeat="{{ foo }}"> |
| 210 // + #document-fragment |
| 211 // + <tr class="bar"> |
| 212 // + <td>Bar</td> |
| 213 // |
| 214 static Element _extractTemplateFromAttributeTemplate(Element el) { |
| 215 var template = el.ownerDocument.createElement('template'); |
| 216 el.parentNode.insertBefore(template, el); |
| 217 |
| 218 for (var name in el.attributes.keys.toList()) { |
| 219 switch (name) { |
| 220 case 'template': |
| 221 el.attributes.remove(name); |
| 222 break; |
| 223 case 'repeat': |
| 224 case 'bind': |
| 225 case 'ref': |
| 226 template.attributes[name] = el.attributes.remove(name); |
| 227 break; |
| 228 } |
| 229 } |
| 230 |
| 231 return template; |
| 232 } |
| 233 |
| 234 static void _liftNonNativeChildrenIntoContent(TemplateBindExtension template, |
| 235 Element el, bool useRoot) { |
| 236 |
| 237 var content = template.content; |
| 238 if (useRoot) { |
| 239 content.append(el); |
| 240 return; |
| 241 } |
| 242 |
| 243 var child; |
| 244 while ((child = el.firstChild) != null) { |
| 245 content.append(child); |
| 246 } |
| 247 } |
| 248 |
| 249 /** |
| 250 * This used to decorate recursively all templates from a given node. |
| 251 * |
| 252 * By default [decorate] will be called on templates lazily when certain |
| 253 * properties such as [model] are accessed, but it can be run eagerly to |
| 254 * decorate an entire tree recursively. |
| 255 */ |
| 256 // TODO(rafaelw): Review whether this is the right public API. |
| 257 static void bootstrap(Node content) { |
| 258 void _bootstrap(template) { |
| 259 if (!TemplateBindExtension.decorate(template)) { |
| 260 bootstrap(templateBind(template).content); |
| 261 } |
| 262 } |
| 263 |
| 264 // Need to do this first as the contents may get lifted if |node| is |
| 265 // template. |
| 266 // TODO(jmesserly): content is DocumentFragment or Element |
| 267 var descendents = |
| 268 (content as dynamic).querySelectorAll(_allTemplatesSelectors); |
| 269 if (isSemanticTemplate(content)) { |
| 270 _bootstrap(content); |
| 271 } |
| 272 |
| 273 descendents.forEach(_bootstrap); |
| 274 } |
| 275 |
| 276 static final String _allTemplatesSelectors = |
| 277 'template, ' + |
| 278 _SEMANTIC_TEMPLATE_TAGS.keys.map((k) => "$k[template]").join(", "); |
| 279 |
| 280 static bool _initStyles; |
| 281 |
| 282 static void _injectStylesheet() { |
| 283 if (_initStyles == true) return; |
| 284 _initStyles = true; |
| 285 |
| 286 var style = new StyleElement(); |
| 287 style.text = r''' |
| 288 template, |
| 289 thead[template], |
| 290 tbody[template], |
| 291 tfoot[template], |
| 292 th[template], |
| 293 tr[template], |
| 294 td[template], |
| 295 caption[template], |
| 296 colgroup[template], |
| 297 col[template], |
| 298 option[template] { |
| 299 display: none; |
| 300 }'''; |
| 301 document.head.append(style); |
| 302 } |
| 303 } |
| 304 |
| 305 class _TemplateBinding extends NodeBinding { |
| 306 TemplateBindExtension _ext; |
| 307 |
| 308 // TODO(jmesserly): MDV uses TemplateIterator as the node, see: |
| 309 // https://github.com/Polymer/mdv/issues/127 |
| 310 _TemplateBinding(ext, name, model, path) |
| 311 : _ext = ext, super(ext._node, name, model, path) { |
| 312 _ext._templateIterator.inputs.bind(property, model, path); |
| 313 } |
| 314 |
| 315 // These are no-ops because we don't use the underlying PathObserver. |
| 316 void _observePath() {} |
| 317 void boundValueChanged(newValue) {} |
| 318 |
| 319 void close() { |
| 320 if (closed) return; |
| 321 var templateIterator = _ext._templateIterator; |
| 322 if (templateIterator != null) templateIterator.inputs.unbind(property); |
| 323 super.close(); |
| 324 } |
| 325 } |
| OLD | NEW |