rappasoft / laravel-livewire-tables

A dynamic table component for Laravel Livewire
https://rappasoft.com/docs/laravel-livewire-tables/v2/introduction
MIT License
1.7k stars 319 forks source link

[Bug]: Undefined property: stdClass::$id when using select all checkbox #1692

Open wimurk opened 3 months ago

wimurk commented 3 months ago

What happened?

image When clicking on the checkbox an error occurs

image

After investigating i came across this: In this function image

I noticed this function doesnt include the selects used in the columns method.

image

When using dd in the elequent method the response is: image

It misses all the columns i added in the columns section. image

How to reproduce the bug

No response

Package Version

latest

PHP Version

8.2.x

Laravel Version

10

Alpine Version

latest

Theme

Tailwind 3.x

Notes

No response

Error Message

Undefined property: stdClass::$id
lrljoe commented 3 months ago

Make sure that you either have a column for the "id" field, or that it is added via setAdditionalSelects in your configure() method

wimurk commented 3 months ago

Make sure that you either have a column for the "id" field, or that it is added via setAdditionalSelects in your configure() method

I have the column set as primary key and i show it in the table.

RishabSwift commented 3 months ago

Same issue here. Also have an ID column visible on the table itself... so no need to setAdditionalSelects. This happens when I do $this->setAllSelected()

Update: I was able to resolve it by manually adding id to the query:

public function builder(): Builder
{
      return MyModel::query()->select(['id', 'col', ...]);
}
stale[bot] commented 2 months ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

lrljoe commented 1 month ago

You don't need to add the fields to the setAdditionalSelects so long as it's not a label() Column, if it is, then by design it doesn't add it inro the query.

Adding a select() method in your builder method may result in oddities, as it builds the query and selects the relevant fields internally.

G4Zz0L1 commented 4 weeks ago

I have the same bug from quite a while. My id is a normal column, not a label, and it's showed in the table like the rest. I don't have any additional select added to the builder or configure methods. How should I make the "select all" working on this case?

wimurk commented 4 weeks ago

Why is this issue closed? The problem still persists and the only fix is to add the id column to the builder query. The setAdditionalSelects as suggested doesn't work either. Adding a Column::('', 'id') won't work either.

I suggest always adding the id column to the table query when the primary key is set to id. In my opinion, this should be standard behavior.

lrljoe commented 3 weeks ago

Why is this issue closed? The problem still persists and the only fix is to add the id column to the builder query. The setAdditionalSelects as suggested doesn't work either. Adding a Column::('', 'id') won't work either.

I suggest always adding the id column to the table query when the primary key is set to id. In 01my opinion, this should be standard behavior.

Issues are auto-closed by stalebot, not intentionally by maintainers, apologies!

I use setAdditionalSelects for ID across close to 500 tables (none have an ID column), without issue.

Can you share your configure() method please?

lrljoe commented 3 weeks ago

For Bulk Actions, the "selected" array is a list of IDs that are selected. The idea behind this, is that you are manipulating an array of selected items, i.e. a list of selected Models (using the primary key of the Model). You then do "something" with that, such as kicking off a background job, or passing it into an "Export to CSV" action..

Populating "selected" with the full Model, would be prohibitively expensive in terms of memory/CPU, and would cause the Livewire Lifecycle to become quite large and inefficient.

What is it you're trying to do?

lrljoe commented 3 weeks ago

I have the same bug from quite a while. My id is a normal column, not a label, and it's showed in the table like the rest. I don't have any additional select added to the builder or configure methods. How should I make the "select all" working on this case?

Are you getting the same error? At what point does it occur?

G4Zz0L1 commented 3 weeks ago

The error is caused by the fact that the id column is not inserted into the query once "select all" has been clicked from the table. Without the "setAdditionalSelects" method to insert the "id" column forcibly, the error mentioned in the first post pops up.

lrljoe commented 3 weeks ago

The error is caused by the fact that the id column is not inserted into the query once "select all" has been clicked from the table. Without the "setAdditionalSelects" method to insert the "id" column forcibly, the error mentioned in the first post pops up.

Can you share your full table component please so that I can try to replicate your issue.

wxnko commented 3 weeks ago

The problem only occurs when you define selected fields inside builder

public function builder(): Builder {
    return foo::query()->select(['some_field']); // use setAdditionalSelects instead select
}

In above scenario you have to add 'id' field inside query. Adding it via setAdditionalSelects or id column doesn't work.

Removing select method from query inside builder and moving "some_field" to setAdditionalSelects works fine. In that case you don't have to add 'id' field inside setAdditionalSelects or via new column

wimurk commented 3 weeks ago

