Polymer 2动态合并模板

时间:2017-05-22 02:51:52

标签: polymer polymer-2.x

我正在尝试构建呈现JSON对象集合的通用Web组件,例如树视图和多列表视图(在两个列表之间移动项目)。我想复制iron-list使用的模式,其中包含单个项目表示的模板被传递到组件中以供重用。

例如,给定此Web组件模板:



<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>
&#13;
&#13;
&#13;

这个用法:

&#13;
&#13;
 <intworkspace-tree tree="{{testTree}}">
      <template><paper-item-body>[[node.name]]</paper-item-body>  </template>
  </intworkspace-tree>
  
&#13;
&#13;
&#13; 我想在一个层次结构中呈现JSON树数组,该层次结构将Web组件的模板与通过槽提供的模板结合起来,以呈现不透明的JSON对象。到目前为止,我已经确定了两种组合模板的方法:

  1. 利用Polymer.Templatize.templatize API加载模板,创建/标记新实例,并使用DOM API将它们附加在一起,并将它们添加到Web组件的影子DOM中。

  2. 访问模板内容,将它们组合在一起,创建并导入新模板,然后根据需要进行克隆。

  3. 经过多次逆境后,我能够成功实施#1而不是#2,这是我的问题的动力。 #2对我更有吸引力,因为我更容易合并模板一次而不是合并他们生成的标记实例,这种方法似乎是我可以重用嵌套模板的唯一方法,如dom-repeat。

    我的主要障碍是,一旦聚合物或者它的填充物被加载,模板就变得不透明,并且只能被聚合物模板化功能使用。例如,此代码在没有任何Polymer导入的情况下工作正常:

    &#13;
    &#13;
    <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>
    &#13;
    &#13;
    &#13;

    在Polymer之外,template.content DOMFragment有子节点并且设置了innerHTML。但是,一旦使用Polymer,template.content就没有子节点,innerHTML就是空的。这使我无法使用DOM API创建一个将可用模板混合在一起的新模板,即

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

    也许通过设计使用标准HTML机制导入模板并不适合我。是否有另一种方法可以在运行时使用Polymer动态创建/合并模板?我的主要动机是,我想重新使用嵌套在模板中的dom-if和dom-repeat Web组件,而无需重新实现其所有功能。

2 个答案:

答案 0 :(得分:6)

经过进一步研究后,我发现了Polymer 2.0的三个特性,使我能够产生令人满意的解决方案:

  1. 每当Polymer处理DOM模板时,默认情况下都会记住它们。此模板缓存可防止费用克隆操作并简化模板绑定。 Polymer 2.0 DOM模板文档解释了可以将preserve-content属性添加到模板中以绕过优化,从而允许使用本机DOM操作来操作模板。

  2. DOM模板文档还描述了获取自定义元素的原始模板的多种方法。一种选择是调用元素的静态template()方法,另一种选择是使用Polymer.DomModule.import()函数。第二种方法对我很感兴趣,因为它允许人们管理超出默认模块模板的多个模板。

  3. Polymer.TemplateStamp API类有一个内部_stampTemplate()函数,用于将模板标记到自定义元素的DOM中。我本来希望使用记录良好的Polymer.Templatize.templatize()函数,但它在模板本身上查找属性和方法,在我的例子中,它不是定义了行为的自定义元素。

  4. 将这三个功能放在一起我能够准备一个动态可重用的合并模板,根据需要使用嵌套的dom-ifs和dom-repeats。

    以下是功能结果:

    组件:

    <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>

    用法:

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

答案 1 :(得分:0)

我在这里添加了对Aaron解决方案的观察,因为我没有足够的声誉来添加评论。

注意此行有双重导入

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

这不是必要的。在chrome和safari工作由于某种原因,但不在FF。

所以使用Polymer,只需使用DomModule导入就足够了

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

希望这有助于某人