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
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-tab
s) 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>