Skip to content
Fix Code Error

Polymer 2 dynamically merging templates

April 22, 2021 by Code Error
Posted By: Anonymous

I am attempting to build generic web components that render JSON object collections, like a tree view and a multi-list view (moving items between two lists). I would like to copy the pattern used by iron-list where a template containing the individual item presentation is passed into the component for reuse.

For example, given this web component template:

_x000D_

_x000D_

<dom-module id="intworkspace-tree">  _x000D_
  <template>_x000D_
    <style include="iron-flex iron-flex-alignment">_x000D_
_x000D_
      paper-icon-item {_x000D_
        --paper-item-min-height: var(--intworkspace-tree-margin,30px);_x000D_
        --paper-item-icon-width : var(--intworkspace-tree-margin,30px);_x000D_
      }_x000D_
_x000D_
      paper-icon-item:focus::before,_x000D_
      paper-icon-item:focus::after {_x000D_
        color: inherit;_x000D_
        opacity: 0;_x000D_
      }_x000D_
_x000D_
     .node {_x000D_
        margin-left: var(--intworkspace-tree-margin,30px);;_x000D_
      }_x000D_
    </style>_x000D_
_x000D_
    <slot id="labelView"></slot>_x000D_
_x000D_
    <template id="nodeView">_x000D_
      <div class="layout vertical">_x000D_
      <paper-icon-item on-tap="nodeSelected">_x000D_
        <iron-icon icon="expand-less" slot="item-icon" hidden$="[[!hasNodes(node)]]"></iron-icon>_x000D_
        <!-- label goes here-->_x000D_
      </paper-icon-item>_x000D_
_x000D_
      <iron-collapse class="node" opened hidden$="[[!hasNodes(node)]]">_x000D_
        <intworkspace-tree tree="[[node.nodes]]" embedded></intworkspace-tree>_x000D_
      </iron-collapse>_x000D_
      </div>_x000D_
  </template>_x000D_
_x000D_
  </template>_x000D_
	..._x000D_
  </dom-module>

_x000D_

_x000D_

_x000D_

and this usage:

_x000D_

_x000D_

 <intworkspace-tree tree="{{testTree}}">_x000D_
      <template><paper-item-body>[[node.name]]</paper-item-body>  </template>_x000D_
  </intworkspace-tree>_x000D_
  

_x000D_

_x000D_

_x000D_

I would like to render the JSON tree array in a hierachy that combines the web component’s template along with template provided through the slot to render the opaque JSON objects. So far I have identified two methods of combining the templates:

  1. Utilize the Polymer.Templatize.templatize API to load the templates, create/stamp new instances, and use the DOM API to append them together and add them to the web component’s shadow DOM.

  2. Access the templates contents, combine them together, create and import a new template, and then clone it as needed.

After much adversity I was able to successfully implement #1 but not #2 and that is motivation for my question. #2 is more appealing to me because it is easier for me to merge templates once rather than merging their resulting stamped instances and this approach seems to be the only way I can reuse nested templates like dom-repeat.

My main obstacle is that once Polymer or perhaps it’s polyfill is loaded the templates become opaque and can only be utilized by Polymer templatize functionality. For instance, this code works fine without any Polymer imports:

_x000D_

_x000D_

<template>_x000D_
  <div>Template Contents</div>_x000D_
</template>_x000D_
<div>_x000D_
  Template Test_x000D_
</div>_x000D_
  <script>_x000D_
  let template = document.querySelector("template");_x000D_
  let clone = document.importNode(template.content,true);_x000D_
  document.querySelector("div").appendChild(clone);_x000D_
  </script>

_x000D_

_x000D_

_x000D_

Outside of Polymer the template.content DOMFragment has children and innerHTML is set. However once Polymer is used the template.content has no children and the innerHTML is empty. This prevents me from using the DOM API to create a new template that blends the available templates together, i.e.

_x000D_

_x000D_

