Choices.js with Livewire for multiple select

1.4k views Asked by At

I'm new to Livewire after discovering it a few months back and I'm a bit rusty on Vanilla JS.

I have created a Livewire component to filter a table of news articles but I'm struggling to use the Choices.js library to select multiple categories and place them in to an array.

Livewire component:

public $filters = [
   ...
   'categories' => [],
   ...
];

public function render()
{
   $reports = Article::query()
      ->where('active', true)
      ->when($this->filters['categories'], function ($query, $category_ids) {
         $query->whereHas('categories', function ($query) use ($category_ids) {
            $query->whereIn('category_id', $category_ids);
         });
      })
      ->get();

   $categories = Category::all();

   return view('livewire.reports.list-reports', compact('categories'));
}

Blade:

...
<div class="mb-2" wire:ignore>
   <label class="mb-1">Category</label>
   <select wire:model="filters.categories" class="form-control js-choice" multiple id="filter-categories">
   @foreach ($categories as $category)
      <option value="{{ $category->id }}">{{ $category->type }}</option>
   @endforeach
   </select>
</div>
...
@section('page-scripts')
<script src="{{ asset('assets/js/user/plugins/choices/choices.js') }}"></script>
@endsection

choices.js file:

import Choices from "choices.js";


const elements = document.querySelectorAll('.js-choice');

elements.forEach((item) => {
    const choices = new Choices(item, {
        removeItems: true,
        removeItemButton: true,
        searchEnabled: true,
        placeholder: true,
        placeholderValue: 'Select your options'
    });
    return choices;
});

The problem

Without using choices.js and using the native browser multiple select box, it works perfectly fine, with the filter.categories property outputting "categories": ["13","2","26"...] and updating the controller query accordingly, displaying all news articles which have the selected category ID's.

But using choices.js, it only outputs the last clicked value and looks like this: "categories": {"value":"13"}. As it is not in an array, it simply just returns all of the news articles with an id of 13 in this instance. Clicking on multiple options just changes the property to the last clicked option. Also, clearing all of the options from the select box doesn't reset the filters.categories property back to empty, it just populates it with the ID of the last option that was closed.

How can I collect the ID's of the selected options and place them in to the filter.categories array and also remove them when I remove the item?

I know I need to put some logic in to the choices.js file to access the detail.value of the selected options but I'm really struggling to get it to work. I've explored Alpine as I believe this may be the key but I don't know where to start.

Any help is greatly appreciated, I've been stuck on this for a couple of days.

1

There are 1 answers

0
itsgrimace On

I don't have experience with choices.js so I'll do my best based on my experience using slimselect.js.

Basically when JS is applied to a select box it often abstracts away what you are actually clicking on, and those elements are already in a wire:ignore block so that part of the DOM is not reloaded.

So when I'm using something like you've described then I use the JS callback functions to set the properties of the Livewire Component.

In it's simplest form it will look something like this;

//where dataArray is the JS array you want to assign to the livewire component
@this.set(filters.categories, dataArray);

Just looking at the choices.js documentation you can use the 'choice' event.

const element = document.getElementById('filter-categories');
element.addEventListener(
  'choice',
  function(event) {
    //this is just selecting what the choices element's data is. not sure on syntax sorry.
    var dataArray = this.choices;
    @this.set(filters.categories, dataArray);
  },
  false,
);

Hope this helps