scripter2021
scripter2021

Reputation: 391

How to convert Vue2 code to pure web components

I want to implement the autocomplete search (the one on the left) from this codepen to pure web components. But something went wrong because slots don't work and something else also doesn't work but I can't figure out what it is. What I have so far

Search-select

const template = `
    <p>
        <slot name="autocomp" results="${this.results}"
            searchList="${(event) => this.setQuery(event)}"
        >
            fgfgfg
        </slot>
        yo
    </p>
`;

class SearchSelect extends HTMLElement {
    constructor() {
        super();

        this.query = "";

        this.results = [];

        this.options = [
            "Inside Out",
            "John Wick",
            "Jurassic World",
            "The Lord of the Rings",
            "Pacific Rim",
            "Pirates of the Caribbean",
            "Planet of the Apes",
            "Saw",
            "Sicario",
            "Zombies",
        ];

        this.shadow = this.attachShadow({ mode: "open" });
    }

    setQuery(event) {
        console.log(event.target);
        this.query = event.target.value;
    }

    get options() {
        return this.getAttribute("options");
    }

    set options(val) {
        this.setAttribute("options", val);
    }

    static get observedAttributes() {
        return ["options", "filterMethod"];
    }

    filterMethod(options, query) {
        return options.filter((option) =>
            option.toLowerCase().includes(query.toLowerCase())
        );
    }

    attributeChangedCallback(prop, oldValue, newValue) {
        if (prop === "options") {
            this.results = this.filterMethod(this.options, this.query);
            this.render();
        }

        if (prop === "filterMethod") {
            this.results = this.filterMethod(this.options, this.query);

            this.render();
        }
    }

    render() {
        this.shadow.innerHTML = template;
    }

    connectedCallback() {
        this.render();
    }
}

customElements.define("search-select", SearchSelect);

Autocomplete

const templ = `
<search-select>
<div class="autocomplete">
    <input
        type="text"
        placeholder="Type to search list"
        onchange="${this.searchList}"
        onfocus="${this.showDropdown}"
        onblur="${this.hideDropdown}"
    />

    <div class="autocomplete-dropdown" v-if="dropdownVisible">
        <ul class="autocomplete-search-results-list">
            ${this.result}
        </ul>
    </div>
</div>
</search-select>
`;

class Autocomplete extends HTMLElement {
    constructor() {
        super();

        this.dropdownVisible = false;

        this.rslts = "";

        this.shadow = this.attachShadow({ mode: "open" });
    }

    get results() {
        return this.getAttribute("results");
    }

    set results(val) {
        this.setAttribute("results", val);
    }

    get searchList() {
        return this.getAttribute("searchList");
    }

    showDropdown() {
        this.dropdownVisible = true;
    }

    hideDropdown() {
        this.dropdownVisible = false;
    }

    attributeChangedCallback(prop, oldValue, newValue) {
        this.render();
    }

    render() {
        this.shadow.innerHTML = templ;
    }

    connectedCallback() {
        this.render();
    }
}

customElements.define("auto-complete", Autocomplete);

Upvotes: 1

Views: 1305

Answers (1)

Harshal Patil
Harshal Patil

Reputation: 21030

Your current approach is completely wrong. Vue is reactive framework. Web components do not provide reactivity out of box.

The translation of Vue2 component to direct Web component is not straight forward. The slots do not work because Vue.js slots are not the same as Web component slots. They are just conceptually modeled after them.

First, when you use the Vue.js slot, you are practically putting some part of the vDOM (produced as a result of JSX) defined by the calling component into the Search or Autocomplete component. It is not a real DOM. Web components, on the other hand, provide slot which actually accepts a real DOM (light DOM).

Next, your render method is practically useless. You are simply doing this.shadow.innerHTML = template; which will simply append the string as HTML into the real DOM. You are not resolving the template nodes. Vue.js provides a reactivity out of box (that's why you need Vue/React). Web components do not provide such reactivity. On each render, you are re-creating entire DOM which is not a good way to do it. When you are not using any framework to build web component, you should construct all the required DOM in connectedCallback and then keep on selectively updating using DOM manipulation API. This is imperative approach to building UIs.

Third, you are using named slot while consuming it in auto complete, you are not specifying the named slot. So whatever is the HTML you see is not getting attached to the Shadow DOM.

You will need to

Building a complex component like Auto Complete needs a basic reactivity system in place that takes care of efficiently and automatically updating the DOM. If you do not need full framework, consider using Stencil, LitElement, etc. If you can use Vue.js, just use it and wrap it into Web component using helper function.

For Vue 2, you can use the wrapper helper library. For Vue 3, you can use the built-in helper.

Upvotes: 3

Related Questions