Skip to content

Commit

Permalink
Runtime stamped dom-if
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinpschaaf committed Jun 19, 2019
1 parent 27ed93a commit e690dfe
Show file tree
Hide file tree
Showing 10 changed files with 295 additions and 140 deletions.
138 changes: 92 additions & 46 deletions lib/elements/dom-if.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ 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
Expand Down Expand Up @@ -85,7 +87,7 @@ export class DomIf extends PolymerElement {
constructor() {
super();
this.__renderDebouncer = null;
this.__invalidProps = null;
this.__syncProps = null;
this.__instance = null;
this._lastIf = false;
this.__ctor = null;
Expand Down Expand Up @@ -181,8 +183,11 @@ export class DomIf extends PolymerElement {
let parentNode = wrap(this).parentNode;
// Guard against element being detached while render was queued
if (parentNode) {
if (!this.__ctor) {
let template = this._templateInfo ? this : /** @type {HTMLTemplateElement} */(wrap(this).querySelector('template'));
if (!this.__template) {
// When `removeNestedTemplates` is true, the "template" is the element
// itself, which has been given a `_templateInfo` property
let template = this._templateInfo ? this :
/** @type {HTMLTemplateElement} */(wrap(this).querySelector('template'));
if (!template) {
// Wait until childList changes and template should be there by then
let observer = new MutationObserver(() => {
Expand All @@ -196,37 +201,63 @@ export class DomIf extends PolymerElement {
observer.observe(this, {childList: true});
return false;
}
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;
this.__template = template;
if (!fastDomIf) {
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.__syncProps = this.__syncProps || Object.create(null);
this.__syncProps[root(prop)] = true;
}
}
}
}
});
});
}
}
if (!this.__instance) {
this.__instance = new this.__ctor();
wrap(parentNode).insertBefore(this.__instance.root, this);
if (fastDomIf) {
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(this.__template, true);
// Install runEffects hook that prevents running property effects
// (and any nested template effects) when the `if` is false
templateInfo.runEffects = runEffects => {
if (this.if) {
runEffects();
} else {
this.__syncProps = runEffects;
}
};
// Stamp the template, and set its DocumentFragment to the "instance"
this.__instance = host._stampTemplate(this.__template, templateInfo);
wrap(parentNode).insertBefore(this.__instance, this);
} else {
this.__instance = new this.__ctor();
wrap(parentNode).insertBefore(this.__instance.root, this);
}
} else {
this.__syncHostProperties();
let c$ = this.__instance.children;
let c$ = fastDomIf ? this.__instance.templateInfo.childNodes :
this.__instance.children;
if (c$ && c$.length) {
// Detect case where dom-if was re-attached in new position
let lastChild = wrap(this).previousSibling;
Expand All @@ -242,33 +273,41 @@ export class DomIf extends PolymerElement {
}

__syncHostProperties() {
let props = this.__invalidProps;
let props = this.__syncProps;
this.__syncProps = null;
if (props) {
for (let prop in props) {
this.__instance._setPendingProperty(prop, this.__dataHost[prop]);
if (fastDomIf) {
props();
} else {
for (let prop in props) {
this.__instance._setPendingProperty(prop, this.__dataHost[prop]);
}
this.__instance._flushProperties();
}
this.__invalidProps = null;
this.__instance._flushProperties();
}
}

__teardownInstance() {
if (this.__instance) {
let c$ = this.__instance.children;
if (c$ && c$.length) {
// use first child parent, for case when dom-if may have been detached
let parent = wrap(c$[0]).parentNode;
// Instance children may be disconnected from parents when dom-if
// detaches if a tree was innerHTML'ed
if (parent) {
parent = wrap(parent);
for (let i=0, n; (i<c$.length) && (n=c$[i]); i++) {
parent.removeChild(n);
if (fastDomIf) {
this._removeBoundDom(this.__instance);
} else {
let c$ = this.__instance.children;
if (c$ && c$.length) {
// use first child parent, for case when dom-if may have been detached
let parent = wrap(c$[0]).parentNode;
// Instance children may be disconnected from parents when dom-if
// detaches if a tree was innerHTML'ed
if (parent) {
parent = wrap(parent);
for (let i=0, n; (i<c$.length) && (n=c$[i]); i++) {
parent.removeChild(n);
}
}
}
}
this.__syncProps = null;
this.__instance = null;
this.__invalidProps = null;
}
}

Expand All @@ -283,7 +322,14 @@ export class DomIf extends PolymerElement {
_showHideChildren() {
let hidden = this.__hideTemplateChildren__ || !this.if;
if (this.__instance) {
this.__instance._showHideChildren(hidden);
if (fastDomIf) {
showHideChildren(hidden, this.__instance.templateInfo.childNodes);
} else {
this.__instance._showHideChildren(hidden);
}
if (!hidden) {
this.__syncHostProperties();
}
}
}

Expand Down
5 changes: 4 additions & 1 deletion lib/elements/dom-repeat.js
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,10 @@ 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 = this._templateInfo ? this : /** @type {HTMLTemplateElement} */(this.querySelector('template'));
// When `removeNestedTemplates` is true, the "template" is the element
// itself, which has been given a `_templateInfo` property
let template = this.template = this._templateInfo ? this :
/** @type {HTMLTemplateElement} */(this.querySelector('template'));
if (!template) {
// // Wait until childList changes and template should be there by then
let observer = new MutationObserver(() => {
Expand Down
98 changes: 65 additions & 33 deletions lib/mixins/property-effects.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +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';

const btp = window.location.search.match(/btp/);
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
Expand Down Expand Up @@ -1974,11 +1972,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 = () => {
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);
} else {
baseRunEffects();
}
}

Expand Down Expand Up @@ -2684,14 +2694,23 @@ export const PropertyEffects = dedupingMixin(superClass => {
// and link into list 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 (wasPreBound || !this.__templateInfo) {
this.__templateInfo = templateInfo;
} else {
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.__templateInfo = templateInfo;
}
return this.__templateInfo = templateInfo;
return templateInfo;
}

/**
Expand Down Expand Up @@ -2736,13 +2755,13 @@ export const PropertyEffects = dedupingMixin(superClass => {
* @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
Expand All @@ -2757,8 +2776,7 @@ export const PropertyEffects = dedupingMixin(superClass => {
setupBindings(this, templateInfo);
// Flush properties into template nodes if already booted
if (this.__dataReady) {
runEffects(this, templateInfo.propertyEffects, this.__data, null,
false, templateInfo.nodeList);
this._runEffectsForTemplate(templateInfo, this.__data, null, false);
}
return dom;
}
Expand All @@ -2776,23 +2794,22 @@ export const PropertyEffects = dedupingMixin(superClass => {
_removeBoundDom(dom) {
// Unlink template info
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);
}
}

Expand Down Expand Up @@ -2923,18 +2940,33 @@ export const PropertyEffects = dedupingMixin(superClass => {
this, node, templateInfo, nodeInfo);
const parent = node.parentNode;
const nestedTemplateInfo = nodeInfo.templateInfo;
if (btp && parent.localName.match(/(dom-repeat|dom-if)/)) {
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);
nodeInfo.parentInfo.templateInfo = nestedTemplateInfo;
nodeInfo = nodeInfo.parentInfo;
nodeInfo.noted = true;
noted = false;
}
// Merge host props into outer template and add bindings
let hostProps = nestedTemplateInfo.hostProps;
let mode = '{';
for (let source in hostProps) {
let parts = [{ mode, source, dependencies: [source], hostProp: true }];
addBinding(this, templateInfo, nodeInfo, 'property', '_host_' + source, parts);
if (fastDomIf && isDomIf) {
if (hostProps) {
if (!removeNestedTemplates) {
nodeInfo.parentInfo.noted = true;
}
templateInfo.hostProps =
Object.assign(nestedTemplateInfo.hostProps || {}, hostProps);
}
} 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;
}
Expand Down
Loading

0 comments on commit e690dfe

Please sign in to comment.