depends what you want to do with it
Livewire - Many to Many - Alternative to Select2
Select2 is based on jquery and bootstrap. What would be the best/better approach to achieve this when using the TALL Stack.
Customer and Company has a Many to Many relationship
and I want to assign companies to a customer
@snapey I also need to remove the companies from the customer when a particular company is no longer required to be linked to a customer
and a select box is the best way to do that?
I think he'd like to have to search ability in the select box @snapey
I recently found this article really useful
https://chrisdicarlo.ca/blog/-alpinejs-and-livewire-autocomplete/
it doesn't work for multiple values, but that's not how i would select multiple companies
@snapey How would you select multiple companies?
As you are doing this in livewire, I would not select multiple companies. I would select one and add it to the list of companies already attached to the customer.
Using the article mentioned before, create a type-ahead search box, then when you find the right one, select it and in the component, do the attach.
What the article doesn't mention is that you ideally need a hidden field for the selected company. When the user selects the company, put its id in the hidden field using alpine.
Here, I use the techniques in the article to create a school picker.
Autocomplete.php (parent class)
<?php
namespace App\Http\Livewire;
use Livewire\Component;
abstract class Autocomplete extends Component
{
public $results;
public $search;
public $selected;
public $showDropdown;
public $initial;
abstract public function query();
public function mount()
{
$this->showDropdown = false;
$this->results = collect();
}
public function updatedSelected()
{
$this->emitSelf('valueSelected', $this->selected);
}
public function updatedSearch()
{
if (strlen($this->search) < 2) {
$this->results = collect();
$this->showDropdown = false;
return;
}
if ($this->query()) {
$this->results = $this->query()->get();
} else {
$this->results = collect();
}
$this->selected = '';
$this->showDropdown = true;
}
public function render()
{
return view('livewire.autocomplete');
}
}
The above is common to any auto complete component
The 'school' picker instance
<?php
namespace App\Http\Livewire;
use App\School;
class SchoolPicker extends Autocomplete
{
protected $listeners = ['valueSelected'];
public $fieldname = 'school';
public function valueSelected(School $school)
{
$this->selected = $school->id;
$this->emitUp('schoolSelected', $school);
}
public function query()
{
return School::where('name', 'like', '%' . $this->search . '%')->orderBy('name');
}
public function resultPresenter($result)
{
return sprintf(
'%s, %s - %s',
$result->name,
$result->postcode,
$result->region,
);
}
}
and the generic autocomplete view
<div class="">
<style>
.autocomplete .results ul {
border:1px solid #ccc;
font-size:14px;
padding:5px;
margin-top:0;
}
.autocomplete .results li {
cursor:pointer;
margin-top:4px; margin-bottom:4px;
padding:0px 5px;
}
.autocomplete .results li:hover {
background-color:#ccc;
}
.autocomplete .results .bg-highlight {
background-color:#ccc;
}
</style>
<div class="autocomplete">
<div x-data="{
open: @entangle('showDropdown'),
search: @entangle('search'),
selected: @entangle('selected'),
highlightedIndex: 0,
highlightPrevious() {
if (this.highlightedIndex > 0) {
this.highlightedIndex = this.highlightedIndex - 1;
this.scrollIntoView();
}
},
highlightNext() {
if (this.highlightedIndex < this.$refs.results.children.length - 1) {
this.highlightedIndex = this.highlightedIndex + 1;
this.scrollIntoView();
}
},
scrollIntoView() {
this.$refs.results.children[this.highlightedIndex].scrollIntoView({
block: 'nearest',
behavior: 'smooth'
});
},
updateSelected(id, name) {
this.selected = id;
this.search = name;
this.open = false;
this.highlightedIndex = 0;
},
}">
<input type="hidden" name="{{ $fieldname }}_id" wire:model="selected" />
<div x-on:value-selected="updateSelected($event.detail.id, $event.detail.name)">
<span>
<div>
<input
type="text" name="{{ $fieldname }}"
class="form-control"
autocomplete="off"
wire:model.debounce.300ms="search"
x-on:keydown.arrow-down.stop.prevent="highlightNext()"
x-on:keydown.arrow-up.stop.prevent="highlightPrevious()"
x-on:keydown.enter.stop.prevent="$dispatch('value-selected', {
id: $refs.results.children[highlightedIndex].getAttribute('data-result-id'),
name: $refs.results.children[highlightedIndex].getAttribute('data-result-name')
})">
</div>
</span>
<div x-show="open" x-on:click.away="open = false" class="results">
<ul x-ref="results">
@forelse($results as $index => $result)
<li wire:key="{{ $result->id }}" x-on:click.stop="$dispatch(`value-selected`, {
id: {{ $result->id }},
name: `{{ $result->name }}`
})" :class="{
'bg-highlight': {{ $index }} === highlightedIndex,
'text-white': {{ $index }} === highlightedIndex
}" data-result-id="{{ $result->id }}" data-result-name="{{ $result->name }}">
<span>
{!! $this->resultPresenter($result) !!}
</span>
</li>
@empty
<li>No results found</li>
@endforelse
</ul>
</div>
</div>
</div>
</div>
</div>
This unfortunately was on a bootstrap project so I had to throw in some styles into the component. In TALL stack you would apply the tailwind css classes direct
You can build your own select option drop down, or even have a table in a modal where user can select from. I normally use a searchable table, and a modal is so easy to do.
If you are still stuck on this try https://mary-ui.com/docs/components/choices
Please or to participate in this conversation.