Skip to content

Instantly share code, notes, and snippets.

@maksimerohin
Last active June 30, 2023 16:07
Show Gist options
  • Save maksimerohin/253d9975738d57b5db6269968862501d to your computer and use it in GitHub Desktop.
Save maksimerohin/253d9975738d57b5db6269968862501d to your computer and use it in GitHub Desktop.
[Laravel Livewire Tables] Rappasoft Tables #laravel #livewire

Creating bulk actions

To create bulk actions, you must specify a method and a button title in the $bulkActions component property.

public array $bulkActions = [
    'exportSelected' => 'Export',
];

The following method is only available in v1.16 and above

As of v1.16 you can define bulk action with a method, so you can perform other actions to determine what your actions are or perform translations on the strings:

public function bulkActions(): array
{
    // Figure out what actions the admin gets
    ...

    return [
        'activate'   => __('Activate'),
        'deactivate' => __('Deactivate'),
    ];
}

The key is the Livewire method to call, and the value is the name of the item in the bulk actions dropdown.

You can define your method to do whatever you want:

public function exportSelected()
{
    // Do something with the selected rows.
}

See Getting the selected rows query or Getting the selected keys to understand how to work with the selected data.

Selected rows query

In the component, you have access to $this->selectedRowsQuery which is a Builder instance of the selected rows.

public function exportSelected()
{
    if ($this->selectedRowsQuery->count() > 0) {
        // Do something with the selected rows
    }

    // Notify there is nothing to export
}

Resetting after bulk actions

If your bulk action is changing the outcome of your table, i.e. you are deleting rows or changing criteria that would alter the row set with the search criteria you have, then you may have unexpected results after the bulk action runs.

There is no one good way to handle this, so there are a few options available to you:

1. Reset all the filters and criteria.

You can reset all the filters, search, page, sorts, etc. With this method, that will essentially reload the table to what it was on the first page load with your bulk changes since the query will re-run:

public function deleteSelected()
{
    // Delete the rows

    $this->resetAll();
}

2. Reset specific criteria

You may at the end of your bulk action method reset specific parts of the UI with any of the following methods:

public function myBulkAction()
{
    // Do something with the rows

    // Use any of these to reset the UI to a point that makes sense after your bulk action is run:
    $this->resetFilters(); // Remove all the filters
    $this->resetSearch(); // Remove the search query
    $this->resetSorts(); // Remove the sorts
    $this->resetBulk(); // Clear the selected rows
    $this->resetPage($this->pageName()); // Go back to page 1
}

Creating filters

Creating filters is not required, and the filters box will be hidden if none are defined.

To create filters, you return an array of Filter class objects from the filters() method.

The current types of filters are: select, multiSelect, date, and datetime (for supported browsers).

There are two steps to making a filter:

Adding the Filter object to filters. Specifying how that filter acts on the query.

1. Adding the Filter object to filters.

public function filters(): array
{
    return [
        'type' => Filter::make('User Type')
            ->select([
                '' => 'Any',
                User::TYPE_ADMIN => 'Administrators',
                User::TYPE_USER => 'Users',
            ]),
        'active' => Filter::make('Active')
            ->select([
                '' => 'Any',
                'yes' => 'Yes',
                'no' => 'No',
            ]),
        'verified' => Filter::make('E-mail Verified')
            ->select([
                '' => 'Any',
                1 => 'Yes',
                0 => 'No',
            ]),
         'date' => Filter::make('Date')
            ->date([
                'min' => now()->subYear()->format('Y-m-d'), // Optional
                'max' => now()->format('Y-m-d') // Optional
            ]),
         'tags' => Filter::make('Tags')
            ->multiSelect([
                'tag1' => 'Tags 1',
                'tag2' => 'Tags 2',
                'tag3' => 'Tags 3',
                'tag4' => 'Tags 4',
            ]),
    ];
}

When using the select box filter, the keys of the options you supply will be validated on select to make sure they match one of the options on the backend, otherwise it will be changed to null for safety.

String or integer keys are supported.

2. Specifying how that filter acts on the 'query'.

To apply the filter in your query, you first check its existence, and then just append some constraints.

public function query(): Builder
{
    return User::query()
        ->when($this->getFilter('type'), fn ($query, $type) => $query->where('type', $type))
        ->when($this->getFilter('active'), fn ($query, $active) => $query->where('active', $active === 'yes'));
}

As you can see we are just using the built-in Eloquent when method to check existence of our filter, and then apply the query.

2.1. Working with numeric keys:

If your filter has numeric keys, you may run into issues when you have a key that equals zero.

You will have to explicitly check:

public function query(): Builder
{
    return User::with('attributes', 'parent')
        ->when($this->getFilter('email'), fn ($query, $email) => $email === 'yes' ? $query->whereNotNull('email') : $query->whereNull('email'))
        ->when($this->hasFilter('verified'), function ($query) {
            if ($this->getFilter('verified') === 1) {
                $query = $query->whereNotNull('email');
            } else {
                $query = $query->whereNull('email');
            }
        });
}

The search filter

Sometimes the default search behavior may not meet your requirements. If this is the case, skip using the searchable() method on columns and define your own behavior directly on the query.

public function query(): Builder
{
    return User::query()
        ->when($this->getFilter('search'), fn ($query, $term) => $query->where('name', 'like', '%'.$term.'%')
            ->orWhere('email', 'like', '%'.$term.'%'));
}

You can make this even more streamlined by adding a search scope to your model like so:

public function scopeSearch($query, $term)
{
    return $query->where(
        fn ($query) => $query->where('name', 'like', '%'.$term.'%')
            ->orWhere('email', 'like', '%'.$term.'%')
    );
}

And then using it like this:

public function query(): Builder
{
    return User::query()
        ->when($this->getFilter('search'), fn ($query, $term) => $query->search($term));
}

Setting a default

You can set a default filter by overriding the $filters property on the component using the same key you used to define your filters() method;

public array $filters = [
    'type' => 'user',
];

The value must match one of the values specified on your filter's options.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment