SortableJS / Vue.Draggable

Vue drag-and-drop component based on Sortable.js
https://sortablejs.github.io/Vue.Draggable/
MIT License
20.12k stars 2.9k forks source link

Revert original positions if AJAX request fails #347

Closed Kocal closed 6 years ago

Kocal commented 6 years ago

Related to https://github.com/SortableJS/Vue.Draggable/issues/45#issuecomment-313503906

Hey, I would like to know how is it possible to cancel a drop and so revert to original positions, if my AJAX request fails.

I know that I can hook onMoveCallback and return false to revert to original positions, but AJAX request works asynchronously, not synchronously...

So I found an example with Sortable only (https://github.com/RubaXa/Sortable/issues/266) which use onUpdate event, but I don't know how to reproduce it with Vue.Draggable.

Here is my code:

<template>
  <div>
    <draggable
      v-model="items"
      :options="options" 
      @start="onDraggableStart" 
      @update="onDraggableUpdate"
    >
      <div v-for="item in items">{{ item.name }}</div>
    </draggable>
  </div>
</template>

<script>
export default {
  data() {
    return {
      options: {
        disabled: false,
      }
    }
  },
  methods: {
    onDraggableStart() {
      // will not work because I don't have access to Sortable instance
      this.currentOrder = this.toArray();
    },
    onDraggableUpdate() {
      this.options.disabled = true;

      axios
        .post(...)
        .then(() => ())
        .catch((error) => {
           // Revert order
          this.sort(this.currentOrder); // will not work too
        })
        .finally(() => this.options.disabled = false);
    },
  }
}
</script>

Maybe I'm missing something... Thanks!

Kocal commented 6 years ago

Well... When I was writing my issue, I thought about a dirty solution, but it's working as expected..

I need to specify a ref to <draggable>, and now I can access private Sortable instance and make my things like that:

<template>
  <div>
    <draggable
      ref="draggable"
      v-model="items"
      :options="options" 
      @start="onDraggableStart" 
      @update="onDraggableUpdate"
    >
      <div v-for="item in items">{{ item.name }}</div>
    </draggable>
  </div>
</template>

<script>
export default {
  data() {
    return {
      options: {
        disabled: false,
      }
    }
  },
  methods: {
    onDraggableStart() {
      this.currentOrder = this.$refs.draggable._sortable.toArray();
    },
    onDraggableUpdate() {
      this.options.disabled = true;

      axios
        .post(...)
        .then(() => ())
        .catch((error) => {
           // Revert order
          this.$refs.draggable._sortable.sort(this.draggableCurrentOrder);
        })
        .finally(() => this.options.disabled = false);
    },
  }
}
</script>

I think you can close this issue, and put label « question » I guess.

devpake commented 6 years ago

I was looking for a built in solution for reverting the order just like you. If I assume that your draggable list is defined in your Vue instance's data (don't see your list defined in code), then the below is a simple solution to revert:

onDraggableUpdate(event) {
    ...
    .catch(error => {
        let moved = this.list.splice(event.newIndex, 1);
        this.list.splice(event.oldIndex, 0, moved);
    })
    .finally( ...);
    ...
}
David-Desmaisons commented 6 years ago

Hello, this is much more a question for stackoverflow than an issue for this repo. Vue.draggable kepts drag-and-drop operation in sync with an array synchroneously. That is its scope. If you have to propagate this changes asynchroneously with an ajax call, and potencially revert them as the operation failed this is a different topic with no built-in solution provided. That said vue.draggable provides tools that can be used to build such a behavior. For example you may: -have two arrays original having the same elements (data from server) -use on array in a vue.draggable component -listen to the change of this array and perform ajax call when they happens -in case of success => copy the session array into the server array -in case of failure => copy the server array into the session array -use disabled option to avoid dnd during ajax call

It all depends on how u want to build user interaction and how your data and API are build.s Check also issue #390

rolandjlevy commented 5 years ago

I was looking for a built in solution for reverting the order just like you. If I assume that your draggable list is defined in your Vue instance's data (don't see your list defined in code), then the below is a simple solution to revert:

onDraggableUpdate(event) {
    ...
    .catch(error => {
        let moved = this.list.splice(event.newIndex, 1);
        this.list.splice(event.oldIndex, 0, moved);
    })
    .finally( ...);
    ...
}

Hi @davidvleung

Thanks, the solution you provided was just what I was looking for. However, to make it work I needed to reference the first element in the variable 'moved' because it is an array from the splice method.

So I changed this line

this.list.splice(event.oldIndex, 0, moved);

to this

this.list.splice(event.oldIndex, 0, moved[0]);

serdarcevher commented 4 years ago

2020 update: In addition to @rolandjlevy's modifications, I also needed to modify the "event.newIndex" part to "event.moved.newIndex" (and the same for the oldIndex). So the code became:


onDraggableUpdate(event) {
    ...
    .catch(error => {
       let moved = this.list.splice(event.moved.newIndex, 1);
       this.list.splice(event.moved.oldIndex, 0, moved[0]);
    })
    .finally( ...);
    ...
}
didotsonev commented 3 months ago

For anyone landing on this issue: Much easier solution is to store the initial order of all cards in all columns on card movement start and if something fails to restore the order.

<div v-for="(column, columnIndex) in columns" :key="columnIndex">
    <h2>{{ column.name }}</h2>
    <draggable
        :list="column.cards"
        group="cards"
        @start="saveInitialOrder"
        @change="move"
    >
        <template #item="{ element }">
            <div >{{ element.name }}</div>
        </template>
    </draggable>
</div>

// data:
columns: [
    { name: 'Column 1', cards: [{ id: 1, name: 'Card 1.1' }, { id: 2, name: 'Card 1.2' }] },
    { name: 'Column 2', cards: [{ id: 3, name: 'Card 2.1' }, { id: 4, name: 'Card 2.2' }] },
    { name: 'Column 3', cards: [{ id: 5, name: 'Card 3.1' }, { id: 6, name: 'Card 3.2' }] }
],
initialColumns: []

// methods
saveInitialOrder() {
    // Save the initial order of columns and cards
    this.initialColumns = JSON.parse(JSON.stringify(this.columns));
},
move() {
    // storing

    if (..storing failed..) {
        this.restoreInitialOrder();
    }
},
restoreInitialOrder() {
    // Restore the initial order of columns and cards
    this.columns = JSON.parse(JSON.stringify(this.initialColumns));
}