如何在vue.js中定位自定义元素(本机Web组件)?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何在vue.js中定位自定义元素(本机Web组件)?相关的知识,希望对你有一定的参考价值。
我有一个自定义的Web组件treez-tab-folder
。顾名思义它代表一个标签文件夹。这是一个jsfiddle和一些图像来演示它的用法(可能需要Chrome浏览器才能正常工作):
https://jsfiddle.net/fg4dL2rx/
我想使用vue.js将javascript对象的属性绑定到选项卡文件夹的某些属性。如果我在div
中使用treez-tab
作为Vue实例(el: '#firstContent'
)的目标元素,则绑定按预期工作(也见上面的jsfiddle):
<body>
<treez-tab-folder id="tabFolder">
<treez-tab title="First tab">
<div id='firstContent'>{{message}}</div>
</treez-tab>
<treez-tab title="Second tab">
<div>Second tab content</div>
</treez-tab>
</treez-tab-folder>
<script>
new Vue({
el: '#firstContent',
data: {
message: 'First tab content'
}
});
</script>
</body>
但是,如果我尝试将自定义元素treez-tab-folder
直接用作Vue实例(el: '#tabFolder'
)的目标元素,则jsfiddle示例将停止工作:
https://jsfiddle.net/etomuf8v/
现在选项卡标题构造了两次,标签内容似乎丢失了:
<body>
<treez-tab-folder id="tabFolder">
<treez-tab title="First tab">
<div id='firstContent'>{{message}}</div>
</treez-tab>
<treez-tab title="Second tab">
<div>Second tab content</div>
</treez-tab>
</treez-tab-folder>
<script>
new Vue({
el: '#tabFolder',
data: {
message: 'First tab content'
}
});
</script>
</body>
=>如何修复我的Web组件?要么
=> vue.js是否禁止使用自定义元素作为目标?
我认为当vue.js替换/刷新dom时,我的组件的处理可能无法正常工作。因此,我试图实施disconnectedCallback
来杀死剩余的元素。这没有用。
这是我的自定义标签元素:
<script>
class TreezTabFolderHeader extends htmlElement {}
window.customElements.define('treez-tab-folder-header', TreezTabFolderHeader);
class TreezTabHeader extends HTMLElement {}
window.customElements.define('treez-tab-header', TreezTabHeader);
class TreezTabFolder extends HTMLElement {
constructor(){
super();
this.tabFolderHeader=undefined;
}
connectedCallback() {
if(!this.tabFolderHeader){
this.style.display='block';
this.tabFolderHeader = document.createElement('treez-tab-folder-header');
this.insertBefore(this.tabFolderHeader, this.firstChild);
}
}
disconnectedCallback(){
while (this.firstChild) {
this.removeChild(this.firstChild);
}
}
}
window.customElements.define('treez-tab-folder', TreezTabFolder);
class TreezTab extends HTMLElement {
constructor(){
super();
console.log('tab constructor');
this.tabHeader=undefined;
}
static get observedAttributes() {
return ['title'];
}
connectedCallback() {
console.log('connected callback');
if(!this.tabHeader){
var headers = this.parentElement.children[0];
this.tabHeader = this.createTabHeader(this.parentElement);
this.tabHeader.innerText = this.title;
headers.appendChild(this.tabHeader);
this.showFirstTab(this.parentElement);
}
}
disconnectedCallback(){
console.log('disconnected callback');
while (this.firstChild) {
this.removeChild(this.firstChild);
}
}
adoptedCallback(){
console.log('adopted callback');
}
attributeChangedCallback(attr, oldValue, newValue) {
if(attr==='title' && this.tabHeader){
this.tabHeader.innerText= newValue;
}
}
createTabHeader(tabs){
var tabHeader = document.createElement('treez-tab-header');
tabHeader.onclick=()=>{
var tabHeaders = tabs.children[0].children;
for(var index=1;index<tabs.children.length;index++){
var tab = tabs.children[index];
tab.style.display='none';
var tabHeader = tabHeaders[index-1];
tabHeader.classList.remove('selected')
}
this.style.display='block';
this.tabHeader.classList.add('selected')
};
return tabHeader;
}
showFirstTab(tabs){
var firstHeader = tabs.children[0].children[0];
firstHeader.classList.add('selected')
tabs.children[1].style.display="block"
for(var index=2;index<tabs.children.length;index++){
tabs.children[index].style.display="none";
}
}
}
window.customElements.define('treez-tab', TreezTab);
</script>
和css文件
treez-tab-folder {
background-color:#f2f2f2;
width:100%;
height:100%;
padding-top:2px;
font-family: Arial,sans-serif;
font-size: 12px;
}
treez-tab-folder-header {
margin-left:-3px;
color: #777777;
}
treez-tab-header {
background-color:#f2f2f2;
display:inline-block;
margin-left: 2px;
padding:8px;
padding-top:1px;
padding-bottom:3px;
border: 1px solid;
border-color:#cccccc;
box-shadow: 2px -2px 2px -2px rgba(0,0,0,0.2);
transform: translate(0px, 1px);
}
treez-tab-header:hover {
background-color:#e1e1e1;
}
treez-tab-header.selected {
border-bottom: none;
background-color:#e1e1e1;
transform: translate(0px, 2px);
padding-top:2px;
}
treez-tab {
background-color:#e1e1e1;
border-top: 1px solid;
border-color:#cccccc;
border-bottom: none;
box-shadow: 2px -2px 2px -2px rgba(0,0,0,0.2);
height:100%;
vertical-alignment:bottom;
}
问题是Vue希望使用DOM作为模板,当它看到您的Web组件时,DOM已经被Web组件重写了。 DOM中有treez-tab-header
元素不在标记中。因此,当Vue重写DOM时,它会编写这些元素,并且Web组件会执行其操作,编写更多这些元素。
解决方案是make a template具有纯粹的标记,以便Vue可以完成其工作并提出组件所需的DOM设置。
在这个片段中,我为Vue定义了一个模板,而不是从元素中读取它。我还将Vue附加到包装纸div
。原则上,我可以将它附加到一个空的Web组件标记,但在这种情况下,组件的内容没有一个根节点(有两个treez-tab
s),所以我不能创建它们的模板。
class TreezTabFolderHeader extends HTMLElement {}
window.customElements.define('treez-tab-folder-header', TreezTabFolderHeader);
class TreezTabHeader extends HTMLElement {}
window.customElements.define('treez-tab-header', TreezTabHeader);
class TreezTabFolder extends HTMLElement {
constructor() {
super();
this.tabFolderHeader = undefined;
}
connectedCallback() {
if (!this.tabFolderHeader) {
this.style.display = 'block';
this.tabFolderHeader = document.createElement('treez-tab-folder-header');
this.insertBefore(this.tabFolderHeader, this.firstChild);
}
}
disconnectedCallback() {
while (this.firstChild) {
this.removeChild(this.firstChild);
}
}
}
window.customElements.define('treez-tab-folder', TreezTabFolder);
class TreezTab extends HTMLElement {
constructor() {
super();
console.log('tab constructor');
this.tabHeader = undefined;
}
static get observedAttributes() {
return ['title'];
}
connectedCallback() {
console.log('connected callback');
if (!this.tabHeader) {
var headers = this.parentElement.children[0];
this.tabHeader = this.createTabHeader(this.parentElement);
this.tabHeader.innerText = this.title;
headers.appendChild(this.tabHeader);
this.showFirstTab(this.parentElement);
}
}
disconnectedCallback() {
console.log('disconnected callback');
while (this.firstChild) {
this.removeChild(this.firstChild);
}
}
adoptedCallback() {
console.log('adopted callback');
}
attributeChangedCallback(attr, oldValue, newValue) {
if (attr === 'title' && this.tabHeader) {
this.tabHeader.innerText = newValue;
}
}
createTabHeader(tabs) {
var tabHeader = document.createElement('treez-tab-header');
tabHeader.onclick = () => {
var tabHeaders = tabs.children[0].children;
for (var index = 1; index < tabs.children.length; index++) {
var tab = tabs.children[index];
tab.style.display = 'none';
var tabHeader = tabHeaders[index - 1];
tabHeader.classList.remove('selected')
}
this.style.display = 'block';
this.tabHeader.classList.add('selected')
};
return tabHeader;
}
showFirstTab(tabs) {
var firstHeader = tabs.children[0].children[0];
firstHeader.classList.add('selected')
tabs.children[1].style.display = "block"
for (var index = 2; index < tabs.children.length; index++) {
tabs.children[index].style.display = "none";
}
}
}
window.customElements.define('treez-tab', TreezTab);
new Vue({
el: '#app',
template: '#app-template',
data: {
message: 'First tab content'
}
});
treez-tab-folder {
background-color: #f2f2f2;
width: 100%;
height: 100%;
padding-top: 2px;
font-family: Arial, sans-serif;
font-size: 12px;
}
treez-tab-folder-header {
margin-left: -3px;
color: #777777;
}
treez-tab-header {
background-color: #f2f2f2;
display: inline-block;
margin-left: 2px;
padding: 8px;
padding-top: 1px;
padding-bottom: 3px;
border: 1px solid;
border-color: #cccccc;
box-shadow: 2px -2px 2px -2px rgba(0, 0, 0, 0.2);
transform: translate(0px, 1px);
}
treez-tab-header:hover {
background-color: #e1e1e1;
}
treez-tab-header.selected {
border-bottom: none;
background-color: #e1e1e1;
transform: translate(0px, 2px);
padding-top: 2px;
}
treez-tab {
background-color: #e1e1e1;
border-top: 1px solid;
border-color: #cccccc;
border-bottom: none;
box-shadow: 2px -2px 2px -2px rgba(0, 0, 0, 0.2);
height: 100%;
vertical-alignment: bottom;
}
<script src="//unpkg.com/vue@latest/dist/vue.js"></script>
<div id='app'></div>
<template id="app-template">
<treez-tab-folder id="tabFolder">
<treez-tab title="First tab">
<div id='firstContent'>{{message}}</div>
</treez-tab>
<treez-tab title="Second tab">
<div>Second tab content</div>
</treez-tab>
</treez-tab-folder>
</template>
以上是关于如何在vue.js中定位自定义元素(本机Web组件)?的主要内容,如果未能解决你的问题,请参考以下文章
如何创建从 vue.js 实例到自定义原生 Web 组件的双向绑定?