I'm currently available for full time hire! Inquire Here

Laravel Livewire Tables Documentation

🎉 Enjoying this package? Consider sponsoring me on GitHub or buying me a beer.

This is the documentation for v2 but the latest version is v3. You can switch versions in the menu at the top. Check your current version with the following command:

composer show rappasoft/laravel-livewire-tables

Advanced Example

1<?php
2 
3namespace App\Http\Livewire;
4 
5use App\Models\Tag;
6use App\Models\User;
7use Illuminate\Database\Eloquent\Builder;
8use Rappasoft\LaravelLivewireTables\DataTableComponent;
9use Rappasoft\LaravelLivewireTables\Views\Columns\BooleanColumn;
10use Rappasoft\LaravelLivewireTables\Views\Column;
11use Rappasoft\LaravelLivewireTables\Views\Columns\ImageColumn;
12use Rappasoft\LaravelLivewireTables\Views\Filters\SelectFilter;
13use Rappasoft\LaravelLivewireTables\Views\Filters\MultiSelectFilter;
14use Rappasoft\LaravelLivewireTables\Views\Filters\DateFilter;
15 
16class UsersTable extends DataTableComponent
17{
18 public string $tableName = 'users';
19 public array $users = [];
20 
21 public $columnSearch = [
22 'name' => null,
23 'email' => null,
24 ];
25 
26 public function configure(): void
27 {
28 $this->setPrimaryKey('id')
29 ->setReorderEnabled()
30 ->setSingleSortingDisabled()
31 ->setHideReorderColumnUnlessReorderingEnabled()
32 ->setFilterLayoutSlideDown()
33 ->setRememberColumnSelectionDisabled()
34 ->setSecondaryHeaderTrAttributes(function($rows) {
35 return ['class' => 'bg-gray-100'];
36 })
37 ->setSecondaryHeaderTdAttributes(function(Column $column, $rows) {
38 if ($column->isField('id')) {
39 return ['class' => 'text-red-500'];
40 }
41 
42 return ['default' => true];
43 })
44 ->setFooterTrAttributes(function($rows) {
45 return ['class' => 'bg-gray-100'];
46 })
47 ->setFooterTdAttributes(function(Column $column, $rows) {
48 if ($column->isField('name')) {
49 return ['class' => 'text-green-500'];
50 }
51 
52 return ['default' => true];
53 })
54 ->setUseHeaderAsFooterEnabled()
55 ->setHideBulkActionsWhenEmptyEnabled();
56 }
57 
58 public function columns(): array
59 {
60 return [
61 ImageColumn::make('Avatar')
62 ->location(function($row) {
63 return asset('img/logo-'.$row->id.'.png');
64 })
65 ->attributes(function($row) {
66 return [
67 'class' => 'w-8 h-8 rounded-full',
68 ];
69 }),
70 Column::make('Order', 'sort')
71 ->sortable()
72 ->collapseOnMobile()
73 ->excludeFromColumnSelect(),
74 Column::make('ID', 'id')
75 ->sortable()
76 ->setSortingPillTitle('Key')
77 ->setSortingPillDirections('0-9', '9-0')
78 ->secondaryHeader(function($rows) {
79 return $rows->sum('id');
80 })
81 ->html(),
82 Column::make('Name')
83 ->sortable()
84 ->searchable()
85 ->view('tables.cells.actions')
86 ->secondaryHeader(function() {
87 return view('tables.cells.input-search', ['field' => 'name', 'columnSearch' => $this->columnSearch]);
88 })
89 ->html(),
90 Column::make('E-mail', 'email')
91 ->sortable()
92 ->searchable()
93 ->secondaryHeader(function() {
94 return view('tables.cells.input-search', ['field' => 'email', 'columnSearch' => $this->columnSearch]);
95 }),
96 Column::make('Address', 'address.address')
97 ->sortable()
98 ->searchable()
99 ->collapseOnTablet(),
100 Column::make('Address Group', 'address.group.name')
101 ->sortable()
102 ->searchable()
103 ->collapseOnTablet(),
104 Column::make('Group City', 'address.group.city.name')
105 ->sortable()
106 ->searchable()
107 ->collapseOnTablet(),
108 BooleanColumn::make('Active')
109 ->sortable()
110 ->collapseOnMobile(),
111 Column::make('Verified', 'email_verified_at')
112 ->sortable()
113 ->collapseOnTablet(),
114 Column::make('Tags')
115 ->label(fn($row) => $row->tags->pluck('name')->implode(', '))
116 ];
117 }
118 
119 public function filters(): array
120 {
121 return [
122 MultiSelectFilter::make('Tags')
123 ->options(
124 Tag::query()
125 ->orderBy('name')
126 ->get()
127 ->keyBy('id')
128 ->map(fn($tag) => $tag->name)
129 ->toArray()
130 )->filter(function(Builder $builder, array $values) {
131 $builder->whereHas('tags', fn($query) => $query->whereIn('tags.id', $values));
132 })
133 ->setFilterPillValues([
134 '3' => 'Tag 1',
135 ]),
136 SelectFilter::make('E-mail Verified', 'email_verified_at')
137 ->setFilterPillTitle('Verified')
138 ->options([
139 '' => 'Any',
140 'yes' => 'Yes',
141 'no' => 'No',
142 ])
143 ->filter(function(Builder $builder, string $value) {
144 if ($value === 'yes') {
145 $builder->whereNotNull('email_verified_at');
146 } elseif ($value === 'no') {
147 $builder->whereNull('email_verified_at');
148 }
149 }),
150 SelectFilter::make('Active')
151 ->setFilterPillTitle('User Status')
152 ->setFilterPillValues([
153 '1' => 'Active',
154 '0' => 'Inactive',
155 ])
156 ->options([
157 '' => 'All',
158 '1' => 'Yes',
159 '0' => 'No',
160 ])
161 ->filter(function(Builder $builder, string $value) {
162 if ($value === '1') {
163 $builder->where('active', true);
164 } elseif ($value === '0') {
165 $builder->where('active', false);
166 }
167 }),
168 SelectFilter::make('Address Group')
169 ->options([
170 '' => 'All',
171 AddressGroup::query()
172 ->orderBy('type')
173 ->get()
174 ->groupBy('type')
175 ->map(fn ($addressGroup) => $addressGroup->pluck('type', 'id')->filter())
176 ->toArray(),
177 ])
178 ->filter(function(Builder $builder, string $value) {
179 $builder->where('address_groups.type', $value);
180 }),
181 DateFilter::make('Verified From')
182 ->config([
183 'min' => '2020-01-01',
184 'max' => '2021-12-31',
185 ])
186 ->filter(function(Builder $builder, string $value) {
187 $builder->where('email_verified_at', '>=', $value);
188 }),
189 DateFilter::make('Verified To')
190 ->filter(function(Builder $builder, string $value) {
191 $builder->where('email_verified_at', '<=', $value);
192 }),
193 ];
194 }
195 
196 public function builder(): Builder
197 {
198 return User::query()
199 ->when($this->columnSearch['name'] ?? null, fn ($query, $name) => $query->where('users.name', 'like', '%' . $name . '%'))
200 ->when($this->columnSearch['email'] ?? null, fn ($query, $email) => $query->where('users.email', 'like', '%' . $email . '%'));
201 }
202 
203 public function bulkActions(): array
204 {
205 return [
206 'activate' => 'Activate',
207 'deactivate' => 'Deactivate',
208 ];
209 }
210 
211 public function activate()
212 {
213 User::whereIn('id', $this->getSelected())->update(['active' => true]);
214 
215 $this->clearSelected();
216 }
217 
218 public function deactivate()
219 {
220 User::whereIn('id', $this->getSelected())->update(['active' => false]);
221 
222 $this->clearSelected();
223 }
224 
225 public function reorder($items): void
226 {
227 foreach ($items as $item) {
228 User::find((int)$item['value'])->update(['sort' => (int)$item['order']]);
229 }
230 }
231}