let newTemplate = document.createElement("template");_x000D_
newTemplate.content = ... // combine #labelView > template.content with #nodeView.content _x000D_
let nodeView = document.importNode(newTemplate.content,true);_x000D_
nodeView.tree=...

_x000D_

_x000D_

_x000D_

Perhaps by design importing templates using the standard HTML mechanism didn’t work for me. Is there another way to dynamically create/merge templates at runtime with Polymer? Again my main motivation is that I would like to re-use the dom-if and dom-repeat web components nested in a template without reimplementing all of their functionality.

Solution

After additional research I discovered three features of Polymer 2.0 that enabled me to produce a satisfactory solution:

  1. Whenever Polymer processes DOM templates it memoizes them by default. This template caching prevents expense cloning operations and simplifies template binding. The Polymer 2.0 DOM templating documentation explains that the preserve-content attribute can be added to a template to bypass the optimization allowing the template to be manipulated using native DOM operations.

  2. The DOM templating documentation also describes multiple methods of obtaining a custom element’s raw template. One option is to call the element’s static template() method and another option is to use the Polymer.DomModule.import() function. This second method was of interest to me since it allows one to manage multiple templates beyond the default module template.

  3. The Polymer.TemplateStamp API class has an internal _stampTemplate() function that is used to stamp a template into the custom element’s DOM. I would have preferred to have used the well documented Polymer.Templatize.templatize() function but it looks for properties and methods on the template itself which in my case was not a custom element with behaviors defined on it.

Putting these three features together I was able to prepare a dynamic reusable merged template utlizing nested dom-ifs and a dom-repeats as I desired.

Here is the functional result:

Component:

_x000D_

_x000D_

<link rel="import" href="../polymer/polymer-element.html">_x000D_
<link rel="import" href="../iron-collapse/iron-collapse.html">_x000D_
<link rel="import" href="../paper-item/paper-icon-item.html">_x000D_
<link rel="import" href="../paper-item/paper-item-body.html">_x000D_
<link rel="import" href="../iron-flex-layout/iron-flex-layout-classes.html">_x000D_
<link rel="import" href="../iron-icons/iron-icons.html">_x000D_
<link rel="import" href="../iron-icon/iron-icon.html">_x000D_
_x000D_
_x000D_
<dom-module id="intworkspace-tree">_x000D_
  <template>_x000D_
    <!-- style includes don't work in stamped template, only in the shadowRoot -->_x000D_
    <style include="iron-flex iron-flex-alignment">_x000D_
_x000D_
    paper-icon-item {_x000D_
      --paper-item-min-height: var(--intworkspace-tree-margin,30px);_x000D_
      --paper-item-icon-width : var(--intworkspace-tree-margin,30px);_x000D_
    }_x000D_
_x000D_
    paper-icon-item:focus::before,_x000D_
    paper-icon-item:focus::after {_x000D_
      color: inherit;_x000D_
      opacity: 0;_x000D_
    }_x000D_
_x000D_
   .node {_x000D_
      margin-left: var(--intworkspace-tree-margin,30px);;_x000D_
    }_x000D_
  </style>_x000D_
_x000D_
    <slot id="labelView"></slot>_x000D_
  </template>_x000D_
_x000D_
  <template id="nodeView">_x000D_
_x000D_
 _x000D_
_x000D_
    <template is="dom-repeat" items="{{tree}}" as="node" index-as="n">_x000D_
        <div class="layout vertical">_x000D_
          <!--<div>index: [[n]]</div>_x000D_
          <div>name: [[node.name]]</div>-->_x000D_
          <paper-icon-item on-tap="nodeSelected">_x000D_
            <template is="dom-if" if="[[hasNodes(node)]]">_x000D_
              <iron-icon icon="expand-more" slot="item-icon" hidden$="[[!hasNodes(node)]]"></iron-icon>_x000D_
            </template>_x000D_
            <!-- label goes here-->_x000D_
          </paper-icon-item>_x000D_
          <template is="dom-if" if="[[hasNodes(node)]]">_x000D_
            <iron-collapse class="node" opened>_x000D_
              <intworkspace-tree tree="[[node.nodes]]" node-template="[[nodeTemplate]]" embedded></intworkspace-tree>_x000D_
            </iron-collapse>_x000D_
          </template>_x000D_
        </div>_x000D_
    </template>_x000D_
  </template>_x000D_
