繁体   English   中英

如何在 vue.js 中定位自定义元素(本机 Web 组件)?

[英]How to target custom element (native web component) in vue.js?

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

https://jsfiddle.net/fg4dL2rx/

在此处输入图像描述

我想使用 vue.js 将 JavaScript 对象的属性绑定到选项卡文件夹的某些属性。 如果我在treez-tab中使用div作为 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 组件? 或者

=> vues.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 组件会做它的事情,编写更多这些元素。

解决方案是制作一个具有纯正标记的模板,以便 Vue 可以完成它的工作并提出组件期望的 DOM 设置。

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

 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>

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM