Back to Course |
Practical Livewire 3: Order Management System Step-by-Step

Products Live Search

In this lesson, we will add live search functionality to the table.

filtered products

First, we will get a list of categories and countries. For this, we will need public properties for both and we will assign them in the mount() method.

app/Http/Livewire/ProductsList.php:

use App\Models\Product;
use App\Models\Country;
 
class ProductsList extends Component
{
use WithPagination;
 
public array $categories = [];
 
public array $countries = [];
 
public function mount(): void
{
$this->categories = Category::pluck('name', 'id')->toArray();
$this->countries = Country::pluck('name', 'id')->toArray();
}
 
public function render(): View
{
$products = Product::paginate(10);
 
return view('livewire.products-list', [
'products' => $products,
]);
}
}

Now let's add options to the frontend. In the blade file, in the tables thead we will add another row.

resources/views/livewire/products-list.blade.php:

<tr>
<td></td>
<td class="px-2 py-2">
<input wire:model.live.debounce="searchColumns.name" type="text" placeholder="Search..."
class="w-full text-sm rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" />
</td>
<td class="px-2 py-1">
<select wire:model.live="searchColumns.category_id"
class="w-full text-sm rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
<option value="">-- choose category --</option>
@foreach($categories as $id => $category)
<option value="{{ $id }}">{{ $category }}</option>
@endforeach
</select>
</td>
<td class="px-2 py-1">
<select wire:model.live="searchColumns.country_id"
class="w-full text-sm rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
<option value="">-- choose country --</option>
@foreach($countries as $id => $country)
<option value="{{ $id }}">{{ $country }}</option>
@endforeach
</select>
</td>
<td class="px-2 py-1 text-sm">
<div>
From
<input wire:model.live.debounce="searchColumns.price.0" type="number"
class="mr-2 w-full text-sm rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" />
</div>
<div>
to
<input wire:model.live.debounce="searchColumns.price.1" type="number"
class="w-full text-sm rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" />
</div>
</td>
<td></td>
</tr>

Now table should look like this:

products table with filters

Every filter column is binded to some property, so we need to add them to the component.

app/Livewire/ProductsList.php:

class ProductsList extends Component
{
use WithPagination;
 
public array $categories = [];
 
public array $countries = [];
 
public array $searchColumns = [
'name' => '',
'price' => ['', ''],
'description' => '',
'category_id' => 0,
'country_id' => 0,
];
 
// ...
}

Now we can make a query to filter the table.

app/Livewire/ProductsList.php:

class ProductsList extends Component
{
// ...
 
public function render(): View
{
$products = Product::paginate(10);
$products = Product::query()
->select(['products.*', 'countries.id as countryId', 'countries.name as countryName',])
->join('countries', 'countries.id', '=', 'products.country_id')
->with('categories');
 
foreach ($this->searchColumns as $column => $value) {
if (!empty($value)) {
$products->when($column == 'price', function ($products) use ($value) {
if (is_numeric($value[0])) {
$products->where('products.price', '>=', $value[0] * 100);
}
if (is_numeric($value[1])) {
$products->where('products.price', '<=', $value[1] * 100);
}
})
->when($column == 'category_id', fn($products) => $products->whereRelation('categories', 'id', $value))
->when($column == 'country_id', fn($products) => $products->whereRelation('country', 'id', $value))
->when($column == 'name', fn($products) => $products->where('products.' . $column, 'LIKE', '%' . $value . '%'));
}
}
 
return view('livewire.products-list', [
'products' => $products,
'products' => $products->paginate(10)
]);
}
}

Notice: query here is optional, I'm using it just for code formatting.

The query here isn't anything special. The special part here is that we need manually to tell what to select and we need to join countries manually, also we need to rename two fields' names from the countries table. Because we renamed them, also we need to change the value in the blade file.

<td class="px-6 py-4 text-sm leading-5 text-gray-900 whitespace-no-wrap">
{{ $product->country->name }}
{{ $product->countryName }}
</td>

Next, we go through every search column, and if its value isn't empty we continue. Then, we instead of many ifs we use conditional clauses, and if its value is true, only then the query will be made. Also, take a look when we filter by price, we need to multiply by 100, because we save the value in DB to cents, but a user can enter a value with a comma.

filtered products