# Shop: filter section

# Add form elements to the view

  • Add all form elements that we need for the filter to the view

Dropdown records per page (optional)

  • Because we are lazy developers we don't want to write all the options manually
  • A more dynamic way for building the dropdown could be:


 
 
 


<x-tmk.form.select id="perPage" 
                   class="block mt-1 w-full">
        @foreach([3,6,9,12,15,18,24] as $recordsPerPage)
             <option value="{{$recordsPerPage}}">{{$recordsPerPage}}</option>
        @endforeach
</x-tmk.form.select>
1
2
3
4
5
6

# Bind properties to form elements

  • Add 3 properties to the Shop component (one property for each form element)
  • Bind the properties to the form elements with the wire:model attribute

# Fill the genre select element

  • Get, inside the render() method, all the genres that has records and add them to the select element

IMPORTANT

We can't use @foreach($allGenres as $genre) because $genre is already a property in the component.
That's the reason why we use @foreach($allGenres as $g) instead of @foreach($allGenres as $genre)

# Update the range input element with min and max values

TIP

  • Remember that the render() method is called every time a property is updated
  • Because the minimum and maximum price is not going to change, we can use the render() method, but this will slow down the page load and that's not what we want
  • Livewire has a mount() method that is called only once, when the component is loaded and just before the first render() method is called
  • Set the min and max values of the range input element to the minimum and maximum price of the records
  • Therefor we need the extra properties $priceMin and $priceMax
    • The min and max values will also be calculated in the mount() method because they are not going to change

# Basic filter

  • Now that all elements are set up, we can start filtering the records
  • The filter will be based on the $name, $genre and $price properties and will be applied in the render() method

WARNING

  • Reload the browser and click on page 6 of the navigation
  • Search for bo and you will see that view is not properly updated Search bo
  • You're still on page 6, but the one record that was found is on page 1!
  • The paginate() method is not (yet) aware of this
  • When you use the paginate() in combination with a filter, you always have to reset the page in the updated() method
  • Add the updated() method to the component and test the filter again:
 
 
 
 








public function updated($propertyName, $propertyValue)
{
    // dump($propertyName, $propertyValue);
    $this->resetPage();
}

public function showTracks(Record $record){ ... }

public function mount() { ... }

public function render() { ... }
1
2
3
4
5
6
7
8
9
10
11

REMARKS

  • The updated($propertyName, $propertyValue) (opens new window) method is called every time a property is updated
  • The (optional) $propertyName and $propertyValue parameters contains the name and the new value of the property that was updated
  • For now we don't need the $propertyNameand $propertyValue parameters yet, but it is good practice to add them anyway
  • You can also use individual updated() methods for each property, e.g.
    • updatedName($value), updatedGenre($value), updatedPrice($value), updatedPerPage($value), updatedAllGenres($value), ...

# Advanced filter

# orWhere() clause

  • How can we use the $name property to search for a record title OR artist?

# Create a query scope for the orWhere() clause

  • There is only one line of code that is different between the where() and orWhere() clauses
  • The more lines of code you have to repeat, the more difficult it is to maintain your code
  • So this is a good moment to create a query scope for the search in title or artist attributes
  • Open the app/Models/Record.php model and add an extra scope method

Type hinting

  • Got to the menu Laravel > Generate Helper Code to regenerate the updated type hinting for the Record model
  • Select searchTitleOrArtist() (NOT scopeSearchTitleOrArtist()) from the list
    Type hinting

# Give feedback if the result is empty

  • It's a good practice to give some feedback to the user if the result is empty
  • We'll do this by adding an alert box below the form
  • Use the Blade @if directive in combination with the isEmpty() (opens new window) method to show the alert only if the result is empty

# Update the pagination behavior

  • There is one little problem with the pagination
    1. Reset the filter and go to the last page
    2. Open on the details of a record
    3. Close the modal
  • After that, we're redirected back to the first page, because with every change in one of the properties, the updated() method is called: public function updated() { $this->resetPage(); }
  • We only want to reset the page when the filter has been changed, not when anything else is changed
  • So we need to use a more specific paginator behavior inside the updated() method
    • The paginator will only reset the page when the $perPage, $name, $genre or $price property has changed
  • Update the updated() method to:



 



public function updated($propertyName, $propertyValue)
{
    // dump($propertyName, $propertyValue);
    if (in_array($propertyName, ['perPage', 'name', 'genre', 'price']))
        $this->resetPage();
}
1
2
3
4
5
6

# Better UX

  • With a few minor changes, you can increase the user experience (UX) significantly

# Debounce the input fields

  • Every keystroke in a filter field and moving the price slider, triggers a new search and a roundtrip to the server
  • This behavior is very annoying (and slow)
  • This can be solved by debouncing (opens new window) the input fields:
    • Line 3: replace wire:model="name" with wire:model.debounce.500ms="name"
    • Line 8: replace wire:model="price" with wire:model.debounce.500ms="price"


 




 






...
<x-jet-input id="name" type="text"
     wire:model.debounce.500ms="name"
     class="block mt-1 w-full"
     placeholder="Filter Artist Or Record"/>
...  
<x-jet-input type="range" id="price" name="price
     wire:model.debounce.500ms="price"
     min="{{ $priceMin }}"
     max="{{ $priceMax }}"
     oninput="pricefilter.value = price.value"
     class="block mt-4 w-full h-2 bg-indigo-100 accent-indigo-600 appearance-none"/>
...                            
1
2
3
4
5
6
7
8
9
10
11
12
13

# Clear the filter field

  • We already placed a little X over the input field to clear the filter but it's not working yet
  • Make an Alpine component:
    • Line 2: initialize an Alpine component with the x-data attribute
      Bind (entangle) the Alpine name property to the $name property of the Livewire component
    • LIne 5 - 6: replace the server side Livewire bounding (wire:model) with the client side Alpine bounding (x-model)
    • Line 10: the div with the X is only visible when the $name property is not empty
    • Line 11: add a @click event to the X to empty the value of the input field with the name = '' statement

 


 
 



 
 





<div
    x-data="{ name: @entangle('name') }"
    class="relative">
    <x-jet-input id="name" type="text"
                 x-model.debounce.500ms="name"
                 {{--wire:model.debounce.500ms="name"--}}
                 class="block mt-1 w-full"
                 placeholder="Filter Artist Or Record"/>
    <div
        x-show="name"
        @click="name = '';"
        class="w-5 absolute right-4 top-3 cursor-pointer">
        <x-phosphor-x-duotone/>
    </div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Clear the filter

# EXERCISES

# 1: Alternative feedback message

  • Show the selected genre and the price within the feedback message Exercise 1

# 2: iTunes top songs Belgium

# Basic version

TIP

This is a live feed and the content changes daily. Compare your result with the live preview (@it-fact.be)

# Advanced version

Last Updated: 11/16/2022, 11:04:20 AM