DEV Community

Cover image for Livewire 3 multiple select with choice.js
Vitalik
Vitalik

Posted on

Livewire 3 multiple select with choice.js

I didn't find a good, simple example where I could reuse multi select with choice.js for my component. That's why I decided to write my own. I do some manipulations with options and wire:change due to a weird interaction between livewire and choice, similar issue is still hanging open in the repository https://github.com/Choices-js/Choices/issues/943

Make blade component

php artisan make:component MultiSelect --view
Enter fullscreen mode Exit fullscreen mode
@props(['options' => [], 'placeholderValue' => '', 'model'])

@php
    $uniqId = 'select' . uniqid();
@endphp

<div wire:ignore
     x-data
     x-init="
        $nextTick(() => {
            const choices = new Choices($refs.{{ $uniqId }}, {
                removeItems: true,
                removeItemButton: true,
                placeholderValue: '{{ $placeholderValue }}',
             })
       })"
>
    <select
        x-ref="{{ $uniqId }}"
        wire:change="$set('{{ $model }}', [...$event.target.options].map(option => option.value))"
        {{ $attributes }}
        multiple
    >
        @foreach($options as $option)
            <option value="{{ $option }}">{{ $option }}</option>
        @endforeach
    </select>
</div>

@pushOnce('styles')
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/choices.js/public/assets/styles/choices.min.css"/>
@endPushOnce
@pushOnce('scripts')
    <script src="https://cdn.jsdelivr.net/npm/choices.js/public/assets/scripts/choices.min.js"></script>
@endPushOnce
Enter fullscreen mode Exit fullscreen mode

And then you can reuse it in a component

<x-multi-select
    model="some_public_property"
    class="select-custom"
    placeholderValue="Select all you want"                             
    :options="['foo', 'bar']"
/>
Enter fullscreen mode Exit fullscreen mode

Don't forget to change or add a @stack('styles') and @stack('scripts') to your layout. Or if you know that the selects will be all over the site, it might be easier to just add a link to the CDN

Top comments (2)

Collapse
 
isaac_wachira_2d4db312977 profile image
Isaac Wachira

Here are even better snippets:

So the first piece of code is a better multi-select component which can be used to specify the value to be displayed and information to act as the value of the selected option. Also I have added the capability to select a group of items at once.

@props(['options' => [], 'placeholderValue' => '', 'model', 'valueKey' => 'id', 'displayName'])

@php
    $uniqId = 'select' . uniqid();
@endphp
<div wire:ignore x-data x-init="$nextTick(() => {
    const choices = new Choices($refs.{{ $uniqId }}, {
        removeItems: true,
        removeItemButton: true,
        placeholderValue: '{{ $placeholderValue }}',
    })

    // This is to handle 'Send to Group' checkbox
    @this.on('selectGroup', (selectedOptions) => {

        if (selectedOptions[0].length > 0) {
            //Deselect the previously selected options
            choices.removeActiveItems();
           // Select the provided options
            selectedOptions[0].map((option) => {
                choices.setChoiceByValue(`${option}`, true);
            });
        } else {
            // Deselect all options
            choices.removeActiveItems();
        }
    })
})">
    <select x-ref="{{ $uniqId }}"
        wire:change="$set('{{ $model }}', [...$event.target.options].filter(option => option.selected).map(option => option.value))"
        {{ $attributes }} multiple>
        @foreach ($options as $option)
            <option value="{{ $option[$valueKey] }}">{{ $option[$displayName] }}</option>
        @endforeach
    </select>
</div>
@pushOnce('styles')
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/choices.js/public/assets/styles/choices.min.css" />
@endPushOnce
@pushOnce('scripts')
    <script src="https://cdn.jsdelivr.net/npm/choices.js/public/assets/scripts/choices.min.js"></script>
@endPushOnce
Enter fullscreen mode Exit fullscreen mode

The function below is used to gather the values one would be interested at and send to the multi-select component using events. I prefer using events since they good for real time updates. I have declared my variable which is holding the selected options in my form:
public SendToUserForm $form;

public function updatedSendToAll($value)
{
  if ($value) {
    // Select all options
    $this->form->selectedUsers = collect($this->users)->pluck('reference')->toArray();
 } else {
    // Clear selection
   $this->form->selectedUsers = [];
 }

  // Dispatch an event to update Choices.js
  $this->dispatch('selectGroup', $this->form->selectedUsers);
}
Enter fullscreen mode Exit fullscreen mode

Finally on the view:

<x-multi-select model="form.selectedUsers" class="select-custom" placeholderValue="Select users"  :options="$users" valueKey="reference" displayName="name" />

<div class="form-check form-check-inline">
   <input wire:model.live="selectAll" class="form-check-input single-user-checkbox" type="checkbox" id="all">
   <label class="form-check-label" for="all">Send to Group</label>
</div>
Enter fullscreen mode Exit fullscreen mode
Collapse
 
liruxme profile image
David

wire:change="$set('{{ $model }}', [...$event.target.options].map(option => option.value))"

I think this line should be changed to

wire:change="$set('{{ $model }}', [...$event.target.selectedOptions].map(option => option.value))"

to just get the selected options.