Skip to content

Instantly share code, notes, and snippets.

@mithicher
Last active April 25, 2024 16:10
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save mithicher/9944232624cbad4b1cb5d3d2cac87a97 to your computer and use it in GitHub Desktop.
Save mithicher/9944232624cbad4b1cb5d3d2cac87a97 to your computer and use it in GitHub Desktop.
Tom Select Livewire Blade Component
/* Component Usage
// Data for options
$users = \App\User::limit(6)->get()->transform(fn($user) => [
'id' => $user->id,
'title' => $user->name,
'subtitle' => $user->email
]);
// Usage in view
<x-tom-select
id="testUser"
name="testUser"
wire:model="testUser"
:options="$users"
placeholder="Pick a user"
items="{{ $testUser }}" // for multiselect edit eg. 1,2,3 comma separated ids
// multiple // enable for multiple select
/>
*/
@props([
'options' => [],
'items' => null
])
<div wire:ignore>
<select
x-data="{
tomSelectInstance: null,
options: {{ collect($options) }},
items: [{{ $items }}],
renderTemplate(data, escape) {
return `<div class='flex items-center'>
<span class='mr-3 w-8 h-8 rounded-full bg-gray-100'><img src='https://avatars.dicebear.com/api/initials/${escape(data.title)}.svg' class='w-8 h-8 rounded-full'/></span>
<div><span class='block font-medium text-gray-700'>${escape(data.title)}</span>
<span class='block text-gray-500'>${escape(data.subtitle)}</span></div>
</div>`;
},
itemTemplate(data, escape) {
return `<div>
<span class='block font-medium text-gray-700'>${escape(data.title)}</span>
</div>`;
}
}"
x-init="tomSelectInstance = new TomSelect($refs.input, {
valueField: 'id',
labelField: 'title',
searchField: 'title',
options: options,
items: items,
@if (! empty($items) && ! $attributes->has('multiple'))
placeholder: undefined,
@endif
render: {
option: renderTemplate,
item: itemTemplate
}
});"
x-ref="input"
x-cloak
{{ $attributes }}
placeholder="Pick some links..."></select>
</div>
@once
@push('styles')
<link href="https://cdn.jsdelivr.net/npm/tom-select@1.1/dist/css/tom-select.css" rel="stylesheet">
<style>
.ts-input {
padding: 10px 8px;
border-radius: 0.5rem;
border-color: rgba(209, 213, 219, 1.0);
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
}
.ts-input.focus {
outline: 2px solid transparent;
outline-offset: 2px;
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), 0 0 0 3px rgba(199, 210, 254, 0.5);
border-color: rgba(165, 180, 252, 1.0);
}
.ts-input.dropdown-active {
border-radius: 0.5rem 0.5rem 0 0;
}
.ts-dropdown {
margin: -5px 0 0 0;
border-radius: 0 0 0.5rem 0.5rem;
padding-bottom: 4px;
}
.ts-control.single .ts-input:after {
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' viewBox='0 0 24 24' stroke='%239CA3AF'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M8 9l4-4 4 4m0 6l-4 4-4-4' /%3E%3C/svg%3E");
display: block;
position: absolute;
top: 10px;
right: 8px;
width: 24px;
height: 24px;
border: none;
}
</style>
@endpush
@push('scripts')
<script src="https://cdn.jsdelivr.net/npm/tom-select@1.1/dist/js/tom-select.complete.min.js"></script>
@endpush
@endonce
@thumbtech
Copy link

Have you tried this with 2.0.0 yet?

