@@ -142,28 +142,55 @@ class DomIfBase extends PolymerElement {
142
142
}
143
143
}
144
144
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
+ */
145
155
__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 ;
162
175
}
163
- this . __template = template ;
164
176
return true ;
165
177
}
166
178
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
+ */
167
194
__ensureInstance ( ) {
168
195
let parentNode = wrap ( this ) . parentNode ;
169
196
if ( ! this . __hasInstance ( ) ) {
@@ -172,13 +199,13 @@ class DomIfBase extends PolymerElement {
172
199
return false ;
173
200
}
174
201
// Find the template (when false, there was no template yet)
175
- if ( ! this . __template && ! this . __ensureTemplate ( ) ) {
202
+ if ( ! this . __ensureTemplate ( ) ) {
176
203
return false ;
177
204
}
178
205
this . __createAndInsertInstance ( parentNode ) ;
179
206
} else {
180
207
// Move instance children if necessary
181
- let children = this . __instanceChildren ( ) ;
208
+ let children = this . __getInstanceNodes ( ) ;
182
209
if ( children && children . length ) {
183
210
// Detect case where dom-if was re-attached in new position
184
211
let lastChild = wrap ( this ) . previousSibling ;
@@ -198,12 +225,22 @@ class DomIfBase extends PolymerElement {
198
225
* that multiple changes trigger only a single render. The render method
199
226
* should be called if, for example, template rendering is required to
200
227
* validate application state.
228
+ *
201
229
* @return {void }
202
230
*/
203
231
render ( ) {
204
232
flush ( ) ;
205
233
}
206
234
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
+ */
207
244
__render ( ) {
208
245
if ( this . if ) {
209
246
if ( ! this . __ensureInstance ( ) ) {
@@ -224,6 +261,25 @@ class DomIfBase extends PolymerElement {
224
261
}
225
262
}
226
263
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
+ */
227
283
class DomIfFast extends DomIfBase {
228
284
229
285
constructor ( ) {
@@ -233,14 +289,34 @@ class DomIfFast extends DomIfBase {
233
289
this . __squelchedRunEffects = null ;
234
290
}
235
291
292
+ /**
293
+ * Implementation of abstract API needed by DomIfBase.
294
+ *
295
+ * @return {boolean } True when an instance has been created.
296
+ */
236
297
__hasInstance ( ) {
237
298
return Boolean ( this . __instance ) ;
238
299
}
239
300
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 ( ) {
241
308
return this . __instance . templateInfo . childNodes ;
242
309
}
243
310
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
+ */
244
320
__createAndInsertInstance ( parentNode ) {
245
321
const host = this . __dataHost || this ;
246
322
if ( strictTemplatePolicy ) {
@@ -255,13 +331,24 @@ class DomIfFast extends DomIfBase {
255
331
templateInfo . runEffects = ( runEffects , changedProps ) => {
256
332
if ( this . if ) {
257
333
const invalidProps = this . __invalidProps ;
334
+ // Mix any props that changed while the `if` was false into `changedProps`
258
335
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.
259
343
this . __squelchedRunEffects = this . __invalidProps = null ;
260
344
this . _showHideChildren ( ) ;
261
345
changedProps = Object . assign ( invalidProps , changedProps ) ;
262
346
}
263
347
runEffects ( changedProps ) ;
264
348
} 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
265
352
this . __invalidProps = Object . assign ( this . __invalidProps || { } , changedProps ) ;
266
353
this . __squelchedRunEffects = runEffects ;
267
354
}
@@ -271,6 +358,11 @@ class DomIfFast extends DomIfBase {
271
358
wrap ( parentNode ) . insertBefore ( this . __instance , this ) ;
272
359
}
273
360
361
+ /**
362
+ * Run effects for any properties that changed while the `if` was false.
363
+ *
364
+ * @return {void }
365
+ */
274
366
__syncHostProperties ( ) {
275
367
const props = this . __invalidProps ;
276
368
if ( props ) {
@@ -279,16 +371,25 @@ class DomIfFast extends DomIfBase {
279
371
}
280
372
}
281
373
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
+ */
282
382
__teardownInstance ( ) {
383
+ const host = this . __dataHost || this ;
283
384
if ( this . __instance ) {
284
- this . _removeBoundDom ( this . __instance ) ;
385
+ host . _removeBoundDom ( this . __instance ) ;
285
386
this . __syncProps = null ;
286
387
this . __instance = null ;
287
388
}
288
389
}
289
390
290
391
/**
291
- * Shows or hides the template instance top level child elements . For
392
+ * Shows or hides the template instance top level child nodes . For
292
393
* text nodes, `textContent` is removed while "hidden" and replaced when
293
394
* "shown."
294
395
* @return {void }
@@ -307,6 +408,12 @@ class DomIfFast extends DomIfBase {
307
408
}
308
409
}
309
410
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
+ */
310
417
class DomIfLegacy extends DomIfBase {
311
418
312
419
constructor ( ) {
@@ -316,14 +423,35 @@ class DomIfLegacy extends DomIfBase {
316
423
this . __invalidProps = null ;
317
424
}
318
425
426
+ /**
427
+ * Implementation of abstract API needed by DomIfBase.
428
+ *
429
+ * @return {boolean } True when an instance has been created.
430
+ */
319
431
__hasInstance ( ) {
320
432
return Boolean ( this . __instance ) ;
321
433
}
322
434
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 ( ) {
324
442
return this . __instance . children ;
325
443
}
326
444
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
+ */
327
455
__createAndInsertInstance ( parentNode ) {
328
456
// Ensure we have an instance constructor
329
457
if ( ! this . __ctor ) {
@@ -357,6 +485,13 @@ class DomIfLegacy extends DomIfBase {
357
485
wrap ( parentNode ) . insertBefore ( this . __instance . root , this ) ;
358
486
}
359
487
488
+ /**
489
+ * Implementation of abstract API needed by DomIfBase.
490
+ *
491
+ * Removes the instance and any nodes it created.
492
+ *
493
+ * @return {void }
494
+ */
360
495
__teardownInstance ( ) {
361
496
if ( this . __instance ) {
362
497
let c$ = this . __instance . children ;
@@ -377,6 +512,12 @@ class DomIfLegacy extends DomIfBase {
377
512
}
378
513
}
379
514
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
+ */
380
521
__syncHostProperties ( ) {
381
522
let props = this . __invalidProps ;
382
523
if ( props ) {
0 commit comments