Choices
This component is intended to be used to build complex selection interfaces for single and multiple values. It also supports search on frontend or server, when dealing with large lists.
Selection
By default, it will look up for:
$object->id
for option value.$object->name
for option display label.$object->avatar
for avatar picture.
@php $users = $this->users; @endphp {{-- Notice `single` --}}<x-choices label="Single" wire:model="user_id" :options="$users" single /> {{-- public array $users_multi_ids = []; --}}<x-choices label="Multiple" wire:model="users_multi_ids" :options="$users" /> {{-- Custom options --}}<x-choices label="Custom options" wire:model="user_custom_id" :options="$users" option-label="username" option-sub-label="city.name" option-avatar="other_avatar" icon="o-users" height="max-h-96" {{-- Default is `max-h-64` --}} hint="It has custom options" single />
Select All
This option only works for multiple and non-searchable exclusively.
@php $users = $this->users; @endphp {{-- Notice `allow-all` --}}<x-choices label="Multiple" wire:model="users_all_ids" :options="$users" allow-all /> <x-choices label="Multiple" wire:model="users_all2_ids" :options="$users" allow-all allow-all-text="Select all stuff" remove-all-text="Delete all things" />
Compact mode
This option only works for multiple and non-searchable exclusively.
@php $users = $this->users; @endphp {{-- Notice `compact` --}}<x-choices label="Compact" wire:model="users_compact_ids" :options="$users" compact /> <x-choices label="Compact label" wire:model="users_compact2_ids" :options="$users" compact compact-text="stuff chosen" />
You can combine allow-all
and compact
@php $users = $this->users; @endphp <x-choices label="Select All + Compact" wire:model="users_all_compact_ids" :options="$users" compact allow-all />
Searchable (frontend)
If you judge you don't have a huge list of items, you can make it searchable offline on "frontend side". But, if you have a huge list it is a better idea to make it searchable on "server side", otherwise you can face some slow down on frontend. See on next section.
@php $users = $this->users; @endphp {{-- Notice `searchable` --}}{{-- Notice this is a different component, but with same API --}}<x-choices-offline label="Single (frontend)" wire:model="user_searchable_offline_id" :options="$users" placeholder="Search ..." single searchable /> <x-choices-offline label="Multiple (frontend)" wire:model="users_multi_searchable_offline_ids" :options="$users" placeholder="Search ..." searchable />
Searchable (server)
When dealing with large options list use searchable
parameter. By default, it calls search()
method to get fresh options from "server
side" while
typing.
You can change the method's name by using search-function
parameter.
@php $usersSearchable = $this->usersSearchable; $usersMultiSearchable = $this->usersMultiSearchable; @endphp {{-- Notice `searchable` + `single` --}}<x-choices label="Searchable + Single" wire:model="user_searchable_id" :options="$usersSearchable" placeholder="Search ..." single searchable /> {{-- Notice custom `search-function` --}}<x-choices label="Searchable + Multiple" wire:model="users_multi_searchable_ids" :options="$usersMultiSearchable" placeholder="Search ..." search-function="searchMulti" no-result-text="Ops! Nothing here ..." searchable />
You must also consider displaying pre-selected items on list, when it first renders and while searching. There are many approaches to make it work, but here is an example for single search using Volt.
new class extends Component { // Selected option public ?int $user_searchable_id = null; // Options list public Collection $usersSearchable; public function mount() { // Fill options when component first renders $this->search(); } // Also called as you type public function search(string $value = '') { // Besides the search results, you must include on demand selected option $selectedOption = User::where('id', $this->user_searchable_id)->get(); $this->usersSearchable = User::query() ->where('name', 'like', "%$value%") ->take(5) ->orderBy('name') ->get() ->merge($selectedOption); // <-- Adds selected option }}
Sometimes you don't want to hit a datasource on every keystroke.
So, you can make use of debounce
to control over how often a network request is sent.
Another approach is to use min-chars
attribute to avoid hit search method itself until you have typed such amount of chars.
@php $usersSearchableMinChars = $this->usersSearchableMinChars; @endphp {{-- Notice `min-chars` and `debounce` --}}<x-choices label="Searchable + Single + Debounce + Min chars" wire:model="user_searchable_min_chars_id" :options="$usersSearchableMinChars" search-function="searchMinChars" debounce="300ms" {{-- Default is `250ms`--}} min-chars="2" {{-- Default is `0`--}} single searchable />
You can pass any extra arbitrary search parameters like this.
{{-- Notice `search-function` with extra arbitrary parameters --}}<x-choices label="Extra parameters" wire:model="user_id" :options="$users" search-function="searchExtra(123, 'thing')" searchable />
public function search(string $value = '', int $extra1 = 0, string $extra2 = ''){ // The first parameter is the default and comes from the search input.}
Slots
You have full control on rendering items by using the @scope('item', $object)
special blade directive.
It injects the current $object
from the loop's context and achieves the same behavior that you would expect from the Vue/React scoped slots.
You can customize the list item and selected item slot. Searchable (online) works with blade syntax.
<div> @php $users = $this->users; @endphp </div> <x-choices label="Slots (online)" wire:model="user_custom_slot_id" :options="$users" single> {{-- Item slot--}} @scope('item', $user) <x-list-item :item="$user" sub-value="bio"> <x-slot:avatar> <x-icon name="o-user" class="bg-orange-100 p-2 w-8 h8 rounded-full" /> </x-slot:avatar> <x-slot:actions> <x-badge :value="$user->username" /> </x-slot:actions> </x-list-item> @endscope {{-- Selection slot--}} @scope('selection', $user) {{ $user->name }} ({{ $user->username }}) @endscope</x-choices>
You can append or prepend anything like this. Make sure to use appropriated css round class on left or right.
<div> @php $users = $this->users @endphp </div> <x-choices label="Slots" wire:model="user_custom_slot_id" :options="$users" single> <x-slot:prepend> {{-- Add `rounded-e-none` (RTL support) --}} <x-button icon="o-trash" class="rounded-e-none" /> </x-slot:prepend> <x-slot:append> {{-- Add `rounded-e-none` (RTL support) --}} <x-button label="Create" icon="o-plus" class="rounded-s-none btn-primary" /> </x-slot:append></x-choices>
Note about large numbers
This components uses the options id
values to handle selection.
It tries to determine if these values are a int
or string
.
But, due to Javascript limitation with large numbers like these bellow, it will break.
public array $options = [ [ 'id' => 264454000038134081, # <-- Javascript won't handle this number 'name' => 'Test 1', ], [ 'id' => '264454000038134082', # <-- It is good! 'name' => 'Test 2', ],];
As workaround, define the id
as a string and use values-as-string attribute instead.
<x-choices ... values-as-string /><x-choices-offline ... values-as-string />
Events
You can catch component events just like described on Livewire docs.
In this case the component will trigger the @change-selection
and it will contain the selected items keys.
The payload contains a single key or an array of keys, depending on how you set the component. Because it is a custom event, you must
access the key(s) via the detail.value
property on the event.
<x-choices ... @change-selection="console.log($event.detail.value)" /><x-choices-offline ... @change-selection="console.log($event.detail.value)" />