vincent15000's avatar

Sort items with AlpineJS - Display bug

Hello,

UPDATED => new elements found for this bug, I have created a new post.

https://laracasts.com/discuss/channels/livewire/livewire-alpine-alpine-sort-plugin

I have this code.

I need to sort the groups.

The groups are ordered according to the order field stored in the table.

When I drag and drop to sort the tabs, I have a display bug : when I drag and drop several times, sometimes when I click on a tab, another tab disappears and I need to refresh the browser to get it back on the screen.

Something else (it's not a bug, but I don't see how to do that) : I can drag to the right the after the create link, but I'd like to stop the drag just before the create link.

Do you have any idea why some tab disappears ?

Do you have any idea how to prevent dragging too far to the right ?

Thanks for your help.

V

0 likes
10 replies
Jsanwo64's avatar

Likely cause for the the disappearing tab bug

This usually happens when the DOM order (after drag) and the Livewire data order (the $wire.entangle('groups')) get out of sync

After sorting, you should update Livewire’s server-side property to keep the two in sync.

You can do this by setting $wire.groups after sorting, or better, by asking Livewire to refresh from the backend once the sort API call completes.

try this and see what happens

sort: () => {
    const ids = Array.from(document.querySelectorAll('[data-group_id]')).map(el => el.dataset.group_id);

    axios.put('{{ route('tables.groups.sort', compact('table')) }}', { ids })
        .then(response => {
            console.log('Ordre mis à jour avec succès', response.data);

            // Update Alpine array order
            this.groups.sort((a, b) => ids.indexOf(a.id.toString()) - ids.indexOf(b.id.toString()));

            // Tell Livewire that groups changed
            $wire.set('groups', this.groups);

        })
        .catch(error => {
            console.error('Erreur lors de la mise à jour :', error);
        });
}

Make sure a.id and b.id are both strings or both integers when comparing, otherwise the order may fail and Livewire keys become mismatched.

1 like
Jsanwo64's avatar

try this for the second part

if no isssue, that is because your sortable target is this element. So you need to wrap the sortable items (the groups) in their own container, separate from the create link. as seen in

<div class="flex gap-1" x-sort="sort">
1 like
vincent15000's avatar
// Update Alpine array order
this.groups.sort((a, b) => ids.indexOf(a.id.toString()) - ids.indexOf(b.id.toString()));

// Tell Livewire that groups changed
$wire.set('groups', this.groups);

Why do I have to tell Livewire that groups have changed ? Doesn't $wire.entangle().live (with .live) do that automatically ?

Jsanwo64's avatar

$wire.entangle('groups').live does not detect in-place mutations like .sort()

When you do:

this.groups.sort(...)

you’re modifying the same array reference, and Livewire doesn’t notice the change even with .live.

That’s why you must explicitly do:

$wire.set('groups', this.groups);

Unless you replace the array entirely, like this:

this.groups = [...this.groups].sort(...)

In that case, .live would sync automatically.

1 like
vincent15000's avatar

Oh thank you ... this is probably the solution. I will check this tomorrow and I tell you if it works.

vincent15000's avatar

None of your suggestions works.

Hmmm ... I just noticed that the problem occurs only when I drop a tab on the last position.

Then as soon as I click on a tab to switch to it, the last tab disappears.

vincent15000's avatar

The problem comes from the context.

The sort() function I defined is a callback used by an Alpine plugin.

So is there any problem to access the Alpine context wth this ?

The only way seems to be to get the groups via $wire.get().

$wire.get('groups').sort((a, b) => ids.indexOf(a.id) - ids.indexOf(b.id));

But it's not a solution for me because I have the same problem with a tab that disappears when I drop a tab on the last position.

bvfi-dev's avatar

Hey, just a suggestion, I have similar code, but I use livewire components and then use alpineJS to sort the livewire components, and it works great, as long as I provide the components keys. Heres my example:

<ul x-ref="section_list"
		x-sort="() => $wire.saveOrder([...$refs.section_list.children].map(li => li.dataset.id))"
		x-sort:config="{ handle: '.can-drag', filter: '.no-drag', preventOnFilter: false }"
>

		@foreach ($this->sections as $section)
			<li x-sort:item="{{ $section->id }}"
				 data-id="{{ $section->id }}"
				 wire:key="section-{{ $section->id }}"
				
			>
                <livewire:academy.modules.content-list
							:section-id="$expandSectionId"
							:section-order="$section->order"
							:key="'content-' .$section->id .'-'. $section->order"
                  />

And then inside the content-list component I use another x-sort, for a sub-sort. The difference is, with my code you cant put content outside their section, but it can be adjusted to work any way you need it to, the main thing is to group it with livewire components. The code becomes more manageable that way as well. I save my order like so:

	public function saveOrder(array $orderedIds): void
	{
		foreach ($orderedIds as $index => $id) {
			AcademySection::whereKey($id)->update(['order' => $index + 1]);
		}
	}
1 like

Please or to participate in this conversation.