Skip to content

Commit 8dc3976

Browse files
authored
Document inline drag-and-drop sorting for lists and relations (#260)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Related: wintercms/winter#1491, wintercms/storm#235, wintercms/winter#1472
1 parent cdd12e8 commit 8dc3976

4 files changed

Lines changed: 69 additions & 5 deletions

File tree

backend/lists.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,19 @@ Option | Description
7676
`showTotals` | displays the summed values for the columns in the form of `totalOnPage (totalForQuery)` in the list header and footer. Default: `true`.
7777
`treeExpanded` | if tree nodes should be expanded by default. Default: `false`.
7878
`customViewPath` | specify a custom view path to override partials used by the list, optional.
79+
`sortable` | enables drag-and-drop reordering of records directly in the list, see [reordering records](#reordering-records). Default: `false`.
80+
81+
### Reordering records
82+
83+
Set `sortable` to `true` to let backend users reorder the list with drag-and-drop. The list model must use the [`Sortable` trait](../database/traits#sortable) so it has a `sort_order` column.
84+
85+
```yaml
86+
sortable: true
87+
```
88+
89+
When enabled, a drag handle is shown on each row, column header sorting and pagination are disabled (every record is shown in its stored order), and dropping a row persists the new order to the model's sort order column via AJAX.
90+
91+
> **NOTE:** Reordering applies to flat lists. For reordering tree structures, or for a dedicated standalone reordering page, use the [Reorder behavior](reorder).
7992

8093
### Adding a toolbar
8194

backend/relations.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ Option | Type | Description
114114
`recordUrl` | List | link each list record to another page. Eg: **users/update/:id**. The `:id` part is replaced with the record identifier.
115115
`customViewPath` | List | specify a custom view path to override partials used by the list.
116116
`recordOnClick` | List | custom JavaScript code to execute when clicking on a record.
117+
`sortable` | List | enables drag-and-drop reordering of the related records, see [reordering relations](#reordering-relations). Requires the parent model to use the [`HasSortableRelations` trait](../database/traits#hassortablerelations). Default: `false`.
117118
`toolbarPartial` | Both | a reference to a controller partial file with the toolbar buttons. Eg: **_relation_toolbar.htm**. This option overrides the *toolbarButtons* option.
118119
`toolbarButtons` | Both | the set of buttons to display. This can be formatted as an array or a pipe separated string, or set to `false` to show no buttons. Available options are: `create`, `update`, `delete`, `add`, `remove`, `refresh`, `link`, & `unlink`. Example: `add\|remove`. <br/> Additionally, you can customize the text inside these buttons by setting this property to an associative array, with the key being the button type and the value being the text for that button. Example: `create: 'Assign User'`. The value also supports translation.
119120

@@ -289,6 +290,21 @@ phone:
289290
list: $/acme/user/models/phone/columns.yaml
290291
```
291292

293+
### Reordering relations
294+
295+
Pivot-based relations (`belongsToMany`, `morphToMany`, `morphedByMany`) can be reordered with drag-and-drop directly in the relation manager. The parent model must use the [`HasSortableRelations` trait](../database/traits#hassortablerelations) and declare the relation in its `$sortableRelations` property, and the pivot table must have a sort order column. Then set `sortable: true` on the relation's `view` configuration:
296+
297+
```yaml
298+
authors:
299+
label: Author
300+
view:
301+
list: $/acme/blog/models/author/columns.yaml
302+
toolbarButtons: link|unlink
303+
sortable: true
304+
```
305+
306+
A drag handle is shown on each related record; dropping persists the new order to the pivot's sort order column. Reordering also works while the parent record is being created, before it is saved — the order is stored against the [deferred binding](../database/relations#deferred-binding) and committed together with the record.
307+
292308
## Displaying a relation manager
293309

294310
Before relations can be managed on any page, the target model must first be initialized in the controller by calling the `initRelation` method.

backend/reorder.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
The **Reorder behavior** is a controller [behavior](../services/behaviors) that provides features for sorting and reordering database records. The behavior provides a page called Reorder using the controller action `reorder`. This page displays a list of records with a drag handle allowing them to be sorted and in some cases restructured.
66

7+
> **NOTE:** To let users reorder records inline with drag-and-drop without a dedicated page — directly in a [list](lists#reordering-records) or a [relation manager](relations#reordering-relations) — see those sections. The Reorder behavior documented here provides a dedicated standalone page, which is best suited to models with deep tree structures.
8+
79
The behavior depends on a [model class](../database/model) which must implement one of the following [model traits](../database/traits):
810

911
1. [`Winter\Storm\Database\Traits\Sortable`](../database/traits#sortable)

database/traits.md

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -838,16 +838,49 @@ class ApiData extends Model
838838

839839
## HasSortableRelations
840840

841-
Add this trait to your model in order to allow its relations to be sorted/reordered.
841+
Sorted relations store a sort order value in the pivot table of a `belongsToMany`, `morphToMany`, or `morphedByMany` relation, so the related records keep a custom order for each parent record. Apply the `Winter\Storm\Database\Traits\HasSortableRelations` trait and define a `$sortableRelations` property that maps each relation name to its pivot sort order column.
842842

843843
```php
844-
class MyModel extends model
844+
class Article extends \Winter\Storm\Database\Model
845845
{
846846
use \Winter\Storm\Database\Traits\HasSortableRelations;
847847

848848
/**
849-
* @var array Relations that can be sorted/reordered and the column name to use for sorting/reordering.
849+
* @var array Relations that can be reordered and the pivot column used for sorting.
850850
*/
851-
public $sortableRelations = ['relation_name' => 'sort_order_column'];
852-
...
851+
public $sortableRelations = ['authors' => 'sort_order'];
852+
853+
public $belongsToMany = [
854+
'authors' => [
855+
\Acme\Blog\Models\Author::class,
856+
'table' => 'acme_blog_articles_authors',
857+
],
858+
];
853859
}
860+
```
861+
862+
Ensure the pivot table has the sort order column, for example in a migration:
863+
864+
```php
865+
$table->integer('sort_order')->default(0);
866+
```
867+
868+
You must create the column yourself (as in the migration above) — the trait does not create it. When the model boots, the trait includes that column in the relation's pivot data, so its value is loaded onto each record's `pivot`, and applies an `order by {pivot_table}.{column} asc` clause so the relation is always returned in its stored order. The trait also assigns the next sort order value to newly attached records, appending them to the end of the relation.
869+
870+
Use the `setRelationOrder` method to reorder a relation programmatically. The second argument is the related record ids in their new order; the optional third argument provides the sort order value to assign to each (when omitted, a sequential `1..N` order is assigned in the given order):
871+
872+
```php
873+
// Reorder by ids only — assigns sort orders 1, 2, 3 in the given order
874+
$article->setRelationOrder('authors', [$author3->id, $author1->id, $author2->id]);
875+
876+
// Reorder by ids (second argument) with explicit sort order values (third argument)
877+
$article->setRelationOrder('authors', [$author1->id, $author2->id, $author3->id], [3, 2, 1]);
878+
```
879+
880+
You can check whether a relation is configured as sortable with `isSortableRelation`:
881+
882+
```php
883+
$article->isSortableRelation('authors'); // true
884+
```
885+
886+
> **NOTE:** To let backend users reorder a relation with drag-and-drop directly in a form, see [reordering relations](../backend/relations#reordering-relations) in the RelationController documentation.

0 commit comments

Comments
 (0)