skip to Main Content

I’m building a full page Livewire component that filters Meilisearch results.

When I check one of the filters the remaning checkboxes disappear, and I am struggling to understand why. I followed a tutorial to figure out how to build the bulk of the component and in the tutorial the behaviour didn’t occur. I’ve deconstructed the component to the bare minimum and the behaviour happens as soon as I wire up the checkboxes.

Here is the component:

 <?php

namespace AppHttpLivewire;

use LivewireComponent;
use AppModelsCourseDate;
use AppModelsCourse;

class CourseBrowser extends Component
{

    public $queryFilters = [];

    public $priceRange = [
        'min' => null,
        'max' => null
    ];

    public function mount()
    {
        $this->queryFilters = [
            'venue' => [],
            'type' => [],
            'category' => [],
            'days'  => [],
            'supplier' => []
        ];
    }

    public function render()
    {

        $search = CourseDate::search(
            '',
            function ($meilisearch, string $query, array $options) {

                $filters = collect($this->queryFilters)
                    ->filter(fn ($filter) => !empty($filter))
                    ->recursive()
                    ->map(function ($value, $key) {
                        return $value->map(fn ($value) => $key . ' = "' . $value . '"');
                    })
                    ->flatten()
                    ->join(' AND ');

                $options['facets'] = ['venue', 'category', 'type', 'supplier', 'days'];
                $options['filter'] = null;

                if ($filters) {
                    $options['filter'] = $filters;
                }

                if ($this->priceRange['max']) {
                    $options['filter'] .= (isset($options['filter']) ? ' AND ' : '') . 'price <= ' . $this->priceRange['max'];
                }

                return $meilisearch->search($query, $options);
            }
        )->raw();

        $coursedates = CourseDate::find(collect($search['hits'])->pluck('id'));

        $minPrice = Course::all()->min('price');
        $maxPrice = Course::all()->max('price');

        $this->priceRange['min'] = $this->priceRange['min'] ?: $minPrice;
        $this->priceRange['max'] = $this->priceRange['max'] ?: $maxPrice;

        return view('livewire.course-browser')
            ->with(
                [
                    'coursedates' => $coursedates,
                    'filters' => $search['facetDistribution'],
                    'minPrice' => $minPrice,
                    'maxPrice' => $maxPrice,
                ]
            );
    }
}

Here is the Blade view. I had originally posted an edited section showing just the checkboxes but I have edited this question to show the view in its entirety.


