Search code examples
vuejs3vue-multiselect

I can't get slot templates to work with vue-multiselect 3.0.0 beta 1


I have tried the sample in the doc and tried updating to use Vue 3 slot options and it still doesn't work. Has anyone gotten this to work.

Using the way the docs show it. https://vue-multiselect.js.org/#sub-custom-option-template
https://jsfiddle.net/c2e968g5

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue-multiselect.esm.css" asp-append-version="true" />
<style>
    /* Used to hide VueJS templates */
    [v-cloak] {
        display: none;
    }
</style>
<script type="importmap">
    {
      "imports": {
        "vue": "https://unpkg.com/[email protected]/dist/vue.esm-browser.prod.js",
        "vue_multiselect": "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue-multiselect.esm.min.js"
      }
    }
</script>
<script type="module">
    import { createApp } from 'vue'
    import VueMultiselect from "vue_multiselect";

    const app = createApp({
        data() {
            return {
                value: null,
                options: [{ name: "John", last: "Smith" }, { name: "Bob", last: "Chair" }, {name:"Chuck",last:"Norris"}]
            }
        },
        components: {
            vuemultiselect: VueMultiselect
        }
    }).mount('#app');
</script>
<div id="app" v-cloak>
    <vuemultiselect
                    v-model="value"
                    placeholder="Start typing to find address"
                    open-direction="bottom"
                    :options="options"
                    :options-limit="100"
                    :limit="20"
                    :searchable="true"
                    :hide-selected="false"
                    :close-on-select="true"
                    :clear-on-select="false"
                    :multiple="false"
                    :internal-search="true"
                    :max-height="300"
                    :show-no-results="false"
                    :preserve-search="false"
                    :allow-empty="true"
                    :reset-after="false">
        <template slot="singleLabel" slot-scope="props">
            <span>{{props.option.name}}</span>
        </template>
        <template slot="option" slot-scope="props">
            <b>{{props.option.name}}</b>
            <br />
            <span>{{props.option.last}}</span>
        </template>
    </vuemultiselect>
</div>

Using new Vue 3 slot options
https://jsfiddle.net/c2e968g5/1/

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue-multiselect.esm.css" asp-append-version="true" />
<style>
    /* Used to hide VueJS templates */
    [v-cloak] {
        display: none;
    }
</style>
<script type="importmap">
    {
      "imports": {
        "vue": "https://unpkg.com/[email protected]/dist/vue.esm-browser.prod.js",
        "vue_multiselect": "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue-multiselect.esm.min.js"
      }
    }
</script>
<script type="module">
    import { createApp } from 'vue'
    import VueMultiselect from "vue_multiselect";

    const app = createApp({
        data() {
            return {
                value: null,
                options: [{ name: "John", last: "Smith" }, { name: "Bob", last: "Chair" }, {name:"Chuck",last:"Norris"}]
            }
        },
        components: {
            vuemultiselect: VueMultiselect
        }
    }).mount('#app');
</script>
<div id="app" v-cloak>
    <vuemultiselect
                    v-model="value"
                    placeholder="Start typing to find address"
                    open-direction="bottom"
                    :options="options"
                    :options-limit="100"
                    :limit="20"
                    :searchable="true"
                    :hide-selected="false"
                    :close-on-select="true"
                    :clear-on-select="false"
                    :multiple="false"
                    :internal-search="true"
                    :max-height="300"
                    :show-no-results="false"
                    :preserve-search="false"
                    :allow-empty="true"
                    :reset-after="false">
        <template slot="singleLabel"  v-slot="{ name }">
            <span>{{name}}</span>
        </template>
        <template slot="option" v-slot="{ name, last }">
            <b>{{name}}</b>
            <br />
            <span>{{last}}</span>
        </template>
    </vuemultiselect>
</div>

Solution

  • The right way to write it is:

    <template v-slot:option="{ option }">
      <b>{{ option.name }}</b>
    </template>
    

    or shorthand:

    <template #option="{ option }">
      <b>{{ option.name }}</b>
    </template>
    

    So there is no slot property anymore, and the slot name goes after the v-slot: (or the shorthand #), slot props follow the statement immediately:

    v-slot:option="{ option, search, index }"
    

    The problem with the singleLabel slot is particular to your setup, where you are using the multiselect in HTML directly, and not in a component, which means that the browser sees it before Vue does and performs changes to it.

    So from a statement like

    <template v-slot:singleLabel="{ option }">
    

    the camelCased singleLabel is turned into lowercase singlelabel:

    <template v-slot:singlelabel="{ option }">
    

    But that slot indeed does not exist.

    The problem does not occur if you put the vuemultiselect in a component, see the fiddle.

    If that is not an option, as a hack, you could use a dynamic slot:

    <template v-slot:[slotname]="{ option }">
    

    where slotname (again, lowercase) is a variable containing the slot name as a string, i.e. "singleLabel".