mirror of
https://github.com/Polymer/polymer.git
synced 2026-08-19 01:14:44 -05:00
Merge pull request #5560 from Polymer/legacy-undefined-noBatch-altTemplates
Optimizations to template elements - fastDomIf & removeNestedTemplates
This commit is contained in:
+454
-97
@@ -16,29 +16,17 @@ import { microTask } from '../utils/async.js';
|
||||
import { root } from '../utils/path.js';
|
||||
import { wrap } from '../utils/wrap.js';
|
||||
import { hideElementsGlobally } from '../utils/hide-template-controls.js';
|
||||
import { fastDomIf, strictTemplatePolicy } from '../utils/settings.js';
|
||||
import { showHideChildren } from '../utils/templatize.js';
|
||||
|
||||
/**
|
||||
* The `<dom-if>` element will stamp a light-dom `<template>` child when
|
||||
* the `if` property becomes truthy, and the template can use Polymer
|
||||
* data-binding and declarative event features when used in the context of
|
||||
* a Polymer element's template.
|
||||
*
|
||||
* When `if` becomes falsy, the stamped content is hidden but not
|
||||
* removed from dom. When `if` subsequently becomes truthy again, the content
|
||||
* is simply re-shown. This approach is used due to its favorable performance
|
||||
* characteristics: the expense of creating template content is paid only
|
||||
* once and lazily.
|
||||
*
|
||||
* Set the `restamp` property to true to force the stamped content to be
|
||||
* created / destroyed when the `if` condition changes.
|
||||
*
|
||||
* @customElement
|
||||
* @polymer
|
||||
* @extends PolymerElement
|
||||
* @summary Custom element that conditionally stamps and hides or removes
|
||||
* template content based on a boolean flag.
|
||||
* @summary Base class for dom-if element; subclassed into concrete
|
||||
* implementation.
|
||||
*/
|
||||
export class DomIf extends PolymerElement {
|
||||
class DomIfBase extends PolymerElement {
|
||||
|
||||
// Not needed to find template; can be removed once the analyzer
|
||||
// can find the tag name from customElements.define call
|
||||
@@ -85,11 +73,12 @@ export class DomIf extends PolymerElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.__renderDebouncer = null;
|
||||
this.__invalidProps = null;
|
||||
this.__instance = null;
|
||||
this._lastIf = false;
|
||||
this.__ctor = null;
|
||||
this.__hideTemplateChildren__ = false;
|
||||
/** @type {!HTMLTemplateElement|undefined} */
|
||||
this.__template;
|
||||
/** @type {!TemplateInfo|undefined} */
|
||||
this._templateInfo;
|
||||
}
|
||||
|
||||
__debounceRender() {
|
||||
@@ -143,31 +132,119 @@ export class DomIf extends PolymerElement {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures a template has been assigned to `this.__template`. If it has not
|
||||
* yet been, it querySelectors for it in its children and if it does not yet
|
||||
* exist (e.g. in parser-generated case), opens a mutation observer and
|
||||
* waits for it to appear (returns false if it has not yet been found,
|
||||
* otherwise true). In the `removeNestedTemplates` case, the "template" will
|
||||
* be the `dom-if` element itself.
|
||||
*
|
||||
* @return {boolean} True when a template has been found, false otherwise
|
||||
*/
|
||||
__ensureTemplate() {
|
||||
if (!this.__template) {
|
||||
// When `removeNestedTemplates` is true, the "template" is the element
|
||||
// itself, which has been given a `_templateInfo` property
|
||||
const thisAsTemplate = /** @type {!HTMLTemplateElement} */ (
|
||||
/** @type {!HTMLElement} */ (this));
|
||||
let template = thisAsTemplate._templateInfo ?
|
||||
thisAsTemplate :
|
||||
/** @type {!HTMLTemplateElement} */
|
||||
(wrap(thisAsTemplate).querySelector('template'));
|
||||
if (!template) {
|
||||
// Wait until childList changes and template should be there by then
|
||||
let observer = new MutationObserver(() => {
|
||||
if (wrap(this).querySelector('template')) {
|
||||
observer.disconnect();
|
||||
this.__render();
|
||||
} else {
|
||||
throw new Error('dom-if requires a <template> child');
|
||||
}
|
||||
});
|
||||
observer.observe(this, {childList: true});
|
||||
return false;
|
||||
}
|
||||
this.__template = template;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures a an instance of the template has been created and inserted. This
|
||||
* method may return false if the template has not yet been found or if
|
||||
* there is no `parentNode` to insert the template into (in either case,
|
||||
* connection or the template-finding mutation observer firing will queue
|
||||
* another render, causing this method to be called again at a more
|
||||
* appropriate time).
|
||||
*
|
||||
* Subclasses should implement the following methods called here:
|
||||
* - `__hasInstance`
|
||||
* - `__createAndInsertInstance`
|
||||
* - `__getInstanceNodes`
|
||||
*
|
||||
* @return {boolean} True if the instance was created, false otherwise.
|
||||
*/
|
||||
__ensureInstance() {
|
||||
let parentNode = wrap(this).parentNode;
|
||||
if (!this.__hasInstance()) {
|
||||
// Guard against element being detached while render was queued
|
||||
if (!parentNode) {
|
||||
return false;
|
||||
}
|
||||
// Find the template (when false, there was no template yet)
|
||||
if (!this.__ensureTemplate()) {
|
||||
return false;
|
||||
}
|
||||
this.__createAndInsertInstance(parentNode);
|
||||
} else {
|
||||
// Move instance children if necessary
|
||||
let children = this.__getInstanceNodes();
|
||||
if (children && children.length) {
|
||||
// Detect case where dom-if was re-attached in new position
|
||||
let lastChild = wrap(this).previousSibling;
|
||||
if (lastChild !== children[children.length-1]) {
|
||||
for (let i=0, n; (i<children.length) && (n=children[i]); i++) {
|
||||
wrap(parentNode).insertBefore(n, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces the element to render its content. Normally rendering is
|
||||
* asynchronous to a provoking change. This is done for efficiency so
|
||||
* that multiple changes trigger only a single render. The render method
|
||||
* should be called if, for example, template rendering is required to
|
||||
* validate application state.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
render() {
|
||||
flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the key rendering steps:
|
||||
* 1. Ensure a template instance has been stamped (when true)
|
||||
* 2. Remove the template instance (when false and restamp:true)
|
||||
* 3. Sync the hidden state of the instance nodes with the if/restamp state
|
||||
* 4. Fires the `dom-change` event when necessary
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
__render() {
|
||||
if (this.if) {
|
||||
if (!this.__ensureInstance()) {
|
||||
// No template found yet
|
||||
return;
|
||||
}
|
||||
this._showHideChildren();
|
||||
} else if (this.restamp) {
|
||||
this.__teardownInstance();
|
||||
}
|
||||
if (!this.restamp && this.__instance) {
|
||||
this._showHideChildren();
|
||||
}
|
||||
this._showHideChildren();
|
||||
if (this.if != this._lastIf) {
|
||||
this.dispatchEvent(new CustomEvent('dom-change', {
|
||||
bubbles: true,
|
||||
@@ -177,81 +254,313 @@ export class DomIf extends PolymerElement {
|
||||
}
|
||||
}
|
||||
|
||||
__ensureInstance() {
|
||||
let parentNode = wrap(this).parentNode;
|
||||
// Guard against element being detached while render was queued
|
||||
if (parentNode) {
|
||||
if (!this.__ctor) {
|
||||
let template = /** @type {HTMLTemplateElement} */(wrap(this).querySelector('template'));
|
||||
if (!template) {
|
||||
// Wait until childList changes and template should be there by then
|
||||
let observer = new MutationObserver(() => {
|
||||
if (wrap(this).querySelector('template')) {
|
||||
observer.disconnect();
|
||||
this.__render();
|
||||
} else {
|
||||
throw new Error('dom-if requires a <template> child');
|
||||
}
|
||||
});
|
||||
observer.observe(this, {childList: true});
|
||||
return false;
|
||||
// Ideally these would be annotated as abstract methods in an abstract class,
|
||||
// but closure compiler is finnicky
|
||||
/* eslint-disable valid-jsdoc */
|
||||
/**
|
||||
* Abstract API to be implemented by subclass: Returns true if a template
|
||||
* instance has been created and inserted.
|
||||
*
|
||||
* @protected
|
||||
* @return {boolean} True when an instance has been created.
|
||||
*/
|
||||
__hasInstance() { }
|
||||
|
||||
/**
|
||||
* Abstract API to be implemented by subclass: Returns the child nodes stamped
|
||||
* from a template instance.
|
||||
*
|
||||
* @protected
|
||||
* @return {Array<Node>} Array of child nodes stamped from the template
|
||||
* instance.
|
||||
*/
|
||||
__getInstanceNodes() { }
|
||||
|
||||
/**
|
||||
* Abstract API to be implemented by subclass: Creates an instance of the
|
||||
* template and inserts it into the given parent node.
|
||||
*
|
||||
* @protected
|
||||
* @param {Node} parentNode The parent node to insert the instance into
|
||||
* @return {void}
|
||||
*/
|
||||
__createAndInsertInstance(parentNode) { } // eslint-disable-line no-unused-vars
|
||||
|
||||
/**
|
||||
* Abstract API to be implemented by subclass: Removes nodes created by an
|
||||
* instance of a template and any associated cleanup.
|
||||
*
|
||||
* @protected
|
||||
* @return {void}
|
||||
*/
|
||||
__teardownInstance() { }
|
||||
|
||||
/**
|
||||
* Abstract API to be implemented by subclass: Shows or hides any template
|
||||
* instance childNodes based on the `if` state of the element and its
|
||||
* `__hideTemplateChildren__` property.
|
||||
*
|
||||
* @protected
|
||||
* @return {void}
|
||||
*/
|
||||
_showHideChildren() { }
|
||||
/* eslint-enable valid-jsdoc */
|
||||
}
|
||||
|
||||
/**
|
||||
* The version of DomIf used when `fastDomIf` setting is in use, which is
|
||||
* optimized for first-render (but adds a tax to all subsequent property updates
|
||||
* on the host, whether they were used in a given `dom-if` or not).
|
||||
*
|
||||
* This implementation avoids use of `Templatizer`, which introduces a new scope
|
||||
* (a non-element PropertyEffects instance), which is not strictly necessary
|
||||
* since `dom-if` never introduces new properties to its scope (unlike
|
||||
* `dom-repeat`). Taking advantage of this fact, the `dom-if` reaches up to its
|
||||
* `__dataHost` and stamps the template directly from the host using the host's
|
||||
* runtime `_stampTemplate` API, which binds the property effects of the
|
||||
* template directly to the host. This both avoids the intermediary
|
||||
* `Templatizer` instance, but also avoids the need to bind host properties to
|
||||
* the `<template>` element and forward those into the template instance.
|
||||
*
|
||||
* In this version of `dom-if`, the `this.__instance` method is the
|
||||
* `DocumentFragment` returned from `_stampTemplate`, which also serves as the
|
||||
* handle for later removing it using the `_removeBoundDom` method.
|
||||
*/
|
||||
class DomIfFast extends DomIfBase {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.__instance = null;
|
||||
this.__syncInfo = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of abstract API needed by DomIfBase.
|
||||
*
|
||||
* @override
|
||||
* @return {boolean} True when an instance has been created.
|
||||
*/
|
||||
__hasInstance() {
|
||||
return Boolean(this.__instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of abstract API needed by DomIfBase.
|
||||
*
|
||||
* @override
|
||||
* @return {Array<Node>} Array of child nodes stamped from the template
|
||||
* instance.
|
||||
*/
|
||||
__getInstanceNodes() {
|
||||
return this.__instance.templateInfo.childNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of abstract API needed by DomIfBase.
|
||||
*
|
||||
* Stamps the template by calling `_stampTemplate` on the `__dataHost` of this
|
||||
* element and then inserts the resulting nodes into the given `parentNode`.
|
||||
*
|
||||
* @override
|
||||
* @param {Node} parentNode The parent node to insert the instance into
|
||||
* @return {void}
|
||||
*/
|
||||
__createAndInsertInstance(parentNode) {
|
||||
const host = this.__dataHost || this;
|
||||
if (strictTemplatePolicy) {
|
||||
if (!this.__dataHost) {
|
||||
throw new Error('strictTemplatePolicy: template owner not trusted');
|
||||
}
|
||||
}
|
||||
// Pre-bind and link the template into the effects system
|
||||
const templateInfo = host._bindTemplate(
|
||||
/** @type {!HTMLTemplateElement} */ (this.__template), true);
|
||||
// Install runEffects hook that prevents running property effects
|
||||
// (and any nested template effects) when the `if` is false
|
||||
templateInfo.runEffects = (runEffects, changedProps, hasPaths) => {
|
||||
const syncInfo = this.__syncInfo;
|
||||
if (this.if) {
|
||||
// Mix any props that changed while the `if` was false into `changedProps`
|
||||
if (syncInfo) {
|
||||
// If there were properties received while the `if` was false, it is
|
||||
// important to sync the hidden state with the element _first_, so that
|
||||
// new bindings to e.g. `textContent` do not get stomped on by
|
||||
// pre-hidden values if `_showHideChildren` were to be called later at
|
||||
// the next render. Clearing `__invalidProps` here ensures
|
||||
// `_showHideChildren`'s call to `__syncHostProperties` no-ops, so
|
||||
// that we don't call `runEffects` more often than necessary.
|
||||
this.__syncInfo = null;
|
||||
this._showHideChildren();
|
||||
changedProps = Object.assign(syncInfo.changedProps, changedProps);
|
||||
hasPaths = hasPaths || syncInfo.hasPaths;
|
||||
}
|
||||
this.__ctor = templatize(template, this, {
|
||||
// dom-if templatizer instances require `mutable: true`, as
|
||||
// `__syncHostProperties` relies on that behavior to sync objects
|
||||
mutableData: true,
|
||||
/**
|
||||
* @param {string} prop Property to forward
|
||||
* @param {*} value Value of property
|
||||
* @this {DomIf}
|
||||
*/
|
||||
forwardHostProp: function(prop, value) {
|
||||
if (this.__instance) {
|
||||
if (this.if) {
|
||||
this.__instance.forwardHostProp(prop, value);
|
||||
} else {
|
||||
// If we have an instance but are squelching host property
|
||||
// forwarding due to if being false, note the invalidated
|
||||
// properties so `__syncHostProperties` can sync them the next
|
||||
// time `if` becomes true
|
||||
this.__invalidProps = this.__invalidProps || Object.create(null);
|
||||
this.__invalidProps[root(prop)] = true;
|
||||
runEffects(changedProps, hasPaths);
|
||||
} else {
|
||||
// Accumulate any values changed while `if` was false, along with the
|
||||
// runEffects method to sync them, so that we can replay them once `if`
|
||||
// becomes true
|
||||
if (syncInfo) {
|
||||
syncInfo.hasPaths = syncInfo.hasPaths || hasPaths;
|
||||
Object.assign(syncInfo.changedProps, changedProps);
|
||||
} else {
|
||||
this.__syncInfo = {
|
||||
runEffects,
|
||||
changedProps: Object.assign({}, changedProps),
|
||||
hasPaths
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
// Stamp the template, and set its DocumentFragment to the "instance"
|
||||
this.__instance = host._stampTemplate(
|
||||
/** @type {!HTMLTemplateElement} */ (this.__template), templateInfo);
|
||||
wrap(parentNode).insertBefore(this.__instance, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run effects for any properties that changed while the `if` was false.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
__syncHostProperties() {
|
||||
const syncInfo = this.__syncInfo;
|
||||
if (syncInfo) {
|
||||
this.__syncInfo = null;
|
||||
syncInfo.runEffects(syncInfo.changedProps, syncInfo.hasPaths);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of abstract API needed by DomIfBase.
|
||||
*
|
||||
* Remove the instance and any nodes it created. Uses the `__dataHost`'s
|
||||
* runtime `_removeBoundDom` method.
|
||||
*
|
||||
* @override
|
||||
* @return {void}
|
||||
*/
|
||||
__teardownInstance() {
|
||||
const host = this.__dataHost || this;
|
||||
if (this.__instance) {
|
||||
host._removeBoundDom(this.__instance);
|
||||
this.__instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of abstract API needed by DomIfBase.
|
||||
*
|
||||
* Shows or hides the template instance top level child nodes. For
|
||||
* text nodes, `textContent` is removed while "hidden" and replaced when
|
||||
* "shown."
|
||||
*
|
||||
* @override
|
||||
* @return {void}
|
||||
* @protected
|
||||
* @suppress {visibility}
|
||||
*/
|
||||
_showHideChildren() {
|
||||
const hidden = this.__hideTemplateChildren__ || !this.if;
|
||||
if (this.__instance && Boolean(this.__instance.__hidden) !== hidden) {
|
||||
this.__instance.__hidden = hidden;
|
||||
showHideChildren(hidden, this.__instance.templateInfo.childNodes);
|
||||
if (!hidden) {
|
||||
this.__syncHostProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The "legacy" implementation of `dom-if`, implemented using `Templatizer`.
|
||||
*
|
||||
* In this version, `this.__instance` is the `TemplateInstance` returned
|
||||
* from the templatized constructor.
|
||||
*/
|
||||
class DomIfLegacy extends DomIfBase {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.__ctor = null;
|
||||
this.__instance = null;
|
||||
this.__invalidProps = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of abstract API needed by DomIfBase.
|
||||
*
|
||||
* @override
|
||||
* @return {boolean} True when an instance has been created.
|
||||
*/
|
||||
__hasInstance() {
|
||||
return Boolean(this.__instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of abstract API needed by DomIfBase.
|
||||
*
|
||||
* @override
|
||||
* @return {Array<Node>} Array of child nodes stamped from the template
|
||||
* instance.
|
||||
*/
|
||||
__getInstanceNodes() {
|
||||
return this.__instance.children;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of abstract API needed by DomIfBase.
|
||||
*
|
||||
* Stamps the template by creating a new instance of the templatized
|
||||
* constructor (which is created lazily if it does not yet exist), and then
|
||||
* inserts its resulting `root` doc fragment into the given `parentNode`.
|
||||
*
|
||||
* @override
|
||||
* @param {Node} parentNode The parent node to insert the instance into
|
||||
* @return {void}
|
||||
*/
|
||||
__createAndInsertInstance(parentNode) {
|
||||
// Ensure we have an instance constructor
|
||||
if (!this.__ctor) {
|
||||
this.__ctor = templatize(
|
||||
/** @type {!HTMLTemplateElement} */ (this.__template), this, {
|
||||
// dom-if templatizer instances require `mutable: true`, as
|
||||
// `__syncHostProperties` relies on that behavior to sync objects
|
||||
mutableData: true,
|
||||
/**
|
||||
* @param {string} prop Property to forward
|
||||
* @param {*} value Value of property
|
||||
* @this {DomIfLegacy}
|
||||
*/
|
||||
forwardHostProp: function(prop, value) {
|
||||
if (this.__instance) {
|
||||
if (this.if) {
|
||||
this.__instance.forwardHostProp(prop, value);
|
||||
} else {
|
||||
// If we have an instance but are squelching host property
|
||||
// forwarding due to if being false, note the invalidated
|
||||
// properties so `__syncHostProperties` can sync them the next
|
||||
// time `if` becomes true
|
||||
this.__invalidProps =
|
||||
this.__invalidProps || Object.create(null);
|
||||
this.__invalidProps[root(prop)] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!this.__instance) {
|
||||
this.__instance = new this.__ctor();
|
||||
wrap(parentNode).insertBefore(this.__instance.root, this);
|
||||
} else {
|
||||
this.__syncHostProperties();
|
||||
let c$ = this.__instance.children;
|
||||
if (c$ && c$.length) {
|
||||
// Detect case where dom-if was re-attached in new position
|
||||
let lastChild = wrap(this).previousSibling;
|
||||
if (lastChild !== c$[c$.length-1]) {
|
||||
for (let i=0, n; (i<c$.length) && (n=c$[i]); i++) {
|
||||
wrap(parentNode).insertBefore(n, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
__syncHostProperties() {
|
||||
let props = this.__invalidProps;
|
||||
if (props) {
|
||||
for (let prop in props) {
|
||||
this.__instance._setPendingProperty(prop, this.__dataHost[prop]);
|
||||
}
|
||||
this.__invalidProps = null;
|
||||
this.__instance._flushProperties();
|
||||
});
|
||||
}
|
||||
// Create and insert the instance
|
||||
this.__instance = new this.__ctor();
|
||||
wrap(parentNode).insertBefore(this.__instance.root, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of abstract API needed by DomIfBase.
|
||||
*
|
||||
* Removes the instance and any nodes it created.
|
||||
*
|
||||
* @override
|
||||
* @return {void}
|
||||
*/
|
||||
__teardownInstance() {
|
||||
if (this.__instance) {
|
||||
let c$ = this.__instance.children;
|
||||
@@ -267,26 +576,74 @@ export class DomIf extends PolymerElement {
|
||||
}
|
||||
}
|
||||
}
|
||||
this.__invalidProps = null;
|
||||
this.__instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forwards any properties that changed while the `if` was false into the
|
||||
* template instance and flushes it.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
__syncHostProperties() {
|
||||
let props = this.__invalidProps;
|
||||
if (props) {
|
||||
for (let prop in props) {
|
||||
this.__instance._setPendingProperty(prop, this.__dataHost[prop]);
|
||||
}
|
||||
this.__instance._flushProperties();
|
||||
this.__invalidProps = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of abstract API needed by DomIfBase.
|
||||
*
|
||||
* Shows or hides the template instance top level child elements. For
|
||||
* text nodes, `textContent` is removed while "hidden" and replaced when
|
||||
* "shown."
|
||||
* @return {void}
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @return {void}
|
||||
* @suppress {visibility}
|
||||
*/
|
||||
_showHideChildren() {
|
||||
let hidden = this.__hideTemplateChildren__ || !this.if;
|
||||
if (this.__instance) {
|
||||
const hidden = this.__hideTemplateChildren__ || !this.if;
|
||||
if (this.__instance && Boolean(this.__instance.__hidden) !== hidden) {
|
||||
this.__instance.__hidden = hidden;
|
||||
this.__instance._showHideChildren(hidden);
|
||||
if (!hidden) {
|
||||
this.__syncHostProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The `<dom-if>` element will stamp a light-dom `<template>` child when
|
||||
* the `if` property becomes truthy, and the template can use Polymer
|
||||
* data-binding and declarative event features when used in the context of
|
||||
* a Polymer element's template.
|
||||
*
|
||||
* When `if` becomes falsy, the stamped content is hidden but not
|
||||
* removed from dom. When `if` subsequently becomes truthy again, the content
|
||||
* is simply re-shown. This approach is used due to its favorable performance
|
||||
* characteristics: the expense of creating template content is paid only
|
||||
* once and lazily.
|
||||
*
|
||||
* Set the `restamp` property to true to force the stamped content to be
|
||||
* created / destroyed when the `if` condition changes.
|
||||
*
|
||||
* @customElement
|
||||
* @polymer
|
||||
* @extends DomIfBase
|
||||
* @constructor
|
||||
* @summary Custom element that conditionally stamps and hides or removes
|
||||
* template content based on a boolean flag.
|
||||
*/
|
||||
export const DomIf = fastDomIf ? DomIfFast : DomIfLegacy;
|
||||
|
||||
customElements.define(DomIf.is, DomIf);
|
||||
|
||||
@@ -301,6 +301,8 @@ export class DomRepeat extends domRepeatBase {
|
||||
this.__ctor = null;
|
||||
this.__isDetached = true;
|
||||
this.template = null;
|
||||
/** @type {TemplateInfo} */
|
||||
this._templateInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -339,9 +341,15 @@ export class DomRepeat extends domRepeatBase {
|
||||
// until ready, since won't have its template content handed back to
|
||||
// it until then
|
||||
if (!this.__ctor) {
|
||||
let template = this.template = /** @type {HTMLTemplateElement} */(this.querySelector('template'));
|
||||
// When `removeNestedTemplates` is true, the "template" is the element
|
||||
// itself, which has been given a `_templateInfo` property
|
||||
const thisAsTemplate = /** @type {!HTMLTemplateElement} */ (
|
||||
/** @type {!HTMLElement} */ (this));
|
||||
let template = this.template = thisAsTemplate._templateInfo ?
|
||||
thisAsTemplate :
|
||||
/** @type {!HTMLTemplateElement} */ (this.querySelector('template'));
|
||||
if (!template) {
|
||||
// // Wait until childList changes and template should be there by then
|
||||
// Wait until childList changes and template should be there by then
|
||||
let observer = new MutationObserver(() => {
|
||||
if (this.querySelector('template')) {
|
||||
observer.disconnect();
|
||||
|
||||
+119
-42
@@ -19,7 +19,7 @@ import { camelToDashCase, dashToCamelCase } from '../utils/case-map.js';
|
||||
import { PropertyAccessors } from './property-accessors.js';
|
||||
/* for annotated effects */
|
||||
import { TemplateStamp } from './template-stamp.js';
|
||||
import { sanitizeDOMValue, legacyUndefined, legacyNoBatch, legacyNotifyOrder, orderedComputed } from '../utils/settings.js';
|
||||
import { sanitizeDOMValue, legacyUndefined, legacyNoBatch, legacyNotifyOrder, orderedComputed, removeNestedTemplates, fastDomIf } from '../utils/settings.js';
|
||||
|
||||
// Monotonically increasing unique ID used for de-duping effects triggered
|
||||
// from multiple properties in the same turn
|
||||
@@ -1285,9 +1285,6 @@ function upper(name) {
|
||||
* @appliesMixin PropertyAccessors
|
||||
* @summary Element class mixin that provides meta-programming for Polymer's
|
||||
* template binding and data observation system.
|
||||
* @template T
|
||||
* @param {function(new:T)} superClass Class to apply mixin to.
|
||||
* @return {function(new:T)} superClass with mixin applied.
|
||||
*/
|
||||
export const PropertyEffects = dedupingMixin(superClass => {
|
||||
|
||||
@@ -1361,9 +1358,6 @@ export const PropertyEffects = dedupingMixin(superClass => {
|
||||
this.__templateInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!Object<string, string>} Effect prototype property name map.
|
||||
*/
|
||||
get PROPERTY_EFFECT_TYPES() {
|
||||
return TYPES;
|
||||
}
|
||||
@@ -1989,11 +1983,23 @@ export const PropertyEffects = dedupingMixin(superClass => {
|
||||
if (this[TYPES.PROPAGATE]) {
|
||||
runEffects(this, this[TYPES.PROPAGATE], changedProps, oldProps, hasPaths);
|
||||
}
|
||||
let templateInfo = this.__templateInfo;
|
||||
while (templateInfo) {
|
||||
if (this.__templateInfo) {
|
||||
this._runEffectsForTemplate(this.__templateInfo, changedProps, oldProps, hasPaths);
|
||||
}
|
||||
}
|
||||
|
||||
_runEffectsForTemplate(templateInfo, changedProps, oldProps, hasPaths) {
|
||||
const baseRunEffects = (changedProps, hasPaths) => {
|
||||
runEffects(this, templateInfo.propertyEffects, changedProps, oldProps,
|
||||
hasPaths, templateInfo.nodeList);
|
||||
templateInfo = templateInfo.nextTemplateInfo;
|
||||
for (let info=templateInfo.firstChild; info; info=info.nextSibling) {
|
||||
this._runEffectsForTemplate(info, changedProps, oldProps, hasPaths);
|
||||
}
|
||||
};
|
||||
if (templateInfo.runEffects) {
|
||||
templateInfo.runEffects(baseRunEffects, changedProps, hasPaths);
|
||||
} else {
|
||||
baseRunEffects(changedProps, hasPaths);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2686,7 +2692,7 @@ export const PropertyEffects = dedupingMixin(superClass => {
|
||||
*/
|
||||
_bindTemplate(template, instanceBinding) {
|
||||
let templateInfo = this.constructor._parseTemplate(template);
|
||||
let wasPreBound = this.__templateInfo == templateInfo;
|
||||
let wasPreBound = this.__preBoundTemplateInfo == templateInfo;
|
||||
// Optimization: since this is called twice for proto-bound templates,
|
||||
// don't attempt to recreate accessors if this template was pre-bound
|
||||
if (!wasPreBound) {
|
||||
@@ -2696,17 +2702,39 @@ export const PropertyEffects = dedupingMixin(superClass => {
|
||||
}
|
||||
if (instanceBinding) {
|
||||
// For instance-time binding, create instance of template metadata
|
||||
// and link into list of templates if necessary
|
||||
// and link into tree of templates if necessary
|
||||
templateInfo = /** @type {!TemplateInfo} */(Object.create(templateInfo));
|
||||
templateInfo.wasPreBound = wasPreBound;
|
||||
if (!wasPreBound && this.__templateInfo) {
|
||||
let last = this.__templateInfoLast || this.__templateInfo;
|
||||
this.__templateInfoLast = last.nextTemplateInfo = templateInfo;
|
||||
templateInfo.previousTemplateInfo = last;
|
||||
return templateInfo;
|
||||
if (!this.__templateInfo) {
|
||||
// Set the info to the root of the tree
|
||||
this.__templateInfo = templateInfo;
|
||||
} else {
|
||||
// Append this template info onto the end of its parent template's
|
||||
// list, which will determine the tree structure via which property
|
||||
// effects are run; if this template was not nested in another
|
||||
// template, use the root template (the first stamped one) as the
|
||||
// parent. Note, `parent` is the `templateInfo` instance for this
|
||||
// template's parent (containing) template, which was set up in
|
||||
// `applyTemplateContent`. While a given template's `parent` is set
|
||||
// apriori, it is only added to the parent's child list at the point
|
||||
// that it is being bound, since a template may or may not ever be
|
||||
// stamped, and may be stamped more than once (in which case instances
|
||||
// of the template info will be in the tree under its parent more than
|
||||
// once).
|
||||
const parent = templateInfo.parent || this.__templateInfo;
|
||||
const previous = parent.lastChild;
|
||||
parent.lastChild = templateInfo;
|
||||
templateInfo.previousSibling = previous;
|
||||
if (previous) {
|
||||
previous.nextSibling = templateInfo;
|
||||
} else {
|
||||
parent.firstChild = templateInfo;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.__preBoundTemplateInfo = templateInfo;
|
||||
}
|
||||
return this.__templateInfo = templateInfo;
|
||||
return templateInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2747,17 +2775,20 @@ export const PropertyEffects = dedupingMixin(superClass => {
|
||||
* in the main element template.
|
||||
*
|
||||
* @param {!HTMLTemplateElement} template Template to stamp
|
||||
* @param {TemplateInfo=} templateInfo Optional bound template info associated
|
||||
* with the template to be stamped; if omitted the template will be
|
||||
* automatically bound.
|
||||
* @return {!StampedTemplate} Cloned template content
|
||||
* @override
|
||||
* @protected
|
||||
*/
|
||||
_stampTemplate(template) {
|
||||
_stampTemplate(template, templateInfo) {
|
||||
templateInfo = templateInfo || /** @type {!TemplateInfo} */(this._bindTemplate(template, true));
|
||||
// Ensures that created dom is `_enqueueClient`'d to this element so
|
||||
// that it can be flushed on next call to `_flushProperties`
|
||||
hostStack.beginHosting(this);
|
||||
let dom = super._stampTemplate(template);
|
||||
let dom = super._stampTemplate(template, templateInfo);
|
||||
hostStack.endHosting(this);
|
||||
let templateInfo = /** @type {!TemplateInfo} */(this._bindTemplate(template, true));
|
||||
// Add template-instance-specific data to instanced templateInfo
|
||||
templateInfo.nodeList = dom.nodeList;
|
||||
// Capture child nodes to allow unstamping of non-prototypical templates
|
||||
@@ -2770,10 +2801,17 @@ export const PropertyEffects = dedupingMixin(superClass => {
|
||||
dom.templateInfo = templateInfo;
|
||||
// Setup compound storage, 2-way listeners, and dataHost for bindings
|
||||
setupBindings(this, templateInfo);
|
||||
// Flush properties into template nodes if already booted
|
||||
if (this.__dataReady) {
|
||||
runEffects(this, templateInfo.propertyEffects, this.__data, null,
|
||||
false, templateInfo.nodeList);
|
||||
// Flush properties into template nodes; the check on `__dataClientsReady`
|
||||
// ensures we don't needlessly run effects for an element's initial
|
||||
// prototypical template stamping since they will happen as a part of the
|
||||
// first call to `_propertiesChanged`. This flag is set to true
|
||||
// after running the initial propagate effects, and immediately before
|
||||
// flushing clients. Since downstream clients could cause stamping on
|
||||
// this host (e.g. a fastDomIf `dom-if` being forced to render
|
||||
// synchronously), this flag ensures effects for runtime-stamped templates
|
||||
// are run at this point during the initial element boot-up.
|
||||
if (this.__dataClientsReady) {
|
||||
this._runEffectsForTemplate(templateInfo, this.__data, null, false);
|
||||
}
|
||||
return dom;
|
||||
}
|
||||
@@ -2789,25 +2827,27 @@ export const PropertyEffects = dedupingMixin(superClass => {
|
||||
* @protected
|
||||
*/
|
||||
_removeBoundDom(dom) {
|
||||
// Unlink template info
|
||||
// Unlink template info; Note that while the child is unlinked from its
|
||||
// parent list, a template's `parent` reference is never removed, since
|
||||
// this is is determined by the tree structure and applied at
|
||||
// `applyTemplateContent` time.
|
||||
let templateInfo = dom.templateInfo;
|
||||
if (templateInfo.previousTemplateInfo) {
|
||||
templateInfo.previousTemplateInfo.nextTemplateInfo =
|
||||
templateInfo.nextTemplateInfo;
|
||||
const {previousSibling, nextSibling, parent} = templateInfo;
|
||||
if (previousSibling) {
|
||||
previousSibling.nextSibling = nextSibling;
|
||||
} else if (parent) {
|
||||
parent.firstChild = nextSibling;
|
||||
}
|
||||
if (templateInfo.nextTemplateInfo) {
|
||||
templateInfo.nextTemplateInfo.previousTemplateInfo =
|
||||
templateInfo.previousTemplateInfo;
|
||||
if (nextSibling) {
|
||||
nextSibling.previousSibling = previousSibling;
|
||||
} else if (parent) {
|
||||
parent.lastChild = previousSibling;
|
||||
}
|
||||
if (this.__templateInfoLast == templateInfo) {
|
||||
this.__templateInfoLast = templateInfo.previousTemplateInfo;
|
||||
}
|
||||
templateInfo.previousTemplateInfo = templateInfo.nextTemplateInfo = null;
|
||||
// Remove stamped nodes
|
||||
let nodes = templateInfo.childNodes;
|
||||
for (let i=0; i<nodes.length; i++) {
|
||||
let node = nodes[i];
|
||||
node.parentNode.removeChild(node);
|
||||
wrap(wrap(node).parentNode).removeChild(node);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2936,12 +2976,49 @@ export const PropertyEffects = dedupingMixin(superClass => {
|
||||
// Change back to just super.methodCall()
|
||||
let noted = propertyEffectsBase._parseTemplateNestedTemplate.call(
|
||||
this, node, templateInfo, nodeInfo);
|
||||
const parent = node.parentNode;
|
||||
const nestedTemplateInfo = nodeInfo.templateInfo;
|
||||
const isDomIf = parent.localName === 'dom-if';
|
||||
const isDomRepeat = parent.localName === 'dom-repeat';
|
||||
// Remove nested template and redirect its host bindings & templateInfo
|
||||
// onto the parent (dom-if/repeat element)'s nodeInfo
|
||||
if (removeNestedTemplates && (isDomIf || isDomRepeat)) {
|
||||
parent.removeChild(node);
|
||||
// Use the parent's nodeInfo (for the dom-if/repeat) to record the
|
||||
// templateInfo, and use that for any host property bindings below
|
||||
nodeInfo = nodeInfo.parentInfo;
|
||||
nodeInfo.templateInfo = nestedTemplateInfo;
|
||||
// Ensure the parent dom-if/repeat is noted since it now may have host
|
||||
// bindings; it may not have been if it did not have its own bindings
|
||||
nodeInfo.noted = true;
|
||||
noted = false;
|
||||
}
|
||||
// Merge host props into outer template and add bindings
|
||||
let hostProps = nodeInfo.templateInfo.hostProps;
|
||||
let mode = '{';
|
||||
for (let source in hostProps) {
|
||||
let parts = [{ mode, source, dependencies: [source], hostProp: true }];
|
||||
addBinding(this, templateInfo, nodeInfo, 'property', '_host_' + source, parts);
|
||||
let hostProps = nestedTemplateInfo.hostProps;
|
||||
if (fastDomIf && isDomIf) {
|
||||
// `fastDomIf` mode uses runtime-template stamping to add accessors/
|
||||
// effects to properties used in its template; as such we don't need to
|
||||
// tax the host element with `_host_` bindings for the `dom-if`.
|
||||
// However, in the event it is nested in a `dom-repeat`, it is still
|
||||
// important that its host properties are added to the
|
||||
// TemplateInstance's `hostProps` so that they are forwarded to the
|
||||
// TemplateInstance.
|
||||
if (hostProps) {
|
||||
templateInfo.hostProps =
|
||||
Object.assign(templateInfo.hostProps || {}, hostProps);
|
||||
// Ensure the dom-if is noted so that it has a __dataHost, since
|
||||
// `fastDomIf` uses the host for runtime template stamping; note this
|
||||
// was already ensured above in the `removeNestedTemplates` case
|
||||
if (!removeNestedTemplates) {
|
||||
nodeInfo.parentInfo.noted = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let mode = '{';
|
||||
for (let source in hostProps) {
|
||||
let parts = [{ mode, source, dependencies: [source], hostProp: true }];
|
||||
addBinding(this, templateInfo, nodeInfo, 'property', '_host_' + source, parts);
|
||||
}
|
||||
}
|
||||
return noted;
|
||||
}
|
||||
|
||||
@@ -72,9 +72,11 @@ function applyEventListener(inst, node, nodeInfo) {
|
||||
}
|
||||
|
||||
// push configuration references at configure time
|
||||
function applyTemplateContent(inst, node, nodeInfo) {
|
||||
function applyTemplateContent(inst, node, nodeInfo, parentTemplateInfo) {
|
||||
if (nodeInfo.templateInfo) {
|
||||
node._templateInfo = nodeInfo.templateInfo;
|
||||
// Give the node an instance of this templateInfo and set its parent
|
||||
node._templateInfo = Object.create(nodeInfo.templateInfo);
|
||||
node._templateInfo.parent = parentTemplateInfo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,9 +106,6 @@ function createNodeEventHandler(context, eventName, methodName) {
|
||||
* @mixinFunction
|
||||
* @polymer
|
||||
* @summary Element class mixin that provides basic template parsing and stamping
|
||||
* @template T
|
||||
* @param {function(new:T)} superClass Class to apply mixin to.
|
||||
* @return {function(new:T)} superClass with mixin applied.
|
||||
*/
|
||||
export const TemplateStamp = dedupingMixin(
|
||||
/**
|
||||
@@ -258,7 +257,11 @@ export const TemplateStamp = dedupingMixin(
|
||||
if (element.hasAttributes && element.hasAttributes()) {
|
||||
noted = this._parseTemplateNodeAttributes(element, templateInfo, nodeInfo) || noted;
|
||||
}
|
||||
return noted;
|
||||
// Checking `nodeInfo.noted` allows a child node of this node (who gets
|
||||
// access to `parentInfo`) to cause the parent to be noted, which
|
||||
// otherwise has no return path via `_parseTemplateChildNodes` (used by
|
||||
// some optimizations)
|
||||
return noted || nodeInfo.noted;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -437,16 +440,22 @@ export const TemplateStamp = dedupingMixin(
|
||||
* is removed and stored in notes as well.
|
||||
*
|
||||
* @param {!HTMLTemplateElement} template Template to stamp
|
||||
* @param {TemplateInfo=} templateInfo Optional template info associated
|
||||
* with the template to be stamped; if omitted the template will be
|
||||
* automatically parsed.
|
||||
* @return {!StampedTemplate} Cloned template content
|
||||
* @override
|
||||
*/
|
||||
_stampTemplate(template) {
|
||||
_stampTemplate(template, templateInfo) {
|
||||
// Polyfill support: bootstrap the template if it has not already been
|
||||
if (template && !template.content &&
|
||||
window.HTMLTemplateElement && HTMLTemplateElement.decorate) {
|
||||
HTMLTemplateElement.decorate(template);
|
||||
}
|
||||
let templateInfo = this.constructor._parseTemplate(template);
|
||||
// Accepting the `templateInfo` via an argument allows for creating
|
||||
// instances of the `templateInfo` by the caller, useful for adding
|
||||
// instance-time information to the prototypical data
|
||||
templateInfo = templateInfo || this.constructor._parseTemplate(template);
|
||||
let nodeInfo = templateInfo.nodeInfoList;
|
||||
let content = templateInfo.content || template.content;
|
||||
let dom = /** @type {DocumentFragment} */ (document.importNode(content, true));
|
||||
@@ -457,7 +466,7 @@ export const TemplateStamp = dedupingMixin(
|
||||
for (let i=0, l=nodeInfo.length, info; (i<l) && (info=nodeInfo[i]); i++) {
|
||||
let node = nodes[i] = findTemplateNode(dom, info);
|
||||
applyIdToMap(this, dom.$, node, info);
|
||||
applyTemplateContent(this, node, info);
|
||||
applyTemplateContent(this, node, info, templateInfo);
|
||||
applyEventListener(this, node, info);
|
||||
}
|
||||
dom = /** @type {!StampedTemplate} */(dom); // eslint-disable-line no-self-assign
|
||||
|
||||
+65
-12
@@ -23,7 +23,8 @@ export const useNativeCustomElements = !(window.customElements.polyfillWrapFlush
|
||||
* `rootPath` to provide a stable application mount path when
|
||||
* using client side routing.
|
||||
*/
|
||||
export let rootPath = pathFromUrl(document.baseURI || window.location.href);
|
||||
export let rootPath = window.Polymer && window.Polymer.rootPath ||
|
||||
pathFromUrl(document.baseURI || window.location.href);
|
||||
|
||||
/**
|
||||
* Sets the global rootPath property used by `ElementMixin` and
|
||||
@@ -51,7 +52,8 @@ export const setRootPath = function(path) {
|
||||
*
|
||||
* @type {(function(*,string,string,Node):*)|undefined}
|
||||
*/
|
||||
export let sanitizeDOMValue = window.Polymer && window.Polymer.sanitizeDOMValue || undefined;
|
||||
export let sanitizeDOMValue =
|
||||
window.Polymer && window.Polymer.sanitizeDOMValue || undefined;
|
||||
|
||||
/**
|
||||
* Sets the global sanitizeDOMValue available via this module's exported
|
||||
@@ -70,7 +72,8 @@ export const setSanitizeDOMValue = function(newSanitizeDOMValue) {
|
||||
* scrolling performance.
|
||||
* Defaults to `false` for backwards compatibility.
|
||||
*/
|
||||
export let passiveTouchGestures = false;
|
||||
export let passiveTouchGestures =
|
||||
window.Polymer && window.Polymer.setPassiveTouchGestures || false;
|
||||
|
||||
/**
|
||||
* Sets `passiveTouchGestures` globally for all elements using Polymer Gestures.
|
||||
@@ -88,7 +91,8 @@ export const setPassiveTouchGestures = function(usePassive) {
|
||||
* disallowed, `<dom-bind>` is disabled, and `<dom-if>`/`<dom-repeat>`
|
||||
* templates will only evaluate in the context of a trusted element template.
|
||||
*/
|
||||
export let strictTemplatePolicy = false;
|
||||
export let strictTemplatePolicy =
|
||||
window.Polymer && window.Polymer.strictTemplatePolicy || false;
|
||||
|
||||
/**
|
||||
* Sets `strictTemplatePolicy` globally for all elements
|
||||
@@ -107,7 +111,8 @@ export const setStrictTemplatePolicy = function(useStrictPolicy) {
|
||||
* getter and the `html` tag function. To enable legacy loading of templates
|
||||
* via dom-module, set this flag to true.
|
||||
*/
|
||||
export let allowTemplateFromDomModule = false;
|
||||
export let allowTemplateFromDomModule =
|
||||
window.Polymer && window.Polymer.allowTemplateFromDomModule || false;
|
||||
|
||||
/**
|
||||
* Sets `lookupTemplateFromDomModule` globally for all elements
|
||||
@@ -127,7 +132,8 @@ export const setAllowTemplateFromDomModule = function(allowDomModule) {
|
||||
* If no includes or relative urls are used in styles, these steps can be
|
||||
* skipped as an optimization.
|
||||
*/
|
||||
export let legacyOptimizations = false;
|
||||
export let legacyOptimizations =
|
||||
window.Polymer && window.Polymer.legacyOptimizations || false;
|
||||
|
||||
/**
|
||||
* Sets `legacyOptimizations` globally for all elements to enable optimizations
|
||||
@@ -144,7 +150,8 @@ export const setLegacyOptimizations = function(useLegacyOptimizations) {
|
||||
/**
|
||||
* Setting to add warnings useful when migrating from Polymer 1.x to 2.x.
|
||||
*/
|
||||
export let legacyWarnings = false;
|
||||
export let legacyWarnings =
|
||||
window.Polymer && window.Polymer.legacyWarnings || false;
|
||||
|
||||
/**
|
||||
* Sets `legacyWarnings` globally for all elements to migration warnings.
|
||||
@@ -160,7 +167,8 @@ export const setLegacyWarnings = function(useLegacyWarnings) {
|
||||
* Setting to perform initial rendering synchronously when running under ShadyDOM.
|
||||
* This matches the behavior of Polymer 1.
|
||||
*/
|
||||
export let syncInitialRender = false;
|
||||
export let syncInitialRender =
|
||||
window.Polymer && window.Polymer.syncInitialRender || false;
|
||||
|
||||
/**
|
||||
* Sets `syncInitialRender` globally for all elements to enable synchronous
|
||||
@@ -179,7 +187,8 @@ export const setSyncInitialRender = function(useSyncInitialRender) {
|
||||
* observers around undefined values. Observers and computed property methods
|
||||
* are not called until no argument is undefined.
|
||||
*/
|
||||
export let legacyUndefined = false;
|
||||
export let legacyUndefined =
|
||||
window.Polymer && window.Polymer.legacyUndefined || false;
|
||||
|
||||
/**
|
||||
* Sets `legacyUndefined` globally for all elements to enable legacy
|
||||
@@ -197,7 +206,8 @@ export const setLegacyUndefined = function(useLegacyUndefined) {
|
||||
* Setting to retain the legacy Polymer 1 behavior for setting properties. Does
|
||||
* not batch property sets.
|
||||
*/
|
||||
export let legacyNoBatch = false;
|
||||
export let legacyNoBatch =
|
||||
window.Polymer && window.Polymer.legacyNoBatch || false;
|
||||
|
||||
/**
|
||||
* Sets `legacyNoBatch` globally for all elements to enable legacy
|
||||
@@ -216,7 +226,8 @@ export const setLegacyNoBatch = function(useLegacyNoBatch) {
|
||||
* fire change events with respect to other effects. In Polymer 1.x they fire
|
||||
* before observers; in 2.x they fire after all other effect types.
|
||||
*/
|
||||
export let legacyNotifyOrder = false;
|
||||
export let legacyNotifyOrder =
|
||||
window.Polymer && window.Polymer.legacyNotifyOrder || false;
|
||||
|
||||
/**
|
||||
* Sets `legacyNotifyOrder` globally for all elements to enable legacy
|
||||
@@ -233,7 +244,8 @@ export const setLegacyNotifyOrder = function(useLegacyNotifyOrder) {
|
||||
* Setting to ensure computed properties are computed in order to ensure
|
||||
* re-computation never occurs in a given turn.
|
||||
*/
|
||||
export let orderedComputed = false;
|
||||
export let orderedComputed =
|
||||
window.Polymer && window.Polymer.orderedComputed || false;
|
||||
|
||||
/**
|
||||
* Sets `orderedComputed` globally for all elements to enable ordered computed
|
||||
@@ -263,3 +275,44 @@ export let cancelSyntheticClickEvents = true;
|
||||
export const setCancelSyntheticClickEvents = function(useCancelSyntheticClickEvents) {
|
||||
cancelSyntheticClickEvents = useCancelSyntheticClickEvents;
|
||||
};
|
||||
|
||||
/**
|
||||
* Setting to remove nested templates inside `dom-if` and `dom-repeat` as
|
||||
* part of element template parsing. This is a performance optimization that
|
||||
* eliminates most of the tax of needing two elements due to the loss of
|
||||
* type-extended templates as a result of the V1 specification changes.
|
||||
*/
|
||||
export let removeNestedTemplates =
|
||||
window.Polymer && window.Polymer.removeNestedTemplates || false;
|
||||
|
||||
/**
|
||||
* Sets `removeNestedTemplates` globally, to eliminate nested templates
|
||||
* inside `dom-if` and `dom-repeat` as part of template parsing.
|
||||
*
|
||||
* @param {boolean} useRemoveNestedTemplates enable or disable removing nested
|
||||
* templates during parsing
|
||||
* @return {void}
|
||||
*/
|
||||
export const setRemoveNestedTemplates = function(useRemoveNestedTemplates) {
|
||||
removeNestedTemplates = useRemoveNestedTemplates;
|
||||
};
|
||||
|
||||
/**
|
||||
* Setting to place `dom-if` elements in a performance-optimized mode that takes
|
||||
* advantage of lighter-weight host runtime template stamping to eliminate the
|
||||
* need for an intermediate Templatizer `TemplateInstance` to mange the nodes
|
||||
* stamped by `dom-if`. Under this setting, any Templatizer-provided API's
|
||||
* such as `modelForElement` will not be available for nodes stamped by
|
||||
* `dom-if`.
|
||||
*/
|
||||
export let fastDomIf = window.Polymer && window.Polymer.fastDomIf || false;
|
||||
|
||||
/**
|
||||
* Sets `fastDomIf` globally, to put `dom-if` in a performance-optimized mode.
|
||||
*
|
||||
* @param {boolean} useFastDomIf enable or disable `dom-if` fast-mode
|
||||
* @return {void}
|
||||
*/
|
||||
export const setFastDomIf = function(useFastDomIf) {
|
||||
fastDomIf = useFastDomIf;
|
||||
};
|
||||
|
||||
+106
-68
@@ -102,10 +102,49 @@ function upgradeTemplate(template, constructor) {
|
||||
* @implements {Polymer_PropertyEffects}
|
||||
* @private
|
||||
*/
|
||||
const templateInstanceBase = PropertyEffects(
|
||||
// This cast shouldn't be neccessary, but Closure doesn't understand that
|
||||
// "class {}" is a constructor function.
|
||||
/** @type {function(new:Object)} */(class {}));
|
||||
const templateInstanceBase = PropertyEffects(class {});
|
||||
|
||||
export function showHideChildren(hide, children) {
|
||||
for (let i=0; i<children.length; i++) {
|
||||
let n = children[i];
|
||||
// Ignore non-changes
|
||||
if (Boolean(hide) != Boolean(n.__hideTemplateChildren__)) {
|
||||
// clear and restore text
|
||||
if (n.nodeType === Node.TEXT_NODE) {
|
||||
if (hide) {
|
||||
n.__polymerTextContent__ = n.textContent;
|
||||
n.textContent = '';
|
||||
} else {
|
||||
n.textContent = n.__polymerTextContent__;
|
||||
}
|
||||
// remove and replace slot
|
||||
} else if (n.localName === 'slot') {
|
||||
if (hide) {
|
||||
n.__polymerReplaced__ = document.createComment('hidden-slot');
|
||||
wrap(wrap(n).parentNode).replaceChild(n.__polymerReplaced__, n);
|
||||
} else {
|
||||
const replace = n.__polymerReplaced__;
|
||||
if (replace) {
|
||||
wrap(wrap(replace).parentNode).replaceChild(n, replace);
|
||||
}
|
||||
}
|
||||
}
|
||||
// hide and show nodes
|
||||
else if (n.style) {
|
||||
if (hide) {
|
||||
n.__polymerDisplay__ = n.style.display;
|
||||
n.style.display = 'none';
|
||||
} else {
|
||||
n.style.display = n.__polymerDisplay__;
|
||||
}
|
||||
}
|
||||
}
|
||||
n.__hideTemplateChildren__ = hide;
|
||||
if (n._showHideChildren) {
|
||||
n._showHideChildren(hide);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @polymer
|
||||
@@ -212,45 +251,7 @@ class TemplateInstanceBase extends templateInstanceBase {
|
||||
* @protected
|
||||
*/
|
||||
_showHideChildren(hide) {
|
||||
let c = this.children;
|
||||
for (let i=0; i<c.length; i++) {
|
||||
let n = c[i];
|
||||
// Ignore non-changes
|
||||
if (Boolean(hide) != Boolean(n.__hideTemplateChildren__)) {
|
||||
if (n.nodeType === Node.TEXT_NODE) {
|
||||
if (hide) {
|
||||
n.__polymerTextContent__ = n.textContent;
|
||||
n.textContent = '';
|
||||
} else {
|
||||
n.textContent = n.__polymerTextContent__;
|
||||
}
|
||||
// remove and replace slot
|
||||
} else if (n.localName === 'slot') {
|
||||
if (hide) {
|
||||
n.__polymerReplaced__ = document.createComment('hidden-slot');
|
||||
wrap(wrap(n).parentNode).replaceChild(n.__polymerReplaced__, n);
|
||||
} else {
|
||||
const replace = n.__polymerReplaced__;
|
||||
if (replace) {
|
||||
wrap(wrap(replace).parentNode).replaceChild(n, replace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (n.style) {
|
||||
if (hide) {
|
||||
n.__polymerDisplay__ = n.style.display;
|
||||
n.style.display = 'none';
|
||||
} else {
|
||||
n.style.display = n.__polymerDisplay__;
|
||||
}
|
||||
}
|
||||
}
|
||||
n.__hideTemplateChildren__ = hide;
|
||||
if (n._showHideChildren) {
|
||||
n._showHideChildren(hide);
|
||||
}
|
||||
}
|
||||
showHideChildren(hide, this.children);
|
||||
}
|
||||
/**
|
||||
* Overrides default property-effects implementation to intercept
|
||||
@@ -325,9 +326,9 @@ TemplateInstanceBase.prototype.__hostProps;
|
||||
* @private
|
||||
*/
|
||||
const MutableTemplateInstanceBase = MutableData(
|
||||
// This cast shouldn't be necessary, but Closure doesn't seem to understand
|
||||
// this constructor.
|
||||
/** @type {function(new:TemplateInstanceBase)} */(TemplateInstanceBase));
|
||||
// This cast shouldn't be neccessary, but Closure doesn't understand that
|
||||
// TemplateInstanceBase is a constructor function.
|
||||
/** @type {function(new:TemplateInstanceBase)} */ (TemplateInstanceBase));
|
||||
|
||||
function findMethodHost(template) {
|
||||
// Technically this should be the owner of the outermost template.
|
||||
@@ -372,23 +373,41 @@ function createTemplatizerClass(template, templateInfo, options) {
|
||||
/**
|
||||
* Adds propagate effects from the template to the template instance for
|
||||
* properties that the host binds to the template using the `_host_` prefix.
|
||||
*
|
||||
*
|
||||
* @suppress {missingProperties} class.prototype is not defined for some reason
|
||||
*/
|
||||
function addPropagateEffects(template, templateInfo, options, methodHost) {
|
||||
function addPropagateEffects(target, templateInfo, options, methodHost) {
|
||||
let userForwardHostProp = options.forwardHostProp;
|
||||
if (userForwardHostProp && templateInfo.hasHostProps) {
|
||||
// Under the `removeNestedTemplates` optimization, a custom element like
|
||||
// `dom-if` or `dom-repeat` can itself be treated as the "template"; this
|
||||
// flag is used to switch between upgrading a `<template>` to be a property
|
||||
// effects client vs. adding the effects directly to the custom element
|
||||
const isTemplate = target.localName == 'template';
|
||||
// Provide data API and property effects on memoized template class
|
||||
let klass = templateInfo.templatizeTemplateClass;
|
||||
if (!klass) {
|
||||
/**
|
||||
* @constructor
|
||||
* @extends {DataTemplate}
|
||||
*/
|
||||
let templatizedBase = options.mutableData ? MutableDataTemplate : DataTemplate;
|
||||
/** @private */
|
||||
klass = templateInfo.templatizeTemplateClass =
|
||||
class TemplatizedTemplate extends templatizedBase {};
|
||||
if (isTemplate) {
|
||||
/**
|
||||
* @constructor
|
||||
* @extends {DataTemplate}
|
||||
*/
|
||||
let templatizedBase = options.mutableData ? MutableDataTemplate : DataTemplate;
|
||||
/** @private */
|
||||
klass = templateInfo.templatizeTemplateClass =
|
||||
class TemplatizedTemplate extends templatizedBase {};
|
||||
} else {
|
||||
/**
|
||||
* @constructor
|
||||
* @extends {PolymerElement}
|
||||
*/
|
||||
const templatizedBase = target.constructor;
|
||||
// Create a cached subclass of the base custom element class onto which
|
||||
// to put the template-specific propagate effects
|
||||
/** @private */
|
||||
klass = templateInfo.templatizeTemplateClass =
|
||||
class TemplatizedTemplateExtension extends templatizedBase {};
|
||||
}
|
||||
// Add template - >instances effects
|
||||
// and host <- template effects
|
||||
let hostProps = templateInfo.hostProps;
|
||||
@@ -402,19 +421,36 @@ function addPropagateEffects(template, templateInfo, options, methodHost) {
|
||||
warnOnUndeclaredProperties(templateInfo, options, methodHost);
|
||||
}
|
||||
}
|
||||
upgradeTemplate(template, klass);
|
||||
// Mix any pre-bound data into __data; no need to flush this to
|
||||
// instances since they pull from the template at instance-time
|
||||
if (template.__dataProto) {
|
||||
if (target.__dataProto) {
|
||||
// Note, generally `__dataProto` could be chained, but it's guaranteed
|
||||
// to not be since this is a vanilla template we just added effects to
|
||||
Object.assign(template.__data, template.__dataProto);
|
||||
Object.assign(target.__data, target.__dataProto);
|
||||
}
|
||||
if (isTemplate) {
|
||||
upgradeTemplate(target, klass);
|
||||
// Clear any pending data for performance
|
||||
target.__dataTemp = {};
|
||||
target.__dataPending = null;
|
||||
target.__dataOld = null;
|
||||
target._enableProperties();
|
||||
} else {
|
||||
// Swizzle the cached subclass prototype onto the custom element
|
||||
Object.setPrototypeOf(target, klass.prototype);
|
||||
// Check for any pre-bound instance host properties, and do the
|
||||
// instance property delete/assign dance for those (directly into data;
|
||||
// not need to go through accessor since they are pulled at instance time)
|
||||
const hostProps = templateInfo.hostProps;
|
||||
for (let prop in hostProps) {
|
||||
prop = '_host_' + prop;
|
||||
if (prop in target) {
|
||||
const val = target[prop];
|
||||
delete target[prop];
|
||||
target.__data[prop] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Clear any pending data for performance
|
||||
template.__dataTemp = {};
|
||||
template.__dataPending = null;
|
||||
template.__dataOld = null;
|
||||
template._enableProperties();
|
||||
}
|
||||
}
|
||||
/* eslint-enable valid-jsdoc */
|
||||
@@ -604,7 +640,7 @@ function warnOnUndeclaredProperties(templateInfo, options, methodHost) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -621,8 +657,10 @@ function warnOnUndeclaredProperties(templateInfo, options, methodHost) {
|
||||
* model.set('item.checked', true);
|
||||
* }
|
||||
*
|
||||
* @param {HTMLTemplateElement} template The model will be returned for
|
||||
* elements stamped from this template
|
||||
* @param {HTMLElement} template The model will be returned for
|
||||
* elements stamped from this template (accepts either an HTMLTemplateElement)
|
||||
* or a `<dom-if>`/`<dom-repeat>` element when using `removeNestedTemplates`
|
||||
* optimization.
|
||||
* @param {Node=} node Node for which to return a template model.
|
||||
* @return {TemplateInstanceBase} Template instance representing the
|
||||
* binding scope for the element
|
||||
@@ -633,7 +671,7 @@ export function modelForElement(template, node) {
|
||||
// An element with a __templatizeInstance marks the top boundary
|
||||
// of a scope; walk up until we find one, and then ensure that
|
||||
// its __dataHost matches `this`, meaning this dom-repeat stamped it
|
||||
if ((model = node.__templatizeInstance)) {
|
||||
if ((model = node.__dataHost ? node : node.__templatizeInstance)) {
|
||||
// Found an element stamped by another template; keep walking up
|
||||
// from its __dataHost
|
||||
if (model.__dataHost != template) {
|
||||
|
||||
@@ -71,7 +71,11 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||
'unit/path.html',
|
||||
'unit/templatize.html',
|
||||
'unit/dom-repeat.html',
|
||||
'unit/dom-repeat.html?removeNestedTemplates=true',
|
||||
'unit/dom-if.html',
|
||||
'unit/dom-if.html?removeNestedTemplates=true',
|
||||
'unit/dom-if.html?fastDomIf=true',
|
||||
'unit/dom-if.html?removeNestedTemplates=true&fastDomIf=true',
|
||||
'unit/dom-bind.html',
|
||||
'unit/array-selector.html',
|
||||
'unit/polymer-dom.html',
|
||||
|
||||
@@ -301,9 +301,25 @@ Polymer({
|
||||
return val;
|
||||
}
|
||||
});
|
||||
|
||||
Polymer({
|
||||
is: 'prop-observer',
|
||||
properties: {
|
||||
prop: {
|
||||
observer: 'propChanged'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.propChanged = sinon.spy();
|
||||
}
|
||||
});
|
||||
|
||||
Polymer({
|
||||
_template: html`
|
||||
<template is="dom-if" if="{{b}}" restamp="{{restamp}}">{{guarded(a)}}</template>
|
||||
<template is="dom-if" if="{{b}}" restamp="{{restamp}}">
|
||||
{{guarded(a)}}
|
||||
<prop-observer id="observer" prop="[[c.d]]"></prop-observer>
|
||||
</template>
|
||||
`,
|
||||
|
||||
is: 'x-guard-separate-props',
|
||||
|
||||
+14
-1
@@ -14,6 +14,11 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||
<script src="../../node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"></script>
|
||||
<script src="wct-browser-config.js"></script>
|
||||
<script src="../../node_modules/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module">
|
||||
import { setRemoveNestedTemplates, setFastDomIf } from '../../lib/utils/settings.js';
|
||||
setRemoveNestedTemplates(location.search.match(/removeNestedTemplates/));
|
||||
setFastDomIf(location.search.match(/fastDomIf/));
|
||||
</script>
|
||||
<script type="module" src="../../polymer-legacy.js"></script>
|
||||
<script type="module" src="./dom-if-elements.js"></script>
|
||||
</head>
|
||||
@@ -867,18 +872,26 @@ suite('timing', function() {
|
||||
let el = document.createElement('x-guard-separate-props');
|
||||
el.restamp = restamp;
|
||||
document.body.appendChild(el);
|
||||
el.a = 'ok';
|
||||
el.b = true;
|
||||
flush();
|
||||
el.a = 'ok';
|
||||
el.c = {d: 'ok'};
|
||||
assert.equal(el.shadowRoot.textContent.trim(), 'ok');
|
||||
assert.equal(el.shadowRoot.querySelector('#observer').propChanged.callCount, 1);
|
||||
el.b = false;
|
||||
el.a = 'notok';
|
||||
el.set('c.d', 'notok');
|
||||
flush();
|
||||
assert.equal(el.shadowRoot.textContent.trim(), '');
|
||||
if (!restamp) {
|
||||
assert.equal(el.shadowRoot.querySelector('#observer').propChanged.callCount, 1);
|
||||
}
|
||||
el.set('c.d', 'changed');
|
||||
el.a = 'changed';
|
||||
el.b = true;
|
||||
flush();
|
||||
assert.equal(el.shadowRoot.textContent.trim(), 'changed');
|
||||
assert.equal(el.shadowRoot.querySelector('#observer').propChanged.callCount, restamp ? 1 : 2);
|
||||
document.body.removeChild(el);
|
||||
});
|
||||
|
||||
|
||||
@@ -464,9 +464,9 @@ Polymer({
|
||||
Polymer({
|
||||
_template: html`
|
||||
<template is="dom-repeat" items="{{items}}" id="outer">
|
||||
<template is="dom-if" if="">
|
||||
<template is="dom-if" if="" name="outerIf">
|
||||
<template is="dom-repeat" items="{{item.items}}" id="inner">
|
||||
<template is="dom-if" if="">
|
||||
<template is="dom-if" if="" name="innerIf">
|
||||
<button on-click="handleClick">{{item.prop}}</button>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -14,6 +14,10 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||
<script src="../../node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"></script>
|
||||
<script src="wct-browser-config.js"></script>
|
||||
<script src="../../node_modules/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module">
|
||||
import { setRemoveNestedTemplates } from '../../lib/utils/settings.js';
|
||||
setRemoveNestedTemplates(location.search.match(/removeNestedTemplates/));
|
||||
</script>
|
||||
<script type="module" src="../../polymer-legacy.js"></script>
|
||||
<script type="module" src="./dom-repeat-elements.js"></script>
|
||||
<style>
|
||||
@@ -108,7 +112,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||
</dom-repeat>
|
||||
</div>
|
||||
|
||||
|
||||
<test-fixture id="primitiveLarge">
|
||||
<template>
|
||||
<x-primitive-large></x-primitive-large>
|
||||
|
||||
@@ -198,8 +198,8 @@ class XParsing extends PolymerElement {
|
||||
}
|
||||
return noted;
|
||||
}
|
||||
_bindTemplate(template) {
|
||||
return this.templateInfoForTesting = super._bindTemplate(template);
|
||||
_bindTemplate(template, instanceBinding) {
|
||||
return this.templateInfoForTesting = super._bindTemplate(template, instanceBinding);
|
||||
}
|
||||
}
|
||||
customElements.define('x-parsing', XParsing);
|
||||
|
||||
Reference in New Issue
Block a user