Skip to content

Commit 3d841ed

Browse files
Merge pull request #114 from sebastienheyd/feat/datatable-row-reorder
feat(datatables): add row reorder support with drag-and-drop
2 parents c357b97 + 9c811ce commit 3d841ed

File tree

5 files changed

+175
-3
lines changed

5 files changed

+175
-3
lines changed

docs/docs/8.x/datatables/options.md

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class ExampleDatatable extends Datatable
4242
| [noSearching()](#nosearching) | visible | Disable searching |
4343
| [noInfo()](#noinfo) | visible | Disable table informations |
4444
| [locale()](#locale) | [] | Set different locale for generic buttons |
45+
| [rowReorder()](#rowreorder) | disabled | Enables drag-and-drop row reordering |
4546

4647
---
4748

@@ -278,4 +279,67 @@ $this->locale([
278279
])
279280
```
280281

281-
> Default locale can be found in the [`datatable.php`](https://github.com/sebastienheyd/boilerplate/blob/master/src/resources/lang/en/datatable.php) lang file.
282+
> Default locale can be found in the [`datatable.php`](https://github.com/sebastienheyd/boilerplate/blob/master/src/resources/lang/en/datatable.php) lang file.
283+
284+
## rowReorder
285+
286+
Enables drag-and-drop row reordering on the DataTable. When a row is dragged to a new position, an AJAX POST request is sent to persist the new order.
287+
288+
```php
289+
$this->rowReorder('position')
290+
```
291+
292+
Parameters:
293+
294+
| parameter | default | description |
295+
| --- | --- | --- |
296+
| `$dataSrc` | `'order'` | The column data source used for ordering |
297+
| `$updateUrl` | `''` | Custom URL for the reorder AJAX call. If empty, uses the default route `boilerplate.datatables.reorder` |
298+
| `$fullRow` | `true` | If `true`, the whole row is draggable. If `false`, only the first cell is draggable |
299+
300+
### Complete example
301+
302+
```php
303+
use Sebastienheyd\Boilerplate\Datatables\Column;
304+
use Sebastienheyd\Boilerplate\Datatables\Datatable;
305+
use Illuminate\Http\JsonResponse;
306+
307+
class TasksDatatable extends Datatable
308+
{
309+
public $slug = 'tasks';
310+
311+
public function setUp()
312+
{
313+
$this->rowReorder('position')
314+
->order('position', 'asc');
315+
}
316+
317+
public function columns(): array
318+
{
319+
return [
320+
Column::add('Title')->data('title'),
321+
Column::add('Position')->data('position')->hidden(),
322+
];
323+
}
324+
325+
public function datasource()
326+
{
327+
return Task::query();
328+
}
329+
330+
public function reorder(array $changes): JsonResponse
331+
{
332+
foreach ($changes as $change) {
333+
Task::where('id', $change['id'])->update(['position' => $change['position']]);
334+
}
335+
336+
return response()->json(['success' => true]);
337+
}
338+
}
339+
```
340+
341+
The `reorder()` method receives an array of changes, where each item contains:
342+
- `id`: the row identifier
343+
- `position`: the new value for the ordering column
344+
345+
After a successful reorder, the DataTable automatically redraws to reflect the new order.

src/Controllers/DatatablesController.php

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Sebastienheyd\Boilerplate\Controllers;
44

5+
use Illuminate\Http\JsonResponse;
56
use Illuminate\Http\Request;
67
use ReflectionException;
78

@@ -25,6 +26,32 @@ public function make(Request $request, string $slug)
2526
return $this->getDatatable($slug)->make();
2627
}
2728

29+
/**
30+
* Handle row reorder request.
31+
*
32+
* @param Request $request
33+
* @param string $slug
34+
* @return JsonResponse
35+
*
36+
* @throws ReflectionException
37+
*/
38+
public function reorder(Request $request, string $slug): JsonResponse
39+
{
40+
if (! $request->ajax()) {
41+
abort(404);
42+
}
43+
44+
$datatable = $this->getDatatable($slug);
45+
46+
if (! method_exists($datatable, 'reorder')) {
47+
abort(404);
48+
}
49+
50+
$datatable->setUp();
51+
52+
return $datatable->reorder($request->input('changes', []));
53+
}
54+
2855
/**
2956
* Get DataTable class for the given slug.
3057
*
@@ -33,7 +60,7 @@ public function make(Request $request, string $slug)
3360
*
3461
* @throws ReflectionException
3562
*/
36-
private function getDatatable(string $slug)
63+
protected function getDatatable(string $slug)
3764
{
3865
$datatable = app('boilerplate.datatables')->load(app_path('Datatables'))->getDatatable($slug);
3966

src/Datatables/Datatable.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ abstract class Datatable
2727
protected array $locale = [];
2828
protected array $permissions = ['backend_access'];
2929
protected array $orderAttr = [];
30+
protected array $rowReorder = [];
31+
protected string $rowReorderUrl = '';
3032
protected array $attributes = [
3133
'filters' => true,
3234
'info' => true,
@@ -383,6 +385,55 @@ public function stateSave(): Datatable
383385
return $this;
384386
}
385387

388+
/**
389+
* Enables row reorder (drag-and-drop) on the DataTable.
390+
*
391+
* @param string $dataSrc Column data source used for ordering
392+
* @param string $updateUrl URL for the reorder AJAX call
393+
* @param bool $fullRow If true, the whole row is draggable
394+
* @return $this
395+
*/
396+
public function rowReorder(string $dataSrc = 'order', string $updateUrl = '', bool $fullRow = true): Datatable
397+
{
398+
$this->rowReorder = [
399+
'dataSrc' => $dataSrc,
400+
'selector' => $fullRow ? 'tr' : 'td:first-child',
401+
'update' => false,
402+
];
403+
404+
$this->rowReorderUrl = $updateUrl;
405+
406+
return $this;
407+
}
408+
409+
/**
410+
* Gets the rowReorder configuration as JSON string.
411+
*
412+
* @return string|null
413+
*/
414+
public function getRowReorderConfig(): ?string
415+
{
416+
if (empty($this->rowReorder)) {
417+
return null;
418+
}
419+
420+
return json_encode($this->rowReorder);
421+
}
422+
423+
/**
424+
* Gets the rowReorder URL for AJAX updates.
425+
*
426+
* @return string
427+
*/
428+
public function getRowReorderUrl(): string
429+
{
430+
if (! empty($this->rowReorderUrl)) {
431+
return $this->rowReorderUrl;
432+
}
433+
434+
return route('boilerplate.datatables.reorder', $this->slug, false);
435+
}
436+
386437
/**
387438
* Shows checkboxes as first column.
388439
*

src/resources/views/components/datatable.blade.php

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
@endif
5656
</table>
5757
</div>
58-
@include('boilerplate::load.async.datatables', ['buttons' => true])
58+
@include('boilerplate::load.async.datatables', ['buttons' => true, 'rowReorder' => !empty($datatable->rowReorder)])
5959
@include('boilerplate::load.pusher')
6060
@component('boilerplate::minify')
6161
<script>
@@ -87,6 +87,9 @@
8787
stateSaveParams: $.fn.dataTable.saveFiltersState,
8888
stateLoadParams: $.fn.dataTable.loadFiltersState,
8989
@endif
90+
@if($datatable->getRowReorderConfig())
91+
rowReorder: {!! $datatable->getRowReorderConfig() !!},
92+
@endif
9093
ajax: {
9194
url: '{!! route('boilerplate.datatables', $datatable->slug, false) !!}',
9295
type: 'post',
@@ -109,6 +112,32 @@
109112
});
110113
111114
window.{{ \Str::camel($id) }}.locale = {!! $datatable->getLocale() !!}
115+
116+
@if($datatable->getRowReorderConfig())
117+
window.{{ \Str::camel($id) }}.on('row-reorder', function(e, diff) {
118+
if (!diff.length) return;
119+
var changes = [];
120+
for (var i = 0; i < diff.length; i++) {
121+
var rowId = diff[i].node.id || window.{{ \Str::camel($id) }}.row(diff[i].node).data().id;
122+
if (!rowId) continue;
123+
changes.push({id: rowId, position: diff[i].newData});
124+
}
125+
$.ajax({
126+
url: '{!! $datatable->getRowReorderUrl() !!}',
127+
type: 'POST',
128+
data: {_token: '{{ csrf_token() }}', changes: changes},
129+
success: function() {
130+
window.{{ \Str::camel($id) }}.draw('full-hold');
131+
},
132+
error: function() {
133+
window.{{ \Str::camel($id) }}.draw('full-hold');
134+
if (typeof toastr !== 'undefined') {
135+
toastr.error(window.{{ \Str::camel($id) }}.locale.reorderError || 'An error occurred while reordering.');
136+
}
137+
}
138+
});
139+
});
140+
@endif
112141
});
113142
114143
whenAssetIsLoaded('echo', () => {

src/routes/boilerplate.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101

102102
// Datatables
103103
Route::post('datatables/{slug}', [DatatablesController::class, 'make'])->name('datatables');
104+
Route::post('datatables/{slug}/reorder', [DatatablesController::class, 'reorder'])->name('datatables.reorder');
104105
Broadcast::channel('dt.{name}.{signature}', function ($user, $name, $signature) {
105106
return channel_hash_equals($signature, 'dt', $name);
106107
});

0 commit comments

Comments
 (0)