3

I am using the yajra laravel datatables package.

I'm trying to figure out how I can refactor this little bit of controller code to an individual class (UsersDataTable) which I am using method injection to make accessible. This class extends the base DataTable class but I have not added any additional functionality to this class yet. This is what I need assistance undertanding.

    public function index(Request $request, UsersDataTable $table, UserFilters $requestFilter)
    {
        $this->authorize('viewList', User::class);

        if ($request->ajax()) {
            $query = User::with('employment');
            $requestFilter->apply($query);

            return $table->eloquent($query)
                ->addColumn('action', 'users.partials.action-cell')
                ->filterColumn('name', function ($query, $keyword) {
                    $sql = "CONCAT(users.first_name, ' ', users.last_name)  like ?";
                    $query->whereRaw($sql, ["%{$keyword}%"]);
                })
                ->filterColumn('id', function ($query, $keyword) {
                    $query->where($query->qualifyColumn('id'), $keyword);
                })
                ->toJson();
        }

        return view('users.index');
    }
<?php

namespace App\DataTables;

use Yajra\DataTables\DataTables;

class UsersDataTable extends DataTables
{

}
<!--begin: Datatable -->
<table id="users_table" data-table="users.index" class="table table-hover"></table>
const table = $('[data-table="users.index"]');
// begin first table
table.DataTable({
    ajax: {
        url: window.location.href,
        data(params) {
            params.status = filterData.status;
            params.started_at = filterData.started_at;
        },
        error: function(xhr, error, code) {
            console.log(JSON.parse(xhr.responseText.errors));
            console.log(xhr);
            console.log(error);
            new Noty({
                type: "error",
                layout: "topRight",
                text: JSON.parse(xhr.responseText.errors)
            }).show();
        }
    },
    columns: [
        { data: "id", title: "User ID" },
        { data: "name", title: "Name" },
        { data: "hometown", title: "Hometown" },
        {
            data: "employment.started_at",
            title: "Date Started",
            searchable: false
        },
        { data: "status", title: "Status", searchable: false },
        {
            data: "action",
            title: "Action",
            orderable: false,
            responsivePriority: -1
        }
    ],
    initComplete(settings) {
        rowCounter.html(`${settings.fnRecordsTotal()} Total`);
    }
});

My expected results is that my controller code can me refactored to go inside of the UsersDataTable class.

UPDATE:

I am working on what was provided to me below however, I have since decided to separate the view from its API to get the collection of users for the table to use so I won't need to create the table with the package. The problem is none of the code in the dataTable method is being ran. What am I doing wrong?

So currently as an update, I have the following.

public function index(UsersDataTable $dataTable, UserFilters $requestFilter)
{
    $this->authorize('viewList', User::class);

    $query = User::query();
    $requestFilter->apply($query);

    return $dataTable->eloquent($query)->toJson();
}



<?php

namespace App\DataTables;

use App\Models\User;
use App\Filters\UserFilters;
use Yajra\DataTables\DataTables;

class UsersDataTable extends DataTables
{
    /** @var userFilters */
    private $userFilters;

    /**
     * UserDataTable constructor.
     *
     * @param UserFilters $userFilters
     */
    public function __construct(UserFilters $userFilters)
    {
        $this->userFilters = $userFilters;
    }

    /**
     * Build DataTable class.
     *
     * @param mixed $query Results from query() method.
     * @return \Yajra\DataTables\DataTableAbstract
     */
     public function dataTable($query)
     {
         return datatables($query)
            ->editColumn('started_at', function (User $user) {
                return $user->currentEmployment->started_at->format('Y-m-d H:s');
        })
             ->editColumn('name', function (User $user) {
                return $user->full_name;
            })
            ->filterColumn('id', function ($query, $keyword) {
                $query->where($query->qualifyColumn('id'), $keyword);
            })
            ->filterColumn('name', function ($query, $keyword) {
                $sql = "CONCAT(users.first_name, ' ', users.last_name)  like ?";
                $query->whereRaw($sql, ["%{$keyword}%"]);
            })
            ->addColumn('action', 'users.partials.action-cell');
    }

    /**
     * Get query source of dataTable.
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function query($builder)
    {
        $query = User::with('employment');

        $this->userFilters->apply($query);

        return $query;
    }
}

1 Answers1

2

You will basically need to define a UserDataTable like the following:

class UsersDataTable extends DataTables
{
    /** @var UserFilters */
    private $userFilters;

    /**
     * UserDataTable constructor.
     *
     * @param UserFilters $userFilters
     */
    public function __construct(UserFilters $userFilters)
    {
        $this->userFilters = $userFilters;
    }

