x
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div class="flex items-start gap" style="max-inline-size: 800px"> <div class="card flex flex-col gap i-full" data-controller="sortable" data-parent-id="1" data-sortable-group-value="shared"> <div class="border p-3" data-url-value="https://csszero.lazaronixon.com/sortables/10">Item 1</div> <div class="border p-3" data-url-value="https://csszero.lazaronixon.com/sortables/11">Item 2</div> <div class="border p-3" data-url-value="https://csszero.lazaronixon.com/sortables/12">Item 3</div> <div class="border p-3" data-url-value="https://csszero.lazaronixon.com/sortables/13">Item 4</div> <div class="border p-3" data-url-value="https://csszero.lazaronixon.com/sortables/14">Item 5</div> <div class="border p-3" data-url-value="https://csszero.lazaronixon.com/sortables/15">Item 6</div> </div> <div class="card flex flex-col gap i-full" data-controller="sortable" data-parent-id="2" data-sortable-group-value="shared"> <div class="border p-3" data-url-value="https://csszero.lazaronixon.com/sortables/20">Item 1</div> <div class="border p-3" data-url-value="https://csszero.lazaronixon.com/sortables/21">Item 2</div> <div class="border p-3" data-url-value="https://csszero.lazaronixon.com/sortables/22">Item 3</div> <div class="border p-3" data-url-value="https://csszero.lazaronixon.com/sortables/23">Item 4</div> <div class="border p-3" data-url-value="https://csszero.lazaronixon.com/sortables/24">Item 5</div> <div class="border p-3" data-url-value="https://csszero.lazaronixon.com/sortables/25">Item 6</div> </div></div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div class="flex items-start gap" style="max-inline-size: 800px"> <div class="card flex flex-col gap i-full" data-controller="sortable" data-parent-id="1" data-sortable-group-value="shared"> <%= tag.div "Item 1", class: "border p-3", data: { url_value: main_app.sortable_url(10) } %> <%= tag.div "Item 2", class: "border p-3", data: { url_value: main_app.sortable_url(11) } %> <%= tag.div "Item 3", class: "border p-3", data: { url_value: main_app.sortable_url(12) } %> <%= tag.div "Item 4", class: "border p-3", data: { url_value: main_app.sortable_url(13) } %> <%= tag.div "Item 5", class: "border p-3", data: { url_value: main_app.sortable_url(14) } %> <%= tag.div "Item 6", class: "border p-3", data: { url_value: main_app.sortable_url(15) } %> </div> <div class="card flex flex-col gap i-full" data-controller="sortable" data-parent-id="2" data-sortable-group-value="shared"> <%= tag.div "Item 1", class: "border p-3", data: { url_value: main_app.sortable_url(20) } %> <%= tag.div "Item 2", class: "border p-3", data: { url_value: main_app.sortable_url(21) } %> <%= tag.div "Item 3", class: "border p-3", data: { url_value: main_app.sortable_url(22) } %> <%= tag.div "Item 4", class: "border p-3", data: { url_value: main_app.sortable_url(23) } %> <%= tag.div "Item 5", class: "border p-3", data: { url_value: main_app.sortable_url(24) } %> <%= tag.div "Item 6", class: "border p-3", data: { url_value: main_app.sortable_url(25) } %> </div></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
.