如何在vue.js中定位自定义元素(原生Web组件)? [英] How to target custom element (native web component) in vue.js?

查看:801
本文介绍了如何在vue.js中定位自定义元素(原生Web组件)?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个自定义Web组件 treez-tab-folder 。顾名思义它代表一个标签文件夹。这里是一个jsfiddle和一些图片来演示它的用法(可能需要Chrome浏览器正常工作):



我想使用vue.js将JavaScript对象的属性绑定到选项卡文件夹的某些属性。如果我在 treez-tab 内使用 div 作为Vue实例的目标元素( el:'#firstContent'),绑定按预期工作(也参见上面的jsfiddle):

 <身体GT; 
< treez-tab-folder id =tabFolder>
< treez-tab title =第一个标签>
< div id ='firstContent'> {{message}}< / div>
< / treez-tab>

< treez-tab title =第二个标签>
< div>第二个标签页内容< / div>
< / treez-tab>
< / treez-tab-folder>

< script>
new Vue({
el:'#firstContent',
data:{
message:'第一个标签内容'
}
});
< / script>
< / body>

但是,如果我尝试使用自定义元素 treez-tab-folder 直接作为Vue实例的目标元素( el:'#tabFolder') ,jsfiddle示例停止工作:





现在标签页头被构造了两次,并且标签内容似乎缺少:

 < body> 
< treez-tab-folder id =tabFolder>
< treez-tab title =第一个标签>
< div id ='firstContent'> {{message}}< / div>
< / treez-tab>

< treez-tab title =第二个标签>
< div>第二个标签页内容< / div>
< / treez-tab>
< / treez-tab-folder>

< script>
new Vue({
el:'#tabFolder',
data:{
message:'第一个标签内容'
}
});
< / script>
< / body>

=>如何解决我的网络组件?



=> vues.js forbit是否使用自定义元素作为目标? 当vue.js替换/刷新dom时,处理我的组件可能无法正常工作。因此我试图执行 disconnectedCallback 来杀死剩余的元素。这没有帮助。






以下是我的自定义标签元素:

 <脚本> 

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); ($ this)



disconnectCallback(){
while(this.firstChild){
this.removeChild(this.firstChild);
}
}
}
window.customElements.define('treez-tab-folder',TreezTabFolder);


class TreezTab扩展HTMLElement {

构造函数(){
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);



disconnectCallback(){
console.log('disconnected callback');
while(this.firstChild){
this.removeChild(this.firstChild);



receivedCallback(){
console.log('adopted callback');

$ b 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')
};
返回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;
宽度:100%;
身高:100%;
padding-top:2px;
font-family:Arial,sans-serif;
font-size:12px;
}

treez-tab-folder-header {
margin-left:-3px;
颜色:#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);
身高:100%;
vertical-alignment:bottom;



解决方案

Vue希望使用DOM作为模板,当它看到你的Web组件时,DOM已经被Web组件重写了。 DOM中有 treez-tab-header 元素,它们不在标记中。因此,当Vue重写DOM时,它会写入这些元素,并且Web组件会完成它的工作,编写更多这些元素。 解决方案是创建一个模板,这个模板具有纯粹的标记,这样Vue就可以完成它的任务,并提出DOM设置组件需要。



在这段代码中,我为Vue定义了一个模板,而不是从元素中读取它。我也将Vue附加到一个包装 div 。原则上,我可以将它附加到一个空的Web组件标签,但在这种情况下,组件的内容没有单个根节点(有两个 treez-tab

data-console =falsedata-babel =false>

treez-tab-folder {background-color: #f2f2f2;宽度:100%;身高:100%; padding-top:2px; font-family:Arial,sans-serif; font-size:12px;} treez-tab-folder-header {margin-left:-3px;颜色:#777777;} treez-tab-header {background-color:#f2f2f2;显示: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);身高: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 =第一个标签> < div id ='firstContent'> {{message}}< / div> < / treez发表于-标签> < treez-tab title =第二个标签> < div>第二个标签内容< / div> < / treez发表于-标签> < / treez-tab-folder>< / template>

I have a custom web component treez-tab-folder. As the name suggests it represents a tab folder. Here is a jsfiddle and some image to demonstrate its usage (might require Chrome browser to work correctly):

https://jsfiddle.net/fg4dL2rx/

I would like to use vue.js to bind the properties of a JavaScript object to some attributes of the tab folder. If I use a div inside a treez-tab as target element for the Vue instance (el: '#firstContent'), the binding works as expected (also see above 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>

However, if I try to use the custom element treez-tab-folder directly as target element for the Vue instance (el: '#tabFolder'), the jsfiddle example stops working:

https://jsfiddle.net/etomuf8v/

Now the tab headers are constructed twice and the tab content seems to be missing:

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

=> How can I fix my web component? or

=> Does vues.js forbit to use custom elements as target?

I thought that the disposal of my components might not work correctly when vue.js is replacing/refreshing the dom. Therefore I tried to implement disconnectedCallback to kill remaining elements. That did not help.


Here are my custom tab elements:

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

and the css file

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;

}

解决方案

The problem is that Vue expects to use the DOM as a template, and by the time it sees your web component, the DOM has already been rewritten by the web component. There are treez-tab-header elements in the DOM that are not in the markup. So when Vue rewrites the DOM, it writes those elements, and the web component does its thing, writing more of those elements.

The solution is to make a template that has the unadulterated markup so that Vue can do its thing and come up with the DOM setup that your component expects.

In this snippet, I define a template for Vue rather than reading it from the element. I also attach the Vue to a wrapper div. In principle, I could attach it to an empty web component tag, but in this case, the contents of the component don't have a single root node (there are two treez-tabs) so I can't make a template of them.

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组件)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