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

前端 未结 1 2012
眼角桃花
眼角桃花 2020-12-19 19:51

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

相关标签:
1条回答
  • 2020-12-19 20:31

    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>

    0 讨论(0)
提交回复
热议问题