Is it possible to use (nested) Livewire components with Livewire Sortable?
Question like in the title. Is it possible to use (nested) Livewire components with Livewire Sortable?
I am working on a project which have boards, which has groupsm which has tasks. Boards, group and tasks are Livewire componets. When I try to implement Sortable I on groups I get onwanted/weird results.
Is it even possible to combine it with Livewire components?
- And if so, what is the best way to implement?
- Should
wire:sortable.itembe on the component element which is being looped? Or should it be inside the component view template? - I searched trough the issues and I saw more topics. How sould the
keyandwire:keytags be used? And where should they be placed? Inside the component view template or in the parent?
Any help to point me in the right direction is appreciated.
Edit: I am also using https://github.com/livewire-ui/modal. It seems to me when combining all of those things (livewire, livewire Sortable and Livewire UI modal) things get too complicated. I don't know maybe I am doing something wrong.
Hi, I had a similar use case, I needed to sort parent components, and inside the parent components there are multiple sortables aswell. I ended up modifying the source code of this package. Maybe I'll do some refactoring and add a PR. All you need to do is (instead of importing this package) adding the following modified JS, the shopify/draggable package and make a few modifications on your markup. Hope it helps someone:
npm i @shopify/draggable@next
Add wire:sortable.key="your-unique-key-per-sortable" to every wire:sortable, wire:sortable.item and wire:sortable.handle, e.g:
<ul wire:sortable="updateDisplayOrder" wire:sortable.key="sk-1">
@foreach($items as $key => $item)
<li wire:key="item-{{$key}}"
class="list-group-item"
wire:sortable.item="{{ $key }}"
wire:sortable.key="sk-1">
...
</li>
@endforeach
</ul>
Add this JS somewhere on your page. This extends the "sortable" directive to look for the wire:sortable.key property.
import Sortable from '@shopify/draggable/lib/sortable';
if (typeof window.livewire === 'undefined') {
throw 'Livewire Sortable Plugin: window.livewire is undefined. Make sure @livewireScripts is placed above this script include'
}
window.livewire.directive('sortable-group', (el, directive, component) => {
if (directive.modifiers.includes('item-group')) {
// This will take care of new items added from Livewire during runtime.
el.closest('[wire\\:sortable-group]').livewire_sortable.addContainer(el)
}
// Only fire the rest of this handler on the "root" directive.
if (directive.modifiers.length > 0) return
let options = {
draggable: '[wire\\:sortable-group\\.item]',
mirror: {
constrainDimensions: true,
},
}
if (el.querySelector('[wire\\:sortable-group\\.handle]')) {
options.handle = '[wire\\:sortable-group\\.handle]'
}
const sortable = el.livewire_sortable = new Sortable([], options);
sortable.on('sortable:stop', () => {
setTimeout(() => {
let groups = []
el.querySelectorAll('[wire\\:sortable-group\\.item-group]').forEach((el, index) => {
let items = []
el.querySelectorAll('[wire\\:sortable-group\\.item]').forEach((el, index) => {
items.push({order: index + 1, value: el.getAttribute('wire:sortable-group.item')})
})
groups.push({
order: index + 1,
value: el.getAttribute('wire:sortable-group.item-group'),
items: items,
})
})
component.call(directive.method, groups)
}, 1)
})
})
window.livewire.directive('sortable', (el, directive, component) => {
// Only fire this handler on the "root" directive.
if (directive.modifiers.length > 0) return
let key = el.getAttribute('wire:sortable.key');
let draggable = '[wire\\:sortable\\.item]';
let handle = '[wire\\:sortable\\.handle]';
if (key) {
let keyAttr = '[wire\\:sortable\\.key="' + key + '"]';
draggable += keyAttr;
handle += keyAttr;
}
let options = {
draggable: draggable,
mirror: {
constrainDimensions: true,
}
}
if (el.querySelector(handle)) {
options.handle = handle;
}
const sortable = new Sortable(el, options);
sortable.on('sortable:stop', () => {
setTimeout(() => {
let items = []
el.querySelectorAll(draggable).forEach((el, index) => {
items.push({order: index + 1, value: el.getAttribute('wire:sortable.item')})
})
component.call(directive.method, items)
}, 1)
})
})
Edit:
You need wire:key on the top level element inside a @foreach.
I usually also put a key on child components so they rerender properly:
<livewire:my-component :key="$something->id . time()" />