Polymer 2 dynamically combines patterns

I am trying to create common web components that display collections of JSON objects, such as tree view and multiple list view (moving items between two lists). I would like to copy the template used by the iron list, where the template containing the view of an individual element is passed to the component for reuse.

For example, this web component template:

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

and this use:

  <intworkspace-tree tree="{{testTree}}"> <template><paper-item-body>[[node.name]]</paper-item-body> </template> </intworkspace-tree> 
I would like to display an array of the JSON tree in a hierarchy that combines the web component template along with the template provided through the slot to render opaque JSON objects. So far, I have defined two methods for combining patterns:
  • Use the Polymer.Templatize.templatize API to load templates, create / stamp new instances, and use the DOM API to add them together and add them to the web component DOM.

  • Access to the contents of the templates, combining them, creating and importing a new template, and then cloning as necessary.

After many troubles, I was able to successfully implement # 1, but not # 2, and this is the motivation for my question. # 2 is more attractive to me because it’s easier for me to combine patterns once, rather than combining their resulting stamped instances, and this approach seems to be the only way to reuse nested patterns such as dom-repeat.

My main obstacle is that after loading Polymer or, possibly, loading polyfill, the templates become opaque and can only be used using the templatize function from Polymer. For example, this code works fine without importing polymer:

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

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

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

Perhaps, by design, importing templates using the standard HTML mechanism did not work for me. Is there any other way to dynamically create / merge templates while working with Polymer? Again, my main motivation is that I would like to reuse the dom-if and dom-repeat web components nested in the template without overriding all their functionality.

+5
source share
2 answers

After further research, I discovered three Polymer 2.0 properties that allowed me to get a satisfactory solution:

  • Whenever Polymer processes DOM templates, it still remembers them. This pattern caching prevents cloning operations and simplifies template binding. The Polymer 2.0 DOM template documentation explains that the save-content attribute can be added to a template to bypass optimization that allows you to manipulate the template using native DOM operations.

  • The DOM template documentation also describes several methods for obtaining a raw element template. One option is to call the static static () method, and another option is to use the Polymer.DomModule.import () function. This second method was of interest to me, since it allows you to manage multiple templates outside the default module template.

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

By combining these three functions, I was able to prepare a dynamic reusable merged template using the nested dom-ifs and dom-repeat that I wanted.

Here is the functional result:

Component:

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

Using:

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

I am adding observation to the Aaron solution here because I do not have enough reputation to add a comment.

Please note that this line has double imports.

 let nodeTemplate = document.importNode(Polymer.DomModule.import(IntTree.is, "#nodeView"), true); 

it's not obligatory. In chrome and safari it works for some reason, but not in FF.

Thus, working with Polymer, it is enough to use DomModule for import.

 let nodeTemplate = Polymer.DomModule.import(IntTree.is, '#nodeView'); 

Hope this helps someone.

0
source

Source: https://habr.com/ru/post/1268077/


All Articles