3

In my project I've got a Rails API backend where I'm using the acts_as_list gem and Vue frontend where i'm using the Vue Draggable package.

The act of dragging works and I'm seeing a PUT request being sent to my server. There are a couple of oddities happening however:

Scenario 1: I drag an item from position 1 to position 2 (so they essentially flip flop). The PUT request is being sent, however an actual update is not occurring.

Scenario 2: I drag an item from position 1 to position 3. This means that position 2 should shift up to 1, 2 should shift up to 1, and 1 should be in 3 (hopefully that makes any sense). This one is odd because sometimes I see the update occurring on my server, but not every time.

What I need is for the entire list to be updated on drag.

TodoList.vue

<template>
    <div class="todos-container" v-if='trips.loaded'>
      <draggable 
        :list='todoList' 
        :options='{animation:150}'
        tag='ul' 
        class='list-group' 
        @change='changed(todoList, $event)'
      >
        <transition-group type='transition'>
          <li v-for='(todo, index) in todoList' :key='todo.id' class='list-group-item'>
            <v-icon class='drag-handle'>drag_handle</v-icon>
            <v-checkbox
              v-model="todoList[index].completed"
              :ripple='false'
              :label='`${todo.title}`'
              color='primary'
              @change='handleTodoClick(todo, index)'
            />
            <v-icon class='remove-todo' @click='handleTodoDelete(todo, index)'>close</v-icon>
          </li>

        </transition-group>
      </draggable>
    </div>
  </todo-list-styles>
</template>

<script>
  import { mapActions } from 'vuex';
  import draggable from 'vuedraggable';

  export default {
    props: {
      trips: {
        type    : Object,
      },
      index: {
        type    : Number,
        required: true,
      }
    },
    computed: {
      todoList() {
        return this.trips.data[this.index].todos;
      }
    },
    methods: {
      ...mapActions('trips', [
        'updateTodoPosition'
      ]),
      handleTodoClick: function(todo, index) {
        console.log('checked')
      },
      handleTodoDelete: function(todo, index) {
        console.log('clicked');
      },
      changed: function(todoList, $event) {
        const {oldIndex, newIndex} = $event.moved;
        const todo = todoList[newIndex];
        const payload = {
          oldIndex,
          newIndex,
          todo,
        };
        this.updateTodoPosition(payload);
      },
    },
    components: {
      draggable,
    },
  }
</script>

Params

Started PUT "/2/update_todo_position" for 127.0.0.1 at 2019-05-13 08:46:09 -0500
Processing by V1::TripsController#update_todo_position as */*
  Parameters: {"oldIndex"=>0, "newIndex"=>2, "todo"=>{"id"=>2, "title"=>"Book Car Rental", "completed"=>true, "position"=>2}, "todo_id"=>"2", "trip"=>{"oldIndex"=>0, "newIndex"=>2, "todo"=>{"id"=>2, "title"=>"Book Car Rental", "completed"=>true, "position"=>2}}}
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
  TodoItem Load (0.2ms)  SELECT  "todo_items".* FROM "todo_items" WHERE "todo_items"."id" = $1 LIMIT $2  [["id", 2], ["LIMIT", 1]]
   (0.2ms)  BEGIN
  CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
  UpcomingHike Load (0.3ms)  SELECT  "upcoming_hikes".* FROM "upcoming_hikes" WHERE "upcoming_hikes"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
   (0.2ms)  COMMIT
Completed 200 OK in 5ms (ActiveRecord: 1.1ms)

trips_controller.rb

module V1
  class TripsController < ApplicationController

    ...

    def update_todo_position
      # TODO: Figure out why position being saved is incorrect
      todo = TodoItem.find(params[:todo][:id])
      todo.update!(position: params[:newIndex])
      head :ok
    end

    ...

  end
end
J. Jackson
  • 3,326
  • 8
  • 34
  • 74

1 Answers1

0

Ended up going a route that I'm not proud of, but works. From my frontend I'm sending over the entire newly ordered list. Then in my controller, I'm rolling through each one and individually updating their position. Definitely not the VueDraggable recommended way of doing it, and absolutely not an efficient way of handling it in my controller:

vue component method:

changed: function(todoList, $event) {
        const {newIndex} = $event.moved;
        const todo = todoList[newIndex];
        const newListOrder = JSON.stringify(this.todoList.map(todo => todo.id));
        const payload = {
          todo,
          newListOrder,
        }
        this.updateTodoPosition(payload);
      },

controller:

def update_todo_position
      newListIds = params[:_json]
      newListIds.each.with_index(1) do |todo, index|
        todo = TodoItem.find(todo)
        todo.update(position: (index))
      end
      head :ok
    end

If anyone has any suggestions, I'd love to hear it!

J. Jackson
  • 3,326
  • 8
  • 34
  • 74