93c361dd36
Original submission is the first patch, fixes are the second. There were two issues with the original- - Issue with URI sanitization where relative URIs on IE were being dropped. - Analyzer issue on missing members for CustomElement. BUG= R=jmesserly@google.com Review URL: https://codereview.chromium.org//23525003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@26757 260f80e4-7a28-3924-810f-c04153c831b5
309 lines
9.8 KiB
Plaintext
309 lines
9.8 KiB
Plaintext
// 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.$dom_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).queryAll(_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);
|
|
}
|
|
}
|