<div>

    <div class="mb-4 text-gray-500">
        <a href="{{ route('home') }}">Home</a> / <a href="{{ route('course.index') }}">Courses</a>
    </div>

    <div class="align-center mt-8 mb-10 flex items-center justify-between">
        <div class="text-3xl font-bold">
            All Courses
        </div>
    </div>
    <div class="grid w-full grid-cols-4 gap-6">
        <div class="col-span-1 rounded-sm bg-white shadow">
            <div class="space-y-2 px-6 py-6">
                <h6 class="text-sm font-bold uppercase">Filters</h6>
                <hr class="mt-4 mb-6 border-gray-200 pb-2">
                <h6 class="text-sm font-bold uppercase">Price </h6>
                <div class="flex gap-1">
                    <input type="range"
                           min="{{ $minPrice }}"
                           max="{{ $maxPrice }}"
                           step="25"
                           class="accent-pink-700"
                           wire:model="priceRange.max" />
                    <span class="font-sm">
                        (£{{ $priceRange['max'] }})
                    </span>
                </div>
                @foreach ($filters as $title => $filter)
                    <h6 class="pt-2 text-sm font-bold uppercase">{{ Str::title($title) }}</h6>
                    @foreach ($filter as $option => $count)
                        <div wire:ignore.self
                             class="flex items-center space-x-2">
                            <input type="checkbox"
                                   class="h-4 w-4 rounded border-gray-300 text-sm text-pink-600 focus:ring-0 focus:ring-offset-0"
                                   wire:model="queryFilters.{{ $title }}"
                                   id="{{ $title }}_{{ strtolower($option) }}"
                                   value="{{ $option }}">
                            <label class="text-sm"
                                   for="{{ $title }}_{{ strtolower($option) }}">{{ $option }} ({{ $count }})</label>
                        </div>
                    @endforeach
                @endforeach
            </div>
        </div>

        <div class="col-span-3">
            <h6 class="text-md my-4 mb-4">{{ $coursedates->count() }} {{ Str::plural('course', $coursedates) }} matching your filters</h6>
            @forelse ($coursedates as $coursedate)
                <div wire:loading.class="opacity-50"
                     class="relative mb-6 w-full rounded-sm bg-white shadow">
                    <span class="absolute top-2 left-2 rounded bg-pink-800 px-3 py-1 font-bold text-white">{{ $coursedate->course->category->name }}</span>
                    <span class="absolute top-2 right-2 rounded bg-pink-600 px-3 py-1 font-bold text-white">{{ $coursedate->course->supplier->name }}</span>
                    <a href="{{ route('course.show', $coursedate->course->slug) }}">
                        <img src="{{ asset('storage/courses/images/' . $coursedate->course->title_image) }}"
                             alt="{{ $coursedate->course->slug }}"
                             class="h-40 w-full object-cover">
                    </a>

                    <div class="px-6 py-4">
                        <h6 class="mb-2 text-lg font-bold"><a href="{{ route('course.show', $coursedate->course->slug) }}">{{ $coursedate->course->name }} </a></h6>
                        <p class="mb-6 text-sm leading-tight">{{ $coursedate->course->tagline }}</p>
                        {{-- Pills Container --}}
                        <div class="flex">
                            <div class="mr-1 flex items-center rounded bg-pink-300 font-bold text-pink-800">
                                <div class="flex h-full items-center rounded-l bg-pink-400 px-2 py-1">
                                    <svg xmlns="http://www.w3.org/2000/svg"
                                         viewBox="0 0 24 24"
                                         fill="currentColor"
                                         class="h-4 w-4 text-pink-200">
                                        <path d="M4.5 3.75a3 3 0 00-3 3v.75h21v-.75a3 3 0 00-3-3h-15z" />
                                        <path fill-rule="evenodd"
                                              d="M22.5 9.75h-21v7.5a3 3 0 003 3h15a3 3 0 003-3v-7.5zm-18 3.75a.75.75 0 01.75-.75h6a.75.75 0 010 1.5h-6a.75.75 0 01-.75-.75zm.75 2.25a.75.75 0 000 1.5h3a.75.75 0 000-1.5h-3z"
                                              clip-rule="evenodd" />
                                    </svg>
                                </div>
                                <div class="flex h-full items-center rounded bg-pink-300 px-3 py-1">
                                    £{{ $coursedate->course->price }}
                                </div>
                            </div>

                            <div class="mr-1 flex items-center rounded bg-pink-500 font-bold text-white">
                                <div class="flex h-full items-center rounded-l bg-pink-600 px-2 py-1">
                                    <svg xmlns="http://www.w3.org/2000/svg"
                                         viewBox="0 0 24 24"
                                         fill="currentColor"
                                         class="h-4 w-4">
                                        <path fill-rule="evenodd"
                                              d="M11.54 22.351l.07.04.028.016a.76.76 0 00.723 0l.028-.015.071-.041a16.975 16.975 0 001.144-.742 19.58 19.58 0 002.683-2.282c1.944-1.99 3.963-4.98 3.963-8.827a8.25 8.25 0 00-16.5 0c0 3.846 2.02 6.837 3.963 8.827a19.58 19.58 0 002.682 2.282 16.975 16.975 0 001.145.742zM12 13.5a3 3 0 100-6 3 3 0 000 6z"
                                              clip-rule="evenodd" />
                                    </svg>
                                </div>
                                <div class="flex h-full items-center rounded bg-pink-500 px-3 py-1">
                                    {{ $coursedate->venue->city }}
                                </div>
                            </div>
                            @foreach ($coursedate->actualdates as $dates)
                                @if ($coursedate->course->number_of_days > 1)
                                    <div class="mr-1 flex items-center rounded bg-pink-200 font-bold text-pink-800">
                                        <div class="flex h-full items-center gap-1 rounded-l bg-pink-200 px-2 py-1 text-xs">
                                            <svg xmlns="http://www.w3.org/2000/svg"
                                                 viewBox="0 0 24 24"
                                                 fill="currentColor"
                                                 class="inline-flex h-5 w-5">
                                                <path d="M12.75 12.75a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM7.5 15.75a.75.75 0 100-1.5.75.75 0 000 1.5zM8.25 17.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM9.75 15.75a.75.75 0 100-1.5.75.75 0 000 1.5zM10.5 17.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM12 15.75a.75.75 0 100-1.5.75.75 0 000 1.5zM12.75 17.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM14.25 15.75a.75.75 0 100-1.5.75.75 0 000 1.5zM15 17.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM16.5 15.75a.75.75 0 100-1.5.75.75 0 000 1.5zM15 12.75a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM16.5 13.5a.75.75 0 100-1.5.75.75 0 000 1.5z" />
                                                <path fill-rule="evenodd"
                                                      d="M6.75 2.25A.75.75 0 017.5 3v1.5h9V3A.75.75 0 0118 3v1.5h.75a3 3 0 013 3v11.25a3 3 0 01-3 3H5.25a3 3 0 01-3-3V7.5a3 3 0 013-3H6V3a.75.75 0 01.75-.75zm13.5 9a1.5 1.5 0 00-1.5-1.5H5.25a1.5 1.5 0 00-1.5 1.5v7.5a1.5 1.5 0 001.5 1.5h13.5a1.5 1.5 0 001.5-1.5v-7.5z"
                                                      clip-rule="evenodd" />
                                            </svg>
                                            {{ $dates->label }}
                                        </div>
                                        <div class="flex h-full items-center rounded-r bg-pink-700 px-2 py-1 text-pink-100">
                                            {{ $dates->date->format('jS M Y') }}
                                        </div>
                                    </div>
                                @else
                                    <div class="mr-1 flex items-center rounded bg-pink-200 text-sm font-bold text-pink-800">
                                        <div class="flex h-full rounded-l bg-pink-200 py-1 px-2 text-sm font-bold text-pink-800">
                                            <svg xmlns="http://www.w3.org/2000/svg"
                                                 viewBox="0 0 24 24"
                                                 fill="currentColor"
                                                 class="inline-flex h-5 w-5">
                                                <path d="M12.75 12.75a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM7.5 15.75a.75.75 0 100-1.5.75.75 0 000 1.5zM8.25 17.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM9.75 15.75a.75.75 0 100-1.5.75.75 0 000 1.5zM10.5 17.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM12 15.75a.75.75 0 100-1.5.75.75 0 000 1.5zM12.75 17.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM14.25 15.75a.75.75 0 100-1.5.75.75 0 000 1.5zM15 17.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM16.5 15.75a.75.75 0 100-1.5.75.75 0 000 1.5zM15 12.75a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM16.5 13.5a.75.75 0 100-1.5.75.75 0 000 1.5z" />
                                                <path fill-rule="evenodd"
                                                      d="M6.75 2.25A.75.75 0 017.5 3v1.5h9V3A.75.75 0 0118 3v1.5h.75a3 3 0 013 3v11.25a3 3 0 01-3 3H5.25a3 3 0 01-3-3V7.5a3 3 0 013-3H6V3a.75.75 0 01.75-.75zm13.5 9a1.5 1.5 0 00-1.5-1.5H5.25a1.5 1.5 0 00-1.5 1.5v7.5a1.5 1.5 0 001.5 1.5h13.5a1.5 1.5 0 001.5-1.5v-7.5z"
                                                      clip-rule="evenodd" />
                                            </svg>
                                        </div>
                                        <div class="flex h-full items-center rounded-r bg-pink-700 px-2 py-1 text-pink-100"> {{ $dates->date->format('jS M Y') }} </div>
                                    </div>
                                @endif
                            @endforeach
                        </div>
                        <div class="mt-6 mb-4">
                        </div>
                    </div>
                </div>
            @empty
                No Course Dates to show
            @endforelse
        </div>
    </div>