The error is caused by the fact that the id column is not inserted into the query once "select all" has been clicked from the table. Without the "setAdditionalSelects" method to insert the "id" column forcibly, the error mentioned in the first post pops up.

Can you share your full table component please so that I can try to replicate your issue.

My full table is

<?php

declare(strict_types=1);

namespace Modules\Invoices\App\Http\Livewire;

use Illuminate\Database\Eloquent\Builder;
use Modules\Assignments\App\Models\TimesheetHour;
use App\Facades\LocaleFormat;
use Modules\Invoices\App\Enum\InvoiceFilterPeriod;
use Modules\Invoices\App\Jobs\Freelance\GenerateInvoiceJob;
use Modules\Invoices\App\Jobs\Freelance\ResendInvoiceJob;
use Modules\Invoices\App\Models\CollectionInvoice;
use Override;
use Rappasoft\LaravelLivewireTables\DataTableComponent;
use Rappasoft\LaravelLivewireTables\Views\Column;
use Rappasoft\LaravelLivewireTables\Views\Filters\SelectFilter;

use function __;
use function auth;
use function str;

final class CollectionInvoiceTable extends DataTableComponent
{
    /**
     * @var string
     */
    public string $tableName = 'collection_invoices';

    /**
     * @var array
     */
    protected $listeners = ['refreshComponent' => '$refresh'];

    /**
     * @return void
     */
    public function configure(): void
    {
        $this->setPrimaryKey('id')
            ->setDefaultSort('created_at', 'desc');
    }

    /**
     * @return array
     */
    public function bulkActions(): array
    {
        return [

        ];
    }

    /**
     * @return array
     */
    public function columns(): array
    {
        return [
            Column::make('', 'id')
                ->view('invoices::collection-invoices.datatable.show')
                ->excludeFromColumnSelect(),

            Column::make(__('invoices::collection-invoices.index.Links'), 'id')
                ->view('invoices::collection-invoices.datatable.modules')
                ->deselected(),

            Column::make(__('invoices::collection-invoices.index.Status'), 'status')
                ->searchable()
                ->view('invoices::collection-invoices.datatable.status'),

            Column::make(__('invoices::collection-invoices.index.Number'), 'number')
                ->deselected()
                ->searchable(),

            Column::make(__('invoices::collection-invoices.index.Description'), 'description')
                ->searchable(),

            Column::make(__('invoices::collection-invoices.index.Invoice date'), 'created_at')
                ->searchable()
                ->sortable()
                ->format(fn (mixed $value) => LocaleFormat::formatDateTime($value)),

            Column::make(__('invoices::collection-invoices.index.Weeknumber'), 'created_at')
                ->searchable()
                ->sortable()
                ->format(fn (mixed $value) => $value->week),

            Column::make(__('invoices::collection-invoices.index.Year'), 'created_at')
                ->searchable()
                ->sortable()
                ->format(fn (mixed $value) => $value->year),

            Column::make(__('invoices::collection-invoices.index.Invoices'), 'id')
                ->searchable(fn (Builder $query, string $searchTerm) => $this->searchOnInvoiceRelation($query, $searchTerm))
                ->view('invoices::collection-invoices.datatable.invoices'),

            Column::make(__('invoices::invoices.index.Net price'), 'id')
                ->view('invoices::collection-invoices.datatable.net-price'),
        ];
    }

    /**
     * @return array
     */
    #[Override]
    public function filters(): array
    {
        return [
            SelectFilter::make(__("invoices::collection-invoices.index.Show empty invoices"), 'with_empty_invoices')
                ->setFilterDefaultValue('0')
                ->options([
                    '0' => __('invoices::collection-invoices.index.No'),
                    '1' => __('invoices::collection-invoices.index.Yes'),
                ])->filter(function (Builder $builder, string $value) {

                    if ($value === '0') {
                        $builder->has('invoices');
                    }
                }),
        ];
    }

    /**
     * @return Builder
     */
    public function builder(): Builder
    {
        return CollectionInvoice::query()
            ->addSelect('base_company_id')
            ->withCount('exactProcessed')
            ->with('invoices.invoiceLines', 'activeQueue', 'baseCompany', 'invoices.invoiceArticles');
    }

    /**
     * @param Builder $query
     * @param string $searchTerm
     * @return Builder
     */
    private function searchOnInvoiceRelation(Builder $query, string $searchTerm): Builder
    {
        return $query->orWhereHas('invoices', function (Builder $query) use ($searchTerm) {
            $query->where('number', 'LIKE', "%$searchTerm%");
        });
    }
}
lrljoe commented 3 weeks ago

The error is caused by the fact that the id column is not inserted into the query once "select all" has been clicked from the table. Without the "setAdditionalSelects" method to insert the "id" column forcibly, the error mentioned in the first post pops up.

