Directly inspired by : Laravel Livewire Tables
This package has been made for Matiere Noire internal's projects and is not destined to be working as a "ready to go" package for now
A dynamic, responsive Laravel Livewire table component with searching, sorting, checkboxes, and pagination.
Make sure you've installed Laravel Livewire.
Installing this package via composer:
composer require matiere-noire/laravel-livewire-table
This package was designed to work well with Laravel frontend scaffolding.
If you're just doing scaffolding now, you'll need to add the Livewire @livewireScripts
and @livewireStyles
blade directives to your resources/views/layouts/app.blade.php
file:
<!-- Styles -->
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
@livewireStyles
...
<!-- Scripts -->
<script src="{{ asset('js/app.js') }}"></script>
@livewireScripts
Using the make
command:
php artisan make:table UserTable --model=User
This creates your new table component in the app/Http/Livewire
folder.
After making a component, you may want to edit the query
and column
methods:
class UserTable extends TableComponent
{
public function query()
{
return User::query();
}
public function columns()
{
return [
Column::make('ID')->searchable()->sortable(),
Column::make('Created At')->searchable()->sortable(),
Column::make('Updated At')->searchable()->sortable(),
];
}
}
You don't have to use the render()
method in your table component or worry about a component view, because the package handles that automatically.
You use table components in views just like any other Livewire component:
@livewire('user-table')
Now all you have to do is update your table component class!
Publishing files is optional.
Publishing the table view files:
php artisan vendor:publish --tag=table-views
Publishing the config file:
php artisan vendor:publish --tag=table-config
Sets the CSS class names to use on the <table>
. Defaults to table-hover
.
Example:
public $table_class = 'table-hover table-striped';
Or, via .env
to apply globally:
TABLE_CLASS="table-hover table-striped"
Sets the CSS class names to use on the <thead>
. Defaults to thead-light
.
Example:
public $thead_class = 'thead-dark';
Or, via .env
to apply globally:
TABLE_THEAD_CLASS="thead-dark"
Sets a custom view to use for the table header (displayed next to the search).
Example:
public $header_view = 'users.table-header';
Protip: any view you reference in your table component can use Livewire actions, triggers, etc!
{{-- resources/views/users/table-header.blade.php --}}
<button class="btn btn-primary" wire:click="createUser">Create User</button>
Sets a custom view to use for the table footer (displayed next to the pagination).
Example:
public $footer_view = 'users.table-footer';
Boolean for if the table should use checkboxes or not. Defaults to true
.
Example:
public $checkbox = false;
Or, via .env
to apply globally:
TABLE_CHECKBOX=false
The side of the table to place checkboxes on. Accepts left
or right
. Defaults to right
.
Example:
public $checkbox_side = 'left';
Or, via .env
to apply globally:
TABLE_CHECKBOX_SIDE="left"
Sets the attribute name to use for $checkbox_values
. Defaults to id
. I recommend keeping this as id
.
Example:
public $checkbox_attribute = 'id';
Contains an array of checked values. For example, if $checkbox_attribute
is set to id
, this will contain an array of checked model id
s.
Then you can use those id
s to do whatever you want in your component. For example, a deleteChecked
button inside a custom $header_view
.
Example deleteChecked
button:
<button class="btn btn-danger" onclick="confirm('Are you sure?') || event.stopImmediatePropagation();" wire:click="deleteChecked">
Delete Checked
</button>
Example deleteChecked
method:
public function deleteChecked()
{
Car::whereIn('id', $this->checkbox_values)->delete();
}
Sets the default attribute to sort by. Defaults to id
. This also works with counts and relationships.
Example:
public $sort_attribute = 'created_at';
Count example (if you added ->withCount('relations')
to the query()
method):
public $sort_attribute = 'relations_count';
Relationship example (if you added ->with('relation')
to the query()
method):
public $sort_attribute = 'relation.name';
Notice the use of the dot notation. You use this when declaring column relationship attributes as well.
Sets the default direction to sort by. Accepts asc
or desc
. Defaults to desc
.
Example:
public $sort_direction = 'asc';
Sets the amount of results to display per page. Defaults to 15
.
Example:
public $per_page = 25;
Or, via .env
to apply globally:
TABLE_PER_PAGE=25
This method returns an Eloquent model query to be used by the table.
Example:
public function query()
{
return Car::with('brand')->withCount('accidents');
}
This method returns an array of Column
s to use in the table.
Example:
public function columns()
{
return [
Column::make('ID')->searchable()->sortable(),
Column::make('Brand Name', 'brand.name')->searchable()->sortable(),
Column::make('Name')->searchable()->sortable(),
Column::make('Color')->searchable()->sortable()->view('cars.table-color'),
Column::make('Accidents', 'accidents_count')->sortable(),
Column::make()->view('cars.table-actions'),
];
}
Declaring Column
s is similar to declaring Laravel Nova fields. Jump to the column declaration section to learn more.
This method is used to compute the <th>
CSS class for the table header.
The column attribute.
Example:
public function thClass($attribute)
{
if ($attribute == 'name') return 'font-italic';
if ($attribute == 'accidents_count') return 'text-right';
if ($attribute == 'brand.name') return 'font-weight-bold';
return null;
}
This method is used to compute the <tr>
CSS class for the table row.
The model instance for the table row.
Example:
public function trClass($model)
{
if ($model->name == 'Silverado') return 'table-secondary';
if ($model->accidents_count > 8) return 'table-danger';
if ($model->brand->name == 'Ford') return 'table-primary';
return null;
}
This method is used to compute the <td>
CSS class for the table data.
The column attribute.
The column value.
Example:
public function tdClass($attribute, $value)
{
if ($attribute == 'name' && $value == 'Silverado') return 'table-secondary';
if ($attribute == 'accidents_count' && $value < 2) return 'table-success';
if ($attribute == 'brand.name' && $value == 'Ford') return 'table-primary';
return null;
}
This method sets the initial table properties. If you have to override it, be sure to call $this->setTableProperties()
.
Example:
public function mount()
{
$this->setTableProperties();
// my custom code
}
This method renders the table component view. If you have to override it, be sure to return $this->tableView()
.
Example:
public function render()
{
// my custom code
return $this->tableView();
}
The Column
class is used to declare your table columns.
public function columns()
{
return [
Column::make('ID')->searchable()->sortable(),
Column::make('Created At')->searchable()->sortable(),
Column::make('Updated At')->searchable()->sortable(),
];
}
The heading to use for the table column, e.g. Created At
. Can be null for view-only columns.
The attribute to use for the table column value. If null, it will use a snake cased $heading
.
You can also specify _count
s and relationship attributes with a dot notation.
For counts, let's say I added withCount()
to my query()
:
public function query()
{
return Car::withCount('accidents');
}
Now I can create a column using this count like so:
Column::make('Accidents', 'accidents_count')->sortable(),
For relationships, let's say I added with()
to my query()
:
public function query()
{
return Car::with('brand');
}
Now I can create a column using any of the relationship attributes like so:
Column::make('Brand ID', 'brand.id')->searchable()->sortable(),
Column::make('Brand Name', 'brand.name')->searchable()->sortable(),
Sets the column to be searchable.
Sets the column to be sortable.
Allows custom logic to be used for sorting. Your supplied callable
will receive the following parameters:
$models
: The current Eloquent query (\Illuminate\Database\Eloquent\Builder
). You should apply your sort logic to this query, and return it.$sort_attribute
: The name of the column currently being sorted. If you used a nested relationship for sorting, it will be properly transformed torelationship_table.column_name
format so the query will be scoped correctly.$sort_direction
: The direction sort direction requested, eitherasc
, ordesc
.
Additionally, your callback will be passed through Laravel's Container so you may inject any dependencies you need in your callback. Make sure your dependencies are listed before the parameters above.
Example:
Column::make('Paint Color')->searchable()->sortable()->sortUsing(function ($models, $sort_attribute, $sort_direction) {
return $models->orderByRaw('?->\'$.color_code\' ?', [$sort_attribute, $sort_direction]);
});
This will sort the paint_color
column using the JSON value color_code
.
SQL Injection warning: Make sure if you are using any of Eloquent's *Raw
methods, you always use the bindings feature.
Sets a custom view to use for the column.
Example:
Column::make('Paint Color')->searchable()->sortable()->view('cars.table-paint-color'),
Notice how the column is still searchable()
and sortable()
, because the Car
model contains a paint_color
attribute!
If you're making a view-only column (for action buttons, etc), just don't make it searchable or sortable:
Column::make()->view('cars.table-actions'),
Custom column views are passed $model
and $column
objects, as well as variables passed from the table component.
For the Paint Color
example, we can use the paint_color
attribute from the model like so:
{{-- resources/views/cars/table-paint-color.blade.php --}}
<i class="fa fa-circle" style="color: {{ $model->paint_color }};"></i>
For the action buttons example, we can use the id
attribute from the model like so:
{{-- resources/views/cars/table-actions.blade.php --}}
<button class="btn btn-primary" wire:click="showCar({{ $model->id }})">Show</button>
<button class="btn btn-primary" wire:click="editCar({{ $model->id }})">Edit</button>
Using a custom view for a relationship column? No problem:
{{-- resources/views/cars/table-brand-name.blade.php --}}
{{ $model->brand->name }}