Search code examples
javascriptvue.jsfrontenddrag-and-dropvuedraggable

Vue Draggable Elements Revert Back to Original Position After Drag and Drop


I'm using vuedraggable in a Vue 3 project to allow reordering of elements within an expansion panel. The elements can be dragged and dropped to a new position within the panel, but they revert back to their original positions when released. I suspect that the underlying list used by vuedraggable isn't being updated when the elements are moved, which causes them to reset to their original positions. Here is the relevant part of my code:

<v-expansion-panels>
  <v-expansion-panel v-for="feature in groupedFeatures" :key="feature.type">
    <v-expansion-panel-title>
      <div>{{ translateFeatureType(feature.type) }}</div>
    </v-expansion-panel-title>
    <v-expansion-panel-text>
      <draggable v-model="feature.details" group="featureGroup" item-key="model_name">
        <template #item="{element, index}">
          <div :key="element.model_name">
            <p><strong>Model:</strong> {{ element.model_name }}</p>
            <p>{{ element.value }}</p>
          </div>
        </template>
      </draggable>
    </v-expansion-panel-text>
  </v-expansion-panel>
</v-expansion-panels>

My imports:

import { ref, onMounted, computed } from 'vue';
import axios from 'axios';
import { useRoute } from 'vue-router';
import draggable from 'vuedraggable';

const route = useRoute();
const features = ref([]);
const messages = ref([]);

The onMounted Function:

onMounted(async () => {
    const threadData = await fetchEmailThreads(route.params.id);
  if (threadData) {
    features.value = threadData.features;
    messages.value = threadData.messages;
  }

And the groupedFeatures Function

const groupedFeatures = computed(() => {
  const featureMap = new Map();
  features.value.forEach(f => {
    if (!featureMap.has(f.type)) {
      featureMap.set(f.type, { type: f.type, details: [] });
    }
    featureMap.get(f.type).details.push({ model_name: f.model_name, value: f.value });
  });
  return Array.from(featureMap.values());
});

This is what the data provided by the server looks like:

{
  "chat_id": 1,
  "features": [
    {
      "model_name": "Vicuna-13B",
      "type": "abstract_feature",
      "value": "---Feature Content---"
    },
    {
      "model_name": "Vicuna-13B",
      "type": "generated_feature",
      "value": "---Feature Content---"
    },
    {
      "model_name": "Vicuna-13B",
      "type": "generated_feature",
      "value": "---Feature Content---"
    },
    {
      "model_name": "Vicuna-13B",
      "type": "order_feature",
      "value": "---Feature Content---"
    },
    {
      "model_name": "Solaris-13B",
      "type": "abstract_feature",
      "value": "---Feature Content---"
    },
    {
      "model_name": "Solaris-13B",
      "type": "generated_feature",
      "value": "---Feature Content---"
    },
    {
      "model_name": "Solaris-13B",
      "type": "generated_feature",
      "value": "---Feature Content---"
    },
    {
      "model_name": "Solaris-13B",
      "type": "order_feature",
      "value": "---Feature Content---"
    }
  ],
  "inst_id": 0,
  "messages": [
    {
      "content": "Message Content...",
      "message_id": 1,
      "sender": "sender1",
      "timestamp": "2022-11-08T14:15:00"
    },
    {
      "content": "Message Content...",
      "message_id": 2,
      "sender": "sender2",
      "timestamp": "2022-11-09T15:45:00"
    }
  ],
  "subject": "Topic"
}

What changes do I need to make so that the elements in the actual list get updated accordingly, and the elements stay in their new positions after being moved?


Solution

  • Your groupedFeatures is not a vue ref, but a computed value. You cannot update it with a callback from draggable.

    Instead of computing it based on value of the features variable I suggest to just calculate it once within the onMounted callback, where you set your features.

    const groupedFeatures = ref([]);
    
    onMounted(async() => {
      const threadData = await fetchEmailThreads(route.params.id);
      if (!threadData) return;
    
      const featureMap = new Map();
      features.value.forEach(f => {
        if (!featureMap.has(f.type)) {
          featureMap.set(f.type, {
            type: f.type,
            details: []
          });
        }
        featureMap.get(f.type).details.push({
          model_name: f.model_name,
          value: f.value
        });
      });
      groupedFeatures.value = Array.from(featureMap.values());
    });
    

    This should fix your issue.