Chromium Code Reviews| Index: tools/dom/templates/html/impl/impl_Element.darttemplate |
| diff --git a/tools/dom/templates/html/impl/impl_Element.darttemplate b/tools/dom/templates/html/impl/impl_Element.darttemplate |
| index 3b3ca0b26dfd5e8516cdd8d37fc41303a0d3d918..199968de616d0b54f78c71f6392f8c8a8050b232 100644 |
| --- a/tools/dom/templates/html/impl/impl_Element.darttemplate |
| +++ b/tools/dom/templates/html/impl/impl_Element.darttemplate |
| @@ -622,9 +622,242 @@ $if DART2JS |
| $else |
| $endif |
| + Map<String, _Binding> _attributeBindings; |
|
blois
2013/05/01 17:00:42
I believe that you want the @Creates('Null') on ev
Jennifer Messerly
2013/05/02 02:58:33
Done. Are statics okay? (_instanceCreated)
also:
|
| + |
| + // TODO(jmesserly): I'm concerned about adding these to every element. |
| + // Conceptually all of these belong on TemplateElement. They are here to |
| + // support browsers that don't have <template> yet. |
| + // However even in the polyfill they're restricted to certain tags |
| + // (see [isTemplate]). So we can probably convert it to a (public) mixin, and |
| + // only mix it in to the elements that need it. |
|
blois
2013/05/01 17:00:42
Once we have element subclassing, can we have a Te
Jennifer Messerly
2013/05/02 02:58:33
yeah we could have a mixin:
class TemplateMixin {
|
| +$if DART2JS |
| + @Creates('Null') |
| +$endif |
| + var _model; |
| + |
| + _TemplateIterator _templateIterator; |
| + |
| + Element _templateInstanceRef; |
| + |
| + // Note: only used if `this is! TemplateElement` |
| + DocumentFragment _templateContent; |
| + |
| + bool _templateIsDecorated; |
| + |
| + // TODO(jmesserly): should path be optional, and default to empty path? |
| + // It is used that way in at least one path in JS TemplateElement tests |
| + // (see "BindImperative" test in original JS code). |
| + @Experimental |
| + void bind(String name, model, String path) { |
| + if (_bindTemplate(name, model, path)) return; |
| + |
| + if (_attributeBindings == null) { |
| + _attributeBindings = new Map<String, _Binding>(); |
| + } |
| + |
| + attributes.remove(name); |
| + |
| + _ChangeHandler changed; |
| + if (name.endsWith('?')) { |
| + name = name.substring(0, name.length - 1); |
| + |
| + changed = (value) { |
| + if (_templateBooleanConversion(value)) { |
| + attributes[name] = ''; |
| + } else { |
| + attributes.remove(name); |
| + } |
| + }; |
| + } else { |
| + changed = (value) { |
| + // TODO(jmesserly): escape value if needed to protect against XSS. |
| + attributes[name] = value == null ? '' : '$value'; |
| + }; |
| + } |
| + |
| + unbind(name); |
| + |
| + _attributeBindings[name] = new _Binding(model, path, changed); |
| + } |
| + |
| + @Experimental |
| + void unbind(String name) { |
| + if (_unbindTemplate(name)) return; |
| + if (_attributeBindings != null) { |
| + var binding = _attributeBindings.remove(name); |
| + if (binding != null) binding.dispose(); |
| + } |
| + } |
| + |
| + @Experimental |
| + void unbindAll() { |
| + _unbindAllTemplate(); |
| + |
| + if (_attributeBindings != null) { |
| + for (var binding in _attributeBindings.values) { |
| + binding.dispose(); |
| + } |
| + _attributeBindings = null; |
| + super.unbindAll(); |
| + } |
| + } |
| + |
| + // TODO(jmesserly): unlike the JS polyfill, we can't mixin |
| + // HTMLTemplateElement at runtime into things that are semantically template |
| + // elements. So instead we implement it here with a runtime check. |
| + // If the bind succeeds, we return true, otherwise we return false and let |
| + // the normal Element.bind logic kick in. |
| + bool _bindTemplate(String name, model, String path) { |
| + if (isTemplate) { |
| + switch (name) { |
| + case 'bind': |
| + case 'repeat': |
| + case 'if': |
| + _ensureTemplate(); |
| + if (_templateIterator == null) { |
| + _templateIterator = new _TemplateIterator(this); |
| + } |
| + _templateIterator.inputs.bind(name, model, path); |
| + return true; |
| + } |
| + } |
| + return false; |
| + } |
| + |
| + bool _unbindTemplate(String name) { |
| + if (isTemplate) { |
| + switch (name) { |
| + case 'bind': |
| + case 'repeat': |
| + case 'if': |
| + _ensureTemplate(); |
| + if (_templateIterator != null) { |
| + _templateIterator.inputs.unbind(name); |
| + } |
| + return true; |
| + } |
| + } |
| + return false; |
| + } |
| + |
| + void _unbindAllTemplate() { |
| + if (isTemplate) { |
| + unbind('bind'); |
| + unbind('repeat'); |
| + unbind('if'); |
| + } |
| + } |
| + |
| + /** |
| + * Gets the template this node refers to. |
| + * This is only supported if [isTemplate] is true. |
| + */ |
| + @Experimental |
| + Element get ref { |
| + _ensureTemplate(); |
| + |
| + Element ref = null; |
| + var refId = attributes['ref']; |
| + if (refId != null) { |
| + ref = document.getElementById(refId); |
| + } |
| + |
| + return ref != null ? ref : _templateInstanceRef; |
| + } |
| + |
| + /** |
| + * Gets the content of this template. |
| + * This is only supported if [isTemplate] is true. |
| + */ |
| + @Experimental |
| + DocumentFragment get content { |
| + _ensureTemplate(); |
| + return _templateContent; |
| + } |
| + |
| + /** |
| + * Creates an instance of the template. |
| + * This is only supported if [isTemplate] is true. |
| + */ |
| + @Experimental |
| + DocumentFragment createInstance() { |
| + _ensureTemplate(); |
| + |
| + var template = ref; |
| + if (template == null) template = this; |
| + |
| + var instance = _createDeepCloneAndDecorateTemplates(template.content, |
| + attributes['syntax']); |
| + |
| + if (TemplateElement._instanceCreated != null) { |
| + TemplateElement._instanceCreated.add(instance); |
| + } |
| + return instance; |
| + } |
| + |
| + /** |
| + * The data model which is inherited through the tree. |
| + * This is only supported if [isTemplate] is true. |
| + * |
| + * Setting this will destructive propagate the value to all descendant nodes, |
| + * and reinstantiate all of the nodes expanded by this template. |
| + * |
| + * Currently this does not support propagation through Shadow DOMs. |
| + */ |
| + @Experimental |
| + get model => _model; |
| + |
| + @Experimental |
| + void set model(value) { |
| + _ensureTemplate(); |
| + |
| + _model = value; |
| + _addBindings(this, model); |
| + } |
| + |
| + // TODO(jmesserly): const set would be better |
| + static const _TABLE_TAGS = const { |
| + 'caption': null, |
| + 'col': null, |
| + 'colgroup': null, |
| + 'tbody': null, |
| + 'td': null, |
| + 'tfoot': null, |
| + 'th': null, |
| + 'thead': null, |
| + 'tr': null, |
| + }; |
| + |
| + bool get _isAttributeTemplate => attributes.containsKey('template') && |
| + (localName == 'option' || _TABLE_TAGS.containsKey(localName)); |
| + |
| + /** |
| + * Returns true if this node is a template. |
| + * |
| + * A node is a template if [tagName] is TEMPLATE, or the node has the |
| + * 'template' attribute and this tag supports attribute form for backwards |
| + * compatibility with existing HTML parsers. The nodes that can use attribute |
| + * form are table elments (THEAD, TBODY, TFOOT, TH, TR, TD, CAPTION, COLGROUP |
| + * and COL) and OPTION. |
| + */ |
| + // TODO(jmesserly): this is not a public MDV API, but it seems like a useful |
| + // place to document which tags our polyfill considers to be templates. |
| + // Otherwise I'd be repeating it in several other places. |
| + // See if we can replace this with a TemplateMixin. |
| + @Experimental |
| + bool get isTemplate => tagName == 'TEMPLATE' || _isAttributeTemplate; |
| + |
| + void _ensureTemplate() { |
| + if (!isTemplate) { |
| + throw new UnsupportedError('$this is not a template.'); |
| + } |
| + TemplateElement.decorate(this); |
| + } |
| + |
| $!MEMBERS |
| } |
| + |
| final _START_TAG_REGEXP = new RegExp('<(\\w+)'); |
| class _ElementFactoryProvider { |
| static const _CUSTOM_PARENT_TAG_MAP = const { |
| @@ -642,19 +875,6 @@ class _ElementFactoryProvider { |
| 'track' : 'audio', |
| }; |
| - // TODO(jmesserly): const set would be better |
| - static const _TABLE_TAGS = const { |
| - 'caption': null, |
| - 'col': null, |
| - 'colgroup': null, |
| - 'tbody': null, |
| - 'td': null, |
| - 'tfoot': null, |
| - 'th': null, |
| - 'thead': null, |
| - 'tr': null, |
| - }; |
| - |
| @DomName('Document.createElement') |
| static Element createElement_html(String html) { |
| // TODO(jacobr): this method can be made more robust and performant. |
| @@ -668,7 +888,7 @@ class _ElementFactoryProvider { |
| final match = _START_TAG_REGEXP.firstMatch(html); |
| if (match != null) { |
| tag = match.group(1).toLowerCase(); |
| - if (Device.isIE && _TABLE_TAGS.containsKey(tag)) { |
| + if (Device.isIE && Element._TABLE_TAGS.containsKey(tag)) { |
| return _createTableForIE(html, tag); |
| } |
| parentTag = _CUSTOM_PARENT_TAG_MAP[tag]; |