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

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