I'm trying to implement the "Drag and Drop Sortable Lists" (https://gorails.com/episodes/rails-drag-and-drop-sortable) with nested_attributes.
Unfortunately, I'm getting the wrong :position. It jumps from 2 to 5 to 7, even when there should be only three positions in total.
I believe the problem is that hidden input tags are being counted as well. If this is the main problem, any way around this?
// config/routes.rb
resources :recipes, except: :index do
resources :steps do
resource :step_position, only: :update
end
end
// app/views/recipes/_form.html.erb
<div data-controller="sortable" data-sortable-url="/recipes/:recipe_id/steps/:id/step_position">
<%= f.fields_for :steps, @recipe.steps.order(position: :asc) do |step| %>
<div data-id="<%= step.object.id %>">
<%= step.text_area :description %>
</div>
<% end %>
</div>
// app/javascript/controllers/sortable_controller.js
import { Controller } from "stimulus";
import Rails from "@rails/ujs";
import { Sortable } from "sortablejs";
export default class extends Controller {
connect() {
this.sortable = Sortable.create(this.element, {
onEnd: this.end.bind(this),
});
}
end(event) {
let id = event.item.dataset.id;
let data = new FormData();
data.append("position", event.newIndex + 1);
Rails.ajax({
url: this.data.get("url").replace(":id", id),
type: "PATCH",
data: data,
});
}
}
// app/controllers/step_positions_controller.rb
class StepPositionsController < ApplicationController
def update
@step = Step.find(params[:step_id])
authorize @step.recipe
@step.insert_at(params[:position].to_i)
head :ok
end
end
Firstly take a look at the parameters being passed to the server. Do these look ok? If not, then the javascript might be to blame.
Secondly,
insert_atisn't the best tool for the job in this case. If you want to explicitly set the position explicitly without interference from the callbacks etc... I usually usewhere(:id => id).update_all(:position => the_position)on each element in the list (provided you know you have all of them in your params.The best solution is to have the javascript send you the item being moved and the position in the list that you want it to be, then just set the position column to that value and
acts_as_listwill shuffle things out of the way for you. The only downside to this is concurrent updates from other users. Your list will get out of sync if this is the case.