DEV Community

Kieran Fahy
Kieran Fahy

Posted on • Edited on

Livewire 3 Multiple Select with Alpine JS

I have built a multiple select dropdown using livewire 3 and alpine. Most of the code I found on the web and adjusted it to work for me. I didn't want to use and additional plugins. Let me know if you have any suggestions.

@props([
    'optionValue'  => '',
    'optionLabel' => '',
    'optionsList' => [],
    'label' => null,
    'disabled' => false,
])

<div>
    @if($label)
        <x-input-label class="mb-1" x-on:click="open" > {{ $label }}</x-input-label>
    @endif

    <div
        wire:key="{{ rand() }}"
        x-data="dropdown()"
        x-init="$nextTick(() => loadOptions())"
    >
        <input name="values" type="hidden" x-bind:value="selectedValues()">
        <div class="inline-block relative w-full">
            <div class="flex flex-col items-center relative">
                <div x-on:click="open" class="w-full">
                    <div class="flex form-select-sm appearance-none w-full px-2 py-1 text-sm
                                                                font-normal
                                                                text-gray-700
                                                                bg-white bg-clip-padding bg-no-repeat
                                                                border border-solid border-gray-300
                                                                rounded
                                                                transition
                                                                ease-in-out
                                                                duration-500
                                                                focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none">
                        <div class="flex flex-auto flex-wrap">
                            <template x-for="(option,index) in selected" :key="options[option].value">
                                <div
                                    class="inline-flex items-center rounded-md bg-gray-50 px-2 py-1 mr-1 text-sm font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10">
                                    <div class="text-xs font-normal leading-none max-w-full flex-initial"
                                         x-model="options[option]" x-text="options[option].text"></div>
                                    <div class="flex flex-auto flex-row-reverse">
                                        <div x-on:click="remove(index,option)">
                                            <svg class="fill-current h-4 w-4 " role="button" viewBox="0 0 20 20">
                                                <path d="M14.348,14.849c-0.469,0.469-1.229,0.469-1.697,0L10,11.819l-2.651,3.029c-0.469,0.469-1.229,0.469-1.697,0
                                           c-0.469-0.469-0.469-1.229,0-1.697l2.758-3.15L5.651,6.849c-0.469-0.469-0.469-1.228,0-1.697s1.228-0.469,1.697,0L10,8.183
                                           l2.651-3.031c0.469-0.469,1.228-0.469,1.697,0s0.469,1.229,0,1.697l-2.758,3.152l2.758,3.15
                                           C14.817,13.62,14.817,14.38,14.348,14.849z"/>
                                            </svg>
                                        </div>
                                    </div>
                                </div>
                            </template>
                            <div x-show="selected.length == 0" class="flex-1">
                                <input placeholder="Select a option"
                                       class="bg-transparent p-1 px-2 appearance-none outline-none h-full w-full text-gray-800"
                                       x-bind:value="selectedValues()"
                                >
                            </div>
                        </div>
                        <div class="text-gray-300 w-8 py-1 pl-2 pr-1 border-l flex items-center border-gray-200">

                            <button type="button" x-show="isOpen() === true" x-on:click="open"
                                    class="cursor-pointer w-6 h-6 text-gray-600 outline-none focus:outline-none">
                                <svg version="1.1" class="fill-current h-4 w-4" viewBox="0 0 20 20">
                                    <path d="M17.418,6.109c0.272-0.268,0.709-0.268,0.979,0s0.271,0.701,0,0.969l-7.908,7.83
    c-0.27,0.268-0.707,0.268-0.979,0l-7.908-7.83c-0.27-0.268-0.27-0.701,0-0.969c0.271-0.268,0.709-0.268,0.979,0L10,13.25
    L17.418,6.109z"/>
                                </svg>

                            </button>
                            <button type="button" x-show="isOpen() === false" @click="close"
                                    class="cursor-pointer w-6 h-6 text-gray-600 outline-none focus:outline-none">
                                <svg class="fill-current h-4 w-4" viewBox="0 0 20 20">
                                    <path d="M2.582,13.891c-0.272,0.268-0.709,0.268-0.979,0s-0.271-0.701,0-0.969l7.908-7.83
    c0.27-0.268,0.707-0.268,0.979,0l7.908,7.83c0.27,0.268,0.27,0.701,0,0.969c-0.271,0.268-0.709,0.268-0.978,0L10,6.75L2.582,13.891z
    "/>
                                </svg>

                            </button>
                        </div>
                    </div>
                </div>
                <div class="w-full">
                    <div x-show.transition.origin.top="isOpen()"
                         class="absolute shadow top-100 bg-white z-40 w-full lef-0 rounded max-h-64 overflow-y-auto text-sm"
                         x-on:click.away="close">
                        <div class="flex flex-col w-full">
                            <template x-for="(option,index) in options" :key="index">
                                <div>
                                    <div
                                        class="cursor-pointer w-full border-gray-100 rounded-t border-b hover:bg-gray-200"
                                        @click="select(index,$event)">
                                        <div x-bind:class="option.selected ? 'border-gray-600' : ''"
                                             class="flex w-full items-center p-2 pl-2 border-transparent border-l-2 relative">
                                            <div class="w-full items-center flex">
                                                <div class="mx-2 leading-6" x-model="option" x-text="option.text"></div>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </template>
                        </div>
                    </div>
                </div>
            </div>
            <!-- on tailwind components page will no work  -->
        </div>
    </div>
    <script>

        function dropdown() {
            return {

                options: [],
                selected: [],
                show: false,
                open() {
                    this.show = true
                },
                close() {
                    this.show = false
                },
                isOpen() {
                    return this.show === true
                },
                select(index, event) {

                    if (!this.options[index].selected) {

                        this.options[index].selected = true;
                        this.options[index].element = event.target;
                        this.selected.push(index);
                        @this.set('{{ $attributes->wire('model')->value() }}', this.selectedValues());


                    } else {
                        this.selected.splice(this.selected.lastIndexOf(index), 1);
                        this.options[index].selected = false
                        @this.set('{{ $attributes->wire('model')->value() }}', this.selectedValues());
                    }
                },
                remove(index, option) {
                    this.options[option].selected = false;
                    this.selected.splice(index, 1);
                    @this.set('{{ $attributes->wire('model')->value() }}', this.selectedValues());
                },
                loadOptions() {
                    const options = @json($optionsList);
                    const selected = @entangle($attributes->wire('model')->value());

                    for (let i = 0; i < options.length; i++) {

                        this.options.push({
                            value: options[i]['{{ $optionValue }}'],
                            text: options[i]['{{ $optionLabel }}'],
                            selected: selected.initialValue.includes(options[i]['{{ $optionValue }}'])
                        });

                    }

                    this.options.forEach((option, index) => {
                        if (option.selected) this.selected.push(index);
                    });

                },
                selectedValues() {
                    return this.selected.map((option) => {
                        return this.options[option].value;
                    })
                },

            }

        }

    </script>


