SortableJS / Vue.Draggable multi-drag option not working
Asked Answered
I

4

5

Recently I have discovered that the SortableJS / Vue.Draggable library (https://github.com/SortableJS/Vue.Draggable) has a new option where multi-drag can be enabled to select multiple elements from an array and move them together (https://github.com/SortableJS/Vue.Draggable/pull/744).

I have seen samples where it works perfectly, for example:

But when I have tried to use it in my project I just can find the way to make it work.

Here are the details of my project:

  • Vue: 2.6.10
  • Vuedraggable: 2.23.2

In my vue component I have imported vuedraggable this way:

import draggable from 'vuedraggable'

and I have applied this way (code has been reduced for this post purpose):

<template>
  <v-flex class="pa-3">
    <div class="instructions-wrapper py-4">
      <v-avatar size="40" color="#4C2159" class="white--text"><b>4</b></v-avatar>
      <div class="px-2">
        <h2>Revisa y asigna</h2>
        <p>Revisa la optimización del sistema y asigna o personaliza una ruta</p>
      </div>
    </div>

    <template v-for="(vehicle, key) in sortedRoutes.routes">
      <v-card class="my-4" :key="vehicle.stops.location_id">
        <v-toolbar color="primary" dark>
          <v-toolbar-title>{{ Object.keys(sortedRoutes.routes).find(key => sortedRoutes.routes[key] === vehicle) }}</v-toolbar-title>
        </v-toolbar>

        <draggable
          :key="vehicle.stops.location_id"
          :list="vehicle.stops"
          :id="key"
          group="vehicle"
          animation="150"
          :multi-drag="true"
          selected-class="multi-drag"
          ghost-class="ghost"
          :move="moveChecker"
          @start="dragStart"
          @end="dragEnd"
        >
          <div v-for="(delivery, index) in vehicle.stops" :key="delivery.id" class="draggable-element">
            <v-list v-if="delivery.location_name !== 'CEDIS'" :key="delivery.title">
              <v-list-tile>
                <v-icon>drag_indicator</v-icon>
                <v-list-tile-avatar>
                  <img :src="`https://ui-avatars.com/api/?color=fff&background=4C2159&size=128&name=${index}`">
                </v-list-tile-avatar>

                <v-list-tile-content>
                  <div>{{delivery.location_name}} {{deliveries.find(key => key.location.company_name === delivery.location_name).start_time_window ? `(${deliveries.find(key => key.location.company_name === delivery.location_name).start_time_window} - ${deliveries.find(key => key.location.company_name === delivery.location_name).end_time_window})` : ''}}
                  </div>
                </v-list-tile-content>
              </v-list-tile>
            </v-list>
          </div>
        </draggable>
      </v-card>
    </template>
  </v-flex>
</template>

I have paid attention to add the select-class attribute that is required for use the multi-drag option by SortableJS / Vue.Draggable documentation.

The object that is being printed as the draggable list is under this JSON structure:

{
  "routes": {
    "vehicle_1": {
      "stops": [
        {
          "stop_id": 0,
          "order_id": 1,
          "location_id": "DEPOT",
          "location_name": "Centro de distribución",
          "lat": -100,
          "lng": 100,
          "arrival_time": "08:00",
          "departure_time": "16:00"
        },
        {
          "stop_id": 1,
          "order_id": 2,
          "location_id": "order_2",
          "location_name": "Foo Zaas",
          "lat": -100,
          "lng": 100,
          "arrival_time": "10:00",
          "departure_time": "10:15"
        }
      ],
      "cost_matrix": [
        [
          {
            "distance_in_meters": 10,
            "travel_time_in_minutes": 10
          },
          {
            "distance_in_meters": 100,
            "travel_time_in_minutes": 100
          }
        ],
        [
          {
            "distance_in_meters": 10,
            "travel_time_in_minutes": 10
          },
          {
            "distance_in_meters": 100,
            "travel_time_in_minutes": 100
          }
        ]
      ],
      "summary": {
        "depot_name": "DEPOT",
        "demand_assigned": 234,
        "distance_in_meters": 3004,
        "travel_time_in_minutes": 157,
        "waiting_time_in_minutes": 70.1
      }
    }
  }
}

Despite all this efforts I can't make it work. I even got to replicate a slimmer version of the code basing me on one of codepens I found before and it works (https://codepen.io/Juan-Sin-Miedos/pen/jOWOyWW)

Why it isn't working on my project?

Any help would be appreciated, thank you very much!

Ison answered 3/6, 2020 at 3:40 Comment(0)
L
8

Because this pull request isn't merged and the latest release is very old, you need to install this version from git instead. This is what you should put into your package.json instead of the version number:

"vuedraggable": "git://github.com/divinespear/Vue.Draggable.git#multi-drag"

Lieselotteliestal answered 3/6, 2020 at 16:17 Comment(0)
R
3
"vuedraggable": "git://github.com/divinespear/Vue.Draggable.git#multi-drag"

Above repo has an issue in dist. Please use this one instead.

"vuedraggable": "git://github.com/midasdev711/Vue-DragDrop.git"
Remediable answered 19/6, 2021 at 6:34 Comment(0)
V
2

If you want to ensure you're always using the ;latest build, you can add the MultiDrag plugin after initialisation from the SortableJS library.

Add the vuedraggable dependency to your pack.json file in the usual way:

"vuedraggable": "^2.24.3"

At the beginning of your component, instead of a basic import such as:

<script>
    import draggable from 'vuedraggable';
    
    // rest of js component code

</script>

Do the following:

<script>
    import { Sortable, MultiDrag } from 'sortablejs';
    import draggable from 'vuedraggable';

    Sortable.mount(new MultiDrag());
    
    // rest of js component code

</script>
Volute answered 8/4, 2021 at 8:32 Comment(1)
I used your import suggestion to enable the MultiDrag plugin, and it works great! But now, how do I trigger the change event for all of the selected items? I was using the @change event and then parsing the "added/moved/removed" event. But when multiple items are selected, it is only triggered for one item.Impudence
C
0

I encountered the same issue and struggled to find a solution.

After trying a couple options I had the idea to simply create a list of elements to add and when an element is dragged to our target draggable, we could add this selection to the data of our draggable instead of the dropped element.

I did like so :

  • Create an object in which we will store the items we want to move (selectedItems) and listen for events on the draggable items such as click, ctrl+click, ect... Like so in my case :
const selectedItems = ref([]);

    const onClick = (item: DocumentPart) => {
        selectedItems.value = [item];
    };

    const onCtrlClick = (item: DocumentPart) => {
        //Manage selectedItems value
        if (selectedItems.value.indexOf(item) == -1) {
            selectedItems.value.push(item);
        } else {
            selectedItems.value = selectedItems.value.filter((element) => element !== item);
        }
    };
  • Then, we listen for changes in our target draggable. On change, if we have no selectedItems - do nothing, behaves as usual, but if we have some selection :
  • Remove the dragged element from the array containing data of the destination draggable (in my case "section.data") because it is also in our selection so without this step, this item will be in double in the target draggable.
  • Add instead all the selectedItems.
<div v-for="section in target" :key="section.id">
<draggable
    class="..."
    :list="section.data"
    @change="onMoveToTarget($event, section)"
    :group="..."
>
</div>
const onMoveToTarget = (
        event: { [x: string]: { element: DocumentPart } },
        section: { id: number; label: string; mandatory: boolean; data: DocumentPart[] },
    ) => {
        if (event.added) {
            if (selectedItems.value.length > 0) {
                const newValue = [...target.value];
                for (const element of newValue) {
                    if (element.id == section.id) {
                        //We start by removing the draged element already added on drop
                        const oldItem = section.data.find(
                            (element) =>
                                element.id== event.added.element.id
                        );
                        section.data = section.data.filter((element) => element !== oldItem);

                        //Add selectem items
                        selectedItems.value.forEach((item) => {
                            section.data.push({ ...item, id: generateUUID() });
                        });
                    }
                }
                //Reset selection
                selectedItems.value = [];
            }
        }
    };

It surely can be improved, for instance instead of removing the dropped element from the data AFTER it has been added when we push the slection, it would be better to prevent the addition BEFORE and simply add the selection. But ATM is does what was needed in my case.

Comfrey answered 31/8, 2023 at 13:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.