Search code examples
ruby-on-railsformssortablejsstimulusjs

Updating position of SortableJS + Rails Form


I've seen a lot of tutorials around using Rails + SortableJS and updating the position of an object via Ajax - however, I'm using SortableJS inside a Rails form with hidden fields that contain the "position" attribute.

How can I update all of the elements of a Sortable group after one has been moved?

// javascript/controllers/drag_controller.js

connect() {
  this.sortable = Sortable.create(this.element, {
    animation: 150,
    onEnd: this.end.bind(this)
  })
}

end(event) {
  // Update "position" field of each sortable object
  // event.newIndex works as the position of the newly moved item
}

Here is the nested form item:

// views/item/_form.html.erb

<%= content_tag :div, class: "nested-fields", data: { new_record: form.object.new_record? } do %>

  <div class="form-group">
    <%= form.label :name %>
    <%= form.text_field :name %>
  </div>
  <%= form.hidden_field :position %>

<% end %>

The form currently works perfectly, aside from the position field. I'm also using acts_as_list which automatically fills in the position on the back-end, but not for users who edit using the form.


Solution

  • In my experience, the gem act_as_list—which you are already using—does what you need: Automatically updating the records. For instance, in irb, using a Message model and destroying one record:

    irb(main):001:0> m = Message.find 11
      Message Load (0.8ms)  SELECT "messages".* FROM "messages" WHERE "messages"."id" = $1 LIMIT $2  [["id", 11], ["LIMIT", 1]]
    => #<Message id: 11, content: "e", created_at: "2021-11-30 14:30:37.896535000 +0000", updated_at: "2021-11-30 14:41:38.871285000 +0000", position: 8>
    irb(main):002:0> m.destroy
      TRANSACTION (0.4ms)  BEGIN
      Message Load (0.8ms)  SELECT "messages".* FROM "messages" WHERE "messages"."id" = $1 LIMIT $2  [["id", 11], ["LIMIT", 1]]
      Message Destroy (2.6ms)  DELETE FROM "messages" WHERE "messages"."id" = $1  [["id", 11]]
      Message Update All (3.0ms)  UPDATE "messages" SET "position" = ("messages"."position" - 1), "updated_at" = '2021-12-01 18:43:34.628679' WHERE (1 = 1) AND ("messages"."position" > 8)
      TRANSACTION (5.0ms)  COMMIT
    

    You can see the last action in the console is Update All, which will update the records by reducing the position attribute in 1.