580 lines
24 KiB
HTML
580 lines
24 KiB
HTML
|
|
<!--
|
||
|
|
@license
|
||
|
|
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
||
|
|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||
|
|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||
|
|
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||
|
|
Code distributed by Google as part of the polymer project is also
|
||
|
|
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||
|
|
-->
|
||
|
|
|
||
|
|
<link rel="import" href="boot.html">
|
||
|
|
<link rel="import" href="../mixins/property-effects.html">
|
||
|
|
<link rel="import" href="../mixins/mutable-data.html">
|
||
|
|
|
||
|
|
<script>
|
||
|
|
(function() {
|
||
|
|
'use strict';
|
||
|
|
|
||
|
|
// Base class for HTMLTemplateElement extension that has property effects
|
||
|
|
// machinery for propagating host properties to children. This is an ES5
|
||
|
|
// class only because Babel (incorrectly) requires super() in the class
|
||
|
|
// constructor even though no `this` is used and it returns an instance.
|
||
|
|
let newInstance = null;
|
||
|
|
/**
|
||
|
|
* @constructor
|
||
|
|
* @extends {HTMLTemplateElement}
|
||
|
|
*/
|
||
|
|
function HTMLTemplateElementExtension() { return newInstance; }
|
||
|
|
HTMLTemplateElementExtension.prototype = Object.create(HTMLTemplateElement.prototype, {
|
||
|
|
constructor: {
|
||
|
|
value: HTMLTemplateElementExtension,
|
||
|
|
writable: true
|
||
|
|
}
|
||
|
|
});
|
||
|
|
/**
|
||
|
|
* @constructor
|
||
|
|
* @implements {Polymer_PropertyEffects}
|
||
|
|
* @extends {HTMLTemplateElementExtension}
|
||
|
|
*/
|
||
|
|
const DataTemplate = Polymer.PropertyEffects(HTMLTemplateElementExtension);
|
||
|
|
/**
|
||
|
|
* @constructor
|
||
|
|
* @implements {Polymer_MutableData}
|
||
|
|
* @extends {DataTemplate}
|
||
|
|
*/
|
||
|
|
const MutableDataTemplate = Polymer.MutableData(DataTemplate);
|
||
|
|
|
||
|
|
// Applies a DataTemplate subclass to a <template> instance
|
||
|
|
function upgradeTemplate(template, constructor) {
|
||
|
|
newInstance = template;
|
||
|
|
Object.setPrototypeOf(template, constructor.prototype);
|
||
|
|
new constructor();
|
||
|
|
newInstance = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Base class for TemplateInstance's
|
||
|
|
/**
|
||
|
|
* @constructor
|
||
|
|
* @implements {Polymer_PropertyEffects}
|
||
|
|
*/
|
||
|
|
const base = Polymer.PropertyEffects(class {});
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @polymer
|
||
|
|
* @customElement
|
||
|
|
* @appliesMixin Polymer.PropertyEffects
|
||
|
|
* @unrestricted
|
||
|
|
*/
|
||
|
|
class TemplateInstanceBase extends base {
|
||
|
|
constructor(props) {
|
||
|
|
super();
|
||
|
|
this._configureProperties(props);
|
||
|
|
this.root = this._stampTemplate(this.__dataHost);
|
||
|
|
// Save list of stamped children
|
||
|
|
let children = this.children = [];
|
||
|
|
for (let n = this.root.firstChild; n; n=n.nextSibling) {
|
||
|
|
children.push(n);
|
||
|
|
n.__templatizeInstance = this;
|
||
|
|
}
|
||
|
|
if (this.__templatizeOwner &&
|
||
|
|
this.__templatizeOwner.__hideTemplateChildren__) {
|
||
|
|
this._showHideChildren(true);
|
||
|
|
}
|
||
|
|
// Flush props only when props are passed if instance props exist
|
||
|
|
// or when there isn't instance props.
|
||
|
|
let options = this.__templatizeOptions;
|
||
|
|
if ((props && options.instanceProps) || !options.instanceProps) {
|
||
|
|
this._enableProperties();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Configure the given `props` by calling `_setPendingProperty`. Also
|
||
|
|
* sets any properties stored in `__hostProps`.
|
||
|
|
* @private
|
||
|
|
* @param {Object} props Object of property name-value pairs to set.
|
||
|
|
* @return {void}
|
||
|
|
*/
|
||
|
|
_configureProperties(props) {
|
||
|
|
let options = this.__templatizeOptions;
|
||
|
|
if (options.forwardHostProp) {
|
||
|
|
for (let hprop in this.__hostProps) {
|
||
|
|
this._setPendingProperty(hprop, this.__dataHost['_host_' + hprop]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// Any instance props passed in the constructor will overwrite host props;
|
||
|
|
// normally this would be a user error but we don't specifically filter them
|
||
|
|
for (let iprop in props) {
|
||
|
|
this._setPendingProperty(iprop, props[iprop]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Forwards a host property to this instance. This method should be
|
||
|
|
* called on instances from the `options.forwardHostProp` callback
|
||
|
|
* to propagate changes of host properties to each instance.
|
||
|
|
*
|
||
|
|
* Note this method enqueues the change, which are flushed as a batch.
|
||
|
|
*
|
||
|
|
* @param {string} prop Property or path name
|
||
|
|
* @param {*} value Value of the property to forward
|
||
|
|
* @return {void}
|
||
|
|
*/
|
||
|
|
forwardHostProp(prop, value) {
|
||
|
|
if (this._setPendingPropertyOrPath(prop, value, false, true)) {
|
||
|
|
this.__dataHost._enqueueClient(this);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Override point for adding custom or simulated event handling.
|
||
|
|
*
|
||
|
|
* @param {!Node} node Node to add event listener to
|
||
|
|
* @param {string} eventName Name of event
|
||
|
|
* @param {function(!Event):void} handler Listener function to add
|
||
|
|
* @return {void}
|
||
|
|
*/
|
||
|
|
_addEventListenerToNode(node, eventName, handler) {
|
||
|
|
if (this._methodHost && this.__templatizeOptions.parentModel) {
|
||
|
|
// If this instance should be considered a parent model, decorate
|
||
|
|
// events this template instance as `model`
|
||
|
|
this._methodHost._addEventListenerToNode(node, eventName, (e) => {
|
||
|
|
e.model = this;
|
||
|
|
handler(e);
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
// Otherwise delegate to the template's host (which could be)
|
||
|
|
// another template instance
|
||
|
|
let templateHost = this.__dataHost.__dataHost;
|
||
|
|
if (templateHost) {
|
||
|
|
templateHost._addEventListenerToNode(node, eventName, handler);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Shows or hides the template instance top level child elements. For
|
||
|
|
* text nodes, `textContent` is removed while "hidden" and replaced when
|
||
|
|
* "shown."
|
||
|
|
* @param {boolean} hide Set to true to hide the children;
|
||
|
|
* set to false to show them.
|
||
|
|
* @return {void}
|
||
|
|
* @protected
|
||
|
|
*/
|
||
|
|
_showHideChildren(hide) {
|
||
|
|
let c = this.children;
|
||
|
|
for (let i=0; i<c.length; i++) {
|
||
|
|
let n = c[i];
|
||
|
|
// Ignore non-changes
|
||
|
|
if (Boolean(hide) != Boolean(n.__hideTemplateChildren__)) {
|
||
|
|
if (n.nodeType === Node.TEXT_NODE) {
|
||
|
|
if (hide) {
|
||
|
|
n.__polymerTextContent__ = n.textContent;
|
||
|
|
n.textContent = '';
|
||
|
|
} else {
|
||
|
|
n.textContent = n.__polymerTextContent__;
|
||
|
|
}
|
||
|
|
// remove and replace slot
|
||
|
|
} else if (n.localName === 'slot') {
|
||
|
|
if (hide) {
|
||
|
|
n.__polymerReplaced__ = document.createComment('hidden-slot');
|
||
|
|
n.parentNode.replaceChild(n.__polymerReplaced__, n);
|
||
|
|
} else {
|
||
|
|
const replace = n.__polymerReplaced__;
|
||
|
|
if (replace) {
|
||
|
|
replace.parentNode.replaceChild(n, replace);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
else if (n.style) {
|
||
|
|
if (hide) {
|
||
|
|
n.__polymerDisplay__ = n.style.display;
|
||
|
|
n.style.display = 'none';
|
||
|
|
} else {
|
||
|
|
n.style.display = n.__polymerDisplay__;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
n.__hideTemplateChildren__ = hide;
|
||
|
|
if (n._showHideChildren) {
|
||
|
|
n._showHideChildren(hide);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Overrides default property-effects implementation to intercept
|
||
|
|
* textContent bindings while children are "hidden" and cache in
|
||
|
|
* private storage for later retrieval.
|
||
|
|
*
|
||
|
|
* @param {!Node} node The node to set a property on
|
||
|
|
* @param {string} prop The property to set
|
||
|
|
* @param {*} value The value to set
|
||
|
|
* @return {void}
|
||
|
|
* @protected
|
||
|
|
*/
|
||
|
|
_setUnmanagedPropertyToNode(node, prop, value) {
|
||
|
|
if (node.__hideTemplateChildren__ &&
|
||
|
|
node.nodeType == Node.TEXT_NODE && prop == 'textContent') {
|
||
|
|
node.__polymerTextContent__ = value;
|
||
|
|
} else {
|
||
|
|
super._setUnmanagedPropertyToNode(node, prop, value);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Find the parent model of this template instance. The parent model
|
||
|
|
* is either another templatize instance that had option `parentModel: true`,
|
||
|
|
* or else the host element.
|
||
|
|
*
|
||
|
|
* @return {!Polymer_PropertyEffects} The parent model of this instance
|
||
|
|
*/
|
||
|
|
get parentModel() {
|
||
|
|
let model = this.__parentModel;
|
||
|
|
if (!model) {
|
||
|
|
let options;
|
||
|
|
model = this;
|
||
|
|
do {
|
||
|
|
// A template instance's `__dataHost` is a <template>
|
||
|
|
// `model.__dataHost.__dataHost` is the template's host
|
||
|
|
model = model.__dataHost.__dataHost;
|
||
|
|
} while ((options = model.__templatizeOptions) && !options.parentModel);
|
||
|
|
this.__parentModel = model;
|
||
|
|
}
|
||
|
|
return model;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Stub of HTMLElement's `dispatchEvent`, so that effects that may
|
||
|
|
* dispatch events safely no-op.
|
||
|
|
*
|
||
|
|
* @param {Event} event Event to dispatch
|
||
|
|
* @return {boolean} Always true.
|
||
|
|
*/
|
||
|
|
dispatchEvent(event) { // eslint-disable-line no-unused-vars
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @type {!DataTemplate} */
|
||
|
|
TemplateInstanceBase.prototype.__dataHost;
|
||
|
|
/** @type {!TemplatizeOptions} */
|
||
|
|
TemplateInstanceBase.prototype.__templatizeOptions;
|
||
|
|
/** @type {!Polymer_PropertyEffects} */
|
||
|
|
TemplateInstanceBase.prototype._methodHost;
|
||
|
|
/** @type {!Object} */
|
||
|
|
TemplateInstanceBase.prototype.__templatizeOwner;
|
||
|
|
/** @type {!Object} */
|
||
|
|
TemplateInstanceBase.prototype.__hostProps;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @constructor
|
||
|
|
* @extends {TemplateInstanceBase}
|
||
|
|
* @implements {Polymer_MutableData}
|
||
|
|
*/
|
||
|
|
const MutableTemplateInstanceBase = Polymer.MutableData(TemplateInstanceBase);
|
||
|
|
|
||
|
|
function findMethodHost(template) {
|
||
|
|
// Technically this should be the owner of the outermost template.
|
||
|
|
// In shadow dom, this is always getRootNode().host, but we can
|
||
|
|
// approximate this via cooperation with our dataHost always setting
|
||
|
|
// `_methodHost` as long as there were bindings (or id's) on this
|
||
|
|
// instance causing it to get a dataHost.
|
||
|
|
let templateHost = template.__dataHost;
|
||
|
|
return templateHost && templateHost._methodHost || templateHost;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* eslint-disable valid-jsdoc */
|
||
|
|
/**
|
||
|
|
* @suppress {missingProperties} class.prototype is not defined for some reason
|
||
|
|
*/
|
||
|
|
function createTemplatizerClass(template, templateInfo, options) {
|
||
|
|
// Anonymous class created by the templatize
|
||
|
|
let base = options.mutableData ?
|
||
|
|
MutableTemplateInstanceBase : TemplateInstanceBase;
|
||
|
|
/**
|
||
|
|
* @constructor
|
||
|
|
* @extends {base}
|
||
|
|
* @private
|
||
|
|
*/
|
||
|
|
let klass = class extends base { };
|
||
|
|
klass.prototype.__templatizeOptions = options;
|
||
|
|
klass.prototype._bindTemplate(template);
|
||
|
|
addNotifyEffects(klass, template, templateInfo, options);
|
||
|
|
return klass;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @suppress {missingProperties} class.prototype is not defined for some reason
|
||
|
|
*/
|
||
|
|
function addPropagateEffects(template, templateInfo, options) {
|
||
|
|
let userForwardHostProp = options.forwardHostProp;
|
||
|
|
if (userForwardHostProp) {
|
||
|
|
// Provide data API and property effects on memoized template class
|
||
|
|
let klass = templateInfo.templatizeTemplateClass;
|
||
|
|
if (!klass) {
|
||
|
|
let base = options.mutableData ? MutableDataTemplate : DataTemplate;
|
||
|
|
klass = templateInfo.templatizeTemplateClass =
|
||
|
|
class TemplatizedTemplate extends base {};
|
||
|
|
// Add template - >instances effects
|
||
|
|
// and host <- template effects
|
||
|
|
let hostProps = templateInfo.hostProps;
|
||
|
|
for (let prop in hostProps) {
|
||
|
|
klass.prototype._addPropertyEffect('_host_' + prop,
|
||
|
|
klass.prototype.PROPERTY_EFFECT_TYPES.PROPAGATE,
|
||
|
|
{fn: createForwardHostPropEffect(prop, userForwardHostProp)});
|
||
|
|
klass.prototype._createNotifyingProperty('_host_' + prop);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
upgradeTemplate(template, klass);
|
||
|
|
// Mix any pre-bound data into __data; no need to flush this to
|
||
|
|
// instances since they pull from the template at instance-time
|
||
|
|
if (template.__dataProto) {
|
||
|
|
// Note, generally `__dataProto` could be chained, but it's guaranteed
|
||
|
|
// to not be since this is a vanilla template we just added effects to
|
||
|
|
Object.assign(template.__data, template.__dataProto);
|
||
|
|
}
|
||
|
|
// Clear any pending data for performance
|
||
|
|
template.__dataTemp = {};
|
||
|
|
template.__dataPending = null;
|
||
|
|
template.__dataOld = null;
|
||
|
|
template._enableProperties();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/* eslint-enable valid-jsdoc */
|
||
|
|
|
||
|
|
function createForwardHostPropEffect(hostProp, userForwardHostProp) {
|
||
|
|
return function forwardHostProp(template, prop, props) {
|
||
|
|
userForwardHostProp.call(template.__templatizeOwner,
|
||
|
|
prop.substring('_host_'.length), props[prop]);
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
function addNotifyEffects(klass, template, templateInfo, options) {
|
||
|
|
let hostProps = templateInfo.hostProps || {};
|
||
|
|
for (let iprop in options.instanceProps) {
|
||
|
|
delete hostProps[iprop];
|
||
|
|
let userNotifyInstanceProp = options.notifyInstanceProp;
|
||
|
|
if (userNotifyInstanceProp) {
|
||
|
|
klass.prototype._addPropertyEffect(iprop,
|
||
|
|
klass.prototype.PROPERTY_EFFECT_TYPES.NOTIFY,
|
||
|
|
{fn: createNotifyInstancePropEffect(iprop, userNotifyInstanceProp)});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (options.forwardHostProp && template.__dataHost) {
|
||
|
|
for (let hprop in hostProps) {
|
||
|
|
klass.prototype._addPropertyEffect(hprop,
|
||
|
|
klass.prototype.PROPERTY_EFFECT_TYPES.NOTIFY,
|
||
|
|
{fn: createNotifyHostPropEffect()});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function createNotifyInstancePropEffect(instProp, userNotifyInstanceProp) {
|
||
|
|
return function notifyInstanceProp(inst, prop, props) {
|
||
|
|
userNotifyInstanceProp.call(inst.__templatizeOwner,
|
||
|
|
inst, prop, props[prop]);
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
function createNotifyHostPropEffect() {
|
||
|
|
return function notifyHostProp(inst, prop, props) {
|
||
|
|
inst.__dataHost._setPendingPropertyOrPath('_host_' + prop, props[prop], true, true);
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Module for preparing and stamping instances of templates that utilize
|
||
|
|
* Polymer's data-binding and declarative event listener features.
|
||
|
|
*
|
||
|
|
* Example:
|
||
|
|
*
|
||
|
|
* // Get a template from somewhere, e.g. light DOM
|
||
|
|
* let template = this.querySelector('template');
|
||
|
|
* // Prepare the template
|
||
|
|
* let TemplateClass = Polymer.Templatize.templatize(template);
|
||
|
|
* // Instance the template with an initial data model
|
||
|
|
* let instance = new TemplateClass({myProp: 'initial'});
|
||
|
|
* // Insert the instance's DOM somewhere, e.g. element's shadow DOM
|
||
|
|
* this.shadowRoot.appendChild(instance.root);
|
||
|
|
* // Changing a property on the instance will propagate to bindings
|
||
|
|
* // in the template
|
||
|
|
* instance.myProp = 'new value';
|
||
|
|
*
|
||
|
|
* The `options` dictionary passed to `templatize` allows for customizing
|
||
|
|
* features of the generated template class, including how outer-scope host
|
||
|
|
* properties should be forwarded into template instances, how any instance
|
||
|
|
* properties added into the template's scope should be notified out to
|
||
|
|
* the host, and whether the instance should be decorated as a "parent model"
|
||
|
|
* of any event handlers.
|
||
|
|
*
|
||
|
|
* // Customize property forwarding and event model decoration
|
||
|
|
* let TemplateClass = Polymer.Templatize.templatize(template, this, {
|
||
|
|
* parentModel: true,
|
||
|
|
* forwardHostProp(property, value) {...},
|
||
|
|
* instanceProps: {...},
|
||
|
|
* notifyInstanceProp(instance, property, value) {...},
|
||
|
|
* });
|
||
|
|
*
|
||
|
|
* @namespace
|
||
|
|
* @memberof Polymer
|
||
|
|
* @summary Module for preparing and stamping instances of templates
|
||
|
|
* utilizing Polymer templating features.
|
||
|
|
*/
|
||
|
|
Polymer.Templatize = {
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns an anonymous `Polymer.PropertyEffects` class bound to the
|
||
|
|
* `<template>` provided. Instancing the class will result in the
|
||
|
|
* template being stamped into a document fragment stored as the instance's
|
||
|
|
* `root` property, after which it can be appended to the DOM.
|
||
|
|
*
|
||
|
|
* Templates may utilize all Polymer data-binding features as well as
|
||
|
|
* declarative event listeners. Event listeners and inline computing
|
||
|
|
* functions in the template will be called on the host of the template.
|
||
|
|
*
|
||
|
|
* The constructor returned takes a single argument dictionary of initial
|
||
|
|
* property values to propagate into template bindings. Additionally
|
||
|
|
* host properties can be forwarded in, and instance properties can be
|
||
|
|
* notified out by providing optional callbacks in the `options` dictionary.
|
||
|
|
*
|
||
|
|
* Valid configuration in `options` are as follows:
|
||
|
|
*
|
||
|
|
* - `forwardHostProp(property, value)`: Called when a property referenced
|
||
|
|
* in the template changed on the template's host. As this library does
|
||
|
|
* not retain references to templates instanced by the user, it is the
|
||
|
|
* templatize owner's responsibility to forward host property changes into
|
||
|
|
* user-stamped instances. The `instance.forwardHostProp(property, value)`
|
||
|
|
* method on the generated class should be called to forward host
|
||
|
|
* properties into the template to prevent unnecessary property-changed
|
||
|
|
* notifications. Any properties referenced in the template that are not
|
||
|
|
* defined in `instanceProps` will be notified up to the template's host
|
||
|
|
* automatically.
|
||
|
|
* - `instanceProps`: Dictionary of property names that will be added
|
||
|
|
* to the instance by the templatize owner. These properties shadow any
|
||
|
|
* host properties, and changes within the template to these properties
|
||
|
|
* will result in `notifyInstanceProp` being called.
|
||
|
|
* - `mutableData`: When `true`, the generated class will skip strict
|
||
|
|
* dirty-checking for objects and arrays (always consider them to be
|
||
|
|
* "dirty").
|
||
|
|
* - `notifyInstanceProp(instance, property, value)`: Called when
|
||
|
|
* an instance property changes. Users may choose to call `notifyPath`
|
||
|
|
* on e.g. the owner to notify the change.
|
||
|
|
* - `parentModel`: When `true`, events handled by declarative event listeners
|
||
|
|
* (`on-event="handler"`) will be decorated with a `model` property pointing
|
||
|
|
* to the template instance that stamped it. It will also be returned
|
||
|
|
* from `instance.parentModel` in cases where template instance nesting
|
||
|
|
* causes an inner model to shadow an outer model.
|
||
|
|
*
|
||
|
|
* All callbacks are called bound to the `owner`. Any context
|
||
|
|
* needed for the callbacks (such as references to `instances` stamped)
|
||
|
|
* should be stored on the `owner` such that they can be retrieved via
|
||
|
|
* `this`.
|
||
|
|
*
|
||
|
|
* When `options.forwardHostProp` is declared as an option, any properties
|
||
|
|
* referenced in the template will be automatically forwarded from the host of
|
||
|
|
* the `<template>` to instances, with the exception of any properties listed in
|
||
|
|
* the `options.instanceProps` object. `instanceProps` are assumed to be
|
||
|
|
* managed by the owner of the instances, either passed into the constructor
|
||
|
|
* or set after the fact. Note, any properties passed into the constructor will
|
||
|
|
* always be set to the instance (regardless of whether they would normally
|
||
|
|
* be forwarded from the host).
|
||
|
|
*
|
||
|
|
* Note that `templatize()` can be run only once for a given `<template>`.
|
||
|
|
* Further calls will result in an error. Also, there is a special
|
||
|
|
* behavior if the template was duplicated through a mechanism such as
|
||
|
|
* `<dom-repeat>` or `<test-fixture>`. In this case, all calls to
|
||
|
|
* `templatize()` return the same class for all duplicates of a template.
|
||
|
|
* The class returned from `templatize()` is generated only once using
|
||
|
|
* the `options` from the first call. This means that any `options`
|
||
|
|
* provided to subsequent calls will be ignored. Therefore, it is very
|
||
|
|
* important not to close over any variables inside the callbacks. Also,
|
||
|
|
* arrow functions must be avoided because they bind the outer `this`.
|
||
|
|
* Inside the callbacks, any contextual information can be accessed
|
||
|
|
* through `this`, which points to the `owner`.
|
||
|
|
*
|
||
|
|
* @memberof Polymer.Templatize
|
||
|
|
* @param {!HTMLTemplateElement} template Template to templatize
|
||
|
|
* @param {Polymer_PropertyEffects=} owner Owner of the template instances;
|
||
|
|
* any optional callbacks will be bound to this owner.
|
||
|
|
* @param {Object=} options Options dictionary (see summary for details)
|
||
|
|
* @return {function(new:TemplateInstanceBase)} Generated class bound to the template
|
||
|
|
* provided
|
||
|
|
* @suppress {invalidCasts}
|
||
|
|
*/
|
||
|
|
templatize(template, owner, options) {
|
||
|
|
options = /** @type {!TemplatizeOptions} */(options || {});
|
||
|
|
if (template.__templatizeOwner) {
|
||
|
|
throw new Error('A <template> can only be templatized once');
|
||
|
|
}
|
||
|
|
template.__templatizeOwner = owner;
|
||
|
|
const ctor = owner ? owner.constructor : TemplateInstanceBase;
|
||
|
|
let templateInfo = ctor._parseTemplate(template);
|
||
|
|
// Get memoized base class for the prototypical template, which
|
||
|
|
// includes property effects for binding template & forwarding
|
||
|
|
let baseClass = templateInfo.templatizeInstanceClass;
|
||
|
|
if (!baseClass) {
|
||
|
|
baseClass = createTemplatizerClass(template, templateInfo, options);
|
||
|
|
templateInfo.templatizeInstanceClass = baseClass;
|
||
|
|
}
|
||
|
|
// Host property forwarding must be installed onto template instance
|
||
|
|
addPropagateEffects(template, templateInfo, options);
|
||
|
|
// Subclass base class and add reference for this specific template
|
||
|
|
/** @private */
|
||
|
|
let klass = class TemplateInstance extends baseClass {};
|
||
|
|
klass.prototype._methodHost = findMethodHost(template);
|
||
|
|
klass.prototype.__dataHost = template;
|
||
|
|
klass.prototype.__templatizeOwner = owner;
|
||
|
|
klass.prototype.__hostProps = templateInfo.hostProps;
|
||
|
|
klass = /** @type {function(new:TemplateInstanceBase)} */(klass); //eslint-disable-line no-self-assign
|
||
|
|
return klass;
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns the template "model" associated with a given element, which
|
||
|
|
* serves as the binding scope for the template instance the element is
|
||
|
|
* contained in. A template model is an instance of
|
||
|
|
* `TemplateInstanceBase`, and should be used to manipulate data
|
||
|
|
* associated with this template instance.
|
||
|
|
*
|
||
|
|
* Example:
|
||
|
|
*
|
||
|
|
* let model = modelForElement(el);
|
||
|
|
* if (model.index < 10) {
|
||
|
|
* model.set('item.checked', true);
|
||
|
|
* }
|
||
|
|
*
|
||
|
|
* @memberof Polymer.Templatize
|
||
|
|
* @param {HTMLTemplateElement} template The model will be returned for
|
||
|
|
* elements stamped from this template
|
||
|
|
* @param {Node=} node Node for which to return a template model.
|
||
|
|
* @return {TemplateInstanceBase} Template instance representing the
|
||
|
|
* binding scope for the element
|
||
|
|
*/
|
||
|
|
modelForElement(template, node) {
|
||
|
|
let model;
|
||
|
|
while (node) {
|
||
|
|
// An element with a __templatizeInstance marks the top boundary
|
||
|
|
// of a scope; walk up until we find one, and then ensure that
|
||
|
|
// its __dataHost matches `this`, meaning this dom-repeat stamped it
|
||
|
|
if ((model = node.__templatizeInstance)) {
|
||
|
|
// Found an element stamped by another template; keep walking up
|
||
|
|
// from its __dataHost
|
||
|
|
if (model.__dataHost != template) {
|
||
|
|
node = model.__dataHost;
|
||
|
|
} else {
|
||
|
|
return model;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
// Still in a template scope, keep going up until
|
||
|
|
// a __templatizeInstance is found
|
||
|
|
node = node.parentNode;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
Polymer.TemplateInstanceBase = TemplateInstanceBase;
|
||
|
|
|
||
|
|
})();
|
||
|
|
|
||
|
|
</script>
|