input-search.blade.php

1@if (config('livewire-tables.theme') === 'tailwind')
2 <div class="flex rounded-md shadow-sm">
3 <input
4 wire:model.debounce="columnSearch.{{ $field }}"
5 placeholder="Search {{ ucfirst($field) }}"
6 type="text"
7 class="block w-full border-gray-300 rounded-md shadow-sm transition duration-150 ease-in-out sm:text-sm sm:leading-5 dark:bg-gray-800 dark:text-white dark:border-gray-600 @if (isset($columnSearch[$field]) && strlen($columnSearch[$field])) rounded-none rounded-l-md focus:ring-0 focus:border-gray-300 @else focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md @endif"
8 />
9 
10 @if (isset($columnSearch[$field]) && strlen($columnSearch[$field]))
11 <span wire:click="$set('columnSearch.{{ $field }}', null)" class="inline-flex items-center px-3 text-gray-500 border border-l-0 border-gray-300 cursor-pointer bg-gray-50 rounded-r-md sm:text-sm dark:bg-gray-700 dark:text-white dark:border-gray-600 dark:hover:bg-gray-600">
12 <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
13 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
14 </svg>
15 </span>
16 @endif
17 </div>
18@endif
19 
20@if (config('livewire-tables.theme') === 'bootstrap-4')
21 <div class="mb-3 mb-md-0 input-group">
22 <input
23 wire:model.debounce="columnSearch.{{ $field }}"
24 placeholder="Search {{ ucfirst($field) }}"
25 type="text"
26 class="form-control"
27 >
28 
29 @if (isset($columnSearch[$field]) && strlen($columnSearch[$field]))
30 <div class="input-group-append">
31 <button wire:click="$set('columnSearch.{{ $field }}', null)" class="btn btn-outline-secondary" type="button">
32 <svg style="width:.75em;height:.75em" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
33 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
34 </svg>
35 </button>
36 </div>
37 @endif
38 </div>
39@endif
40 
41@if (config('livewire-tables.theme') === 'bootstrap-5')
42 <div class="mb-3 mb-md-0 input-group">
43 <input
44 wire:model.debounce="columnSearch.{{ $field }}"
45 placeholder="Search {{ ucfirst($field) }}"
46 type="text"
47 class="form-control"
48 >
49 
50 @if (isset($columnSearch[$field]) && strlen($columnSearch[$field]))
51 <button wire:click="$set('columnSearch.{{ $field }}', null)" class="btn btn-outline-secondary" type="button">
52 <svg style="width:.75em;height:.75em" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
53 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
54 </svg>
55 </button>
56 @endif
57 </div>
58@endif