diff --git a/lib/elements/dom-bind.html b/lib/elements/dom-bind.html
index d08ca0e37c..b91438c364 100644
--- a/lib/elements/dom-bind.html
+++ b/lib/elements/dom-bind.html
@@ -85,8 +85,7 @@
observer.observe(this, {childList: true});
return;
}
- this._bindTemplate(template);
- this.root = this._stampTemplate(template);
+ this.root = this._stampBoundTemplate(template);
this.__children = [];
for (let n=this.root.firstChild; n; n=n.nextSibling) {
this.__children[this.__children.length] = n;
diff --git a/lib/mixins/element-mixin.html b/lib/mixins/element-mixin.html
index 30af1ec1a7..17e1287264 100644
--- a/lib/mixins/element-mixin.html
+++ b/lib/mixins/element-mixin.html
@@ -420,7 +420,7 @@
if (window.ShadyCSS) {
window.ShadyCSS.prepareTemplate(template, is, ext);
}
- proto._bindTemplate(template, propertiesForClass(proto.constructor));
+ proto._bindTemplate(template);
}
function flushPropertiesStub() {}
@@ -620,7 +620,7 @@
ready() {
if (this._template) {
hostStack.beginHosting(this);
- this.root = this._stampTemplate(this._template);
+ this.root = this._stampBoundTemplate(this._template);
hostStack.endHosting(this);
}
super.ready();
@@ -745,6 +745,11 @@
return Polymer.ResolveUrl.resolveUrl(url, base);
}
+ static _parseTemplateContent(template, templateInfo, nodeInfo) {
+ templateInfo.dynamicFns = templateInfo.dynamicFns || propertiesForClass(this);
+ return super._parseTemplateContent(template, templateInfo, nodeInfo);
+ }
+
}
return PolymerElement;
diff --git a/lib/mixins/property-accessors.html b/lib/mixins/property-accessors.html
index 8d1c03074e..e045d2ef15 100644
--- a/lib/mixins/property-accessors.html
+++ b/lib/mixins/property-accessors.html
@@ -129,6 +129,7 @@
_initializeProperties() {
this.__serializing = false;
this.__dataCounter = 0;
+ this.__dataInitialized = false;
this.__dataInvalid = false;
// initialize data with prototype values saved when creating accessors
this.__data = {};
@@ -137,6 +138,16 @@
if (this.__dataProto) {
this._initializeProtoProperties(this.__dataProto);
}
+ // Capture instance properties; these will be set into accessors
+ // during first flush. Don't set them here, since we want
+ // these to overwrite defaults/constructor assignments
+ for (let p in this.__dataHasAccessor) {
+ if (this.hasOwnProperty(p)) {
+ this.__dataInstanceProps = this.__dataInstanceProps || {};
+ this.__dataInstanceProps[p] = this[p];
+ delete this[p];
+ }
+ }
}
/**
@@ -347,15 +358,31 @@
* @protected
*/
_createPropertyAccessor(property, readOnly) {
- saveAccessorValue(this, property);
- Object.defineProperty(this, property, {
- get: function() {
- return this.__data[property];
- },
- set: readOnly ? function() { } : function(value) {
- this._setProperty(property, value);
- }
- });
+ if (!this.hasOwnProperty('__dataHasAccessor')) {
+ this.__dataHasAccessor = Object.assign({}, this.__dataHasAccessor);
+ }
+ if (!this.__dataHasAccessor[property]) {
+ this.__dataHasAccessor[property] = true;
+ saveAccessorValue(this, property);
+ Object.defineProperty(this, property, {
+ get: function() {
+ return this.__data[property];
+ },
+ set: readOnly ? function() { } : function(value) {
+ this._setProperty(property, value);
+ }
+ });
+ }
+ }
+
+ /**
+ * Returns true if this library created an accessor for the given property.
+ *
+ * @param {string} property Property name
+ * @return {boolean} True if an accessor was created
+ */
+ _hasAccessor(property) {
+ return this.__dataHasAccessor && this.__dataHasAccessor[property];
}
/**
@@ -436,12 +463,41 @@
* @protected
*/
_flushProperties() {
- let oldProps = this.__dataOld;
- let changedProps = this.__dataPending;
- this.__dataPending = null;
- this.__dataCounter++;
- this._propertiesChanged(this.__data, changedProps, oldProps);
- this.__dataCounter--;
+ if (!this.__dataInitialized) {
+ this.ready()
+ } else if (this.__dataPending) {
+ let oldProps = this.__dataOld;
+ let changedProps = this.__dataPending;
+ this.__dataPending = null;
+ this.__dataCounter++;
+ this._propertiesChanged(this.__data, changedProps, oldProps);
+ this.__dataCounter--;
+ }
+ }
+
+ /**
+ * Lifecycle callback called the first time properties are being flushed.
+ * Prior to `ready`, all property sets through accessors are queued and
+ * their effects are flushed after this method returns.
+ *
+ * Users may override this function to implement behavior that is
+ * dependent on the element having its properties initialized, e.g.
+ * from defaults (initialized from `constructor`, `_initializeProperties`),
+ * `attributeChangedCallback`, or values propagated from host e.g. via
+ * bindings. `super.ready()` must be called to ensure the data system
+ * becomes enabled.
+ *
+ * @public
+ */
+ ready() {
+ // Update instance properties that shadowed proto accessors; these take
+ // priority over any defaults set in constructor or attributeChangedCallback
+ if (this.__dataInstanceProps) {
+ Object.assign(this, this.__dataInstanceProps);
+ }
+ this.__dataInitialized = true;
+ // Run normal flush
+ this._flushProperties();
}
/**
diff --git a/lib/mixins/property-effects.html b/lib/mixins/property-effects.html
index 2de01ab393..0d4e023b31 100644
--- a/lib/mixins/property-effects.html
+++ b/lib/mixins/property-effects.html
@@ -32,7 +32,6 @@
// Property effect types; effects are stored on the prototype using these keys
const TYPES = {
- ANY: '__propertyEffects',
COMPUTE: '__computeEffects',
REFLECT: '__reflectEffects',
NOTIFY: '__notifyEffects',
@@ -90,14 +89,16 @@
* @param {string} type Type of effect to run
* @param {Object} props Bag of current property changes
* @param {Object=} oldProps Bag of previous values for changed properties
+ * @param {boolean} hasPaths True with `props` contains one or more paths
+ * @param {*} var_args Additional metadata to pass to effect function
* @private
*/
- function runEffects(inst, effects, props, oldProps, hasPaths) {
+ function runEffects(inst, effects, props, oldProps, hasPaths, ...args) {
if (effects) {
let ran;
let id = dedupeId++;
for (let prop in props) {
- if (runEffectsForProperty(inst, effects, id, prop, props, oldProps, hasPaths)) {
+ if (runEffectsForProperty(inst, effects, id, prop, props, oldProps, hasPaths, ...args)) {
ran = true;
}
}
@@ -110,13 +111,15 @@
*
* @param {Object} inst The instance with effects to run
* @param {Array} effects Array of effects
- * @param {number} id Effect run id used for de-duping effects
+ * @param {number} dedupeId Counter used for de-duping effects
* @param {string} prop Name of changed property
- * @param {*} value Value of changed property
- * @param {*} old Previous value of changed property
+ * @param {*} props Changed properties
+ * @param {*} oldProps Old properties
+ * @param {boolean} hasPaths True with `props` contains one or more paths
+ * @param {*} var_args Additional metadata to pass to effect function
* @private
*/
- function runEffectsForProperty(inst, effects, dedupeId, prop, props, oldProps, hasPaths) {
+ function runEffectsForProperty(inst, effects, dedupeId, prop, props, oldProps, hasPaths, ...args) {
let ran;
let rootProperty = hasPaths ? Polymer.Path.root(prop) : prop;
let fxs = effects[rootProperty];
@@ -124,10 +127,10 @@
for (let i=0, l=fxs.length, fx; (i-changed')
* @param {Object} inst Host element instance handling the notification event
- * @param {string} property Child element property that was bound
- * @param {string} path Host property/path that was bound
+ * @param {string} fromProp Child element property that was bound
+ * @param {string} toPath Host property/path that was bound
* @param {boolean} negate Whether the binding was negated
* @private
*/
- function handleNotification(e, inst, property, path, negate) {
+ function handleNotification(event, inst, fromProp, toPath, negate) {
let value;
- let targetPath = e.detail && e.detail.path;
- if (targetPath) {
- path = Polymer.Path.translate(property, path, targetPath);
- value = e.detail && e.detail.value;
+ let detail = event.detail;
+ let fromPath = detail && detail.path;
+ if (fromPath) {
+ toPath = Polymer.Path.translate(fromProp, toPath, fromPath);
+ value = detail && detail.value;
} else {
- value = e.target[property];
+ value = event.target[fromProp];
}
value = negate ? !value : value;
- setPropertyFromNotification(inst, path, value, e);
- }
-
- /**
- * Called by 2-way binding notification event listeners to set a property
- * or path to the host based on a notification from a bound child.
- *
- * @param {string} path Path on this instance to set
- * @param {*} value Value to set to given path
- * @protected
- */
- function setPropertyFromNotification(inst, path, value, event) {
- let detail = event.detail;
- if (detail && detail.queueProperty) {
- if (!inst.__readOnly || !inst.__readOnly[path]) {
- inst._setPendingPropertyOrPath(path, value, true, Boolean(detail.path));
+ if (!inst.__readOnly || !inst.__readOnly[toPath]) {
+ if (inst._setPendingPropertyOrPath(toPath, value, true, Boolean(fromPath))
+ && (!detail || !detail.queueProperty)) {
+ inst._invalidateProperties();
}
- } else {
- inst.set(path, value);
}
}
@@ -382,6 +374,7 @@
* @param {Element} inst The instance the effect will be run on
* @param {Object} changedProps Bag of changed properties
* @param {Object} oldProps Bag of previous values for changed properties
+ * @param {boolean} hasPaths True with `props` contains one or more paths
* @private
*/
function runComputedEffects(inst, changedProps, oldProps, hasPaths) {
@@ -412,7 +405,7 @@
function runComputedEffect(inst, property, props, oldProps, info) {
let result = runMethodEffect(inst, property, props, oldProps, info);
let computedProp = info.methodInfo;
- if (inst.__propertyEffects && inst.__propertyEffects[computedProp]) {
+ if (inst.__dataHasAccessor && inst.__dataHasAccessor[computedProp]) {
inst._setPendingProperty(computedProp, result, true);
} else {
inst[computedProp] = result;
@@ -425,6 +418,7 @@
*
* @param {Element} inst The instance whose props are changing
* @param {Object} changedProps Bag of changed properties
+ * @param {boolean} hasPaths True with `props` contains one or more paths
* @private
*/
function computeLinkedPaths(inst, changedProps, hasPaths) {
@@ -453,6 +447,16 @@
// -- bindings ----------------------------------------------
+ function addBinding(nodeInfo, kind, target, parts, literal) {
+ nodeInfo.bindings = nodeInfo.bindings || [];
+ let binding = {
+ kind, target, parts, literal,
+ isCompound: (parts.length !== 1)
+ };
+ nodeInfo.bindings.push(binding);
+ return binding;
+ }
+
/**
* Adds "binding" property effects for the template annotation
* ("note" for short) and node index specified. These may either be normal
@@ -462,37 +466,36 @@
*
* @param {Object} model Prototype or instance
* @param {Object} note Annotation note returned from Annotator
- * @param {number} index Index into `__templateNodes` list of annotated nodes that the
- * note applies to
+ * @param {number} index Index into `nodeList` that the binding applies to
* @param {Object=} dynamicFns Map indicating whether method names should
* be included as a dependency to the effect.
* @private
*/
- function addBindingEffect(model, note, index, dynamicFns) {
- for (let i=0; i info.value.length) &&
- (info.kind == 'property') && !info.isCompound &&
- node.__propertyEffects && node.__propertyEffects[info.name]) {
+ if (hasPaths && part.source && (path.length > part.source.length) &&
+ (binding.kind == 'property') && !binding.isCompound &&
+ node.__dataHasAccessor && node.__dataHasAccessor[binding.target]) {
let value = props[path];
- path = Polymer.Path.translate(info.value, info.name, path);
+ path = Polymer.Path.translate(part.source, binding.target, path);
if (node._setPendingPropertyOrPath(path, value, false, true)) {
inst._enqueueClient(node);
}
} else {
- // Root or deeper path was set; extract bound path value
- // e.g.: foo="{{obj.sub}}", path: 'obj', set 'foo'=obj.sub
- // or: foo="{{obj.sub}}", path: 'obj.sub.prop', set 'foo'=obj.sub
- if (path != info.value) {
- value = Polymer.Path.get(inst, info.value);
- } else {
- if (hasPaths && Polymer.Path.isPath(path)) {
- value = Polymer.Path.get(inst, path);
- } else {
- value = inst.__data[path];
- }
- }
+ let value = info.evaluator._evaluateBinding(inst, part, path, props, oldProps, hasPaths);
// Propagate value to child
- applyBindingValue(inst, info, value);
+ applyBindingValue(inst, node, binding, part, value);
}
}
@@ -548,19 +544,18 @@
* @param {*} value Value to set
* @private
*/
- function applyBindingValue(inst, info, value) {
- let node = inst.__templateNodes[info.index];
- value = computeBindingValue(node, value, info);
+ function applyBindingValue(inst, node, binding, part, value) {
+ value = computeBindingValue(node, value, binding, part);
if (Polymer.sanitizeDOMValue) {
- value = Polymer.sanitizeDOMValue(value, info.name, info.kind, node);
+ value = Polymer.sanitizeDOMValue(value, binding.target, binding.kind, node);
}
- if (info.kind == 'attribute') {
+ if (binding.kind == 'attribute') {
// Attribute binding
- inst._valueToNodeAttribute(node, value, info.name);
+ inst._valueToNodeAttribute(node, value, binding.target);
} else {
// Property binding
- let prop = info.name;
- if (node.__propertyEffects && node.__propertyEffects[prop]) {
+ let prop = binding.target;
+ if (node.__dataHasAccessor && node.__dataHasAccessor[prop]) {
if (!node.__readOnly || !node.__readOnly[prop]) {
if (node._setPendingProperty(prop, value)) {
inst._enqueueClient(node);
@@ -578,74 +573,26 @@
*
* @param {Node} node Node the value will be set to
* @param {*} value Value to set
- * @param {Object} info Effect metadata
+ * @param {Object} binding Binding metadata
* @return {*} Transformed value to set
* @private
*/
- function computeBindingValue(node, value, info) {
- if (info.negate) {
- value = !value;
- }
- if (info.isCompound) {
- let storage = node.__dataCompoundStorage[info.name];
- storage[info.compoundIndex] = value;
+ function computeBindingValue(node, value, binding, part) {
+ if (binding.isCompound) {
+ let storage = node.__dataCompoundStorage[binding.target];
+ storage[part.compoundIndex] = value;
value = storage.join('');
}
- if (info.kind !== 'attribute') {
+ if (binding.kind !== 'attribute') {
// Some browsers serialize `undefined` to `"undefined"`
- if (info.name === 'textContent' ||
- (node.localName == 'input' && info.name == 'value')) {
+ if (binding.target === 'textContent' ||
+ (node.localName == 'input' && binding.target == 'value')) {
value = value == undefined ? '' : value;
}
}
return value;
}
- /**
- * Adds "binding method" property effects for the template binding
- * ("note" for short), part metadata, and node index specified.
- *
- * @param {Object} model Prototype or instance
- * @param {Object} note Binding note returned from Annotator
- * @param {Object} part The compound part metadata
- * @param {number} index Index into `__templateNodes` list of annotated nodes that the
- * note applies to
- * @param {Object=} dynamicFns Map indicating whether method names should
- * be included as a dependency to the effect.
- * @private
- */
- function addMethodBindingEffect(model, note, part, index, dynamicFns) {
- createMethodEffect(model, part.signature, TYPES.PROPAGATE,
- runMethodBindingEffect, {
- index: index,
- isCompound: note.isCompound,
- compoundIndex: part.compoundIndex,
- kind: note.kind,
- name: note.name,
- negate: part.negate,
- part: part
- }, dynamicFns
- );
- }
-
- /**
- * Implements the "binding method" (inline computed function) effect.
- *
- * Runs the method with the values of the arguments specified in the `info`
- * object and setting the return value to the node property/attribute.
- *
- * @param {Object} inst The instance the effect will be run on
- * @param {string} property Name of property
- * @param {*} value Current value of property
- * @param {*} old Previous value of property
- * @param {Object} info Effect metadata
- * @private
- */
- function runMethodBindingEffect(inst, property, props, oldProps, info) {
- let val = runMethodEffect(inst, property, props, oldProps, info);
- applyBindingValue(inst, info.methodInfo, val);
- }
-
/**
* Returns true if a binding's metadata meets all the requirements to allow
* 2-way binding, and therefore a -changed event listener should be
@@ -660,7 +607,7 @@
* @private
*/
function shouldAddListener(binding) {
- return binding.name &&
+ return binding.target &&
binding.kind != 'attribute' &&
binding.kind != 'text' &&
!binding.isCompound &&
@@ -672,60 +619,47 @@
* instance time to add event listeners for 2-way bindings.
*
* @param {Object} model Prototype (instances not currently supported)
- * @param {number} index Index into `__templateNodes` list of annotated nodes that the
- * event should be added to
- * @param {string} property Property of target node to listen for changes
- * @param {string} path Host path that the change should be propagated to
- * @param {string=} event A custom event name to listen for (e.g. via the
- * `{{prop::eventName}}` syntax)
- * @param {boolean=} negate Whether the notified value should be negated before
- * setting to host path
- * @private
- */
- function addAnnotatedListener(model, index, property, path, event, negate) {
- event = event || (CaseMap.camelToDashCase(property) + '-changed');
- model.__notifyListeners = model.__notifyListeners || [];
- model.__notifyListeners.push({ index, property, path, event, negate });
- }
-
- /**
- * Adds all 2-way binding notification listeners to a host based on
- * `__notifyListeners` metadata recorded by prior calls to`addAnnotatedListener`
- *
- * @param {Object} inst Host element instance
+ * @param {number} index Index into `nodeList` that the event should be added to
+ * @param {Object} binding Binding metadata
* @private
*/
- function setupNotifyListeners(inst) {
- let b$ = inst.__notifyListeners;
- for (let i=0, l=b$.length, info; (i 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, event, colon;
- if (mode == '{' && (colon = value.indexOf('::')) > 0) {
- event = 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({
- value, mode, negate, event, customEvent, signature, rootProperty,
- compoundIndex: parts.length
- });
- lastIndex = bindingRegex.lastIndex;
- }
- // Add a final literal part
- if (lastIndex && lastIndex < text.length) {
- let literal = text.substring(lastIndex);
- if (literal) {
- parts.push({ literal });
- }
- }
- if (parts.length) {
- return parts;
- }
- }
-
function literalFromParts(parts) {
let s = '';
for (let i=0; i= 0) {
+ effects.splice(idx, 1);
+ }
+ }
+
/**
* Returns whether the current prototype/instance has a property effect
* of a certain type.
@@ -1296,7 +1173,7 @@
* @protected
*/
_hasPropertyEffect(property, type) {
- let effects = this[type || TYPES.ANY];
+ let effects = this[type];
return Boolean(effects && effects[property]);
}
@@ -1378,9 +1255,9 @@
*/
_setPendingPropertyOrPath(path, value, shouldNotify, isPathNotification) {
let rootProperty = Polymer.Path.root(Array.isArray(path) ? path[0] : path);
- let hasEffect = this.__propertyEffects && this.__propertyEffects[rootProperty];
+ let hasAccessor = this.__dataHasAccessor && this.__dataHasAccessor[rootProperty];
let isPath = (rootProperty !== path);
- if (hasEffect) {
+ if (hasAccessor) {
if (isPath) {
if (!isPathNotification) {
// Dirty check changes being set to a path against the actual object,
@@ -1590,55 +1467,14 @@
}
/**
- * Overrides PropertyAccessor's default async queuing of
- * `_propertiesChanged`, to instead synchronously flush
- * `_propertiesChanged` unless the `this._asyncEffects` property is true.
- *
- * If this is the first time properties are being flushed, the `ready`
- * callback will be called.
+ * Overrides PropertyAccessor
*
* @override
*/
- _flushProperties() {
- if (!this.__dataInitialized) {
- this.ready()
- } else if (this.__dataPending) {
- super._flushProperties();
- if (!this.__dataCounter) {
- // Clear temporary cache at end of turn
- this.__dataTemp = {};
- }
- }
- }
-
- /**
- * Polymer-specific lifecycle callback called the first time properties
- * are being flushed. Prior to `ready`, all property sets through
- * accessors are queued and their effects are flushed after this method
- * returns.
- *
- * Users may override this function to implement behavior that is
- * dependent on the element having its properties initialized, e.g.
- * from defaults (initialized from `constructor`, `_initializeProperties`),
- * `attributeChangedCallback`, or binding values propagated from host
- * "binding effects". `super.ready()` must be called to ensure the
- * data system becomes enabled.
- *
- * @public
- */
ready() {
- // Update instance properties that shadowed proto accessors; these take
- // priority over any defaults set in `properties` or constructor
- let instanceProps = this.__dataInstanceProps;
- if (instanceProps) {
- initalizeInstanceProperties(this, instanceProps);
- }
- // Enable acceessors
- this.__dataInitialized = true;
- if (this.__dataPending) {
- // Run normal flush
- this._flushProperties();
- } else {
+ let dataPending = this.__dataPending;
+ super.ready();
+ if (!dataPending) {
this._readyClients();
}
}
@@ -1654,30 +1490,6 @@
this.__dataClientsInitialized = true;
}
- /**
- * Stamps the provided template and performs instance-time setup for
- * Polymer template features, including data bindings, declarative event
- * listeners, and the `this.$` map of `id`'s to nodes. A document fragment
- * is returned containing the stamped DOM, ready for insertion into the
- * DOM.
- *
- * Note that for host data to be bound into the stamped DOM, the template
- * must have been previously bound to the prototype via a call to
- * `_bindTemplate`, which performs one-time template binding work.
- *
- * Note that this method currently only supports being called once per
- * instance.
- *
- * @param {HTMLTemplateElement} template Template to stamp
- * @return {DocumentFragment} Cloned template content
- * @protected
- */
- _stampTemplate(template) {
- let dom = super._stampTemplate(template);
- setupBindings(this, template._templateInfo.nodeInfoList);
- return dom;
- }
-
/**
* Implements `PropertyAccessors`'s properties changed callback.
*
@@ -1703,7 +1515,7 @@
let notifyProps = this.__dataToNotify;
this.__dataToNotify = null;
// Propagate properties to clients
- runEffects(this, this.__propagateEffects, changedProps, oldProps, hasPaths);
+ this._propagatePropertyChanges(changedProps, oldProps, hasPaths);
// Flush clients
this._flushClients();
// Reflect properties
@@ -1714,11 +1526,36 @@
if (notifyProps) {
runNotifyEffects(this, notifyProps, changedProps, oldProps, hasPaths);
}
+ // Clear temporary cache at end of turn
+ if (this.__dataCounter == 1) {
+ this.__dataTemp = {};
+ }
// ----------------------------
// window.debug && console.groupEnd(this.localName + '#' + this.id + ': ' + c);
// ----------------------------
}
+ /**
+ * Called to propagate any property changes to stamped template nodes
+ * managed by this element.
+ *
+ * @param {Object} changedProps Bag of changed properties
+ * @param {Object} oldProps Bag of previous values for changed properties
+ * @param {boolean} hasPaths True with `props` contains one or more paths
+ * @protected
+ */
+ _propagatePropertyChanges(changedProps, oldProps, hasPaths) {
+ if (this.__propagateEffects) {
+ runEffects(this, this.__propagateEffects, changedProps, oldProps, hasPaths);
+ }
+ let templateInfo = this.__templateInfo;
+ while (templateInfo) {
+ runEffects(this, templateInfo.propertyEffects, changedProps, oldProps,
+ hasPaths, templateInfo.nodeList);
+ templateInfo = templateInfo.nextTemplateInfo;
+ }
+ }
+
/**
* Aliases one data path as another, such that path notifications from one
* are routed to the other.
@@ -2141,37 +1978,104 @@
// -- binding ----------------------------------------------
/**
- * Creates "binding" property effects for all binding bindings
- * in the provided template that forward host properties into DOM stamped
- * from the template via `_stampTemplate`.
+ * Parses the provided template to ensure binding effects are created
+ * for them, and then ensures property accessors are created for any
+ * dependent properties in the template. Binding effects for bound
+ * templates are stored in a linked list on the instance so that
+ * templates can be efficiently stamped and unstamped.
+ *
+ * This method may be called on the prototype (for prototypical template
+ * binding) once per prototype, or on the instance for either the
+ * prototypically bound template and/or one or more templates to stamp
+ * bound to the instance.
*
* @param {HTMLTemplateElement} template Template containing binding
* bindings
- * @param {Object=} dynamicFns Map indicating whether method names should
- * be included as a dependency to the effect.
+ * @param {DocumentFragment=} dom Stamped DOM for this template to be
+ * bound to the instance (leave undefined for prototypical template
+ * binding)
+ * @return {Object} Template metadata object
* @protected
*/
- _bindTemplate(template, dynamicFns) {
- // Clear any existing propagation effects inherited from superClass
- this.__propagateEffects = {};
- this.__notifyListeners = [];
+ _bindTemplate(template, dom) {
let templateInfo = this.constructor._parseTemplate(template);
- let nodeInfo = templateInfo.nodeInfoList;
- for (let i=0, info; (i} Array of binding part metadata
+ * @protected
+ */
+ static _parseBindings(text, templateInfo) {
+ 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
+ let mode = m[1][0];
+ let negate = Boolean(m[2]);
+ let source = m[3].trim();
+ let customEvent, notifyEvent, colon;
+ if (mode == '{' && (colon = source.indexOf('::')) > 0) {
+ notifyEvent = source.substring(colon + 2);
+ source = source.substring(0, colon);
+ customEvent = true;
+ }
+ let signature = parseMethod(source);
+ let dependencies = [];
+ if (signature) {
+ // Inline computed function
+ let {args, methodName} = signature;
+ for (let i=0; i.content
* is removed and stored in notes as well.
*
- * Note that this method may only be called once per instance (it does
- * not support stamping multiple templates per element instance).
- *
* @param {HTMLTemplateElement} template Template to stamp
* @return {DocumentFragment} Cloned template content
*/
@@ -406,10 +407,10 @@
let dom = document.importNode(content, true);
// NOTE: ShadyDom optimization indicating there is an insertion point
dom.__noInsertionPoint = !templateInfo.hasInsertionPoint;
+ let nodes = dom.nodeList = new Array(nodeInfo.length);
this.$ = {};
- this.__templateNodes = new Array(nodeInfo.length);
- for (let i=0, l=nodeInfo.length, info, node; (i