In this lesson, we will add live search functionality to the table.
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:
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.
Can you further explain the need to multiply by 100 in the product price search? It is displaying the price stored in the DB divided by 100. The search works fine on the price that is displayed however because it is dividing by 100 in the search.
Not sure if I understand the question. We need to search by price in the DATABASE, which is in cents, that's why we need to multiply the search parameters by 100 to have them in cents, to match the database records.
Very nicen specialy the search parts
my five cents: there are lots of way to store money in the database, but perhaps the most common - convention adopted by famous examples such as Stripe - is storing in the database as cents, due to the problem around float-poiting numbers: compilers treat them differently and they've got thus no quite precision. Integers are more reliable
I found the query is bit complex. Can't we simplify it by using Eloquent ORM?
Not much to simplify, I think, as the task itself is pretty complex. But you can give it a try and post your suggestions :)
It is not really an improvement but my query looks like this. just got rid of the "foreach" loop. "to" is set to max price in products. If "to" is 0, query doesn't return any product. It is not bug but feature ;)
I simplified the code by moving the functionality to local scopes.
When I search by category I got the following error
Integrity constraint violation: 1052 Column 'id' in where clause is ambiguous, I am on Laravel 10
Post your full query formatted in php markdown
Solution: replace the last 'id' in the following query with 'categories.id'
Hello all! I not think it is a improvement, but I like to have a message when my filter result in no records found.
Tips: If you save your file in views/livewire dont' dorget to include livewire in your includes:
A problem that I have is that when I type something in any search field the autofocus is gone (and so is the text coursor) as soon as the search updates, so I have to click the field again to be able to continue to type in that field. Does anyone experience the sam or have I missed something?
Don't have such problem
Hello...
Nothing happens when I select from the dropdown or type something on the search inputs... All previuos lessons are working as expected, but not this one...
Here is the code:
--- Component namespace App\Livewire;
use App\Models\Category; use App\Models\Country; use App\Models\Product; use Livewire\Component; use Livewire\WithPagination; use Illuminate\Contracts\View\View;
class ProductList extends Component { use WithPagination;
}
--- View
{{ __('Productos') }}
Sorry, but if you wan't help edit your message to put code in a gist instead of hear. Other important question are you using livewire v2 or v3? They have some minor but very important differences. The most important is how requests are being sent to the server.
This Lesson does not seem to be working when using Livewire 3.4. I went through the docs but couldn't find something that helps
Because this course was made before v3.
@Nerijus, do you know a way to make it work on Livewire 3, or at least point me where in the docs should I go?
its not just about the docs but you should know how livewire works. Povilas approved to update this course for livewire v3. When it will happen cannot say. Would guess next month.
Hi Ikaro, we updated the course text in full to Livewire 3, including the repository. Good job, Nerijus.
Not sure if adding the category_product pivot table happened when the course was updated to Livewire v3 but I had to modify the query to include that table in order to get the category search to work;