</div>


Enter fullscreen mode Exit fullscreen mode

I then add it to my livewire component like so

<x-form.multi-select
                            wire:model.live="grades"
                            :options-list="\Config::get('applications.grades')"
                            :option-label="'name'"
                            :option-value="'id'"
                            label="Grades" />
Enter fullscreen mode Exit fullscreen mode

Thanks

Agent.ai Challenge image

Congrats to the Agent.ai Challenge Winners 🏆

The wait is over! We are excited to announce the winners of the Agent.ai Challenge.

From meal planners to fundraising automators to comprehensive stock analysts, our team of judges hung out with a lot of agents and had a lot to deliberate over. There were so many creative and innovative submissions, it is always so difficult to select our winners.

Read more →

Top comments (0)

Agent.ai Challenge image

Congrats to the Agent.ai Challenge Winners 🏆

The wait is over! We are excited to announce the winners of the Agent.ai Challenge.

From meal planners to fundraising automators to comprehensive stock analysts, our team of judges hung out with a lot of agents and had a lot to deliberate over. There were so many creative and innovative submissions, it is always so difficult to select our winners.

Read more →

👋 Kindness is contagious

Engage with a sea of insights in this enlightening article, highly esteemed within the encouraging DEV Community. Programmers of every skill level are invited to participate and enrich our shared knowledge.

A simple "thank you" can uplift someone's spirits. Express your appreciation in the comments section!

On DEV, sharing knowledge smooths our journey and strengthens our community bonds. Found this useful? A brief thank you to the author can mean a lot.

Okay