</div>


The component in itself works fine. But as you can see in the gif below, the non selected checkboxes disappear, so I cannot apply multiple filters.

Checkboxes that arent selected disappear

I suspect the problem lies in the way I’m storing the filters in the array but I can’t see the woods for the trees at the moment. The filtering itself currently works, and any attempt to change the format of the array results in it not working at all.

Thanks in advance.

3

Answers


  1. Chosen as BEST ANSWER

    I've dumped the contents of the array as it's being filled up on the page

    The array contents using var_export

    Each time you check a box it adds the value into the array. Obviously In the case of the Venue group of checkboxes I can't add a second value because the unselected ones disappear. But what I'd expect is an array that looks like this for example:

    
     $this->queryFilters = [
                'venue' => ['Norwich', 'Peterborough'],
                'type' => ['In Person'],
                'category' => ['Hair'],
                'days'  => [],
                'supplier' => []
            ];
    
    

    I'd then map through the values and build the filter string for Mielesearch, which happens in the closure futher down.

    "venue = "Norwich" AND venue = "Peterborough" AND type = "In Person" AND category = "Hair"" 
    

  2. There are certain bugs in Livewire, and this is also one of them.

    Try enclosing the each checkbox in a div.

     <div class="">
     <input type="checkbox" wire:model="queryFilters.{{ $title }}"
                                 id="{{ $title }}_{{ strtolower($option) }}"
                                 value="{{ $option }}">
    </div>
    

    If the issue still presist, add wire:ignore.self to container

    <div class="" wire:ignore.self>
    ......
    
    </div>
    
    Login or Signup to reply.
  3. After some perusal of the Meilisearch docs:

    facetDistribution contains an object for every given facet. For each of these facets, there is another object containing all the different values and the count of matching documents. Note that zero values will not be returned: if there are no romance movies matching the query, romance is not displayed.

    That suggests that the behaviour is normal 😳

    On updating the search results, the facetDistribution array is updated with the filters belonging to the results that have been returned, and doesn’t retain the ones with a zero value.

    Before filtering:

     "facetDistribution":{
          "category":{
             "Hair":8
          },
          "days":{
             "1":4,
             "2":4
          },
          "supplier":{
             "Matrix":8
          },
          "type":{
             "In Person":8
          },
          "venue":{
             "Colchester":2,
             "Ipswich":1,
             "Norwich":1,
             "Peterborough":1,
             "Romford":3
          }
       },
    

    After filtering:

     "facetDistribution":{
          "category":{
             "Hair":2
          },
          "days":{
             "1":1,
             "2":1
          },
          "supplier":{
             "Matrix":2
          },
          "type":{
             "In Person":2
          },
          "venue":{
             "Colchester":2
          }
       },
    

    To be honest, I’d expected to retain the filters with a value of zero so I could re-filter and find courses that were present in Colchester OR Norwich, for example. In fact the tutorial I learned the process from appeared to show this behaviour.

    I’m going to rewatch it again in case I misunderstood something!

    Or perhaps the older version of Meilisearch being used in the tutorial allowed this?

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search