UNPKG

27.1 kBJavaScriptView Raw
1/**
2@license
3Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
4This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
5The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
6The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
7Code distributed by Google as part of the polymer project is also
8subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
9*/
10
11/**
12 * Module for preparing and stamping instances of templates that utilize
13 * Polymer's data-binding and declarative event listener features.
14 *
15 * Example:
16 *
17 * // Get a template from somewhere, e.g. light DOM
18 * let template = this.querySelector('template');
19 * // Prepare the template
20 * let TemplateClass = Templatize.templatize(template);
21 * // Instance the template with an initial data model
22 * let instance = new TemplateClass({myProp: 'initial'});
23 * // Insert the instance's DOM somewhere, e.g. element's shadow DOM
24 * this.shadowRoot.appendChild(instance.root);
25 * // Changing a property on the instance will propagate to bindings
26 * // in the template
27 * instance.myProp = 'new value';
28 *
29 * The `options` dictionary passed to `templatize` allows for customizing
30 * features of the generated template class, including how outer-scope host
31 * properties should be forwarded into template instances, how any instance
32 * properties added into the template's scope should be notified out to
33 * the host, and whether the instance should be decorated as a "parent model"
34 * of any event handlers.
35 *
36 * // Customize property forwarding and event model decoration
37 * let TemplateClass = Templatize.templatize(template, this, {
38 * parentModel: true,
39 * forwardHostProp(property, value) {...},
40 * instanceProps: {...},
41 * notifyInstanceProp(instance, property, value) {...},
42 * });
43 *
44 * @summary Module for preparing and stamping instances of templates
45 * utilizing Polymer templating features.
46 */
47
48import './boot.js';
49
50import { PropertyEffects } from '../mixins/property-effects.js';
51import { MutableData } from '../mixins/mutable-data.js';
52import { strictTemplatePolicy, legacyWarnings } from './settings.js';
53import { wrap } from './wrap.js';
54
55// Base class for HTMLTemplateElement extension that has property effects
56// machinery for propagating host properties to children. This is an ES5
57// class only because Babel (incorrectly) requires super() in the class
58// constructor even though no `this` is used and it returns an instance.
59let newInstance = null;
60
61/**
62 * @constructor
63 * @extends {HTMLTemplateElement}
64 * @private
65 */
66function HTMLTemplateElementExtension() { return newInstance; }
67HTMLTemplateElementExtension.prototype = Object.create(HTMLTemplateElement.prototype, {
68 constructor: {
69 value: HTMLTemplateElementExtension,
70 writable: true
71 }
72});
73
74/**
75 * @constructor
76 * @implements {Polymer_PropertyEffects}
77 * @extends {HTMLTemplateElementExtension}
78 * @private
79 */
80const DataTemplate = PropertyEffects(HTMLTemplateElementExtension);
81
82/**
83 * @constructor
84 * @implements {Polymer_MutableData}
85 * @extends {DataTemplate}
86 * @private
87 */
88const MutableDataTemplate = MutableData(DataTemplate);
89
90// Applies a DataTemplate subclass to a <template> instance
91function upgradeTemplate(template, constructor) {
92 newInstance = template;
93 Object.setPrototypeOf(template, constructor.prototype);
94 new constructor();
95 newInstance = null;
96}
97
98/**
99 * Base class for TemplateInstance.
100 * @constructor
101 * @extends {HTMLElement}
102 * @implements {Polymer_PropertyEffects}
103 * @private
104 */
105const templateInstanceBase = PropertyEffects(class {});
106
107export function showHideChildren(hide, children) {
108 for (let i=0; i<children.length; i++) {
109 let n = children[i];
110 // Ignore non-changes
111 if (Boolean(hide) != Boolean(n.__hideTemplateChildren__)) {
112 // clear and restore text
113 if (n.nodeType === Node.TEXT_NODE) {
114 if (hide) {
115 n.__polymerTextContent__ = n.textContent;
116 n.textContent = '';
117 } else {
118 n.textContent = n.__polymerTextContent__;
119 }
120 // remove and replace slot
121 } else if (n.localName === 'slot') {
122 if (hide) {
123 n.__polymerReplaced__ = document.createComment('hidden-slot');
124 wrap(wrap(n).parentNode).replaceChild(n.__polymerReplaced__, n);
125 } else {
126 const replace = n.__polymerReplaced__;
127 if (replace) {
128 wrap(wrap(replace).parentNode).replaceChild(n, replace);
129 }
130 }
131 }
132 // hide and show nodes
133 else if (n.style) {
134 if (hide) {
135 n.__polymerDisplay__ = n.style.display;
136 n.style.display = 'none';
137 } else {
138 n.style.display = n.__polymerDisplay__;
139 }
140 }
141 }
142 n.__hideTemplateChildren__ = hide;
143 if (n._showHideChildren) {
144 n._showHideChildren(hide);
145 }
146 }
147}
148
149/**
150 * @polymer
151 * @customElement
152 * @appliesMixin PropertyEffects
153 * @unrestricted
154 */
155class TemplateInstanceBase extends templateInstanceBase {
156 constructor(props) {
157 super();
158 this._configureProperties(props);
159 /** @type {!StampedTemplate} */
160 this.root = this._stampTemplate(this.__dataHost);
161 // Save list of stamped children
162 let children = [];
163 /** @suppress {invalidCasts} */
164 this.children = /** @type {!NodeList} */ (children);
165 // Polymer 1.x did not use `Polymer.dom` here so not bothering.
166 for (let n = this.root.firstChild; n; n=n.nextSibling) {
167 children.push(n);
168 n.__templatizeInstance = this;
169 }
170 if (this.__templatizeOwner &&
171 this.__templatizeOwner.__hideTemplateChildren__) {
172 this._showHideChildren(true);
173 }
174 // Flush props only when props are passed if instance props exist
175 // or when there isn't instance props.
176 let options = this.__templatizeOptions;
177 if ((props && options.instanceProps) || !options.instanceProps) {
178 this._enableProperties();
179 }
180 }
181 /**
182 * Configure the given `props` by calling `_setPendingProperty`. Also
183 * sets any properties stored in `__hostProps`.
184 * @private
185 * @param {Object} props Object of property name-value pairs to set.
186 * @return {void}
187 */
188 _configureProperties(props) {
189 let options = this.__templatizeOptions;
190 if (options.forwardHostProp) {
191 for (let hprop in this.__hostProps) {
192 this._setPendingProperty(hprop, this.__dataHost['_host_' + hprop]);
193 }
194 }
195 // Any instance props passed in the constructor will overwrite host props;
196 // normally this would be a user error but we don't specifically filter them
197 for (let iprop in props) {
198 this._setPendingProperty(iprop, props[iprop]);
199 }
200 }
201 /**
202 * Forwards a host property to this instance. This method should be
203 * called on instances from the `options.forwardHostProp` callback
204 * to propagate changes of host properties to each instance.
205 *
206 * Note this method enqueues the change, which are flushed as a batch.
207 *
208 * @param {string} prop Property or path name
209 * @param {*} value Value of the property to forward
210 * @return {void}
211 */
212 forwardHostProp(prop, value) {
213 if (this._setPendingPropertyOrPath(prop, value, false, true)) {
214 this.__dataHost._enqueueClient(this);
215 }
216 }
217
218 /**
219 * Override point for adding custom or simulated event handling.
220 *
221 * @override
222 * @param {!Node} node Node to add event listener to
223 * @param {string} eventName Name of event
224 * @param {function(!Event):void} handler Listener function to add
225 * @return {void}
226 */
227 _addEventListenerToNode(node, eventName, handler) {
228 if (this._methodHost && this.__templatizeOptions.parentModel) {
229 // If this instance should be considered a parent model, decorate
230 // events this template instance as `model`
231 this._methodHost._addEventListenerToNode(node, eventName, (e) => {
232 e.model = this;
233 handler(e);
234 });
235 } else {
236 // Otherwise delegate to the template's host (which could be)
237 // another template instance
238 let templateHost = this.__dataHost.__dataHost;
239 if (templateHost) {
240 templateHost._addEventListenerToNode(node, eventName, handler);
241 }
242 }
243 }
244 /**
245 * Shows or hides the template instance top level child elements. For
246 * text nodes, `textContent` is removed while "hidden" and replaced when
247 * "shown."
248 * @param {boolean} hide Set to true to hide the children;
249 * set to false to show them.
250 * @return {void}
251 * @protected
252 */
253 _showHideChildren(hide) {
254 showHideChildren(hide, this.children);
255 }
256 /**
257 * Overrides default property-effects implementation to intercept
258 * textContent bindings while children are "hidden" and cache in
259 * private storage for later retrieval.
260 *
261 * @override
262 * @param {!Node} node The node to set a property on
263 * @param {string} prop The property to set
264 * @param {*} value The value to set
265 * @return {void}
266 * @protected
267 */
268 _setUnmanagedPropertyToNode(node, prop, value) {
269 if (node.__hideTemplateChildren__ &&
270 node.nodeType == Node.TEXT_NODE && prop == 'textContent') {
271 node.__polymerTextContent__ = value;
272 } else {
273 super._setUnmanagedPropertyToNode(node, prop, value);
274 }
275 }
276 /**
277 * Find the parent model of this template instance. The parent model
278 * is either another templatize instance that had option `parentModel: true`,
279 * or else the host element.
280 *
281 * @return {!Polymer_PropertyEffects} The parent model of this instance
282 */
283 get parentModel() {
284 let model = this.__parentModel;
285 if (!model) {
286 let options;
287 model = this;
288 do {
289 // A template instance's `__dataHost` is a <template>
290 // `model.__dataHost.__dataHost` is the template's host
291 model = model.__dataHost.__dataHost;
292 } while ((options = model.__templatizeOptions) && !options.parentModel);
293 this.__parentModel = model;
294 }
295 return model;
296 }
297
298 /**
299 * Stub of HTMLElement's `dispatchEvent`, so that effects that may
300 * dispatch events safely no-op.
301 *
302 * @param {Event} event Event to dispatch
303 * @return {boolean} Always true.
304 * @override
305 */
306 dispatchEvent(event) { // eslint-disable-line no-unused-vars
307 return true;
308 }
309}
310
311/** @type {!DataTemplate} */
312TemplateInstanceBase.prototype.__dataHost;
313/** @type {!TemplatizeOptions} */
314TemplateInstanceBase.prototype.__templatizeOptions;
315/** @type {!Polymer_PropertyEffects} */
316TemplateInstanceBase.prototype._methodHost;
317/** @type {!Object} */
318TemplateInstanceBase.prototype.__templatizeOwner;
319/** @type {!Object} */
320TemplateInstanceBase.prototype.__hostProps;
321
322/**
323 * @constructor
324 * @extends {TemplateInstanceBase}
325 * @implements {Polymer_MutableData}
326 * @private
327 */
328const MutableTemplateInstanceBase = MutableData(
329 // This cast shouldn't be neccessary, but Closure doesn't understand that
330 // TemplateInstanceBase is a constructor function.
331 /** @type {function(new:TemplateInstanceBase)} */ (TemplateInstanceBase));
332
333function findMethodHost(template) {
334 // Technically this should be the owner of the outermost template.
335 // In shadow dom, this is always getRootNode().host, but we can
336 // approximate this via cooperation with our dataHost always setting
337 // `_methodHost` as long as there were bindings (or id's) on this
338 // instance causing it to get a dataHost.
339 let templateHost = template.__dataHost;
340 return templateHost && templateHost._methodHost || templateHost;
341}
342
343/* eslint-disable valid-jsdoc */
344/**
345 * @suppress {missingProperties} class.prototype is not defined for some reason
346 */
347function createTemplatizerClass(template, templateInfo, options) {
348 /**
349 * @constructor
350 * @extends {TemplateInstanceBase}
351 */
352 let templatizerBase = options.mutableData ?
353 MutableTemplateInstanceBase : TemplateInstanceBase;
354
355 // Affordance for global mixins onto TemplatizeInstance
356 if (templatize.mixin) {
357 templatizerBase = templatize.mixin(templatizerBase);
358 }
359
360 /**
361 * Anonymous class created by the templatize
362 * @constructor
363 * @private
364 */
365 let klass = class extends templatizerBase { };
366 /** @override */
367 klass.prototype.__templatizeOptions = options;
368 klass.prototype._bindTemplate(template);
369 addNotifyEffects(klass, template, templateInfo, options);
370 return klass;
371}
372
373/**
374 * Adds propagate effects from the template to the template instance for
375 * properties that the host binds to the template using the `_host_` prefix.
376 *
377 * @suppress {missingProperties} class.prototype is not defined for some reason
378 */
379function addPropagateEffects(target, templateInfo, options, methodHost) {
380 let userForwardHostProp = options.forwardHostProp;
381 if (userForwardHostProp && templateInfo.hasHostProps) {
382 // Under the `removeNestedTemplates` optimization, a custom element like
383 // `dom-if` or `dom-repeat` can itself be treated as the "template"; this
384 // flag is used to switch between upgrading a `<template>` to be a property
385 // effects client vs. adding the effects directly to the custom element
386 const isTemplate = target.localName == 'template';
387 // Provide data API and property effects on memoized template class
388 let klass = templateInfo.templatizeTemplateClass;
389 if (!klass) {
390 if (isTemplate) {
391 /**
392 * @constructor
393 * @extends {DataTemplate}
394 */
395 let templatizedBase =
396 options.mutableData ? MutableDataTemplate : DataTemplate;
397
398 // NOTE: due to https://github.com/google/closure-compiler/issues/2928,
399 // combining the next two lines into one assignment causes a spurious
400 // type error.
401 /** @private */
402 class TemplatizedTemplate extends templatizedBase {}
403 klass = templateInfo.templatizeTemplateClass = TemplatizedTemplate;
404 } else {
405 /**
406 * @constructor
407 * @extends {PolymerElement}
408 */
409 const templatizedBase = target.constructor;
410
411 // Create a cached subclass of the base custom element class onto which
412 // to put the template-specific propagate effects
413 // NOTE: due to https://github.com/google/closure-compiler/issues/2928,
414 // combining the next two lines into one assignment causes a spurious
415 // type error.
416 /** @private */
417 class TemplatizedTemplateExtension extends templatizedBase {}
418 klass = templateInfo.templatizeTemplateClass =
419 TemplatizedTemplateExtension;
420 }
421 // Add template - >instances effects
422 // and host <- template effects
423 let hostProps = templateInfo.hostProps;
424 for (let prop in hostProps) {
425 klass.prototype._addPropertyEffect('_host_' + prop,
426 klass.prototype.PROPERTY_EFFECT_TYPES.PROPAGATE,
427 {fn: createForwardHostPropEffect(prop, userForwardHostProp)});
428 klass.prototype._createNotifyingProperty('_host_' + prop);
429 }
430 if (legacyWarnings && methodHost) {
431 warnOnUndeclaredProperties(templateInfo, options, methodHost);
432 }
433 }
434 // Mix any pre-bound data into __data; no need to flush this to
435 // instances since they pull from the template at instance-time
436 if (target.__dataProto) {
437 // Note, generally `__dataProto` could be chained, but it's guaranteed
438 // to not be since this is a vanilla template we just added effects to
439 Object.assign(target.__data, target.__dataProto);
440 }
441 if (isTemplate) {
442 upgradeTemplate(target, klass);
443 // Clear any pending data for performance
444 target.__dataTemp = {};
445 target.__dataPending = null;
446 target.__dataOld = null;
447 target._enableProperties();
448 } else {
449 // Swizzle the cached subclass prototype onto the custom element
450 Object.setPrototypeOf(target, klass.prototype);
451 // Check for any pre-bound instance host properties, and do the
452 // instance property delete/assign dance for those (directly into data;
453 // not need to go through accessor since they are pulled at instance time)
454 const hostProps = templateInfo.hostProps;
455 for (let prop in hostProps) {
456 prop = '_host_' + prop;
457 if (prop in target) {
458 const val = target[prop];
459 delete target[prop];
460 target.__data[prop] = val;
461 }
462 }
463 }
464 }
465}
466/* eslint-enable valid-jsdoc */
467
468function createForwardHostPropEffect(hostProp, userForwardHostProp) {
469 return function forwardHostProp(template, prop, props) {
470 userForwardHostProp.call(template.__templatizeOwner,
471 prop.substring('_host_'.length), props[prop]);
472 };
473}
474
475function addNotifyEffects(klass, template, templateInfo, options) {
476 let hostProps = templateInfo.hostProps || {};
477 for (let iprop in options.instanceProps) {
478 delete hostProps[iprop];
479 let userNotifyInstanceProp = options.notifyInstanceProp;
480 if (userNotifyInstanceProp) {
481 klass.prototype._addPropertyEffect(iprop,
482 klass.prototype.PROPERTY_EFFECT_TYPES.NOTIFY,
483 {fn: createNotifyInstancePropEffect(iprop, userNotifyInstanceProp)});
484 }
485 }
486 if (options.forwardHostProp && template.__dataHost) {
487 for (let hprop in hostProps) {
488 // As we're iterating hostProps in this function, note whether
489 // there were any, for an optimization in addPropagateEffects
490 if (!templateInfo.hasHostProps) {
491 templateInfo.hasHostProps = true;
492 }
493 klass.prototype._addPropertyEffect(hprop,
494 klass.prototype.PROPERTY_EFFECT_TYPES.NOTIFY,
495 {fn: createNotifyHostPropEffect()});
496 }
497 }
498}
499
500function createNotifyInstancePropEffect(instProp, userNotifyInstanceProp) {
501 return function notifyInstanceProp(inst, prop, props) {
502 userNotifyInstanceProp.call(inst.__templatizeOwner,
503 inst, prop, props[prop]);
504 };
505}
506
507function createNotifyHostPropEffect() {
508 return function notifyHostProp(inst, prop, props) {
509 inst.__dataHost._setPendingPropertyOrPath('_host_' + prop, props[prop], true, true);
510 };
511}
512
513
514/**
515 * Returns an anonymous `PropertyEffects` class bound to the
516 * `<template>` provided. Instancing the class will result in the
517 * template being stamped into a document fragment stored as the instance's
518 * `root` property, after which it can be appended to the DOM.
519 *
520 * Templates may utilize all Polymer data-binding features as well as
521 * declarative event listeners. Event listeners and inline computing
522 * functions in the template will be called on the host of the template.
523 *
524 * The constructor returned takes a single argument dictionary of initial
525 * property values to propagate into template bindings. Additionally
526 * host properties can be forwarded in, and instance properties can be
527 * notified out by providing optional callbacks in the `options` dictionary.
528 *
529 * Valid configuration in `options` are as follows:
530 *
531 * - `forwardHostProp(property, value)`: Called when a property referenced
532 * in the template changed on the template's host. As this library does
533 * not retain references to templates instanced by the user, it is the
534 * templatize owner's responsibility to forward host property changes into
535 * user-stamped instances. The `instance.forwardHostProp(property, value)`
536 * method on the generated class should be called to forward host
537 * properties into the template to prevent unnecessary property-changed
538 * notifications. Any properties referenced in the template that are not
539 * defined in `instanceProps` will be notified up to the template's host
540 * automatically.
541 * - `instanceProps`: Dictionary of property names that will be added
542 * to the instance by the templatize owner. These properties shadow any
543 * host properties, and changes within the template to these properties
544 * will result in `notifyInstanceProp` being called.
545 * - `mutableData`: When `true`, the generated class will skip strict
546 * dirty-checking for objects and arrays (always consider them to be
547 * "dirty").
548 * - `notifyInstanceProp(instance, property, value)`: Called when
549 * an instance property changes. Users may choose to call `notifyPath`
550 * on e.g. the owner to notify the change.
551 * - `parentModel`: When `true`, events handled by declarative event listeners
552 * (`on-event="handler"`) will be decorated with a `model` property pointing
553 * to the template instance that stamped it. It will also be returned
554 * from `instance.parentModel` in cases where template instance nesting
555 * causes an inner model to shadow an outer model.
556 *
557 * All callbacks are called bound to the `owner`. Any context
558 * needed for the callbacks (such as references to `instances` stamped)
559 * should be stored on the `owner` such that they can be retrieved via
560 * `this`.
561 *
562 * When `options.forwardHostProp` is declared as an option, any properties
563 * referenced in the template will be automatically forwarded from the host of
564 * the `<template>` to instances, with the exception of any properties listed in
565 * the `options.instanceProps` object. `instanceProps` are assumed to be
566 * managed by the owner of the instances, either passed into the constructor
567 * or set after the fact. Note, any properties passed into the constructor will
568 * always be set to the instance (regardless of whether they would normally
569 * be forwarded from the host).
570 *
571 * Note that `templatize()` can be run only once for a given `<template>`.
572 * Further calls will result in an error. Also, there is a special
573 * behavior if the template was duplicated through a mechanism such as
574 * `<dom-repeat>` or `<test-fixture>`. In this case, all calls to
575 * `templatize()` return the same class for all duplicates of a template.
576 * The class returned from `templatize()` is generated only once using
577 * the `options` from the first call. This means that any `options`
578 * provided to subsequent calls will be ignored. Therefore, it is very
579 * important not to close over any variables inside the callbacks. Also,
580 * arrow functions must be avoided because they bind the outer `this`.
581 * Inside the callbacks, any contextual information can be accessed
582 * through `this`, which points to the `owner`.
583 *
584 * @param {!HTMLTemplateElement} template Template to templatize
585 * @param {Polymer_PropertyEffects=} owner Owner of the template instances;
586 * any optional callbacks will be bound to this owner.
587 * @param {Object=} options Options dictionary (see summary for details)
588 * @return {function(new:TemplateInstanceBase, Object=)} Generated class bound
589 * to the template provided
590 * @suppress {invalidCasts}
591 */
592export function templatize(template, owner, options) {
593 // Under strictTemplatePolicy, the templatized element must be owned
594 // by a (trusted) Polymer element, indicated by existence of _methodHost;
595 // e.g. for dom-if & dom-repeat in main document, _methodHost is null
596 if (strictTemplatePolicy && !findMethodHost(template)) {
597 throw new Error('strictTemplatePolicy: template owner not trusted');
598 }
599 options = /** @type {!TemplatizeOptions} */(options || {});
600 if (template.__templatizeOwner) {
601 throw new Error('A <template> can only be templatized once');
602 }
603 template.__templatizeOwner = owner;
604 const ctor = owner ? owner.constructor : TemplateInstanceBase;
605 let templateInfo = ctor._parseTemplate(template);
606 // Get memoized base class for the prototypical template, which
607 // includes property effects for binding template & forwarding
608 /**
609 * @constructor
610 * @extends {TemplateInstanceBase}
611 */
612 let baseClass = templateInfo.templatizeInstanceClass;
613 if (!baseClass) {
614 baseClass = createTemplatizerClass(template, templateInfo, options);
615 templateInfo.templatizeInstanceClass = baseClass;
616 }
617 const methodHost = findMethodHost(template);
618 // Host property forwarding must be installed onto template instance
619 addPropagateEffects(template, templateInfo, options, methodHost);
620 // Subclass base class and add reference for this specific template
621 /** @private */
622 let klass = class TemplateInstance extends baseClass {};
623 /** @override */
624 klass.prototype._methodHost = methodHost;
625 /** @override */
626 klass.prototype.__dataHost = /** @type {!DataTemplate} */ (template);
627 /** @override */
628 klass.prototype.__templatizeOwner = /** @type {!Object} */ (owner);
629 /** @override */
630 klass.prototype.__hostProps = templateInfo.hostProps;
631 klass = /** @type {function(new:TemplateInstanceBase)} */(klass); //eslint-disable-line no-self-assign
632 return klass;
633}
634
635function warnOnUndeclaredProperties(templateInfo, options, methodHost) {
636 const declaredProps = methodHost.constructor._properties;
637 const {propertyEffects} = templateInfo;
638 const {instanceProps} = options;
639 for (let prop in propertyEffects) {
640 // Ensure properties with template effects are declared on the outermost
641 // host (`methodHost`), unless they are instance props or static functions
642 if (!declaredProps[prop] && !(instanceProps && instanceProps[prop])) {
643 const effects = propertyEffects[prop];
644 for (let i=0; i<effects.length; i++) {
645 const {part} = effects[i].info;
646 if (!(part.signature && part.signature.static)) {
647 console.warn(`Property '${prop}' used in template but not ` +
648 `declared in 'properties'; attribute will not be observed.`);
649 break;
650 }
651 }
652 }
653 }
654}
655
656/**
657 * Returns the template "model" associated with a given element, which
658 * serves as the binding scope for the template instance the element is
659 * contained in. A template model is an instance of
660 * `TemplateInstanceBase`, and should be used to manipulate data
661 * associated with this template instance.
662 *
663 * Example:
664 *
665 * let model = modelForElement(el);
666 * if (model.index < 10) {
667 * model.set('item.checked', true);
668 * }
669 *
670 * @param {HTMLElement} template The model will be returned for
671 * elements stamped from this template (accepts either an HTMLTemplateElement)
672 * or a `<dom-if>`/`<dom-repeat>` element when using `removeNestedTemplates`
673 * optimization.
674 * @param {Node=} node Node for which to return a template model.
675 * @return {TemplateInstanceBase} Template instance representing the
676 * binding scope for the element
677 */
678export function modelForElement(template, node) {
679 let model;
680 while (node) {
681 // An element with a __templatizeInstance marks the top boundary
682 // of a scope; walk up until we find one, and then ensure that
683 // its __dataHost matches `this`, meaning this dom-repeat stamped it
684 if ((model = node.__dataHost ? node : node.__templatizeInstance)) {
685 // Found an element stamped by another template; keep walking up
686 // from its __dataHost
687 if (model.__dataHost != template) {
688 node = model.__dataHost;
689 } else {
690 return model;
691 }
692 } else {
693 // Still in a template scope, keep going up until
694 // a __templatizeInstance is found
695 node = wrap(node).parentNode;
696 }
697 }
698 return null;
699}
700
701export { TemplateInstanceBase };