Can you share your full table component please so that I can try to replicate your issue.

My full table is

<?php

declare(strict_types=1);

namespace Modules\Invoices\App\Http\Livewire;

use Illuminate\Database\Eloquent\Builder;
use Modules\Assignments\App\Models\TimesheetHour;
use App\Facades\LocaleFormat;
use Modules\Invoices\App\Enum\InvoiceFilterPeriod;
use Modules\Invoices\App\Jobs\Freelance\GenerateInvoiceJob;
use Modules\Invoices\App\Jobs\Freelance\ResendInvoiceJob;
use Modules\Invoices\App\Models\CollectionInvoice;
use Override;
use Rappasoft\LaravelLivewireTables\DataTableComponent;
use Rappasoft\LaravelLivewireTables\Views\Column;
use Rappasoft\LaravelLivewireTables\Views\Filters\SelectFilter;

use function __;
use function auth;
use function str;

final class CollectionInvoiceTable extends DataTableComponent
{
    /**
     * @var string
     */
    public string $tableName = 'collection_invoices';

    /**
     * @var array
     */
    protected $listeners = ['refreshComponent' => '$refresh'];

    /**
     * @return void
     */
    public function configure(): void
    {
        $this->setPrimaryKey('id')
            ->setDefaultSort('created_at', 'desc');
    }

    /**
     * @return array
     */
    public function bulkActions(): array
    {
        return [

        ];
    }

    /**
     * @return array
     */
    public function columns(): array
    {
        return [
            Column::make('', 'id')
                ->view('invoices::collection-invoices.datatable.show')
                ->excludeFromColumnSelect(),

            Column::make(__('invoices::collection-invoices.index.Links'), 'id')
                ->view('invoices::collection-invoices.datatable.modules')
                ->deselected(),

            Column::make(__('invoices::collection-invoices.index.Status'), 'status')
                ->searchable()
                ->view('invoices::collection-invoices.datatable.status'),

            Column::make(__('invoices::collection-invoices.index.Number'), 'number')
                ->deselected()
                ->searchable(),

            Column::make(__('invoices::collection-invoices.index.Description'), 'description')
                ->searchable(),

            Column::make(__('invoices::collection-invoices.index.Invoice date'), 'created_at')
                ->searchable()
                ->sortable()
                ->format(fn (mixed $value) => LocaleFormat::formatDateTime($value)),

            Column::make(__('invoices::collection-invoices.index.Weeknumber'), 'created_at')
                ->searchable()
                ->sortable()
                ->format(fn (mixed $value) => $value->week),

            Column::make(__('invoices::collection-invoices.index.Year'), 'created_at')
                ->searchable()
                ->sortable()
                ->format(fn (mixed $value) => $value->year),

            Column::make(__('invoices::collection-invoices.index.Invoices'), 'id')
                ->searchable(fn (Builder $query, string $searchTerm) => $this->searchOnInvoiceRelation($query, $searchTerm))
                ->view('invoices::collection-invoices.datatable.invoices'),

            Column::make(__('invoices::invoices.index.Net price'), 'id')
                ->view('invoices::collection-invoices.datatable.net-price'),
        ];
    }

    /**
     * @return array
     */
    #[Override]
    public function filters(): array
    {
        return [
            SelectFilter::make(__("invoices::collection-invoices.index.Show empty invoices"), 'with_empty_invoices')
                ->setFilterDefaultValue('0')
                ->options([
                    '0' => __('invoices::collection-invoices.index.No'),
                    '1' => __('invoices::collection-invoices.index.Yes'),
                ])->filter(function (Builder $builder, string $value) {

                    if ($value === '0') {
                        $builder->has('invoices');
                    }
                }),
        ];
    }

    /**
     * @return Builder
     */
    public function builder(): Builder
    {
        return CollectionInvoice::query()
            ->addSelect('base_company_id')
            ->withCount('exactProcessed')
            ->with('invoices.invoiceLines', 'activeQueue', 'baseCompany', 'invoices.invoiceArticles');
    }

    /**
     * @param Builder $query
     * @param string $searchTerm
     * @return Builder
     */
    private function searchOnInvoiceRelation(Builder $query, string $searchTerm): Builder
    {
        return $query->orWhereHas('invoices', function (Builder $query) use ($searchTerm) {
            $query->where('number', 'LIKE', "%$searchTerm%");
        });
    }
}

Does the issue persist if you give your ID column a name (e.g. "ID") as the first parameter in the make command?

As your bulkActions is empty, what are you doing to interact with the selected items? As there is validation to check that bulkActions is not empty in places, as part of the bulk actions code There may be a mismatch somewhere that means it is displaying when it shouldn't be