| 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="boot.html"> |
| 12 <link rel="import" href="../mixins/property-effects.html"> |
| 13 <link rel="import" href="../mixins/mutable-data.html"> |
| 14 |
| 15 <script> |
| 16 (function() { |
| 17 'use strict'; |
| 18 |
| 19 // Base class for HTMLTemplateElement extension that has property effects |
| 20 // machinery for propagating host properties to children. This is an ES5 |
| 21 // class only because Babel (incorrectly) requires super() in the class |
| 22 // constructor even though no `this` is used and it returns an instance. |
| 23 let newInstance = null; |
| 24 function HTMLTemplateElementExtension() { return newInstance; } |
| 25 HTMLTemplateElementExtension.prototype = Object.create(HTMLTemplateElement.p
rototype, { |
| 26 constructor: { |
| 27 value: HTMLTemplateElementExtension, |
| 28 writable: true |
| 29 } |
| 30 }); |
| 31 const DataTemplate = Polymer.PropertyEffects(HTMLTemplateElementExtension); |
| 32 const MutableDataTemplate = Polymer.MutableData(DataTemplate); |
| 33 |
| 34 // Applies a DataTemplate subclass to a <template> instance |
| 35 function upgradeTemplate(template, constructor) { |
| 36 newInstance = template; |
| 37 Object.setPrototypeOf(template, constructor.prototype); |
| 38 new constructor(); |
| 39 newInstance = null; |
| 40 } |
| 41 |
| 42 // Base class for TemplateInstance's |
| 43 /** |
| 44 * @constructor |
| 45 * @implements {Polymer_PropertyEffects} |
| 46 */ |
| 47 const base = Polymer.PropertyEffects(class {}); |
| 48 class TemplateInstanceBase extends base { |
| 49 constructor(props) { |
| 50 super(); |
| 51 this._configureProperties(props); |
| 52 this.root = this._stampTemplate(this.__dataHost); |
| 53 // Save list of stamped children |
| 54 let children = this.children = []; |
| 55 for (let n = this.root.firstChild; n; n=n.nextSibling) { |
| 56 children.push(n); |
| 57 n.__templatizeInstance = this; |
| 58 } |
| 59 if (this.__templatizeOwner.__hideTemplateChildren__) { |
| 60 this._showHideChildren(true); |
| 61 } |
| 62 // Flush props only when props are passed if instance props exist |
| 63 // or when there isn't instance props. |
| 64 let options = this.__templatizeOptions; |
| 65 if ((props && options.instanceProps) || !options.instanceProps) { |
| 66 this._enableProperties(); |
| 67 } |
| 68 } |
| 69 /** |
| 70 * Configure the given `props` by calling `_setPendingProperty`. Also |
| 71 * sets any properties stored in `__hostProps`. |
| 72 * @private |
| 73 * @param {Object} props Object of property name-value pairs to set. |
| 74 */ |
| 75 _configureProperties(props) { |
| 76 let options = this.__templatizeOptions; |
| 77 if (props) { |
| 78 for (let iprop in options.instanceProps) { |
| 79 if (iprop in props) { |
| 80 this._setPendingProperty(iprop, props[iprop]); |
| 81 } |
| 82 } |
| 83 } |
| 84 for (let hprop in this.__hostProps) { |
| 85 this._setPendingProperty(hprop, this.__dataHost['_host_' + hprop]); |
| 86 } |
| 87 } |
| 88 /** |
| 89 * Forwards a host property to this instance. This method should be |
| 90 * called on instances from the `options.forwardHostProp` callback |
| 91 * to propagate changes of host properties to each instance. |
| 92 * |
| 93 * Note this method enqueues the change, which are flushed as a batch. |
| 94 * |
| 95 * @param {string} prop Property or path name |
| 96 * @param {*} value Value of the property to forward |
| 97 */ |
| 98 forwardHostProp(prop, value) { |
| 99 if (this._setPendingPropertyOrPath(prop, value, false, true)) { |
| 100 this.__dataHost._enqueueClient(this); |
| 101 } |
| 102 } |
| 103 /** |
| 104 * @override |
| 105 */ |
| 106 _addEventListenerToNode(node, eventName, handler) { |
| 107 if (this._methodHost && this.__templatizeOptions.parentModel) { |
| 108 // If this instance should be considered a parent model, decorate |
| 109 // events this template instance as `model` |
| 110 this._methodHost._addEventListenerToNode(node, eventName, (e) => { |
| 111 e.model = this; |
| 112 handler(e); |
| 113 }); |
| 114 } else { |
| 115 // Otherwise delegate to the template's host (which could be) |
| 116 // another template instance |
| 117 let templateHost = this.__dataHost.__dataHost; |
| 118 if (templateHost) { |
| 119 templateHost._addEventListenerToNode(node, eventName, handler); |
| 120 } |
| 121 } |
| 122 } |
| 123 /** |
| 124 * Shows or hides the template instance top level child elements. For |
| 125 * text nodes, `textContent` is removed while "hidden" and replaced when |
| 126 * "shown." |
| 127 * @param {boolean} hide Set to true to hide the children; |
| 128 * set to false to show them. |
| 129 * @protected |
| 130 */ |
| 131 _showHideChildren(hide) { |
| 132 let c = this.children; |
| 133 for (let i=0; i<c.length; i++) { |
| 134 let n = c[i]; |
| 135 // Ignore non-changes |
| 136 if (Boolean(hide) != Boolean(n.__hideTemplateChildren__)) { |
| 137 if (n.nodeType === Node.TEXT_NODE) { |
| 138 if (hide) { |
| 139 n.__polymerTextContent__ = n.textContent; |
| 140 n.textContent = ''; |
| 141 } else { |
| 142 n.textContent = n.__polymerTextContent__; |
| 143 } |
| 144 } else if (n.style) { |
| 145 if (hide) { |
| 146 n.__polymerDisplay__ = n.style.display; |
| 147 n.style.display = 'none'; |
| 148 } else { |
| 149 n.style.display = n.__polymerDisplay__; |
| 150 } |
| 151 } |
| 152 } |
| 153 n.__hideTemplateChildren__ = hide; |
| 154 if (n._showHideChildren) { |
| 155 n._showHideChildren(hide); |
| 156 } |
| 157 } |
| 158 } |
| 159 /** |
| 160 * Overrides default property-effects implementation to intercept |
| 161 * textContent bindings while children are "hidden" and cache in |
| 162 * private storage for later retrieval. |
| 163 * |
| 164 * @override |
| 165 */ |
| 166 _setUnmanagedPropertyToNode(node, prop, value) { |
| 167 if (node.__hideTemplateChildren__ && |
| 168 node.nodeType == Node.TEXT_NODE && prop == 'textContent') { |
| 169 node.__polymerTextContent__ = value; |
| 170 } else { |
| 171 super._setUnmanagedPropertyToNode(node, prop, value); |
| 172 } |
| 173 } |
| 174 /** |
| 175 * Find the parent model of this template instance. The parent model |
| 176 * is either another templatize instance that had option `parentModel: tru
e`, |
| 177 * or else the host element. |
| 178 * |
| 179 * @return {Polymer.PropertyEffectsInterface} The parent model of this ins
tance |
| 180 */ |
| 181 get parentModel() { |
| 182 let model = this.__parentModel; |
| 183 if (!model) { |
| 184 let options; |
| 185 model = this |
| 186 do { |
| 187 // A template instance's `__dataHost` is a <template> |
| 188 // `model.__dataHost.__dataHost` is the template's host |
| 189 model = model.__dataHost.__dataHost; |
| 190 } while ((options = model.__templatizeOptions) && !options.parentModel
) |
| 191 this.__parentModel = model; |
| 192 } |
| 193 return model; |
| 194 } |
| 195 } |
| 196 |
| 197 const MutableTemplateInstanceBase = Polymer.MutableData(TemplateInstanceBase
); |
| 198 |
| 199 function findMethodHost(template) { |
| 200 // Technically this should be the owner of the outermost template. |
| 201 // In shadow dom, this is always getRootNode().host, but we can |
| 202 // approximate this via cooperation with our dataHost always setting |
| 203 // `_methodHost` as long as there were bindings (or id's) on this |
| 204 // instance causing it to get a dataHost. |
| 205 let templateHost = template.__dataHost; |
| 206 return templateHost && templateHost._methodHost || templateHost; |
| 207 } |
| 208 |
| 209 function createTemplatizerClass(template, templateInfo, options) { |
| 210 // Anonymous class created by the templatize |
| 211 /** |
| 212 * @unrestricted |
| 213 */ |
| 214 let base = options.mutableData ? |
| 215 MutableTemplateInstanceBase : TemplateInstanceBase; |
| 216 let klass = class extends base { } |
| 217 klass.prototype.__templatizeOptions = options; |
| 218 klass.prototype._bindTemplate(template); |
| 219 addNotifyEffects(klass, template, templateInfo, options); |
| 220 return klass; |
| 221 } |
| 222 |
| 223 function addPropagateEffects(template, templateInfo, options) { |
| 224 let userForwardHostProp = options.forwardHostProp; |
| 225 if (userForwardHostProp) { |
| 226 // Provide data API and property effects on memoized template class |
| 227 let klass = templateInfo.templatizeTemplateClass; |
| 228 if (!klass) { |
| 229 let base = options.mutableData ? MutableDataTemplate : DataTemplate; |
| 230 klass = templateInfo.templatizeTemplateClass = |
| 231 class TemplatizedTemplate extends base {} |
| 232 // Add template - >instances effects |
| 233 // and host <- template effects |
| 234 let hostProps = templateInfo.hostProps; |
| 235 for (let prop in hostProps) { |
| 236 klass.prototype._addPropertyEffect('_host_' + prop, |
| 237 klass.prototype.PROPERTY_EFFECT_TYPES.PROPAGATE, |
| 238 {fn: createForwardHostPropEffect(prop, userForwardHostProp)}); |
| 239 klass.prototype._createNotifyingProperty('_host_' + prop); |
| 240 } |
| 241 } |
| 242 upgradeTemplate(template, klass); |
| 243 // Mix any pre-bound data into __data; no need to flush this to |
| 244 // instances since they pull from the template at instance-time |
| 245 if (template.__dataProto) { |
| 246 // Note, generally `__dataProto` could be chained, but it's guaranteed |
| 247 // to not be since this is a vanilla template we just added effects to |
| 248 Object.assign(template.__data, template.__dataProto); |
| 249 } |
| 250 // Clear any pending data for performance |
| 251 template.__dataTemp = {}; |
| 252 template.__dataPending = null; |
| 253 template.__dataOld = null; |
| 254 template._enableProperties(); |
| 255 } |
| 256 } |
| 257 |
| 258 function createForwardHostPropEffect(hostProp, userForwardHostProp) { |
| 259 return function forwardHostProp(template, prop, props) { |
| 260 userForwardHostProp.call(template.__templatizeOwner, |
| 261 prop.substring('_host_'.length), props[prop]); |
| 262 } |
| 263 } |
| 264 |
| 265 function addNotifyEffects(klass, template, templateInfo, options) { |
| 266 let hostProps = templateInfo.hostProps || {}; |
| 267 for (let iprop in options.instanceProps) { |
| 268 delete hostProps[iprop]; |
| 269 let userNotifyInstanceProp = options.notifyInstanceProp; |
| 270 if (userNotifyInstanceProp) { |
| 271 klass.prototype._addPropertyEffect(iprop, |
| 272 klass.prototype.PROPERTY_EFFECT_TYPES.NOTIFY, |
| 273 {fn: createNotifyInstancePropEffect(iprop, userNotifyInstanceProp)})
; |
| 274 } |
| 275 } |
| 276 if (options.forwardHostProp && template.__dataHost) { |
| 277 for (let hprop in hostProps) { |
| 278 klass.prototype._addPropertyEffect(hprop, |
| 279 klass.prototype.PROPERTY_EFFECT_TYPES.NOTIFY, |
| 280 {fn: createNotifyHostPropEffect()}) |
| 281 } |
| 282 } |
| 283 } |
| 284 |
| 285 function createNotifyInstancePropEffect(instProp, userNotifyInstanceProp) { |
| 286 return function notifyInstanceProp(inst, prop, props) { |
| 287 userNotifyInstanceProp.call(inst.__templatizeOwner, |
| 288 inst, prop, props[prop]); |
| 289 } |
| 290 } |
| 291 |
| 292 function createNotifyHostPropEffect() { |
| 293 return function notifyHostProp(inst, prop, props) { |
| 294 inst.__dataHost._setPendingPropertyOrPath('_host_' + prop, props[prop],
true, true); |
| 295 } |
| 296 } |
| 297 |
| 298 /** |
| 299 * Module for preparing and stamping instances of templates that utilize |
| 300 * Polymer's data-binding and declarative event listener features. |
| 301 * |
| 302 * Example: |
| 303 * |
| 304 * // Get a template from somewhere, e.g. light DOM |
| 305 * let template = this.querySelector('template'); |
| 306 * // Prepare the template |
| 307 * let TemplateClass = Polymer.Templatize.templatize(template); |
| 308 * // Instance the template with an initial data model |
| 309 * let instance = new TemplateClass({myProp: 'initial'}); |
| 310 * // Insert the instance's DOM somewhere, e.g. element's shadow DOM |
| 311 * this.shadowRoot.appendChild(instance.root); |
| 312 * // Changing a property on the instance will propagate to bindings |
| 313 * // in the template |
| 314 * instance.myProp = 'new value'; |
| 315 * |
| 316 * The `options` dictionary passed to `templatize` allows for customizing |
| 317 * features of the generated template class, including how outer-scope host |
| 318 * properties should be forwarded into template instances, how any instance |
| 319 * properties added into the template's scope should be notified out to |
| 320 * the host, and whether the instance should be decorated as a "parent model
" |
| 321 * of any event handlers. |
| 322 * |
| 323 * // Customze property forwarding and event model decoration |
| 324 * let TemplateClass = Polymer.Tempaltize.templatize(template, this, { |
| 325 * parentModel: true, |
| 326 * instanceProps: {...}, |
| 327 * forwardHostProp(property, value) {...}, |
| 328 * notifyInstanceProp(instance, property, value) {...}, |
| 329 * }); |
| 330 * |
| 331 * |
| 332 * @namespace |
| 333 * @memberof Polymer |
| 334 * @summary Module for preparing and stamping instances of templates |
| 335 * utilizing Polymer templating features. |
| 336 */ |
| 337 const Templatize = { |
| 338 |
| 339 /** |
| 340 * Returns an anonymous `Polymer.PropertyEffects` class bound to the |
| 341 * `<template>` provided. Instancing the class will result in the |
| 342 * template being stamped into document fragment stored as the instance's |
| 343 * `root` property, after which it can be appended to the DOM. |
| 344 * |
| 345 * Templates may utilize all Polymer data-binding features as well as |
| 346 * declarative event listeners. Event listeners and inline computing |
| 347 * functions in the template will be called on the host of the template. |
| 348 * |
| 349 * The constructor returned takes a single argument dictionary of initial |
| 350 * property values to propagate into template bindings. Additionally |
| 351 * host properties can be forwarded in, and instance properties can be |
| 352 * notified out by providing optional callbacks in the `options` dictionar
y. |
| 353 * |
| 354 * Valid configuration in `options` are as follows: |
| 355 * |
| 356 * - `forwardHostProp(property, value)`: Called when a property referenced |
| 357 * in the template changed on the template's host. As this library does |
| 358 * not retain references to templates instanced by the user, it is the |
| 359 * templatize owner's responsibility to forward host property changes in
to |
| 360 * user-stamped instances. The `instance.forwardHostProp(property, valu
e)` |
| 361 * method on the generated class should be called to forward host |
| 362 * properties into the template to prevent unnecessary property-changed |
| 363 * notifications. Any properties referenced in the template that are not |
| 364 * defined in `instanceProps` will be notified up to the template's host |
| 365 * automatically. |
| 366 * - `instanceProps`: Dictionary of property names that will be added |
| 367 * to the instance by the templatize owner. These properties shadow any |
| 368 * host properties, and changes within the template to these properties |
| 369 * will result in `notifyInstanceProp` being called. |
| 370 * - `mutableData`: When `true`, the generated class will skip strict |
| 371 * dirty-checking for objects and arrays (always consider them to be |
| 372 * "dirty"). |
| 373 * - `notifyInstanceProp(instance, property, value)`: Called when |
| 374 * an instance property changes. Users may choose to call `notifyPath` |
| 375 * on e.g. the owner to notify the change. |
| 376 * - `parentModel`: When `true`, events handled by declarative event liste
ners |
| 377 * (`on-event="handler"`) will be decorated with a `model` property poin
ting |
| 378 * to the template instance that stamped it. It will also be returned |
| 379 * from `instance.parentModel` in cases where template instance nesting |
| 380 * causes an inner model to shadow an outer model. |
| 381 * |
| 382 * Note that the class returned from `templatize` is generated only once |
| 383 * for a given `<template>` using `options` from the first call for that |
| 384 * template, and the cached class is returned for all subsequent calls to |
| 385 * `templatize` for that template. As such, `options` callbacks should no
t |
| 386 * close over owner-specific properties since only the first `options` is |
| 387 * used; rather, callbacks are called bound to the `owner`, and so context |
| 388 * needed from the callbacks (such as references to `instances` stamped) |
| 389 * should be stored on the `owner` such that they can be retrieved via `th
is`. |
| 390 * |
| 391 * @memberof Polymer.Templatize |
| 392 * @param {HTMLTemplateElement} template Template to templatize |
| 393 * @param {*} owner Owner of the template instances; any optional callback
s |
| 394 * will be bound to this owner. |
| 395 * @param {*=} options Options dictionary (see summary for details) |
| 396 * @return {TemplateInstanceBase} Generated class bound to the template |
| 397 * provided |
| 398 */ |
| 399 templatize(template, owner, options) { |
| 400 options = options || {}; |
| 401 if (template.__templatizeOwner) { |
| 402 throw new Error('A <template> can only be templatized once'); |
| 403 } |
| 404 template.__templatizeOwner = owner; |
| 405 let templateInfo = owner.constructor._parseTemplate(template); |
| 406 // Get memoized base class for the prototypical template, which |
| 407 // includes property effects for binding template & forwarding |
| 408 let baseClass = templateInfo.templatizeInstanceClass; |
| 409 if (!baseClass) { |
| 410 baseClass = createTemplatizerClass(template, templateInfo, options); |
| 411 templateInfo.templatizeInstanceClass = baseClass; |
| 412 } |
| 413 // Host property forwarding must be installed onto template instance |
| 414 addPropagateEffects(template, templateInfo, options); |
| 415 // Subclass base class and add reference for this specific template |
| 416 let klass = class TemplateInstance extends baseClass {}; |
| 417 klass.prototype._methodHost = findMethodHost(template); |
| 418 klass.prototype.__dataHost = template; |
| 419 klass.prototype.__templatizeOwner = owner; |
| 420 klass.prototype.__hostProps = templateInfo.hostProps; |
| 421 return klass; |
| 422 }, |
| 423 |
| 424 /** |
| 425 * Returns the template "model" associated with a given element, which |
| 426 * serves as the binding scope for the template instance the element is |
| 427 * contained in. A template model is an instance of |
| 428 * `TemplateInstanceBase`, and should be used to manipulate data |
| 429 * associated with this template instance. |
| 430 * |
| 431 * Example: |
| 432 * |
| 433 * let model = modelForElement(el); |
| 434 * if (model.index < 10) { |
| 435 * model.set('item.checked', true); |
| 436 * } |
| 437 * |
| 438 * @memberof Polymer.Templatize |
| 439 * @param {HTMLTemplateElement} template The model will be returned for |
| 440 * elements stamped from this template |
| 441 * @param {HTMLElement} el Element for which to return a template model. |
| 442 * @return {TemplateInstanceBase} Template instance representing the |
| 443 * binding scope for the element |
| 444 */ |
| 445 modelForElement(template, el) { |
| 446 let model; |
| 447 while (el) { |
| 448 // An element with a __templatizeInstance marks the top boundary |
| 449 // of a scope; walk up until we find one, and then ensure that |
| 450 // its __dataHost matches `this`, meaning this dom-repeat stamped it |
| 451 if ((model = el.__templatizeInstance)) { |
| 452 // Found an element stamped by another template; keep walking up |
| 453 // from its __dataHost |
| 454 if (model.__dataHost != template) { |
| 455 el = model.__dataHost; |
| 456 } else { |
| 457 return model; |
| 458 } |
| 459 } else { |
| 460 // Still in a template scope, keep going up until |
| 461 // a __templatizeInstance is found |
| 462 el = el.parentNode; |
| 463 } |
| 464 } |
| 465 return null; |
| 466 } |
| 467 } |
| 468 |
| 469 Polymer.Templatize = Templatize; |
| 470 |
| 471 })(); |
| 472 |
| 473 </script> |
| OLD | NEW |