model / draggable
Javascript utility to enable drag&drop ordering between DOM elements
Requires
- model/assets: ^0.4.0
This package is auto-updated.
Last update: 2026-08-26 14:58:26 UTC
README
Javascript utility to enable drag&drop ordering between DOM elements.
This is the ModEl 4 port of the legacy DraggableOrder module. It ships the CSS/JS as an asset
library declared through an AssetsProvider, so model/assets discovers and serves them
automatically — no manual file inclusion needed.
Unlike the v3 module, it has no hard dependency on the legacy FrontEnd module: the helpers it used
to borrow from it (getMouseCoords, getElementCoords, getMouseCoordsInElement, addClass,
removeClass) are now implemented internally or replaced by their native equivalents. onHtmlChange
is still used to re-scan dynamically loaded HTML, but only if FrontEnd happens to be loaded.
Installation
composer require model/draggable
Enabling the assets
The library is registered under the name draggable but is not auto-enabled. Enable it
(typically from a controller's init) wherever a page uses it:
use Model\Assets\Assets; Assets::enable('draggable');
This adds style.css (in the head) and js.js (in the foot) to the asset render list.
Usage
Mark the container with data-draggable-cont (its value is the name you will use to read the order
back), and its direct children become reorderable by dragging:
<div data-draggable-cont="rows"> <div data-draggable-id="1" data-draggable-name="first">First</div> <div data-draggable-id="2" data-draggable-name="second">Second</div> <div data-draggable-id="3" data-draggable-name="third">Third</div> </div>
Only the direct children of the container take part. The container is scanned at
DOMContentLoaded and, if the legacy FrontEnd module is present, on every onHtmlChange — so
rows loaded later via ajax become draggable too. Already processed elements are flagged with
data-draggable-set and skipped. Call checkDraggables() yourself if you inject markup without
FrontEnd.
Note that the container gets position: relative and the dragged element position: absolute, so
the container must be able to host an absolutely positioned child.
Attributes
On the container:
- data-draggable-cont: marks the container; its value is the name used by
getDraggableList. - data-draggable-callback: the body of a JavaScript function run after a successful drop, with
thisbound to the dragged element and two arguments,elementandtarget(see below).
On the children:
- data-draggable-id: an arbitrary identifier handed to the callback. Typically the database id of the row.
- data-draggable-name: an arbitrary name, returned by
getDraggableListin place of the index. - data-draggable-index: the ordering index. Assigned automatically if absent — elements that already carry one keep it, and the ones without are numbered after the highest already in use.
- data-draggable-ignore: excludes the element from the ordering entirely; it stays where it is
and is never a drop target. The attribute may be bare (
data-draggable-ignore) or given any value. - data-draggable-parent: restricts the element to reordering within its own group. Elements
with different
data-draggable-parentvalues are never dragged past one another, so a single container can hold several independent orderings (children of different parents in a tree, for example). Elements without the attribute all belong to the same default group. - data-draggable-grip: put this on a descendant of a child to make that descendant the only handle the drag can start from. Without it the whole child is draggable.
The callback
<div data-draggable-cont="rows" data-draggable-callback=" ajax('reorder', {}, {id: element.id, after: target.id}); ">
element describes the dragged element and target the one it was dropped onto, each as
{id, idx} — id being data-draggable-id and idx the data-draggable-index as it was
before the drop. Inside the body, this is the dragged DOM element.
The callback does not run when the element is dropped back where it started; that is treated as a plain click instead, and the click is forwarded to whatever was originally under the cursor — so rows that are both clickable and draggable keep working.
Reading the order back
getDraggableList('rows'); // ['second', 'first', 'third']
Returns the current order of the container as an array of data-draggable-name values, falling
back to data-draggable-index for elements without a name and to null for those with neither
(including the ignored ones, which keep their slot in the array). Returns an empty array if no
container by that name exists.