_x000D_
  <script>_x000D_
    class IntTree extends Polymer.TemplateStamp(Polymer.Element) {_x000D_
_x000D_
      static get is() {_x000D_
        return 'intworkspace-tree';_x000D_
      }_x000D_
_x000D_
      static get properties() {_x000D_
        return {_x000D_
          tree: {_x000D_
            type: Array,_x000D_
            value: []_x000D_
          },_x000D_
          nodeTemplate: {_x000D_
            type: Object,_x000D_
          }_x000D_
        };_x000D_
      }_x000D_
_x000D_
      ready() {_x000D_
        super.ready();_x000D_
        if (!this.hasAttribute("embedded")) {_x000D_
          let labelTemplate = this.$.labelView.assignedNodes().find((e) => {_x000D_
            return e instanceof HTMLTemplateElement;_x000D_
          });_x000D_
          let nodeTemplate = document.importNode(Polymer.DomModule.import(IntTree.is, "#nodeView"), true);_x000D_
          let repeatTemplate = nodeTemplate.content.querySelector("template[is='dom-repeat']");_x000D_
          let iconItem = repeatTemplate.content.querySelector('paper-icon-item');_x000D_
          iconItem.appendChild(labelTemplate.content);_x000D_
          this.nodeTemplate = nodeTemplate;_x000D_
        }_x000D_
        let nodeInstance = this._stampTemplate(this.nodeTemplate);_x000D_
        this.shadowRoot.appendChild(nodeInstance);_x000D_
      }_x000D_
_x000D_
      hasNodes(node) {_x000D_
        return node.nodes != null && node.nodes.length > 0;_x000D_
      }_x000D_
_x000D_
      nodeSelected(e) {_x000D_
        let collapse = e.currentTarget.parentNode.querySelector("iron-collapse");_x000D_
        let nodeIcon = e.currentTarget.parentNode.querySelector("iron-icon");_x000D_
        if (collapse && nodeIcon) {_x000D_
          collapse.toggle();_x000D_
          if (collapse.opened) {_x000D_
            nodeIcon.icon = "expand-more";_x000D_
          } else {_x000D_
            nodeIcon.icon = "expand-less";_x000D_
          }_x000D_
        }_x000D_
      }_x000D_
    }_x000D_
_x000D_
    window.customElements.define(IntTree.is, IntTree);_x000D_
  </script>_x000D_
</dom-module>

_x000D_

_x000D_

_x000D_

Usage:

_x000D_

_x000D_

<intworkspace-tree tree="{{testTree}}">_x000D_
      <template preserve-content><paper-item-body>[[node.name]]</paper-item-body></template>_x000D_
  </intworkspace-tree>

_x000D_

_x000D_

_x000D_

Answered By: Anonymous

Related Articles

  • How to prevent scrolling the whole page?
  • Active tab issue on page load HTML
  • Web colors in an Android color xml resource file
  • Navbar not filling width of page when reduced to mobile view
  • Fix top buttons on scroll of list below
  • Having trouble with my nav bar/header, It used to work but…
  • How do I keep only the first map and when the game is…
  • How to change the color of vaadin-select-text-field in the…
  • How to show title in hover - css / jquery
  • Adobe XD to responsive html

Disclaimer: This content is shared under creative common license cc-by-sa 3.0. It is generated from StackExchange Website Network.

Post navigation

Previous Post:

Cannot read property ‘concat’ of undefined when inserting new child into recursive Polymer component

Next Post:

How to pass required attributes to element constructor

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

  • Get code errors & solutions at akashmittal.com
© 2022 Fix Code Error