    /**
     * Build DataTable class.
     *
     * @param mixed $query Results from query() method.
     * @return DataTableAbstract
     */
    public function dataTable($query): DataTableAbstract
    {
        return datatables($query)
            ->filterColumn('name', function ($query, $keyword) {
                $sql = "CONCAT(users.first_name, ' ', users.last_name)  like ?";
                $query->whereRaw($sql, ["%{$keyword}%"]);
            })
            ->filterColumn('id', function ($query, $keyword) {
                $query->where($query->qualifyColumn('id'), $keyword);
            })
            ->addColumn('action', 'users.partials.action-cell');
    }

    /**
     * Get query source of dataTable.
     *
     * @return Builder
     */
    public function query(): Builder
    {
        $query = User::with('employment');

        $this->userFilters->apply($query);

        return $query;
    }

    /**
     * Optional method if you want to use html builder.
     *
     * @return HtmlBuilder
     */
    public function html(): HtmlBuilder
    {
        return $this->builder()
            ->columns($this->getColumns())
            ->addAction()
            ->ajax(['type' => 'POST'])
            ->parameters(array_merge(
                $this->getBuilderParameters(),
                [
                    'orderBy' => [[2, 'asc']],
                    'fixedColumns' => [
                        'leftColumns' => 1,
                        'rightColumns' => 1,
                    ],
                ]
            ));
    }

    /**
     * Get columns.
     *
     * @return array
     */
    protected function getColumns(): array
    {
        return [
            ['data' => 'id', 'title' => trans('users.table.header.id')],
            ['data' => 'name', 'title' => trans('users.table.header.name')],
            ['data' => 'email', 'title' => trans('users.table.header.email')],
            ['data' => 'created_at', 'title' => trans('users.table.header.created_at')],
        ];
    }
}

Note: the columns and such may need some tweaking, it is just an example. This config replaces basically your JavaScript config of the table.

You can then use it in your controller like this:

public function index(UserDataTable $dataTable)
{
    return $dataTable->render('users.index');
}

In your views/users/index.blade.php, you just need to render the table and its script:

<!-- The table -->
{{ $dataTable->table([], true) }}

@push('scripts')
    {!! $dataTable->scripts() !!}
@endpush
Namoshek
  • 6,394
  • 2
  • 19
  • 31
  • That's great stuff. Will, that change any of the existing javascript that I have above? – Jeffrey Davidson Aug 22 '19 at 14:30
  • It replaces it, if you render the scripts. Thats the main use of this package anyway. – Namoshek Aug 22 '19 at 14:31
  • The filters should only be applied if there's an ajax request though, – Jeffrey Davidson Aug 22 '19 at 14:41
  • So it should return the json. – Jeffrey Davidson Aug 22 '19 at 14:42
  • The query only executes when it is an ajax request. – Namoshek Aug 22 '19 at 14:44
  • So then are you suggesting this. ```public function index(Request $request, UserDataTable $table) { $this->authorize('viewList', User::class); if ($request->ajax()) { return $table->render('users.index'); } return view('users.index'); }``` – Jeffrey Davidson Aug 22 '19 at 14:53
  • No, only render. It will return the table data as json for ajax requests and the view for non-ajax requests. – Namoshek Aug 22 '19 at 16:05
  • The big advantage of using a custom `DataTables` class is that it can do both, render your table and deliver the data for the table. The `html()` and `getColumns()` methods are basically what generates the JavaScript code, while the other two methods of the class generate and transform the data of the table. For a fully working table, you need nothing more than exactly what my answer contains. – Namoshek Aug 22 '19 at 16:18
  • @JeffreyDavidson Not sure why you make it so complicated. You can inject your own `UserFilters` into the DataTable instance by passing them as second argument to the `app()->make($class)` method: `app()->make(UsersDataTable::class, ['userFilters' => $userFilters])`. Use this instead of constructor/function dependency injection. If you need a more complex example how to split _rendering_ of and _providing data_ to the table into two controller methods, let me know. It can sometimes be necessary when rendering multiple tables on the same page. – Namoshek Jan 01 '20 at 19:42
  • why isn’t that datatables method being hit though? – Jeffrey Davidson Jan 01 '20 at 19:48
  • Most likely an issue with your tables JavaScript code. Use the developer tools in your browser to trace the background requests made by the table. – Namoshek Jan 01 '20 at 19:49
  • It’s a problem on the backend not Frontend. It hits the right endpoint and controller but I’m wondering if it’s the methods I’m calling on the class. – Jeffrey Davidson Jan 01 '20 at 19:53
  • Maybe, I don't know. But as written, your code is unnecessarily complex (and redundant). You can simply keep using the `query()` method on the `UsersDataTable` class. Splitting rendering from providing the data isn't all that easy. – Namoshek Jan 01 '20 at 19:59
  • That's what I'm trying to do is ONLY return a json data. What I am wanting to return is the objects properties with that added cell of actions. What I need to take into account is the fact that when I am sending the ajax request I am also sending possible filters. – Jeffrey Davidson Jan 05 '20 at 20:48