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

3 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.