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 rounded-md pi-4 pb-2" data-url-value="https://csszero.lazaronixon.com/sortables/10"> <div class="inline-flex handle"><span class="icon icon--grip-vertical"></span></div> <span>Item 1</span> </div> <div class="flex items-center gap border rounded-md pi-4 pb-2" data-url-value="https://csszero.lazaronixon.com/sortables/11"> <div class="inline-flex handle"><span class="icon icon--grip-vertical"></span></div> <span>Item 2</span> </div> <div class="flex items-center gap border rounded-md pi-4 pb-2" data-url-value="https://csszero.lazaronixon.com/sortables/12"> <div class="inline-flex handle"><span class="icon icon--grip-vertical"></span></div> <span>Item 3</span> </div> <div class="flex items-center gap border rounded-md pi-4 pb-2" data-url-value="https://csszero.lazaronixon.com/sortables/13"> <div class="inline-flex handle"><span class="icon icon--grip-vertical"></span></div> <span>Item 4</span> </div> <div class="flex items-center gap border rounded-md pi-4 pb-2" data-url-value="https://csszero.lazaronixon.com/sortables/14"> <div class="inline-flex handle"><span class="icon icon--grip-vertical"></span></div> <span>Item 5</span> </div> <div class="flex items-center gap border rounded-md pi-4 pb-2" data-url-value="https://csszero.lazaronixon.com/sortables/15"> <div class="inline-flex handle"><span class="icon icon--grip-vertical"></span></div> <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 rounded-md pi-4 pb-2", data: { url_value: main_app.sortable_url(10) } do %> <div class="inline-flex handle"><span class="icon icon--grip-vertical"></span></div> <span>Item 1</span> <% end %> <%= tag.div class: "flex items-center gap border rounded-md pi-4 pb-2", data: { url_value: main_app.sortable_url(11) } do %> <div class="inline-flex handle"><span class="icon icon--grip-vertical"></span></div> <span>Item 2</span> <% end %> <%= tag.div class: "flex items-center gap border rounded-md pi-4 pb-2", data: { url_value: main_app.sortable_url(12) } do %> <div class="inline-flex handle"><span class="icon icon--grip-vertical"></span></div> <span>Item 3</span> <% end %> <%= tag.div class: "flex items-center gap border rounded-md pi-4 pb-2", data: { url_value: main_app.sortable_url(13) } do %> <div class="inline-flex handle"><span class="icon icon--grip-vertical"></span></div> <span>Item 4</span> <% end %> <%= tag.div class: "flex items-center gap border rounded-md pi-4 pb-2", data: { url_value: main_app.sortable_url(14) } do %> <div class="inline-flex handle"><span class="icon icon--grip-vertical"></span></div> <span>Item 5</span> <% end %> <%= tag.div class: "flex items-center gap border rounded-md pi-4 pb-2", data: { url_value: main_app.sortable_url(15) } do %> <div class="inline-flex handle"><span class="icon icon--grip-vertical"></span></div> <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://esm.sh/@rails/request.js@0.0.12?standalone"import Sortable from "https://esm.sh/sortablejs@1.15.6?standalone"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
.