One of the features requested in our QuickAdminPanel, was drag’n’drop re-ordering of the entries in Datatables. For now, we decided to create a quick demo-project which would show you how to add this functionality.


Here’s the result of what we’ll be creating – in video format.


In this tutorial, we will assume that your Model has an integer field position.

Step 1. Setting the position for new entries

If you have app/Currency.php model and want to add a new currency, here’s the code to add:

class Currency extends Model
{
    // ...

    protected static function boot()
    {
        parent::boot();

        Currency::creating(function ($model) {
            $model->position = Currency::max('position') + 1;
        });
    }
}

Step 2. Reorder Method in Controller

In our Controller, we will add a special function that would save all the positions for all entries.

app/Http/Controllers/Admin/CurrenciesController.php:

class CurrenciesController extends Controller
{

    // ...

    public function reorder(Request $request)
    {
        foreach($request->input('rows', []) as $row)
        {
            Currency::find($row['id'])->update([
                'position' => $row['position']
            ]);
        }

        return response()->noContent();
    }
}

How do we call that method? In routes/web.php:

Route::post('currencies/reorder', 'CurrenciesController@reorder')->name('currencies.reorder');

Step 3. Blade & JavaScript: Hide Position and Reorder

In our QuickAdminPanel generator, we have datatables in resources/views/admin/currencies/index.blade.php, so here’s the JavaScript code for the datatables:

<table class=" table table-bordered table-striped table-hover datatable datatable-Currency">
    ...
</table>

@section('scripts')
@parent
<script>
    $(function () {
      let dtButtons = $.extend(true, [], $.fn.dataTable.defaults.buttons)

      $.extend(true, $.fn.dataTable.defaults, {
        order: [[ 2, 'asc' ]],
        pageLength: 100,
        columnDefs: [
            ...$.fn.dataTable.defaults.columnDefs,
            {
                visible: false,
                searchable: false,
                targets: 2
            }
        ],
        rowReorder: {
            selector: 'tr td:not(:first-of-type,:last-of-type)',
            dataSrc: '2'
        },
      });

      let datatable = $('.datatable-Currency:not(.ajaxTable)').DataTable({ buttons: dtButtons })

        datatable.on('row-reorder', function (e, details) {
            if(details.length) {
                let rows = [];
                details.forEach(element => {
                    rows.push({
                        id: $(element.node).data('entry-id'),
                        position: element.newData
                    });
                });

                $.ajax({
                    headers: {'x-csrf-token': _token},
                    method: 'POST',
                    url: "{{ route('admin.currencies.reorder') }}",
                    data: { rows }
                }).done(function () { location.reload() });
            }

        });
})

</script>
@endsection

In this case, we assume that position is column number 2 (which is third column, numbers start from 0), so we hide it visually, but make it a source of reordering.


Step 3b. AJAX Datatables

The code is a little bit different for AJAX Datatables module with server-side rendering. Here’s an example from a similar the same resources/views/admin/countries/index.blade.php from:

<table class=" table table-bordered table-striped table-hover ajaxTable datatable datatable-Country">
...
</table>

...

<script>
$(function () {
  let dtButtons = $.extend(true, [], $.fn.dataTable.defaults.buttons)

  let dtOverrideGlobals = {
    buttons: dtButtons,
    processing: true,
    serverSide: true,
    retrieve: true,
    aaSorting: [],
    ajax: "{{ route('admin.countries.index') }}",
    columns: [
        { data: 'placeholder', name: 'placeholder' },
        { data: 'id', name: 'id' },
        { data: 'position', name: 'position', visible: false, searchable: false },
        { data: 'name', name: 'name' },
        { data: 'short_code', name: 'short_code' },
        { data: 'actions', name: '{{ trans('global.actions') }}' }
    ],
    order: [[ 2, 'asc' ]],
    pageLength: 100,
    rowReorder: {
        selector: 'tr td:not(:first-of-type,:last-of-type)',
        dataSrc: 'position'
    },
  };

  let datatable = $('.datatable-Country').DataTable(dtOverrideGlobals);
    datatable.on('row-reorder', function (e, details) {
        if(details.length) {
            let rows = [];
            details.forEach(element => {
                rows.push({
                    id: datatable.row(element.node).data().id,
                    position: element.newData
                });
            });

            $.ajax({
                headers: {'x-csrf-token': _token},
                method: 'POST',
                url: "{{ route('admin.countries.reorder') }}",
                data: { rows }
            }).done(function () { datatable.ajax.reload() });
        }
    });
});

</script>

That’s it! Public repository is available here on Github.