svelte-sortablejs
svelte-sortablejs copied to clipboard
Sortable shows new added item not at the end but in between
Thank you very much for the great work and Svelte implementation.
Issue
Resorting by drag and drop does not work correct after an additional item is added. The item the user drops at position A, sortable puts it to position A-1.
Situation
- There is a data array = [1,2,3]
- User resorts to [2,3,1]
- An additional item is added to the array at last position [2,3,1,4]
- But Sortable shows [2,3,4,1]
Goal
The goal ist to save the sorted data (not html) to database. Therefor a array of data is used.
Steps to Reproduce
- Create a svelte page and add the code below.
- Run Svelte
- Drag Item 1 below Item 3
- Add a item to the array by clicking on the Add button.
<script>
import { SortableList } from '@jhubbardsf/svelte-sortablejs';
let data = ['Item 1', 'Item 2', 'Item 3'];
let sortableListData = [...data];
$: {
console.log(`data: ${data}`);
}
const addItem = () => {
const item = `Item ${data.length + 1}`;
data = [...data, item];
sortableListData = [...data];
};
const move = (arr, from, to) => {
const input = [...arr];
let numberOfDeletedElm = 1;
const elm = input.splice(from, numberOfDeletedElm)[0];
numberOfDeletedElm = 0;
input.splice(to, numberOfDeletedElm, elm);
data = [...input];
};
const handleEnd = (evt) => {
move(data, evt.oldIndex, evt.newIndex);
// data = [...data];
};
</script>
<div class="margin-auto ">
<button class="button" on:click={addItem}>Add</button>
<SortableList class="list-group col" animation={150} ghostClass="bg-info" onEnd={handleEnd}>
{#each sortableListData as item}
<div class="list-group-item">
{item}
</div>
{/each}
</SortableList>
</div>
<style>
.margin-auto {
margin: auto;
}
.list-group.col {
background-color: blue;
color: white;
}
.list-group-item {
background-color: gray;
color: white;
margin: 20px 0;
padding: 8px 8px;
}
.button {
padding: 8px;
background-color: green;
color: white;
}
</style>
Thank you in advanced for your response.
Thank you for the detailed PR! I'll take a closer look at it this weekend and hopefully get a fix put in.
I created two better examples.
Resort the item array manually in setValue
Issues
- Drag and after drop does not show the correct order on the screen. Reason in
setValuethe item array is sorted again and the item list is rendered again by Svelte - Adding an item (via button) is correct in the array but not on screen
<script>
import { SortableList } from '@jhubbardsf/svelte-sortablejs';
import { writable } from 'svelte/store';
const items = writable([]);
$items = ['Item 0', 'Item 1', 'Item 2'];
let currentIndices = [...$items.keys()];
// Issues: new items (addItem) should be added at the end, there are added on screen first)
$: {
console.log(`currentIndices: ${currentIndices}`);
}
function setupStore() {
return {
get: getValue,
set: setValue
};
}
function getValue(sortable) {
const order = currentIndices.map((i) => i.toString());
return order || [];
}
function setValue(sortable) {
currentIndices = sortable.toArray().map((i) => parseInt(i));
$items = currentIndices.map((n) => $items[n]);
}
const addItem = () => {
const item = `Item ${$items.length}`;
$items = [...$items, item];
currentIndices = [...currentIndices, currentIndices.length];
$items = currentIndices.map((n) => $items[n]);
};
</script>
<div class="margin-auto ">
<button class="button" on:click={addItem}>Add</button>
<SortableList class="list-group col" animation={150} ghostClass="bg-info" store={setupStore()}>
{#each $items as item, i (item)}
<div class="list-group-item" data-id={currentIndices[i]}>
{`${item} - dataID: ${currentIndices[i]}`}
</div>
{/each}
</SortableList>
<div>
indices order: {currentIndices}
</div>
<div>
items order in array: {$items}
</div>
</div>
<style>
.margin-auto {
margin: auto;
}
.list-group.col {
background-color: blue;
color: white;
}
.list-group-item {
background-color: gray;
color: white;
margin: 20px 0;
padding: 8px 8px;
}
.button {
padding: 8px;
background-color: green;
color: white;
}
</style>
Without resort the item array manually in setValue
Issues
- Drag and drop works fine
- Adding an item (via button) is correct in the array but not on screen
<script>
import { SortableList } from '@jhubbardsf/svelte-sortablejs';
import { writable } from 'svelte/store';
const items = writable([]);
$items = ['Item 0', 'Item 1', 'Item 2'];
let currentIndices = [...$items.keys()];
// Issues: new items (addItem) should be added at the end, there are added on screen first)
$: {
console.log(`currentIndices: ${currentIndices}`);
}
function setupStore() {
return {
get: getValue,
set: setValue
};
}
function getValue(sortable) {
const order = currentIndices.map((i) => i.toString());
return order || [];
}
function setValue(sortable) {
currentIndices = sortable.toArray().map((i) => parseInt(i));
// $items = currentIndices.map((n) => $items[n]);
}
const addItem = () => {
const item = `Item ${$items.length}`;
$items = [...$items, item];
currentIndices = [...currentIndices, currentIndices.length];
// $items = currentIndices.map((n) => $items[n]);
};
</script>
<div class="margin-auto ">
<button class="button" on:click={addItem}>Add</button>
<SortableList class="list-group col" animation={150} ghostClass="bg-info" store={setupStore()}>
{#each $items as item, i (item}
<div class="list-group-item" data-id={i}>
{`${item} - dataID: ${i}`}
</div>
{/each}
</SortableList>
<div>
indices order: {currentIndices}
</div>
<div>
items order in array: {$items}
</div>
</div>
<style>
.margin-auto {
margin: auto;
}
.list-group.col {
background-color: blue;
color: white;
}
.list-group-item {
background-color: gray;
color: white;
margin: 20px 0;
padding: 8px 8px;
}
.button {
padding: 8px;
background-color: green;
color: white;
}
</style>
I modified the code example by adding keys to Svelte each blocks as described keyed-each-blocks
Hey, not sure if you're still having issues with this. Sorry. I just looped over your first example and it was fixed by doing. I think the main issue with Svelte is them not having the unique key indexes like you mentioned.
<script>
import { SortableList } from '@jhubbardsf/svelte-sortablejs';
let data = [{id: 0, val: 'Item 1'}, {id: 1, val: 'Item 2'}, {id: 2, val: 'Item 3'}];
// let sortableListData = [...data];
$: {
console.log(`data: ${data}`);
}
const addItem = () => {
const item = `Item ${data.length + 1}`;
data = [...data, {id: data.length, val: item}];
// sortableListData = [...data];
};
const move = (arr, from, to) => {
const input = [...arr];
let numberOfDeletedElm = 1;
const elm = input.splice(from, numberOfDeletedElm)[0];
numberOfDeletedElm = 0;
input.splice(to, numberOfDeletedElm, elm);
data = [...input];
};
const handleEnd = (evt) => {
move(data, evt.oldIndex, evt.newIndex);
// data = [...data];
};
</script>
<svelte:head>
<title>Home</title>
<meta name="description" content="Svelte demo app" />
</svelte:head>
<section>
<h1>Sortable Test</h1>
<div class="margin-auto ">
<button class="button" on:click={addItem}>Add</button>
<SortableList class="list-group col" animation={150} ghostClass="bg-info" onEnd={handleEnd}>
{#each data as item (item.id)}
<div class="list-group-item">
{item.val}
</div>
{/each}
</SortableList>
</div>
</section>
<style>
.margin-auto {
margin: auto;
}
.list-group.col {
background-color: blue;
color: white;
}
.list-group-item {
background-color: gray;
color: white;
margin: 20px 0;
padding: 8px 8px;
}
.button {
padding: 8px;
background-color: green;
color: white;
}
</style>