xgraph-adapter/Tests/WebViewer/Static/bower_components/polymer/lib/utils/templatize.html

580 lines
24 KiB
HTML
Raw Normal View History

2018-10-19 20:17:48 -04:00
<!--
@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>