I'm new to svelte and am working on a simple drag and drop list using sortablejs as the base. I load the data from a nested array of objects to instantiate the lists, and want any changes from sortable to be replicated to the array, but I can't get it to stop behaving oddly.
I binded the array to the components so that they can send the Sortable order to update the array. The array seems to update correctly, but the actual list becomes very janky: double moving items and undoing moves. I can't wrap my head around what exactly is causing this.
App.svelte
<script>
import List from "./List.svelte";
let items = [
[
{id: 1,name: "one"},
{id: 2,name: "two"},
],
[
{id: 3,name: "three"},
{id: 4,name: "four"},
]
]
</script>
{#each items as category, i}
<h2>Category {i}</h2>
<ul>
<List bind:fullArr={items} index={i}>
{#each category as item}
<li data-id={item.id} >{item.name}</li>
{/each}
</List>
</ul>
{/each}
{JSON.stringify(items)}
List.svelte
<script>
import Sortable from 'sortablejs';
import { onMount } from 'svelte';
export let fullArr;
export let index;
let arr = fullArr[index];
let list;
let sortable;
onMount(() => {
sortable = new Sortable(list, {
group: "list",
onSort: onSort,
});
})
function onSort(){
const order = sortable.toArray()
fullArr[index] = order.map(id => {
//id is searched from full array to account for item going between lists
return fullArr.flat().find(item => item.id == id)
})
}
</script>
<div bind:this={list}>
<slot></slot>
</div>
There is this similar question, but the solution seems to only apply to a singular list and not a group of lists
As in the other question, the #each
block is missing the key - tutorial
{#each category as item (item.id)}
Instead of the List
component, that adds more complexity than benefit, all logic could be handled inside the main component using an action - docs - tutorial
<script>
import Sortable from 'sortablejs';
let items = [
[
{id: 1,name: "one"},
{id: 2,name: "two"},
],
[
{id: 3,name: "three"},
{id: 4,name: "four"},
]
]
function initSortable(list, index) {
const sortable = new Sortable(list, {
group: "list",
onSort() {
const order = sortable.toArray()
const allItems = items.flat()
items[index] = order.map(id => {
return allItems.find(item => item.id == id)
})
},
});
}
</script>
{#each items as category, i}
<h2>Category {i}</h2>
<ul use:initSortable={i} >
{#each category as item (item.id)}
<li data-id={item.id} >{item.name}</li>
{/each}
</ul>
{/each}
{JSON.stringify(items)}
And a different way to update items
using the sortable onEnd
event
<script>
import Sortable from 'sortablejs';
let items = [
[
{id: 1,name: "one"},
{id: 2,name: "two"},
],
[
{id: 3,name: "three"},
{id: 4,name: "four"},
]
]
function initSortable(list) {
const sortable = new Sortable(list, {
group: "list",
onEnd(event) {
const fromListIndex = event.from.dataset.listIndex
const toListIndex = event.to.dataset.listIndex
const movedItem = items[fromListIndex].splice(event.oldIndex, 1)[0]
items[toListIndex].splice(event.newIndex, 0, movedItem)
items = items // important to make the UI update to the changes made to items via .splice method
},
});
}
</script>
{#each items as category, i}
<h2>Category {i}</h2>
<ul data-list-index={i} use:initSortable >
{#each category as item (item.id)}
<li data-id={item.id} >{item.name}</li>
{/each}
</ul>
{/each}
{JSON.stringify(items)}