yajra / laravel-datatables

jQuery DataTables API for Laravel
https://yajrabox.com/docs/laravel-datatables
MIT License
4.76k stars 859 forks source link

Can't specify Column Names in Datatables as a Service #1873

Closed rudolfdanza closed 6 years ago

rudolfdanza commented 6 years ago

I am not able to specify column title when using Datatables as a Service

this code works

<?php

namespace App\DataTables;

use App\Article;
use Yajra\DataTables\Services\DataTable;

class ArticlesDataTable extends DataTable
{
    public function dataTable($query)
    {
        return datatables($query)
            ->addColumn('action', 'admin.articles.datatable.action');
    }

    public function query(Article $model)
    {
        return $model->newQuery()->select($this->getColumns());
    }

    public function html()
    {
        return $this->builder()
                    ->columns($this->getColumns())
                    ->minifiedAjax()
                    ->addAction(['width' => '80px'])
                    ->parameters($this->getBuilderParameters());
    }

    protected function getColumns()
    {
        return [
            'id',
            'sku',
            'name',
            'description',
            'product_id',
            'size_id',
            'option_id',
            'in_stock',
            'order',
        ];
    }

    protected function getBuilderParameters()
    {
        return [
            'dom' => 'Blfrtip',
            'buttons' => [
                'csv',
                'excel',
                'print',
                'reset',
                'reload'
            ],
            'language' => ['url' => 'http://cdn.datatables.net/plug-ins/1.10.19/i18n/Italian.json']
        ];
    }

    protected function filename()
    {
        return 'Articles_' . date('YmdHis');
    }
}

This does not work

protected function getColumns()
    {
        return [
            [
                'data' => 'id',
                'name' => 'id',
                'title' => 'ID',
            ],
            [
                'data' => 'sku',
                'name' => 'sku',
                'title' => 'SKU',
            ],
            [
                'data' => 'name',
                'name' => 'name',
                'title' => 'Nome',
            ],
            [
                'data' => 'description',
                'name' => 'description',
                'title' => 'Descrizione',
            ],
            [
                'data' => 'product_id',
                'name' => 'product_id',
                'title' => 'Prodotto',
            ],
            [
                'data' => 'size_id',
                'name' => 'size_id',
                'title' => 'Taglia',
            ],
            [
                'data' => 'option_id',
                'name' => 'option_id',
                'title' => 'Variante',
            ],
            [
                'data' => 'in_stock',
                'name' => 'in_stock',
                'title' => 'In Magazzino',
            ],
            [
                'data' => 'order',
                'name' => 'order',
                'title' => 'Ordine',
            ],
        ];
    }

It throws this error in an alert: DataTables warning: table id=dataTableBuilder - Exception Message: stripos() expects parameter 1 to be string, array given

Examining the logs this should be the affected file throwing the exception: ErrorException: stripos() expects parameter 1 to be string, array given in /var/www/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/Grammar.php:910

public function wrap($value, $prefixAlias = false)
    {
        if ($this->isExpression($value)) {
            return $this->getValue($value);
        }

        // If the value being wrapped has a column alias we will need to separate out
        // the pieces so we can wrap each of the segments of the expression on its
        // own, and then join these both back together using the "as" connector.
        if (stripos($value, ' as ') !== false) {
            return $this->wrapAliasedValue($value, $prefixAlias);
        }

        // If the given value is a JSON selector we will wrap it differently than a
        // traditional value. We will need to split this path and wrap each part
        // wrapped, etc. Otherwise, we will simply wrap the value as a string.
        if ($this->isJsonSelector($value)) {
            return $this->wrapJsonSelector($value);
        }

        return $this->wrapSegments(explode('.', $value));
    }

System details

rudolfdanza commented 6 years ago

Debugging a little it seems to me that it's calling the Laravel QueryBuilder passing the full getColumns result instead of just the values in data

1st call, successful:

screen shot 2018-10-09 at 22 01 49

2nd call, unsuccessful:

screen shot 2018-10-09 at 22 02 22
rudolfdanza commented 6 years ago

I also tried specifying titles with this structure:

return ['id' => ['title' => 'ID']];

The columns header in the frontend gets the correct title in both ways but passing the whole array to the Querybuilder without stripping it down to just the database columns plain array throws the error

Putting

if (is_array($value)) {
            $value = $value['data'];
        }

inside Grammar.php before the stripos call just to try things out of course solves. Tomorrow in my spare time I'll try to trace back calls and pass the stripped down array to the Querybuilder and send out a PR

rudolfdanza commented 6 years ago

Without messing up with the code this example solves, it could be refactored but I think it should be added to the examples just in case someone faces this issue:

/**
     * Get query source of dataTable.
     *
     * @param \App\Article $model
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function query(Article $model)
    {
        return $model->newQuery()->select($this->getColumns(true));
    }

    /**
     * Optional method if you want to use html builder.
     *
     * @return \Yajra\DataTables\Html\Builder
     */
    public function html()
    {
        return $this->builder()
                    ->columns($this->getColumns())
                    ->minifiedAjax()
                    ->addAction(['width' => '80px'])
                    ->parameters($this->getBuilderParameters());
    }

    /**
     * Get columns.
     *
     * @return array
     */
    protected function getColumns($plain = false)
    {
        $columns = [
            [
                'data' => 'id',
                'name' => 'id',
                'title' => 'ID',
            ],
            [
                'data' => 'sku',
                'name' => 'sku',
                'title' => 'SKU',
            ],
            [
                'data' => 'name',
                'name' => 'name',
                'title' => 'Nome',
            ],
            [
                'data' => 'description',
                'name' => 'description',
                'title' => 'Descrizione',
            ],
            [
                'data' => 'product_id',
                'name' => 'product_id',
                'title' => 'Prodotto',
            ],
            [
                'data' => 'size_id',
                'name' => 'size_id',
                'title' => 'Taglia',
            ],
            [
                'data' => 'option_id',
                'name' => 'option_id',
                'title' => 'Variante',
            ],
            [
                'data' => 'in_stock',
                'name' => 'in_stock',
                'title' => 'In Magazzino',
            ],
            [
                'data' => 'order',
                'name' => 'order',
                'title' => 'Ordine',
            ],
        ];

        if ($plain) {
            return array_column($columns, 'data');
        } else {
            return $columns;
        }
    }