Polymer 2 动态合并模板
Polymer 2 dynamically merging templates
我正在尝试构建呈现 JSON object 集合的通用 Web 组件,例如树视图和 multi-list 视图(在两个列表之间移动项目)。我想复制 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>
和这种用法:
<intworkspace-tree tree="{{testTree}}">
<template><paper-item-body>[[node.name]]</paper-item-body> </template>
</intworkspace-tree>
我想在层次结构中呈现 JSON 树数组,该层次结构结合了 Web 组件的模板以及通过插槽提供的模板来呈现不透明的 JSON objects。到目前为止,我已经确定了两种组合模板的方法:
利用Polymer.Templatize.templatizeAPI加载模板,create/stamp新实例,并使用DOMAPI追加将它们放在一起并将它们添加到 Web 组件的影子 DOM.
访问模板内容,将它们组合在一起,创建并导入新模板,然后根据需要克隆它。
在经历了很多逆境之后,我能够成功地实施#1 而不是#2,这就是我提出问题的动机。 #2 对我来说更有吸引力,因为对我来说合并模板一次比合并它们生成的标记实例更容易,而且这种方法似乎是我可以重用 dom-repeat.
等嵌套模板的唯一方法
我的主要障碍是,一旦加载了 Polymer 或者它的 polyfill,模板就会变得不透明并且只能由 Polymer 模板化功能使用。例如,这段代码在没有任何 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>
在 Polymer 外部 template.content DOM 片段有 children 并且设置了内部 HTML。但是,一旦使用了 Polymer,template.content 就没有 children 并且内部 HTML 是空的。这使我无法使用 DOM API 创建一个将可用模板混合在一起的新模板,即
let newTemplate = document.createElement("template");
newTemplate.content = ... // combine #labelView > template.content with #nodeView.content
let nodeView = document.importNode(newTemplate.content,true);
nodeView.tree=...
也许使用标准 HTML 机制设计导入模板对我不起作用。还有另一种方法可以在运行时使用 Polymer 动态 create/merge 模板吗?同样,我的主要动机是我想 re-use dom-if 和 dom-repeat web 组件嵌套在模板中,而不重新实现它们的所有功能。
经过进一步研究,我发现了 Polymer 2.0 的三个特性,这些特性使我能够生成令人满意的解决方案:
每当 Polymer 处理 DOM 模板时,默认情况下都会记住它们。此模板缓存可防止昂贵的克隆操作并简化模板绑定。 Polymer 2.0 DOM 模板文档说明可以将 preserve-content 属性添加到模板以绕过优化,从而允许使用本机 DOM 操作来操作模板。
DOM 模板文档还描述了多种获取自定义元素原始模板的方法。一种选择是调用元素的静态 template() 方法,另一种选择是使用 Polymer.DomModule.import() 函数。我对第二种方法很感兴趣,因为它允许管理默认模块模板之外的多个模板。
Polymer.TemplateStamp API class 有一个内部 _stampTemplate() 函数,用于将模板标记到自定义元素的 DOM 中。我宁愿使用有据可查的 Polymer.Templatize.templatize() 函数,但它会在模板本身上查找属性和方法,在我的例子中,这不是一个定义了行为的自定义元素。
将这三个功能放在一起,我能够准备一个动态的可重用合并模板,根据需要使用嵌套的 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>
我在这里对 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');
希望这对某人有所帮助
我正在尝试构建呈现 JSON object 集合的通用 Web 组件,例如树视图和 multi-list 视图(在两个列表之间移动项目)。我想复制 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>
和这种用法:
<intworkspace-tree tree="{{testTree}}">
<template><paper-item-body>[[node.name]]</paper-item-body> </template>
</intworkspace-tree>
利用Polymer.Templatize.templatizeAPI加载模板,create/stamp新实例,并使用DOMAPI追加将它们放在一起并将它们添加到 Web 组件的影子 DOM.
访问模板内容,将它们组合在一起,创建并导入新模板,然后根据需要克隆它。
在经历了很多逆境之后,我能够成功地实施#1 而不是#2,这就是我提出问题的动机。 #2 对我来说更有吸引力,因为对我来说合并模板一次比合并它们生成的标记实例更容易,而且这种方法似乎是我可以重用 dom-repeat.
等嵌套模板的唯一方法我的主要障碍是,一旦加载了 Polymer 或者它的 polyfill,模板就会变得不透明并且只能由 Polymer 模板化功能使用。例如,这段代码在没有任何 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>
在 Polymer 外部 template.content DOM 片段有 children 并且设置了内部 HTML。但是,一旦使用了 Polymer,template.content 就没有 children 并且内部 HTML 是空的。这使我无法使用 DOM API 创建一个将可用模板混合在一起的新模板,即
let newTemplate = document.createElement("template");
newTemplate.content = ... // combine #labelView > template.content with #nodeView.content
let nodeView = document.importNode(newTemplate.content,true);
nodeView.tree=...
也许使用标准 HTML 机制设计导入模板对我不起作用。还有另一种方法可以在运行时使用 Polymer 动态 create/merge 模板吗?同样,我的主要动机是我想 re-use dom-if 和 dom-repeat web 组件嵌套在模板中,而不重新实现它们的所有功能。
经过进一步研究,我发现了 Polymer 2.0 的三个特性,这些特性使我能够生成令人满意的解决方案:
每当 Polymer 处理 DOM 模板时,默认情况下都会记住它们。此模板缓存可防止昂贵的克隆操作并简化模板绑定。 Polymer 2.0 DOM 模板文档说明可以将 preserve-content 属性添加到模板以绕过优化,从而允许使用本机 DOM 操作来操作模板。
DOM 模板文档还描述了多种获取自定义元素原始模板的方法。一种选择是调用元素的静态 template() 方法,另一种选择是使用 Polymer.DomModule.import() 函数。我对第二种方法很感兴趣,因为它允许管理默认模块模板之外的多个模板。
Polymer.TemplateStamp API class 有一个内部 _stampTemplate() 函数,用于将模板标记到自定义元素的 DOM 中。我宁愿使用有据可查的 Polymer.Templatize.templatize() 函数,但它会在模板本身上查找属性和方法,在我的例子中,这不是一个定义了行为的自定义元素。
将这三个功能放在一起,我能够准备一个动态的可重用合并模板,根据需要使用嵌套的 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>
我在这里对 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');
希望这对某人有所帮助