Skip to content

Commit 39207cc

Browse files
committed
Updates based on review.
1 parent 4bdbe92 commit 39207cc

File tree

5 files changed

+190
-35
lines changed

5 files changed

+190
-35
lines changed

lib/elements/dom-if.js

Lines changed: 164 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -142,28 +142,55 @@ class DomIfBase extends PolymerElement {
142142
}
143143
}
144144

145+
/**
146+
* Ensures a template has been assigned to `this.__template`. If it has not
147+
* yet been, it querySelectors for it in its children and if it does not yet
148+
* exist (e.g. in parser-generated case), opens a mutation observer and
149+
* waits for it to appear (returns false if it has not yet been found,
150+
* otherwise true). In the `removeNestedTemplates` case, the "template" will
151+
* be the `dom-if` element itself.
152+
*
153+
* @return {boolean} True when a template has been found, false otherwise
154+
*/
145155
__ensureTemplate() {
146-
// When `removeNestedTemplates` is true, the "template" is the element
147-
// itself, which has been given a `_templateInfo` property
148-
let template = this._templateInfo ? this :
149-
/** @type {HTMLTemplateElement} */(wrap(this).querySelector('template'));
150-
if (!template) {
151-
// Wait until childList changes and template should be there by then
152-
let observer = new MutationObserver(() => {
153-
if (wrap(this).querySelector('template')) {
154-
observer.disconnect();
155-
this.__render();
156-
} else {
157-
throw new Error('dom-if requires a <template> child');
158-
}
159-
});
160-
observer.observe(this, {childList: true});
161-
return false;
156+
if (!this.__template) {
157+
// When `removeNestedTemplates` is true, the "template" is the element
158+
// itself, which has been given a `_templateInfo` property
159+
let template = this._templateInfo ? this :
160+
/** @type {HTMLTemplateElement} */(wrap(this).querySelector('template'));
161+
if (!template) {
162+
// Wait until childList changes and template should be there by then
163+
let observer = new MutationObserver(() => {
164+
if (wrap(this).querySelector('template')) {
165+
observer.disconnect();
166+
this.__render();
167+
} else {
168+
throw new Error('dom-if requires a <template> child');
169+
}
170+
});
171+
observer.observe(this, {childList: true});
172+
return false;
173+
}
174+
this.__template = template;
162175
}
163-
this.__template = template;
164176
return true;
165177
}
166178

179+
/**
180+
* Ensures a an instance of the template has been created and inserted. This
181+
* method may return false if the template has not yet been found or if
182+
* there is no `parentNode` to insert the template into (in either case,
183+
* connection or the template-finding mutation observer firing will queue
184+
* another render, causing this method to be called again at a more
185+
* appropriate time).
186+
*
187+
* Subclasses should implement the following methods called here:
188+
* - `__hasInstance`
189+
* - `__createAndInsertInstance`
190+
* - `__getInstanceNodes`
191+
*
192+
* @return {boolean} True if the instance was created, false otherwise.
193+
*/
167194
__ensureInstance() {
168195
let parentNode = wrap(this).parentNode;
169196
if (!this.__hasInstance()) {
@@ -172,13 +199,13 @@ class DomIfBase extends PolymerElement {
172199
return false;
173200
}
174201
// Find the template (when false, there was no template yet)
175-
if (!this.__template && !this.__ensureTemplate()) {
202+
if (!this.__ensureTemplate()) {
176203
return false;
177204
}
178205
this.__createAndInsertInstance(parentNode);
179206
} else {
180207
// Move instance children if necessary
181-
let children = this.__instanceChildren();
208+
let children = this.__getInstanceNodes();
182209
if (children && children.length) {
183210
// Detect case where dom-if was re-attached in new position
184211
let lastChild = wrap(this).previousSibling;
@@ -198,12 +225,22 @@ class DomIfBase extends PolymerElement {
198225
* that multiple changes trigger only a single render. The render method
199226
* should be called if, for example, template rendering is required to
200227
* validate application state.
228+
*
201229
* @return {void}
202230
*/
203231
render() {
204232
flush();
205233
}
206234

235+
/**
236+
* Performs the key rendering steps:
237+
* 1. Ensure a template instance has been stamped (when true)
238+
* 2. Remove the template instance (when false and restamp:true)
239+
* 3. Sync the hidden state of the instance nodes with the if/restamp state
240+
* 4. Fires the `dom-change` event when necessary
241+
*
242+
* @return {void}
243+
*/
207244
__render() {
208245
if (this.if) {
209246
if (!this.__ensureInstance()) {
@@ -224,6 +261,25 @@ class DomIfBase extends PolymerElement {
224261
}
225262
}
226263

264+
/**
265+
* The version of DomIf used when `fastDomIf` setting is in use, which is
266+
* optimized for first-render (but adds a tax to all subsequent property updates
267+
* on the host, whether they were used in a given `dom-if` or not).
268+
*
269+
* This implementation avoids use of `Templatizer`, which introduces a new scope
270+
* (a non-element PropertyEffects instance), which is not strictly necessary
271+
* since `dom-if` never introduces new properties to its scope (unlike
272+
* `dom-repeat`). Taking advantage of this fact, the `dom-if` reaches up to its
273+
* `__dataHost` and stamps the template directly from the host using the host's
274+
* runtime `_stampTemplate` API, which binds the property effects of the
275+
* template directly to the host. This both avoids the intermediary
276+
* `Templatizer` instance, but also avoids the need to bind host properties to
277+
* the `<template>` element and forward those into the template instance.
278+
*
279+
* In this version of `dom-if`, the `this.__instance` method is the
280+
* `DocumentFragment` returned from `_stampTemplate`, which also serves as the
281+
* handle for later removing it using the `_removeBoundDom` method.
282+
*/
227283
class DomIfFast extends DomIfBase {
228284

229285
constructor() {
@@ -233,14 +289,34 @@ class DomIfFast extends DomIfBase {
233289
this.__squelchedRunEffects = null;
234290
}
235291

292+
/**
293+
* Implementation of abstract API needed by DomIfBase.
294+
*
295+
* @return {boolean} True when an instance has been created.
296+
*/
236297
__hasInstance() {
237298
return Boolean(this.__instance);
238299
}
239300

240-
__instanceChildren() {
301+
/**
302+
* Implementation of abstract API needed by DomIfBase.
303+
*
304+
* @return {Array<Node>} Array of child nodes stamped from the template
305+
* instance.
306+
*/
307+
__getInstanceNodes() {
241308
return this.__instance.templateInfo.childNodes;
242309
}
243310

311+
/**
312+
* Implementation of abstract API needed by DomIfBase.
313+
*
314+
* Stamps the template by calling `_stampTemplate` on the `__dataHost` of this
315+
* element and then inserts the resulting nodes into the given `parentNode`.
316+
*
317+
* @param {Node} parentNode The parent node to insert the instance into
318+
* @return {void}
319+
*/
244320
__createAndInsertInstance(parentNode) {
245321
const host = this.__dataHost || this;
246322
if (strictTemplatePolicy) {
@@ -255,13 +331,24 @@ class DomIfFast extends DomIfBase {
255331
templateInfo.runEffects = (runEffects, changedProps) => {
256332
if (this.if) {
257333
const invalidProps = this.__invalidProps;
334+
// Mix any props that changed while the `if` was false into `changedProps`
258335
if (invalidProps) {
336+
// If there were properties received while the `if` was false, it is
337+
// important to sync the hidden state with the element _first_, so that
338+
// new bindings to e.g. `textContent` do not get stomped on by
339+
// pre-hidden values if `_showHideChildren` were to be called later at
340+
// the next render. Clearing `__invalidProps` here ensures
341+
// `_showHideChildren`'s call to `__syncHostProperties` no-ops, so
342+
// that we don't call `runEffects` more often than necessary.
259343
this.__squelchedRunEffects = this.__invalidProps = null;
260344
this._showHideChildren();
261345
changedProps = Object.assign(invalidProps, changedProps);
262346
}
263347
runEffects(changedProps);
264348
} else {
349+
// Store off any values changed while `if` was false, along with the
350+
// runEffects method to sync them, so that we can replay them once `if`
351+
// becomes true
265352
this.__invalidProps = Object.assign(this.__invalidProps || {}, changedProps);
266353
this.__squelchedRunEffects = runEffects;
267354
}
@@ -271,6 +358,11 @@ class DomIfFast extends DomIfBase {
271358
wrap(parentNode).insertBefore(this.__instance, this);
272359
}
273360

361+
/**
362+
* Run effects for any properties that changed while the `if` was false.
363+
*
364+
* @return {void}
365+
*/
274366
__syncHostProperties() {
275367
const props = this.__invalidProps;
276368
if (props) {
@@ -279,16 +371,25 @@ class DomIfFast extends DomIfBase {
279371
}
280372
}
281373

374+
/**
375+
* Implementation of abstract API needed by DomIfBase.
376+
*
377+
* Remove the instance and any nodes it created. Uses the `__dataHost`'s
378+
* runtime `_removeBoundDom` method.
379+
*
380+
* @return {void}
381+
*/
282382
__teardownInstance() {
383+
const host = this.__dataHost || this;
283384
if (this.__instance) {
284-
this._removeBoundDom(this.__instance);
385+
host._removeBoundDom(this.__instance);
285386
this.__syncProps = null;
286387
this.__instance = null;
287388
}
288389
}
289390

290391
/**
291-
* Shows or hides the template instance top level child elements. For
392+
* Shows or hides the template instance top level child nodes. For
292393
* text nodes, `textContent` is removed while "hidden" and replaced when
293394
* "shown."
294395
* @return {void}
@@ -307,6 +408,12 @@ class DomIfFast extends DomIfBase {
307408
}
308409
}
309410

411+
/**
412+
* The "legacy" implementation of `dom-if`, implemented using `Templatizer`.
413+
*
414+
* In this version, `this.__instance` is the `TemplateInstance` returned
415+
* from the templatized constructor.
416+
*/
310417
class DomIfLegacy extends DomIfBase {
311418

312419
constructor() {
@@ -316,14 +423,35 @@ class DomIfLegacy extends DomIfBase {
316423
this.__invalidProps = null;
317424
}
318425

426+
/**
427+
* Implementation of abstract API needed by DomIfBase.
428+
*
429+
* @return {boolean} True when an instance has been created.
430+
*/
319431
__hasInstance() {
320432
return Boolean(this.__instance);
321433
}
322434

323-
__instanceChildren() {
435+
/**
436+
* Implementation of abstract API needed by DomIfBase.
437+
*
438+
* @return {Array<Node>} Array of child nodes stamped from the template
439+
* instance.
440+
*/
441+
__getInstanceNodes() {
324442
return this.__instance.children;
325443
}
326444

445+
/**
446+
* Implementation of abstract API needed by DomIfBase.
447+
*
448+
* Stamps the template by creating a new instance of the templatized
449+
* constructor (which is created lazily if it does not yet exist), and then
450+
* inserts its resulting `root` doc fragment into the given `parentNode`.
451+
*
452+
* @param {Node} parentNode The parent node to insert the instance into
453+
* @return {void}
454+
*/
327455
__createAndInsertInstance(parentNode) {
328456
// Ensure we have an instance constructor
329457
if (!this.__ctor) {
@@ -357,6 +485,13 @@ class DomIfLegacy extends DomIfBase {
357485
wrap(parentNode).insertBefore(this.__instance.root, this);
358486
}
359487

488+
/**
489+
* Implementation of abstract API needed by DomIfBase.
490+
*
491+
* Removes the instance and any nodes it created.
492+
*
493+
* @return {void}
494+
*/
360495
__teardownInstance() {
361496
if (this.__instance) {
362497
let c$ = this.__instance.children;
@@ -377,6 +512,12 @@ class DomIfLegacy extends DomIfBase {
377512
}
378513
}
379514

515+
/**
516+
* Forwards any properties that changed while the `if` was false into the
517+
* template instance and flushes it.
518+
*
519+
* @return {void}
520+
*/
380521
__syncHostProperties() {
381522
let props = this.__invalidProps;
382523
if (props) {

lib/mixins/property-effects.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2681,7 +2681,7 @@ export const PropertyEffects = dedupingMixin(superClass => {
26812681
*/
26822682
_bindTemplate(template, instanceBinding) {
26832683
let templateInfo = this.constructor._parseTemplate(template);
2684-
let wasPreBound = this.__templateInfo == templateInfo;
2684+
let wasPreBound = this.__preBoundTemplateInfo == templateInfo;
26852685
// Optimization: since this is called twice for proto-bound templates,
26862686
// don't attempt to recreate accessors if this template was pre-bound
26872687
if (!wasPreBound) {
@@ -2694,9 +2694,14 @@ export const PropertyEffects = dedupingMixin(superClass => {
26942694
// and link into list of templates if necessary
26952695
templateInfo = /** @type {!TemplateInfo} */(Object.create(templateInfo));
26962696
templateInfo.wasPreBound = wasPreBound;
2697-
if (wasPreBound || !this.__templateInfo) {
2697+
if (!this.__templateInfo) {
2698+
// Set the info to the root of the tree
26982699
this.__templateInfo = templateInfo;
26992700
} else {
2701+
// Append this template info onto the end of its parent template's
2702+
// nested template list; if this template was not nested in another
2703+
// template, use the root template (the first stamped one) as the
2704+
// parent
27002705
const parent = templateInfo.parent || this.__templateInfo;
27012706
const previous = parent.lastChild;
27022707
parent.lastChild = templateInfo;
@@ -2708,7 +2713,7 @@ export const PropertyEffects = dedupingMixin(superClass => {
27082713
}
27092714
}
27102715
} else {
2711-
this.__templateInfo = templateInfo;
2716+
this.__preBoundTemplateInfo = templateInfo;
27122717
}
27132718
return templateInfo;
27142719
}
@@ -2751,6 +2756,9 @@ export const PropertyEffects = dedupingMixin(superClass => {
27512756
* in the main element template.
27522757
*
27532758
* @param {!HTMLTemplateElement} template Template to stamp
2759+
* @param {Object=} templateInfo Optional bound template info associated
2760+
* with the template to be stamped; if omitted the template will be
2761+
* automatically bound.
27542762
* @return {!StampedTemplate} Cloned template content
27552763
* @override
27562764
* @protected

lib/mixins/template-stamp.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,9 @@ export const TemplateStamp = dedupingMixin(
436436
* is removed and stored in notes as well.
437437
*
438438
* @param {!HTMLTemplateElement} template Template to stamp
439+
* @param {Object=} templateInfo Optional template info associated
440+
* with the template to be stamped; if omitted the template will be
441+
* automatically parsed.
439442
* @return {!StampedTemplate} Cloned template content
440443
* @override
441444
*/

0 commit comments

Comments
 (0)