Select and Select Multi

Current status - needs more info but bulk written
Select2 - Do not use this

  • Jquery dependency
  • Causes error and lag when many on screen
  • Hidden errors see firefox debug
    – If you need this working please comment and will post how

Choice.js (https://github.com/jshjohnson/Choices)

  • Does not require wire:ignore
  • No errors
  • No lag
  • No jquery dependency
  • ES6

I will now show you how to get Choice.js working in livewire with alpine.js:

Let’s start by making a Laravel component

php artisan make:component input/select

Add the following code to the new component:

<div 
	x-data
	x-init="() => {
	var choices = new Choices($refs.{{ $attributes['prettyname'] }}, {
		itemSelectText: '',
	});
	choices.passedElement.element.addEventListener(
	  'change',
	  function(event) {
			values = event.detail.value;
		    @this.set('{{ $attributes['wire:model'] }}', values);
	  },
	  false,
	);
	let selected = parseInt(@this.get{!! $attributes['selected'] !!}).toString();
	choices.setChoiceByValue(selected);
	}"
	>
    <select id="{{ $attributes['prettyname'] }}" wire-model="{{ $attributes['wire:model'] }}" wire:change="{{ $attributes['wire:change'] }}" x-ref="{{ $attributes['prettyname'] }}">
    	<option value="">{{ isset($attributes['placeholder']) ? $attributes['placeholder'] : '-- Select --' }}</option> 
    	@if(count($attributes['options'])>0)
	    	@foreach($attributes['options'] as $key=>$option)
	    		<option value="{{$key}}" >{{$option}}</option>
	    	@endforeach
    	@endif
    </select>
</div>

You can now reference this select like so:

<x-input.select wire:model="modelname" prettyname="modelprettyname" :options="$array" selected="('modelname')"/>

For array select, you can do

selected="('modelname')['level1']['level2']"

Else use the model name with the selected value:

selected="('modelname')"

For collections you can do options like this:

:options="$collection->pluck('name', 'id')->toArray()"

For anything else make an array:

@php
	$array=[];
@endphp
@if(count($returnlist)>0)
	@foreach($returnlist as $list)
		@php
		$array[$list['id']]=$list['name'];
		//for collections would be:
		//$array[$type=>id]=$type->name;
		@endphp
	@endforeach
@endif

Multiselect I like to keep these separate but you can have them on the same component with some refactoring.

php artisan make:component input/selectmultiple

Add the following code to the new component:

<div 
	x-data
	x-init="() => {
	var choices = new Choices($refs.{{ $attributes['prettyname'] }}, {
		itemSelectText: '',
		removeItems: true,
	    removeItemButton: true,
	});
	choices.passedElement.element.addEventListener(
	  'change',
	  function(event) {
	  		values = getSelectValues($refs.{{ $attributes['prettyname'] }});
		    @this.set('{{ $attributes['wire:model'] }}', values);
	  },
	  false,
	);
	items = {!! $attributes['selected'] !!};
	if(Array.isArray(items)){
		items.forEach(function(select) {
			choices.setChoiceByValue((select).toString());
		});
	}
	}
	function getSelectValues(select) {
	  var result = [];
	  var options = select && select.options;
	  var opt;
	  for (var i=0, iLen=options.length; i<iLen; i++) {
	    opt = options[i];
	    if (opt.selected) {
	      result.push(opt.value || opt.text);
	    }
	  }
	  return result;
	}
	">
    <select id="{{ $attributes['prettyname'] }}" wire-model="{{ $attributes['wire:model'] }}" wire:change="{{ $attributes['wire:change'] }}" x-ref="{{ $attributes['prettyname'] }}" multiple="multiple">
    	@if(count($attributes['options'])>0)
	    	@foreach($attributes['options'] as $key=>$option)
	    		<option value="{{$key}}" >{{$option}}</option>
	    	@endforeach
    	@endif
    </select>
</div>

You can now reference this multiselect like so:

<x-input.selectmultiple wire:model="modelname" prettyname="modelprettyname" :options="$array" :selected="$selected" />

I tend to add this before each:

@php
	$selected = (json_encode($selectvalues));
@endphp
@php
	$array=[];
@endphp
@if(isset($returnlist) && count($returnlist)>0)
	@foreach($returnlist as $list)
		@php
		$array[$list['id']]=$list['name'];
        //for collections would be:
		//$array[$type=>id]=$type->name;
		@endphp
	@endforeach
@endif

Note: these are not perfect I wrote them quickly but do the Job

I am on discord from time to time if need help as well cheers
Andy

5 Likes

@andylord565 this is actually a very good post.

I have currently setup using Select2 in my project, unfortunately when researching which Select js library I should use, I did not come across Choices.js

Well, seeing this, and their demo page, next time I definitely will go which Choices next time.

Thanks :+1:

@basepack
Your welcome let me know if you need any help :+1:

Ill post the JS versions for this as well for anyone not using Alpinejs when I get chance

@andylord565 Thanks for this, really helped me finally get multiselects working without having to use wire:ignore.

In case it helps anyone else: The one thing I had to change from the above to get it to stop destroying the styling on each Livewire update was to wrap the entire selectMultiple component in blank div with an empty x-data on it.

1 Like

Quick question regarding the multiple select. How do you keep the select list open after choosing an item? Since livewire refreshes it, the list closes and in order to make it appear again you need to click outside of the element and on it again which is not ideal.

Hi, I tried this code for cascading select dropdown but i have error

if i selected a parent, i reset the syles
image

and throws error


can you help me i cant figure it out why its resetting the style and not getting the value.
Thank you in advance

Had to use wire:ignore despite this solution is to not use this. Not sure how others got this to work without it.

Hi, how did you get this to work? I still had to use “wire:ignore” in the main div otherwise livewire would still refresh and the select options would close. I would have to click outside the select box to reopen the options again. Weird.

Yes I also needed wire:ignore but I added it to the surrounding div, so my component is as per the above one but wrapped like this:

<div x-data wire:ignore>
    <!-- as above... -->
</div>

I can’t say I understand why this worked but it preserves the choices component between livewire changes while still keeping the contents of the choices array wired up. :man_shrugging:t2:

1 Like

Oh ok. Thanks for confirming. I thought I was missing something (probably still am). In theory, we shouldn’t need it since the wire:ignore is used in the select tag but something funky is going. I actually didn’t have to wrap an empty div like you did. I just added the wire:ignore in the same div with the x-data and x-init and it seems to work. I’ll have a look under the hood when I get some time but for now, I’ll appreciate the magic. Thanks again!

Is this still working with latest version? The selected data is not updating on change (for edit, i load a new $selected list), it’s working with wire:ignore but the css is messed up on change