I've been searching for a day and a half for a solution.
I've got several tables (Categories) that allow the user to drag and drop each row (Item) to reorder the position of each item on that table (Category). I'm using SortableJS to handle the drag and drop, and I have it so that reordering within the same Category works.
On my backend I've got a Rails API that uses Jbuilder to return JSON, and am using the acts_as_list gem to handle the positions for me.
I'm having issues figuring out how to handle the reorder when I drag an Item from Category A to Category B. I believe the issue lies in the controller action and my inability to come up with a solution to receive updates for multiple categories, and then return the updated category's positions with Jbuilder. Some help would be greatly appreciated!
ItemsList.vue
<script>
methods: {
async dragReorder({ item, to, from, oldIndex, newIndex, oldDraggableIndex, newDraggableIndex, clone, pullMode }) {
// item that was dragged
const draggedItem = JSON.parse(item.getAttribute('data-item'));
// initial payload
const payload = {
category_id: draggedItem.category_id,
category_item_id: draggedItem.pivot.id,
item_id: draggedItem.id,
new_index: newIndex + 1,
user_id: this.user.id,
};
const newCategory = JSON.parse(to.getAttribute('data-category'));
// if item is dragged to new category
if (pullMode) payload.new_category_id = newCategory;
await categoryService.updatePosition(payload);
},
},
mounted() {
this.$nextTick(() => {
const tables = document.querySelectorAll('.items-table-container');
tables.forEach(table => {
const el = table.getElementsByTagName('tbody')[0];
Sortable.create(el, {
animation: 150,
direction: 'vertical',
easing: 'cubic-bezier(.17,.67,.83,.67)',
group: 'items-table-container',
onEnd: this.dragReorder,
});
});
})
},
</script>
category.rb
class Category < ApplicationRecord
has_many :categories_items, -> { order(position: :asc) }
has_many :items, through: :categories_items, source: :item
accepts_nested_attributes_for :items
end
categories_item.rb
class CategoriesItem < ApplicationRecord
belongs_to :category
belongs_to :item
acts_as_list scope: :category
end
item.rb
class Item < ApplicationRecord
has_many :categories_items
has_many :categories, through: :categories_items, source: :category
end
categories_controller.rb
def update_position
item = CategoriesItem.find_by(category: params[:category_id], item: params[:item_id])
# if item was moved to new category
categories = []
if params[:new_category_id]
item.update(category_id: params[:new_category_id])
Item.find(params[:item_id]).update(category_id: params[:new_category_id])
item.insert_at(params[:new_index]) unless !item
categories << Category.find(params[:category_id])
categories << Category.find(params[:new_category_id])
else
item.insert_at(params[:new_index]) unless !item
categories << Category.find(params[:category_id])
end
@categories = categories
end
update_position.json.jbuilder
json.array! @categories do |category|
json.(category, :id, :name, :created_at, :updated_at)
json.categories_items category.categories_items do |category_item|
json.(category_item, :id, :category_id, :item_id, :created_at, :updated_at, :position)
end
end
Got something working finally, here's my working code:
def update_position
@categories = []
## if item moved to new scope ##
if params[:new_category_id]
@category_item.update(category_id: params[:new_category_id])
@category_item.insert_at(params[:new_index])
@categories << Category.find(params[:new_category_id])
@categories << Category.find(params[:category_id])
## else if item was moved within same scope ##
else
@category_item.insert_at(params[:new_index])
@categories << Category.find(params[:category_id])
end
@categories
render :index
end