I moved x-data & x-init to parent div (seemed to want to re-initialize the tom select instance otherwise. But now I get an error when I select an item:

Alpine Expression Error: Cannot read properties of undefined (reading 'jquery')

(line 12 of vanilla.ts)

Thanks for this!

@mithicher
Copy link
Author

@props([
	'options' => [],
	'selectedItems' => []
])

<div wire:ignore>
    <select x-data="{
		tomSelectInstance: null,
		options: {{ collect($options) }},
		items: @json($selectedItems),
 
		renderTemplate(data, escape) {
			return `<div class='flex items-center'>
				<span class='mr-3 w-8 h-8 rounded-full bg-gray-100'><img src='https://avatars.dicebear.com/api/initials/${escape(data.title)}.svg' class='w-8 h-8 rounded-full'/></span>
				<div><span class='block font-medium text-gray-700'>${escape(data.title)}</span>
				<span class='block text-gray-500'>${escape(data.subtitle)}</span></div>
			</div>`;
		},
		itemTemplate(data, escape) {
			return `<div>
				<span class='block font-medium text-gray-700'>${escape(data.title)}</span>
			</div>`;
		}
	}" x-init="tomSelectInstance = new TomSelect($refs.input, {
		valueField: 'id',
		labelField: 'title',
		searchField: 'title',
		options: options,
		items: items,
		@if (! empty($items) && ! $attributes->has('multiple'))
			placeholder: undefined,
		@endif
		render: {
			option: renderTemplate,
			item: itemTemplate
		}
	});" x-ref="input" x-cloak {{ $attributes }} placeholder="Pick some links..."></select>
</div>


<link href="https://cdn.jsdelivr.net/npm/tom-select@2.0.0/dist/css/tom-select.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/tom-select@2.0.0/dist/js/tom-select.complete.min.js"></script>

Example:

<x-tom-select
      id="testUser"
      name="testUser"
      wire:model="testUser"
      :options="[
          [
              'id' => 1,
              'title' => 'John Doe',
              'subtitle' => 'hello@test.test'
          ],
          [
              'id' => 2,
              'title' => 'Winter Doe',
              'subtitle' => 'winter@test.test'
          ],
          [
              'id' => 3,
              'title' => 'Summer Doe',
              'subtitle' => 'summer@test.test'
          ]
      ]"
      placeholder="Pick a user"
  />

For edit Form for single:

<x-tom-select
      id="testUser"
      name="testUser"
      wire:model="testUser"
      :selected-items="[1]"
      :options="[
          [
              'id' => 1,
              'title' => 'John Doe',
              'subtitle' => 'hello@test.test'
          ],
          [
              'id' => 2,
              'title' => 'Winter Doe',
              'subtitle' => 'winter@test.test'
          ],
          [
              'id' => 3,
              'title' => 'Summer Doe',
              'subtitle' => 'summer@test.test'
          ]
      ]"
      placeholder="Pick a user"
  />

For edit Form for multiple:

<x-tom-select
      id="testUser"
      name="testUser"
      wire:model="testUser"
      :selected-items="[1, 2]"
      multiple
      :options="[
          [
              'id' => 1,
              'title' => 'John Doe',
              'subtitle' => 'hello@test.test'
          ],
          [
              'id' => 2,
              'title' => 'Winter Doe',
              'subtitle' => 'winter@test.test'
          ],
          [
              'id' => 3,
              'title' => 'Summer Doe',
              'subtitle' => 'summer@test.test'
          ]
      ]"
      placeholder="Pick a user"
  />

@mithicher
Copy link
Author

mithicher commented Feb 12, 2022

If your are rending tom-select component conditionally then you need to put the both the css and js in you livewire component.

Hope this helps!

@thumbtech
Copy link

thumbtech commented Feb 15, 2022

Thank you so much. I beat my head against the wall trying to make mine work (it always broke when I selected an item).

I think I can reproduce the issue, though I don't understand why it is a problem:

See what happens if you put following just above <x-tom-select ... in your form blade:

<label for="testUser">WTF</label>

In my case, the addition of the label with id of tom select element snaps it when selecting an item!?!?! Am I seeing ghosts?

One other small change I made since my data id's are strings & @JSON throws double-quotes into the rendered blade, busting the alpine x-data attribute: In tom-select component, I changed

...
items: @json($selectedItems),
...

to

...
items: {{ json_encode($selectedItems) }},
...

It seems happy with that, though I've only tested with single string item.

BTW I just realized that adding a wire:key to that label tag addresses the problem. Very edge-y livewire dom diffing issue, I'd say. I would love if you could reproduce as it is lonely out here on the edge ...

@mithicher
Copy link
Author

@thumbtech use {{ collect($selectedItems) }} instead of {{ json_encode($selectedItems) }}. It works well for nested objects.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment