Search code examples
javascriptvue.jsvue-componentweb-componentcustom-element

Webcomponents is re-initializing every time while working with Vue js


I have created a webcomponent for a generic input boxes that i needed across multiple projects. the design functionality remains same only i have to use switch themes on each projects.so i have decided to go on with webcomponents.One of the projects is based on Vue Js.In Vue js the DOM content is re-rendered while each update for enabling reactivity. That re-rendering of vue template is reinitializing my custom webcomponent which will result in loosing all my configurations i have assigned to the component using setters. I know the below solutions. but i wanted to use a setter method.

  • pass data as Attributes
  • Event based passing of configurations.
  • Using Vue-directives.
  • using v-show instead of v-if

    -- Above three solutions doesn't really match with what i am trying to create.

I have created a sample project in jsfiddle to display my issue. Each time i an unchecking and checking the checkbox new instances of my component is creating. which causes loosing the theme i have selected. (please check he active boxes count) For this particular example i want blue theme to be displayed. but it keep changing to red

JSFiddle direct Link

class InputBox extends HTMLElement {
    constructor() {
        super();
        window.activeBoxes ? window.activeBoxes++ : window.activeBoxes = 1;
        var shadow = this.attachShadow({
            mode: 'open'
        });
        var template = `
        	<style>
          	.blue#iElem {
            	background: #00f !important;
            	color: #fff !important;
            }
						.green#iElem {
            	background: #0f0 !important;
            	color: #f00 !important;
            }
            #iElem {
                background: #f00;
                padding: 13px;
                border-radius: 10px;
                color: yellow;
                border: 0;
                outline: 0;
                box-shadow: 0px 0px 14px -3px #000;
              
            }
          </style>
        	<input id="iElem" autocomplete="off" autocorrect="off" spellcheck="false" type="text" />
        `;
        shadow.innerHTML = template;
        this._theme = 'red';
        
        this.changeTheme = function(){
        	this.shadowRoot.querySelector('#iElem').className = '';
    			this.shadowRoot.querySelector('#iElem').classList.add(this._theme);
        }
        
    }
    connectedCallback() {
    	this.changeTheme();
    }
    set theme(val){
    	this._theme = val;
      this.changeTheme();
		}
}
window.customElements.define('search-bar', InputBox);
<!DOCTYPE html>
<html>
  <head>
    <title>Wrapper Component</title>
    <script src="https://unpkg.com/vue"></script>
    <style>
      html,
      body {
        font: 13px/18px sans-serif;
      }
      select {
        min-width: 300px;
      }
      search-bar {
        top: 100px;
        position: absolute;
        left: 300px;
      }
      input {
        min-width: 20px;
        padding: 25px;
        top: 100px;
        position: absolute;
      }
    </style>
  </head>
  <body>
    <div id="el"></div>

    <!-- using string template here to work around HTML <option> placement restriction -->
    <script type="text/x-template" id="demo-template">
      <div>
      	
      	<div class='parent' contentEditable='true' v-if='visible'>
          <search-bar ref='iBox'></search-bar>
        </div>
        <input type='checkbox' v-model='visible'>
      </div>
    </script>

    <script type="text/x-template" id="select2-template">
      <select>
        <slot></slot>
      </select>
    </script>

    <script>
      var vm = new Vue({
        el: "#el",
        template: "#demo-template",
        data: {
          visible: true,
        },
        mounted(){
        		let self = this
            setTimeout(()=>{
              self.$refs.iBox.theme = 'blue';
            } , 0)
        }        
      });
    </script>
  </body>
</html>


Solution

  • <div class='parent' contentEditable='true' v-if='visible'>
         <search-bar ref='iBox'></search-bar>
    </div>
    <input type='checkbox' v-model='visible'>
    

    Vue's v-if will add/remove the whole DIV from the DOM

    So <search-bar> is also added/removed on every checkbox click

    If you want a state for <search-bar> you have to save it someplace outside the <search-bar> component:

    Or change your checkbox code to not use v-if but hide the <div> with any CSS:

    • display: none
    • visibility: hidden
    • opacity: 0
    • move to off screen location
    • height: 0
    • ...

    and/or...

    Managing multiple screen elements with Stylesheets

    You can easily toggle styling using <style> elements:

    <style id="SearchBox" onload="this.disabled=true">
     ... lots of CSS
     ... even more CSS
     ... and more CSS
    </style>
    

    The onload event makes sure the <style> is not applied on page load.

    • activate all CSS styles:
      (this.shadowRoot || document).getElementById("SearchBox").disabled = false

    • remove all CSS styles:
      (this.shadowRoot || document).getElementById("SearchBox").disabled = true

    You do need CSS Properties for this to work in combo with shadowDOM Elements.

    I prefer native over Frameworks. <style v-if='visible'/> will work.. by brutally removing/adding the stylesheet.