| OLD | NEW |
| (Empty) | |
| 1 <!-- |
| 2 @license |
| 3 Copyright (c) 2017 The Polymer Project Authors. All rights reserved. |
| 4 This code may only be used under the BSD style license found at http://polymer.g
ithub.io/LICENSE.txt |
| 5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
| 6 The complete set of contributors may be found at http://polymer.github.io/CONTRI
BUTORS.txt |
| 7 Code distributed by Google as part of the polymer project is also |
| 8 subject to an additional IP rights grant found at http://polymer.github.io/PATEN
TS.txt |
| 9 --> |
| 10 |
| 11 <link rel="import" href="../../polymer-element.html"> |
| 12 <link rel="import" href="../utils/templatize.html"> |
| 13 <link rel="import" href="../utils/debounce.html"> |
| 14 <link rel="import" href="../utils/flush.html"> |
| 15 |
| 16 <script> |
| 17 |
| 18 (function() { |
| 19 'use strict'; |
| 20 |
| 21 /** |
| 22 * The `<dom-if>` element will stamp a light-dom `<template>` child when |
| 23 * the `if` property becomes truthy, and the template can use Polymer |
| 24 * data-binding and declarative event features when used in the context of |
| 25 * a Polymer element's template. |
| 26 * |
| 27 * When `if` becomes falsey, the stamped content is hidden but not |
| 28 * removed from dom. When `if` subsequently becomes truthy again, the content |
| 29 * is simply re-shown. This approach is used due to its favorable performance |
| 30 * characteristics: the expense of creating template content is paid only |
| 31 * once and lazily. |
| 32 * |
| 33 * Set the `restamp` property to true to force the stamped content to be |
| 34 * created / destroyed when the `if` condition changes. |
| 35 * |
| 36 * @polymerElement |
| 37 * @extends Polymer.Element |
| 38 * @memberof Polymer |
| 39 * @summary Custom element that conditionally stamps and hides or removes |
| 40 * template content based on a boolean flag. |
| 41 */ |
| 42 class DomIf extends Polymer.Element { |
| 43 |
| 44 // Not needed to find template; can be removed once the analyzer |
| 45 // can find the tag name from customElements.define call |
| 46 static get is() { return 'dom-if'; } |
| 47 |
| 48 static get template() { return null; } |
| 49 |
| 50 static get properties() { |
| 51 |
| 52 return { |
| 53 |
| 54 /** |
| 55 * Fired whenever DOM is added or removed/hidden by this template (by |
| 56 * default, rendering occurs lazily). To force immediate rendering, cal
l |
| 57 * `render`. |
| 58 * |
| 59 * @event dom-change |
| 60 */ |
| 61 |
| 62 /** |
| 63 * A boolean indicating whether this template should stamp. |
| 64 */ |
| 65 if: { |
| 66 type: Boolean, |
| 67 observer: '__debounceRender' |
| 68 }, |
| 69 |
| 70 /** |
| 71 * When true, elements will be removed from DOM and discarded when `if` |
| 72 * becomes false and re-created and added back to the DOM when `if` |
| 73 * becomes true. By default, stamped elements will be hidden but left |
| 74 * in the DOM when `if` becomes false, which is generally results |
| 75 * in better performance. |
| 76 */ |
| 77 restamp: { |
| 78 type: Boolean, |
| 79 observer: '__debounceRender' |
| 80 } |
| 81 |
| 82 } |
| 83 |
| 84 } |
| 85 |
| 86 constructor() { |
| 87 super(); |
| 88 this.__renderDebouncer = null; |
| 89 this.__invalidProps = null; |
| 90 this.__instance = null; |
| 91 } |
| 92 |
| 93 __debounceRender() { |
| 94 // Render is async for 2 reasons: |
| 95 // 1. To eliminate dom creation trashing if user code thrashes `if` in the |
| 96 // same turn. This was more common in 1.x where a compound computed |
| 97 // property could result in the result changing multiple times, but is |
| 98 // mitigated to a large extent by batched property processing in 2.x. |
| 99 // 2. To avoid double object propagation when a bag including values bound |
| 100 // to the `if` property as well as one or more hostProps could enqueue |
| 101 // the <dom-if> to flush before the <template>'s host property |
| 102 // forwarding. In that scenario creating an instance would result in |
| 103 // the host props being set once, and then the enqueued changes on the |
| 104 // template would set properties a second time, potentially causing an |
| 105 // object to be set to an instance more than once. Creating the |
| 106 // instance async from flushing data ensures this doesn't happen. If |
| 107 // we wanted a sync option in the future, simply having <dom-if> flush |
| 108 // (or clear) its template's pending host properties before creating |
| 109 // the instance would also avoid the problem. |
| 110 this.__renderDebouncer = Polymer.Debouncer.debounce( |
| 111 this.__renderDebouncer |
| 112 , Polymer.Async.microTask |
| 113 , () => this.__render()); |
| 114 Polymer.enqueueDebouncer(this.__renderDebouncer); |
| 115 } |
| 116 |
| 117 disconnectedCallback() { |
| 118 super.disconnectedCallback(); |
| 119 if (!this.parentNode || |
| 120 (this.parentNode.nodeType == Node.DOCUMENT_FRAGMENT_NODE && |
| 121 !this.parentNode.host)) { |
| 122 this.__teardownInstance(); |
| 123 } |
| 124 } |
| 125 |
| 126 connectedCallback() { |
| 127 super.connectedCallback(); |
| 128 if (this.if) { |
| 129 this.__debounceRender(); |
| 130 } |
| 131 } |
| 132 |
| 133 /** |
| 134 * Forces the element to render its content. Normally rendering is |
| 135 * asynchronous to a provoking change. This is done for efficiency so |
| 136 * that multiple changes trigger only a single render. The render method |
| 137 * should be called if, for example, template rendering is required to |
| 138 * validate application state. |
| 139 */ |
| 140 render() { |
| 141 Polymer.flush(); |
| 142 } |
| 143 |
| 144 __render() { |
| 145 if (this.if) { |
| 146 if (!this.__ensureInstance()) { |
| 147 // No template found yet |
| 148 return; |
| 149 } |
| 150 this._showHideChildren(); |
| 151 } else if (this.restamp) { |
| 152 this.__teardownInstance(); |
| 153 } |
| 154 if (!this.restamp && this.__instance) { |
| 155 this._showHideChildren(); |
| 156 } |
| 157 if (this.if != this._lastIf) { |
| 158 this.dispatchEvent(new CustomEvent('dom-change', { |
| 159 bubbles: true, |
| 160 composed: true |
| 161 })); |
| 162 this._lastIf = this.if; |
| 163 } |
| 164 } |
| 165 |
| 166 __ensureInstance() { |
| 167 let parentNode = this.parentNode; |
| 168 // Guard against element being detached while render was queued |
| 169 if (parentNode) { |
| 170 if (!this.__ctor) { |
| 171 let template = this.querySelector('template'); |
| 172 if (!template) { |
| 173 // Wait until childList changes and template should be there by then |
| 174 let observer = new MutationObserver(() => { |
| 175 if (this.querySelector('template')) { |
| 176 observer.disconnect(); |
| 177 this.__render(); |
| 178 } else { |
| 179 throw new Error('dom-if requires a <template> child'); |
| 180 } |
| 181 }) |
| 182 observer.observe(this, {childList: true}); |
| 183 return false; |
| 184 } |
| 185 this.__ctor = Polymer.Templatize.templatize(template, this, { |
| 186 // dom-if templatizer instances require `mutable: true`, as |
| 187 // `__syncHostProperties` relies on that behavior to sync objects |
| 188 mutableData: true, |
| 189 forwardHostProp: function(prop, value) { |
| 190 if (this.__instance) { |
| 191 if (this.if) { |
| 192 this.__instance.forwardHostProp(prop, value); |
| 193 } else { |
| 194 // If we have an instance but are squelching host property |
| 195 // forwarding due to if being false, note the invalidated |
| 196 // properties so `__syncHostProperties` can sync them the next |
| 197 // time `if` becomes true |
| 198 this.__invalidProps = this.__invalidProps || Object.create(nul
l); |
| 199 this.__invalidProps[Polymer.Path.root(prop)] = true; |
| 200 } |
| 201 } |
| 202 } |
| 203 }); |
| 204 } |
| 205 if (!this.__instance) { |
| 206 this.__instance = new this.__ctor(); |
| 207 parentNode.insertBefore(this.__instance.root, this); |
| 208 } else { |
| 209 this.__syncHostProperties(); |
| 210 let c$ = this.__instance.children; |
| 211 if (c$ && c$.length) { |
| 212 // Detect case where dom-if was re-attached in new position |
| 213 let lastChild = this.previousSibling; |
| 214 if (lastChild !== c$[c$.length-1]) { |
| 215 for (let i=0, n; (i<c$.length) && (n=c$[i]); i++) { |
| 216 parentNode.insertBefore(n, this); |
| 217 } |
| 218 } |
| 219 } |
| 220 } |
| 221 } |
| 222 return true; |
| 223 } |
| 224 |
| 225 __syncHostProperties() { |
| 226 let props = this.__invalidProps; |
| 227 if (props) { |
| 228 for (let prop in props) { |
| 229 this.__instance._setPendingProperty(prop, this.__dataHost[prop]); |
| 230 } |
| 231 this.__invalidProps = null; |
| 232 this.__instance._flushProperties(); |
| 233 } |
| 234 } |
| 235 |
| 236 __teardownInstance() { |
| 237 if (this.__instance) { |
| 238 let c$ = this.__instance.children; |
| 239 if (c$ && c$.length) { |
| 240 // use first child parent, for case when dom-if may have been detached |
| 241 let parent = c$[0].parentNode; |
| 242 for (let i=0, n; (i<c$.length) && (n=c$[i]); i++) { |
| 243 parent.removeChild(n); |
| 244 } |
| 245 } |
| 246 this.__instance = null; |
| 247 this.__invalidProps = null; |
| 248 } |
| 249 } |
| 250 |
| 251 _showHideChildren() { |
| 252 let hidden = this.__hideTemplateChildren__ || !this.if; |
| 253 if (this.__instance) { |
| 254 this.__instance._showHideChildren(hidden); |
| 255 } |
| 256 } |
| 257 |
| 258 } |
| 259 |
| 260 customElements.define(DomIf.is, DomIf); |
| 261 |
| 262 Polymer.DomIf = DomIf; |
| 263 |
| 264 })(); |
| 265 </script> |
| OLD | NEW |