blob: c176c5f400043d43ff72a30857fbcfdcfcefa0c0 [file] [log] [blame]
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// WARNING: Do not edit - generated code.
part of $LIBRARYNAME;
/**
* Model-Driven Views (MDV)'s native features enables a wide-range of use cases,
* but (by design) don't attempt to implement a wide array of specialized
* behaviors.
*
* Enabling these features in MDV is a matter of implementing and registering an
* MDV Custom Syntax. A Custom Syntax is an object which contains one or more
* delegation functions which implement specialized behavior. This object is
* registered with MDV via [Element.bindingDelegate]:
*
*
* HTML:
* <template bind>
* {{ What!Ever('crazy')->thing^^^I+Want(data) }}
* </template>
*
* Dart:
* class MySyntax extends BindingDelegate {
* getBinding(model, path, name, node) {
* // The magic happens here!
* }
* }
* ...
* query('template').bindingDelegate = new MySyntax();
* query('template').model = new MyModel();
*
* See <https://github.com/polymer-project/mdv/blob/master/docs/syntax.md> for
* more information about Custom Syntax.
*/
// TODO(jmesserly): move this type to the MDV package? Two issues: we'd lose
// type annotation on [Element.bindingDelegate], and "mdv" is normally imported
// with a prefix.
@Experimental()
abstract class BindingDelegate {
/**
* This syntax method allows for a custom interpretation of the contents of
* mustaches (`{{` ... `}}`).
*
* When a template is inserting an instance, it will invoke this method for
* each mustache which is encountered. The function is invoked with four
* arguments:
*
* - [model]: The data context for which this instance is being created.
* - [path]: The text contents (trimmed of outer whitespace) of the mustache.
* - [name]: The context in which the mustache occurs. Within element
* attributes, this will be the name of the attribute. Within text,
* this will be 'text'.
* - [node]: A reference to the node to which this binding will be created.
*
* If the method wishes to handle binding, it is required to return an object
* which has at least a `value` property that can be observed. If it does,
* then MDV will call [Node.bind on the node:
*
* node.bind(name, retval, 'value');
*
* If the 'getBinding' does not wish to override the binding, it should return
* null.
*/
// TODO(jmesserly): I had to remove type annotations from "name" and "node"
// Normally they are String and Node respectively. But sometimes it will pass
// (int name, CompoundBinding node). That seems very confusing; we may want
// to change this API.
getBinding(model, String path, name, node) => null;
/**
* This syntax method allows a syntax to provide an alterate model than the
* one the template would otherwise use when producing an instance.
*
* When a template is about to create an instance, it will invoke this method
* The function is invoked with two arguments:
*
* - [template]: The template element which is about to create and insert an
* instance.
* - [model]: The data context for which this instance is being created.
*
* The template element will always use the return value of `getInstanceModel`
* as the model for the new instance. If the syntax does not wish to override
* the value, it should simply return the `model` value it was passed.
*/
getInstanceModel(Element template, model) => model;
}
@Experimental()
$(ANNOTATIONS)$(CLASS_MODIFIERS)class $CLASSNAME$EXTENDS$IMPLEMENTS$NATIVESPEC {
$!MEMBERS
// For real TemplateElement use the actual DOM .content field instead of
// our polyfilled expando.
@Experimental()
DocumentFragment get content => _content;
/**
* The MDV package, if available.
*
* This can be used to initialize MDV support via:
*
* import 'dart:html';
* import 'package:mdv/mdv.dart' as mdv;
* main() {
* mdv.initialize();
* }
*/
static Function mdvPackage = (node) {
throw new UnsupportedError("The MDV package is not available. "
"You can enable it with `import 'package:mdv/mdv.dart' as mdv;` and "
"`mdv.initialize()`");
};
/**
* Ensures proper API and content model for template elements.
*
* [instanceRef] can be used to set the [Element.ref] property of [template],
* and use the ref's content will be used as source when createInstance() is
* invoked.
*
* Returns true if this template was just decorated, or false if it was
* already decorated.
*/
@Experimental()
static bool decorate(Element template, [Element instanceRef]) {
// == true check because it starts as a null field.
if (template._templateIsDecorated == true) return false;
_injectStylesheet();
var templateElement = template;
templateElement._templateIsDecorated = true;
var isNative = templateElement is TemplateElement;
var bootstrapContents = isNative;
var liftContents = !isNative;
var liftRoot = false;
if (!isNative && templateElement._isAttributeTemplate) {
if (instanceRef != null) {
// TODO(jmesserly): this is just an assert in MDV.
throw new ArgumentError('instanceRef should not be supplied for '
'attribute templates.');
}
templateElement = _extractTemplateFromAttributeTemplate(template);
templateElement._templateIsDecorated = true;
isNative = templateElement is TemplateElement;
liftRoot = true;
}
if (!isNative) {
var doc = _getTemplateContentsOwner(templateElement.document);
templateElement._templateContent = doc.createDocumentFragment();
}
if (instanceRef != null) {
// template is contained within an instance, its direct content must be
// empty
templateElement._templateInstanceRef = instanceRef;
} else if (liftContents) {
_liftNonNativeChildrenIntoContent(templateElement, template, liftRoot);
} else if (bootstrapContents) {
bootstrap(templateElement.content);
}
return true;
}
// http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#dfn-template-contents-owner
static Document _getTemplateContentsOwner(HtmlDocument doc) {
if (doc.window == null) {
return doc;
}
var d = doc._templateContentsOwner;
if (d == null) {
// TODO(arv): This should either be a Document or HTMLDocument depending
// on doc.
d = doc.implementation.createHtmlDocument('');
while (d.lastChild != null) {
d.lastChild.remove();
}
doc._templateContentsOwner = d;
}
return d;
}
// For non-template browsers, the parser will disallow <template> in certain
// locations, so we allow "attribute templates" which combine the template
// element with the top-level container node of the content, e.g.
//
// <tr template repeat="{{ foo }}"" class="bar"><td>Bar</td></tr>
//
// becomes
//
// <template repeat="{{ foo }}">
// + #document-fragment
// + <tr class="bar">
// + <td>Bar</td>
//
static Element _extractTemplateFromAttributeTemplate(Element el) {
var template = el.document.createElement('template');
el.parentNode.insertBefore(template, el);
for (var name in el.attributes.keys.toList()) {
switch (name) {
case 'template':
el.attributes.remove(name);
break;
case 'repeat':
case 'bind':
case 'ref':
template.attributes[name] = el.attributes.remove(name);
break;
}
}
return template;
}
static void _liftNonNativeChildrenIntoContent(Element template, Element el,
bool useRoot) {
var content = template.content;
if (useRoot) {
content.append(el);
return;
}
var child;
while ((child = el.firstChild) != null) {
content.append(child);
}
}
/**
* This used to decorate recursively all templates from a given node.
*
* By default [decorate] will be called on templates lazily when certain
* properties such as [model] are accessed, but it can be run eagerly to
* decorate an entire tree recursively.
*/
// TODO(rafaelw): Review whether this is the right public API.
@Experimental()
static void bootstrap(Node content) {
void _bootstrap(template) {
if (!TemplateElement.decorate(template)) {
bootstrap(template.content);
}
}
// Need to do this first as the contents may get lifted if |node| is
// template.
// TODO(jmesserly): content is DocumentFragment or Element
var descendents =
(content as dynamic).querySelectorAll(_allTemplatesSelectors);
if (content is Element && (content as Element).isTemplate) {
_bootstrap(content);
}
descendents.forEach(_bootstrap);
}
static final String _allTemplatesSelectors =
'template, option[template], optgroup[template], ' +
Element._TABLE_TAGS.keys.map((k) => "$k[template]").join(", ");
static bool _initStyles;
static void _injectStylesheet() {
if (_initStyles == true) return;
_initStyles = true;
var style = new StyleElement();
style.text = r'''
template,
thead[template],
tbody[template],
tfoot[template],
th[template],
tr[template],
td[template],
caption[template],
colgroup[template],
col[template],
option[template] {
display: none;
}''';
document.head.append(style);
}
/**
* An override to place the contents into content rather than as child nodes.
*
* See also:
*
* * <https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#innerhtml-on-templates>
*/
void setInnerHtml(String html,
{NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
text = null;
var fragment = createFragment(
html, validator: validator, treeSanitizer: treeSanitizer);
content.append(fragment);
}
}