x
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<div class="card flex flex-col gap" style="max-inline-size: 400px" data-controller="sortable" data-parent-id="1" data-sortable-handle-value=".handle"> <div class="flex items-center gap border p-3" data-url-value="https://csszero.lazaronixon.com/sortables/10"> <img class="colorize-black handle" src="/assets/move-c3727cc8.svg" width="16" height="16" /> <span>Item 1</span> </div> <div class="flex items-center gap border p-3" data-url-value="https://csszero.lazaronixon.com/sortables/11"> <img class="colorize-black handle" src="/assets/move-c3727cc8.svg" width="16" height="16" /> <span>Item 2</span> </div> <div class="flex items-center gap border p-3" data-url-value="https://csszero.lazaronixon.com/sortables/12"> <img class="colorize-black handle" src="/assets/move-c3727cc8.svg" width="16" height="16" /> <span>Item 3</span> </div> <div class="flex items-center gap border p-3" data-url-value="https://csszero.lazaronixon.com/sortables/13"> <img class="colorize-black handle" src="/assets/move-c3727cc8.svg" width="16" height="16" /> <span>Item 4</span> </div> <div class="flex items-center gap border p-3" data-url-value="https://csszero.lazaronixon.com/sortables/14"> <img class="colorize-black handler" src="/assets/move-c3727cc8.svg" width="16" height="16" /> <span>Item 5</span> </div> <div class="flex items-center gap border p-3" data-url-value="https://csszero.lazaronixon.com/sortables/15"> <img class="colorize-black handle" src="/assets/move-c3727cc8.svg" width="16" height="16" /> <span>Item 6</span> </div></div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<div class="card flex flex-col gap" style="max-inline-size: 400px" data-controller="sortable" data-parent-id="1" data-sortable-handle-value=".handle"> <%= tag.div class: "flex items-center gap border p-3", data: { url_value: main_app.sortable_url(10) } do %> <%= image_tag "move.svg", class: "colorize-black handle", size: 16 %> <span>Item 1</span> <% end %> <%= tag.div class: "flex items-center gap border p-3", data: { url_value: main_app.sortable_url(11) } do %> <%= image_tag "move.svg", class: "colorize-black handle", size: 16 %> <span>Item 2</span> <% end %> <%= tag.div class: "flex items-center gap border p-3", data: { url_value: main_app.sortable_url(12) } do %> <%= image_tag "move.svg", class: "colorize-black handle", size: 16 %> <span>Item 3</span> <% end %> <%= tag.div class: "flex items-center gap border p-3", data: { url_value: main_app.sortable_url(13) } do %> <%= image_tag "move.svg", class: "colorize-black handle", size: 16 %> <span>Item 4</span> <% end %> <%= tag.div class: "flex items-center gap border p-3", data: { url_value: main_app.sortable_url(14) } do %> <%= image_tag "move.svg", class: "colorize-black handler", size: 16 %> <span>Item 5</span> <% end %> <%= tag.div class: "flex items-center gap border p-3", data: { url_value: main_app.sortable_url(15) } do %> <%= image_tag "move.svg", class: "colorize-black handle", size: 16 %> <span>Item 6</span> <% end %></div>
CSS is not required or multiple files are needed, check the notes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { Controller } from "@hotwired/stimulus"import { put } from "https://cdn.skypack.dev/@rails/request.js@0.0.11?min"import Sortable from "https://cdn.skypack.dev/sortablejs?min"export default class extends Controller { static values = { url: String, group: String, handle: String } connect() { this.sortable = new Sortable(this.element, this.#options) } disconnect() { this.sortable.destroy() } #submit({ item, newIndex, to }) { put(item.dataset.urlValue, { query: { position: newIndex, parent_id: to.dataset.parentId } }) } get #options() { return { animation: 150, onAdd: this.#submit, onUpdate: this.#submit, group: this.groupValue, handle: this.handleValue } }}
Implementation
Each move creates a request like PUT requests_url?position=3&parent_id=1
.