Как обрабатывать перетаскивание между несколькими списками с помощью гема SortablejS и act_as_list?

Полтора дня искал решение.

У меня есть несколько таблиц (категорий), которые позволяют пользователю перетаскивать каждую строку (элемент), чтобы изменить порядок положения каждого элемента в этой таблице (категория). Я использую SortableJS для обработки перетаскивания, и у меня это так, что изменение порядка внутри одного и того же Категория работ.

В моем бэкэнде есть Rails API, который использует Jbuilder для возврата JSON, и я использую гем acts_as_list обрабатывать позиции для меня.

У меня возникают проблемы с выяснением того, как обрабатывать изменение порядка, когда я перетаскиваю элемент из категории A в категорию B. Я считаю, что проблема заключается в действии контроллера и моей неспособности найти решение для получения обновлений для нескольких категорий, и затем верните позиции обновленной категории с помощью Jbuilder. Некоторая помощь будет принята с благодарностью!

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

person J. Jackson    schedule 31.03.2020    source источник


Ответы (2)


acts_as_list позволяет вам установить новый параметр области и положение элемента списка, затем просто сохраните элемент, и элемент будет автоматически перемещен из старой области в новую область в нужной вам позиции (с использованием обратных вызовов after_save).

В связи с этим вы должны иметь возможность просто сделать это, а затем заново получить элементы в каждой из двух ваших областей и вернуть их на внешний интерфейс для обновления вашего дисплея.

person Brendon Muir    schedule 01.04.2020
comment
Кажется, я понял это поздно ночью! Я разместил ответ здесь, не могли бы вы проверить его, чтобы увидеть, хорошее ли это рабочее решение? Кстати, мне нравится, что вы так активно отвечаете на вопросы здесь для вашего собственного камня. Облегчает жизнь таким простым людям, как я! - person J. Jackson; 01.04.2020
comment
Не за что :) Должен признаться, я всего лишь хранитель драгоценного камня. Другие люди сделали большую часть авторства драгоценного камня :) - person Brendon Muir; 02.04.2020

Наконец-то что-то работает, вот мой рабочий код:

    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
person J. Jackson    schedule 01.04.2020
comment
Это в основном правильно, однако вы можете просто включить позицию в предложение обновления: @category_item.update(category_id: params[:new_category_id], position: params[:new_index]). act_as_list предназначен для того, чтобы вы могли установить позицию и область действия, и все будет обработано. Что касается синхронизации списков во внешнем интерфейсе, вы можете просто полагаться на то, что обновление прошло успешно, и не выдвигать на передний план ничего, кроме заголовка :no_content. Зависит от вашего интерфейса и от того, беспокоитесь ли вы о параллельных обновлениях. - person Brendon Muir; 02.04.2020