docs/super-scaffolding/sortable
Super Scaffolding with the --sortable
option
When issuing a bin/super-scaffold crud
command, you can pass the --sortable
option like this:
# E.g. Pages belong to a Site and are sortable via drag-and-drop: bin/super-scaffold crud Page Site,Team name:text_field path:text_area --sortable
The --sortable
option:
- Wraps the table’s body in a
sortable
Stimulus controller, providing drag-and-drop re-ordering; - Adds a
reorder
action to your resource viainclude SortableActions
, triggered automatically on re-order; - Adds a
sort_order
attribute to your model to store the ordering; - Adds a
default_scope
which orders bysort_order
and auto incrementssort_order
on create viainclude Sortable
on the model.
Disabling Saving on Re-order
By default, a call to save the new sort_order
is triggered automatically on re-order.
To disable auto-saving
Add the data-sortable-save-on-reorder-value="false"
param on the sortable
root element:
To manually fire the save action via a button
Since the button won’t be part of the sortable
root element’s descendants (all its direct descendants are sortable by default), you’ll need to wrap both the sortable
element and the save button in a new Stimulus controlled ancestor element.
/* sortable-wrapper_controller.js */ import { Controller } from "@hotwired/stimulus" export default class extends Controller { static targets = [ "sortable" ] saveSortOrder() { if (!this.hasSortableTarget) { return } this.sortableTarget.dispatchEvent(new CustomEvent("save-sort-order")) } }
On the button, add a data-action
Save Sort Order
And on the sortable
element, catch the save-sort-order
event and define it as the sortable
target for the sortable-wrapper
controller:
Events
Under the hood, the sortable
Stimulus controller uses the dragula library.
All of the events that dragula
defines are re-dispatched as native DOM events. The native DOM event name is prefixed with sortable:
dragula event name | DOM event name |
---|---|
drag | sortable:drag |
dragend | sortable:dragend |
drop | sortable:drop |
cancel | sortable:cancel |
remove | sortable:remove |
shadow | sortable:shadow |
over | sortable:over |
out | sortable:out |
cloned | sortable:cloned |
The original event’s listener arguments are passed to the native DOM event as a simple numbered Array under event.detail.args
. See dragula’s list of events for the listener arguments.
Example: Asking for Confirmation on the drop
Event
Let’s say we’d like to ask the user to confirm before saving the new sort order:
> Are you sure you want to place DROPPED ITEM before SIBLING ITEM?
/* confirm-reorder_controller.js */ import { Controller } from "@hotwired/stimulus" export default class extends Controller { static targets = [ "sortable" ] requestConfirmation(event) { const [el, target, source, sibling] = event.detail?.args // sibling will be undefined if dropped in last position, taking a shortcut here const areYouSure = `Are you sure you want to place ${el.dataset.name} before ${sibling.dataset.name}?` // let's suppose each in sortable has a data-name attribute if (confirm(areYouSure)) { this.sortableTarget.dispatchEvent(new CustomEvent('save-sort-order')) } else { this.revertToOriginalOrder() } } prepareForRevertOnCancel(event) { // we're assuming we can swap out the HTML safely this.originalSortableHTML = this.sortableTarget.innerHTML } revertToOriginalOrder() { if (this.originalSortableHTML === undefined) { return } this.sortableTarget.innerHTML = this.originalSortableHTML this.originalSortableHTML = undefined } }
And on the sortable
element, catch the sortable:drop
, sortable:drag
(for catching when dragging starts) and save-sort-order
events. Also define it as the sortable
target for the confirm-reorder
controller: