text: <!--
  @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}
       * @private
       */
      function HTMLTemplateElementExtension() { return newInstance; }
      HTMLTemplateElementExtension.prototype = Object.create(HTMLTemplateElement.prototype, {
        constructor: {
          value: HTMLTemplateElementExtension,
          writable: true
        }
      });
  
      /**
       * @constructor
       * @implements {Polymer_PropertyEffects}
       * @extends {HTMLTemplateElementExtension}
       * @private
       */
      const DataTemplate = Polymer.PropertyEffects(HTMLTemplateElementExtension);
  
      /**
       * @constructor
       * @implements {Polymer_MutableData}
       * @extends {DataTemplate}
       * @private
       */
      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.
       * @constructor
       * @implements {Polymer_PropertyEffects}
       * @private
       */
      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}
       * @private
       */
      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;
        // Affordance for global mixins onto TemplatizeInstance
        if (Polymer.Templatize.mixin) {
          base = Polymer.Templatize.mixin(base);
        }
        /**
         * @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;
            /** @private */
            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) {
          // Under strictTemplatePolicy, the templatized element must be owned
          // by a (trusted) Polymer element, indicated by existence of _methodHost;
          // e.g. for dom-if & dom-repeat in main document, _methodHost is null
          if (Polymer.strictTemplatePolicy && !findMethodHost(template)) {
            throw new Error('strictTemplatePolicy: template owner not trusted');
          }
          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>
  