sortablejs-vue3 icon indicating copy to clipboard operation
sortablejs-vue3 copied to clipboard

Managing state when moving items between lists

Open nlhkabu opened this issue 1 year ago • 6 comments

Thanks again for this lib.

I have several lists on my page, and want to be able to drag items between lists. For this, I give every list the same sortable group option.

This is how I am trying to manage the state when dragging an item between lists:

  1. use remove event to remove item from the state
  2. use add event to add the item back to the state in its new location

My state is updating just fine, however, the orginal DOM element is moved to the new list (and not removed from the DOM). This results in duplicates (because my state also creates a new element).

Currently, it looks like my options are to either:

  1. manually remove the DOM element with event.item.remove() OR
  2. force re-render my component to keep the state in sync

Both of these seem hacky - is there a better approach?

nlhkabu avatar Aug 22 '22 20:08 nlhkabu

For context, here is the problem in action: https://github.com/nlhkabu/sortablejs-vue3/tree/add-store

nlhkabu avatar Aug 22 '22 20:08 nlhkabu

Thanks for the detailed issue + repro! I'll try and investigate tonight or tomorrow and will respond back here

MaxLeiter avatar Aug 23 '22 18:08 MaxLeiter

If you compare the approach of another library they also seem to remove the item onDragAdd manually, allowing for the component to render its own with v-for. I dont think sortableJS provides any options for renderless listing.

https://github.com/SortableJS/Vue.Draggable/blob/017ab498428efef966d72ea2be547cb0213cd6bb/src/vuedraggable.js#L406

Chuuone avatar Sep 06 '22 12:09 Chuuone

Thanks for the info, @Chuuone. I got a little caught up with work and haven't worked on this yet, but that's very helpful.

MaxLeiter avatar Sep 06 '22 17:09 MaxLeiter

Thank you @Chuuone - that's what we decided to do in the end :)

nlhkabu avatar Sep 06 '22 18:09 nlhkabu

I'm facing kind the same behavior, with vue.draggable.next I can move an item from a list to an other. I don't really know how to workaround this.

<draggable
     :component-data="{name:'fade', type: 'transtion-group'}"
     v-model="modules" group="modules"
     item-key="module"
     class="q-pa-md row items-start q-gutter-md"
     :move="test"
     @start="dragging = true"
     @end="dragging = false"
     v-bind="dragOptions">
<Sortable
    :list="modules"
    item-key="module"
    :options="options"
    class="q-pa-md row items-start q-gutter-md"
    group="modules">

goldyfruit avatar Oct 21 '22 03:10 goldyfruit

@goldyfruit are your groups the same? Or am I misunderstanding what you want to do?

MaxLeiter avatar Oct 24 '22 07:10 MaxLeiter

I am also having the same issue. In my case, when I move an item between lists I am removing and adding it in the database, which then reflects the state of the lists. As above my state reflect it correctly, hoverer Sortable duplicates the item in list.

Firstly it creates some ghost item with draggable="false" and then my proper item is added which makes two same items at the end.

sjkey avatar Dec 27 '22 05:12 sjkey

@jakubserwin any API suggestions on how you would like to interact with the lib would be welcome. I use it in a stateless context so I don't need to keep it in-sync besides on load.

MaxLeiter avatar Dec 29 '22 18:12 MaxLeiter

@MaxLeiter - I wonder if supporting this fork of sortable would enable folks to manage state better? https://github.com/SortableJS/Sortable/pull/2195

I have been looking into this as I am experiencing another issue where the DOM and state get out of sync (when adding an item to the list after drag and drop it is added in the wrong place).

I tried to create a fork of your library but got stuck. I would be happy to work through this with you if you think it is a viable option for the lib.

nlhkabu avatar Dec 31 '22 09:12 nlhkabu

I have the same problem. When I use the method to manage the data in pinia, it will affect the sorting of DOM. It is a similar problem to this issue https://github.com/vueuse/vueuse/issues/2924

Gahotx avatar Apr 05 '23 01:04 Gahotx

Anyone got a solution to this yet?

kburton-dev avatar May 12 '23 17:05 kburton-dev

Sorry @nlhkabu, I completely missed your offer. It's probably too late to be of help for you, but that fork seems like a great solution with the caveat of multidrag not working. I intend to look into it soon

MaxLeiter avatar May 31 '23 20:05 MaxLeiter

I'm personally just including the group (not the sortable group, but a key of mine, mostly group / index) in the remove and add methods, don't use the other ones.

<Sortable :list="itemsA"
                  group="same"
                  @add="(event) => handleAdd(event, 'a')"
                  @remove="(event) => handleRemove(event, 'a')"
                  @end="(event) => handleEnd(event, 'a')"
>
...
</Sortable>

<Sortable :list="itemsB"
                  group="same"
                  @add="(event) => handleAdd(event, 'b')"
                  @remove="(event) => handleRemove(event, 'b')"
                  @end="(event) => handleEnd(event, 'b')"
>
...
</Sortable>

Now when you move an item from list a to b -> handleAdd is called with "b" and handleRemove is called with "a".. Alternatively you can get the html item from event.item too (Check SortableEvent).. (Add a data attribute or something to match it easily) and update the store accordingly.. Not sure if there is an easier way @MaxLeiter?

P.S.: handleEnd is for sorting the list in this sample.

yvesh avatar Jun 05 '23 19:06 yvesh

I'm experiencing the same issue where I have two separate Sortable lists that I want to move items between using a shared group name. I load the state of the sortable lists from a pinia store and I detect that items have been moved using the @end event.

When I move an item between the two lists the item is removed from the previous parent but is then duplicated in the new parent list.

I managed to get around this by listening to the @end event, performing my pinia update, and then calling event.item.remove() which removes the shadow element in the new parent group.

async function moveItem(event: { newIndex: number | undefined, oldIndex: number | undefined, item: HTMLElement, to: HTMLElement, parentId: string }) {
  const op = {
    type: EditorOperationType.MOVE,
    fromIndex: event.oldIndex,
    toIndex: event.newIndex,
    fromParentId: parentItem.item.itemId,
    toParentId: destinationParentItem.item.itemId,
    item: draftItem.item,
    itemId: draftItem.item.itemId,
  } as MoveOperation
  myStore.addOperation(op)
  event.item.remove() <- here
}

<template>
   <Sortable
    :list="myStore.state.list"
    item-key="id"
    :options="{
      handle: '.grab-handle',
      group: {
        name: 'items',
      },
    }"
    class="flex flex-col gap-2"
    :data-id="itemId"
    @end="(event) => moveItem(event)"
  >
    <template #item="{ element: item }">
      <div :id="item.id class="flex items-center" :data-id="item.id>
        <div :key="item.id" class="draggable grab-handle">
          <div>
            <Icon name="mdi:drag" class="cursor-move size-5 text-gray-500" />
          </div>
        </div>
        <div class="flex-1">
             {{ item.id }}
        </div>
      </div>
    </template>
  </Sortable>
</template>

update Using event.item.remove() now seems to be causing issues when the underlying state changes from the pinia store and I can no longer retrieve the id of the removed object.

This is still an issue it seems.

update 2 Based on comments here: https://github.com/MaxLeiter/sortablejs-vue3/issues/57#issuecomment-1378155415 it seems that using a dynamic :key on the Sortable list resolves this issue.

garbit avatar May 07 '24 13:05 garbit

Further update to this - when using uuidv4() as the key the list is forced to re-render however this is causing any input fields within the nested list to lose focus when any field in the list is clicked on.

garbit avatar Jun 14 '24 10:06 garbit