diff --git a/lib/mixins/property-effects.html b/lib/mixins/property-effects.html
index de11cd4df3..259bd86ba3 100644
--- a/lib/mixins/property-effects.html
+++ b/lib/mixins/property-effects.html
@@ -646,106 +646,6 @@
applyBindingValue(inst, info.methodInfo, val);
}
- /**
- * Post-processes template bindings (notes for short) provided by the
- * Bindings library for use by the effects system:
- * - Parses bindings for methods into method `signature` objects
- * - Memoizes the root property for path bindings
- * - Recurses into nested templates and processes those templates and
- * extracts any host properties, which are set to the template's
- * `_content._hostProps`
- * - Adds bindings from the host to elements for any nested
- * template's lexically bound "host properties"; template handling
- * elements can then add accessors to the template for these properties
- * to forward host properties into template instances accordingly.
- *
- * @param {Array} notes List of notes to process; the notes are
- * modified in place.
- * @private
- */
- function processAnnotations(notes) {
- if (!notes._processed) {
- for (let i=0; i} notes List of notes to process for a given template
- * @return {Object} Map of host properties that the template
- * (or any nested templates) uses
- * @private
- */
- function discoverTemplateHostProps(notes) {
- let hostProps = {};
- for (let i=0, n; (i-changed event listener should be
@@ -905,6 +805,91 @@
const emptyArray = [];
+ // Regular expressions used for binding
+ const IDENT = '(?:' + '[a-zA-Z_$][\\w.:$\\-*]*' + ')';
+ const NUMBER = '(?:' + '[-+]?[0-9]*\\.?[0-9]+(?:[eE][-+]?[0-9]+)?' + ')';
+ const SQUOTE_STRING = '(?:' + '\'(?:[^\'\\\\]|\\\\.)*\'' + ')';
+ const DQUOTE_STRING = '(?:' + '"(?:[^"\\\\]|\\\\.)*"' + ')';
+ const STRING = '(?:' + SQUOTE_STRING + '|' + DQUOTE_STRING + ')';
+ const ARGUMENT = '(?:' + IDENT + '|' + NUMBER + '|' + STRING + '\\s*' + ')';
+ const ARGUMENTS = '(?:' + ARGUMENT + '(?:,\\s*' + ARGUMENT + ')*' + ')';
+ const ARGUMENT_LIST = '(?:' + '\\(\\s*' +
+ '(?:' + ARGUMENTS + '?' + ')' +
+ '\\)\\s*' + ')';
+ const BINDING = '(' + IDENT + '\\s*' + ARGUMENT_LIST + '?' + ')'; // Group 3
+ const OPEN_BRACKET = '(\\[\\[|{{)' + '\\s*';
+ const CLOSE_BRACKET = '(?:]]|}})';
+ const NEGATE = '(?:(!)\\s*)?'; // Group 2
+ const EXPRESSION = OPEN_BRACKET + NEGATE + BINDING + CLOSE_BRACKET;
+ const bindingRegex = new RegExp(EXPRESSION, "g");
+
+ function parseBindings(text, hostProps) {
+ let parts = [];
+ let lastIndex = 0;
+ let m;
+ // Example: "literal1{{prop}}literal2[[!compute(foo,bar)]]final"
+ // Regex matches:
+ // Iteration 1: Iteration 2:
+ // m[1]: '{{' '[['
+ // m[2]: '' '!'
+ // m[3]: 'prop' 'compute(foo,bar)'
+ while ((m = bindingRegex.exec(text)) !== null) {
+ // Add literal part
+ if (m.index > lastIndex) {
+ parts.push({literal: text.slice(lastIndex, m.index)});
+ }
+ // Add binding part
+ // Mode (one-way or two)
+ let mode = m[1][0];
+ let negate = Boolean(m[2]);
+ let value = m[3].trim();
+ let customEvent, notifyEvent, colon;
+ if (mode == '{' && (colon = value.indexOf('::')) > 0) {
+ notifyEvent = value.substring(colon + 2);
+ value = value.substring(0, colon);
+ customEvent = true;
+ }
+ let signature = parseMethod(value, hostProps);
+ let rootProperty;
+ if (!signature) {
+ rootProperty = Polymer.Path.root(value);
+ hostProps[rootProperty] = true;
+ }
+ parts.push({
+ compoundIndex: parts.length,
+ value,
+ mode,
+ negate,
+ event: notifyEvent,
+ customEvent,
+ signature,
+ rootProperty
+ });
+ lastIndex = bindingRegex.lastIndex;
+ }
+ // Add a final literal part
+ if (lastIndex && lastIndex < text.length) {
+ let literal = text.substring(lastIndex);
+ if (literal) {
+ parts.push({
+ literal: literal
+ });
+ }
+ }
+ if (parts.length) {
+ return parts;
+ }
+ }
+
+ function literalFromParts(parts) {
+ let s = '';
+ for (let i=0; i="expression"
- *
- * Generated data-structure:
- * [
- * {
- * id: '',
- * events: [
- * {
- * mode: ['auto'|''],
- * name: ''
- * value: ''
- * }, ...
- * ],
- * bindings: [
- * {
- * kind: ['text'|'attribute'|'property'],
- * mode: ['auto'|''],
- * name: ''
- * value: ''
- * }, ...
- * ],
- * parent: ,
- * index:
- * },
- * ...
- * ]
- *
- * @param {HTMLTemplateElement} template
- * @param {boolean=} stripWhiteSpace
- * @return {Array}
- */
- function parseTemplateAnnotations(template, stripWhiteSpace, ownerDocument) {
- // TODO(kschaaf): File issue and/or remove when fixed
- // hold a reference to content as _content to prevent odd Chrome gc issue
- // nested templates also may receive their content as _content
- let content = (template._content = template._content || template.content);
- // since a template may be re-used, memo-ize notes.
- if (!content._notes) {
- content._notes = [];
- // TODO(sorvell): whitespace and processAnnotations need to be factored
- // into plugins
- ownerDocument = ownerDocument || template.ownerDocument;
- parseNodeAnnotations(content, content._notes,
- stripWhiteSpace || template.hasAttribute('strip-whitespace'), ownerDocument);
- }
- return content._notes;
- }
-
- // add annotations gleaned from subtree at `node` to `list`
- function parseNodeAnnotations(node, list, stripWhiteSpace, ownerDocument) {
- return node.nodeType === Node.TEXT_NODE ?
- parseTextNodeAnnotation(node, list) :
- parseElementAnnotations(node, list, stripWhiteSpace, ownerDocument);
- }
-
- function parseBindings(text) {
- let parts = [];
- let lastIndex = 0;
- let m;
- // Example: "literal1{{prop}}literal2[[!compute(foo,bar)]]final"
- // Regex matches:
- // Iteration 1: Iteration 2:
- // m[1]: '{{' '[['
- // m[2]: '' '!'
- // m[3]: 'prop' 'compute(foo,bar)'
- while ((m = bindingRegex.exec(text)) !== null) {
- // Add literal part
- if (m.index > lastIndex) {
- parts.push({literal: text.slice(lastIndex, m.index)});
- }
- // Add binding part
- // Mode (one-way or two)
- let mode = m[1][0];
- let negate = Boolean(m[2]);
- let value = m[3].trim();
- let customEvent, notifyEvent, colon;
- if (mode == '{' && (colon = value.indexOf('::')) > 0) {
- notifyEvent = value.substring(colon + 2);
- value = value.substring(0, colon);
- customEvent = true;
- }
- parts.push({
- compoundIndex: parts.length,
- value: value,
- mode: mode,
- negate: negate,
- event: notifyEvent,
- customEvent: customEvent
- });
- lastIndex = bindingRegex.lastIndex;
- }
- // Add a final literal part
- if (lastIndex && lastIndex < text.length) {
- let literal = text.substring(lastIndex);
- if (literal) {
- parts.push({
- literal: literal
- });
- }
- }
- if (parts.length) {
- return parts;
- }
- }
-
- function literalFromParts(parts) {
- let s = '';
- for (let i=0; i="expression"
+ *
+ * Generated data-structure:
+ * [
+ * {
+ * id: '',
+ * events: [
+ * {
+ * mode: ['auto'|''],
+ * name: ''
+ * value: ''
+ * }, ...
+ * ],
+ * bindings: [
+ * {
+ * kind: ['text'|'attribute'|'property'],
+ * mode: ['auto'|''],
+ * name: ''
+ * value: ''
+ * }, ...
+ * ],
+ * parent: ,
+ * index:
+ * },
+ * ...
+ * ]
+ *
+ * @param {HTMLTemplateElement} template
+ * @param {boolean=} stripWhiteSpace
+ * @param {Document=} ownerDocument
+ * @return {Array}
+ */
+ static _prepareTemplate(template, stripWhiteSpace, ownerDocument) {
+ // TODO(kschaaf): File issue and/or remove when fixed
+ // hold a reference to content as _content to prevent odd Chrome gc issue
+ // nested templates also may receive their content as _content
+ let content = (template._content = template._content || template.content);
+ // since a template may be re-used, memo-ize notes.
+ if (!content._notes) {
+ let notes = content._notes = [];
+ notes.ownerDocument = ownerDocument || template.ownerDocument;
+ notes.stripWhiteSpace = stripWhiteSpace || template.hasAttribute('strip-whitespace');
+ this._parseTemplateNode(content, {notes});
+ }
+ return content._notes;
+ }
+
+ // add annotations gleaned from subtree at `node` to `notes`
+ static _parseTemplateNode(node, note) {
+ let noted;
+ if (node.localName == 'template' &&
+ !node.hasAttribute('preserve-content')) {
+ noted |= this._parseTemplate(node, note);
+ } else if (node.localName === 'slot') {
+ node._hasInsertionPoint = true;
+ }
+ if (node.firstChild) {
+ noted |= this._parseTemplateChildNodes(node, note);
+ }
+ if (node.attributes) {
+ noted |= this._parseTemplateNodeAttributes(node, note);
+ }
+ return noted;
+ }
+
+ // add annotations gleaned from children of `root` to `list`, `root`'s
+ // `note` is supplied as it is the note.parent of added annotations
+ static _parseTemplateChildNodes(root, note) {
+ let node = root.firstChild;
+ if (node) {
+ let i = 0;
+ while (node) {
+ // Wrap templates
+ if (node.localName == 'template') {
+ node = wrapTemplateExtension(node);
+ }
+ // collapse adjacent textNodes: fixes an IE issue that can cause
+ // text nodes to be inexplicably split =(
+ // note that root.normalize() should work but does not so we do this
+ // manually.
+ let next = node.nextSibling;
+ if (node.nodeType === Node.TEXT_NODE) {
+ let n = next;
+ while (n && (n.nodeType === Node.TEXT_NODE)) {
+ node.textContent += n.textContent;
+ next = n.nextSibling;
+ root.removeChild(n);
+ n = next;
+ }
+ // optionally strip whitespace
+ if (note.notes.stripWhiteSpace && !node.textContent.trim()) {
+ root.removeChild(node);
+ // decrement index since node is removed
+ i--;
+ }
+ }
+ // if this node didn't get evacipated, parse it.
+ if (node.parentNode) {
+ let childNote = {
+ notes: note.notes,
+ parent: note,
+ index: i
+ }
+ if (this._parseTemplateNode(node, childNote)) {
+ note.notes.push(childNote);
+ }
+ }
+ node = next;
+ i++;
+ }
+ }
+ }
+
+ // Recurse into nested templates
+ // 1. Parse annotations from the template and memoize them on
+ // content._notes (recurses into nested templates)
+ // 2. Remove template.content and store it in annotation list, where it
+ // will be the responsibility of the host to set it back to the template
+ // (this is both an optimization to avoid re-stamping nested template
+ // children and avoids a bug in Chrome where nested template children
+ // upgrade)
+ static _parseTemplate(node, note) {
+ let content = node.content.ownerDocument.createDocumentFragment();
+ content._notes = this._prepareTemplate(node,
+ note.notes.stripWhiteSpace, note.notes.ownerDocument);
+ content.appendChild(node.content);
+ note.templateContent = content;
+ return true;
+ }
+
+ // add annotation data from attributes to the `annotation` for node `node`
+ static _parseTemplateNodeAttributes(node, note) {
+ // Make copy of original attribute list, since the order may change
+ // as attributes are added and removed
+ let attrs = Array.prototype.slice.call(node.attributes);
+ let noted;
+ for (let i=attrs.length-1, a; (a=attrs[i]); i--) {
+ noted |= this._parseTemplateNodeAttribute(node, note, a.name, a.value)
+ }
+ return noted;
+ }
+
+ // construct annotation data from a generic attribute, or undefined
+ static _parseTemplateNodeAttribute(node, note, name, value) {
+ // events (on-*)
+ if (name.slice(0, 3) === 'on-') {
+ node.removeAttribute(name);
+ note.events = note.events || [];
+ note.events.push({
+ name: name.slice(3),
+ value
+ });
+ return true;
+ }
+ // static id
+ else if (name === 'id') {
+ note.id = value;
+ return true;
+ }
+ }
+
/**
* Clones the provided template content and returns a document fragment
* containing the cloned dom.
@@ -494,7 +334,7 @@
window.HTMLTemplateElement && HTMLTemplateElement.decorate) {
HTMLTemplateElement.decorate(template);
}
- let notes = this.__templateNotes = this._parseTemplateAnnotations(template);
+ let notes = this.__templateNotes = this.constructor._prepareTemplate(template);
let dom = document.importNode(template._content || template.content, true);
// NOTE: ShadyDom optimization indicating there is an insertion point
dom.__noInsertionPoint = !notes._hasInsertionPoint;
@@ -509,17 +349,6 @@
return dom;
}
- // preprocess-time
-
- // construct and return a list of annotation records
- // by scanning `template`'s content
- //
- // TODO(sorvell): This should just crawl over a template and call
- // a supplied list of callbacks.
- _parseTemplateAnnotations(template) {
- return parseTemplateAnnotations(template);
- }
-
_addMethodEventListenerToNode(node, eventName, methodName, context) {
context = context || node;
let handler = createNodeEventHandler(context, eventName, methodName);
diff --git a/lib/utils/templatize.html b/lib/utils/templatize.html
index ad1008aee3..adc3febe4b 100644
--- a/lib/utils/templatize.html
+++ b/lib/utils/templatize.html
@@ -218,7 +218,7 @@
class TemplatizedTemplate extends base {}
// Add template - >instances effects
// and host <- template effects
- let hostProps = template._content._hostProps;
+ let hostProps = template._content._notes.hostProps;
for (let prop in hostProps) {
klass.prototype._addPropertyEffect('_host_' + prop,
klass.prototype.PROPERTY_EFFECT_TYPES.PROPAGATE,
@@ -250,7 +250,7 @@
}
function addNotifyEffects(klass, template, options) {
- let hostProps = template._content._hostProps || {};
+ let hostProps = template._content._notes.hostProps || {};
for (let iprop in options.instanceProps) {
delete hostProps[iprop];
let userNotifyInstanceProp = options.notifyInstanceProp;
@@ -404,7 +404,7 @@
klass.prototype._methodHost = findMethodHost(template);
klass.prototype.__dataHost = template;
klass.prototype.__templatizeOwner = owner;
- klass.prototype.__hostProps = template._content._hostProps;
+ klass.prototype.__hostProps = template._content._notes.hostProps;
return klass;
},