Livewire

laratribe/laravel-advanced-filters v1.0.0 · 80 products · no page reload — the panel dispatches, the table re-queries

NameSKUCategoryStatus PriceStockReleased
Orbit Pro 76 SKU-0076 Electronics Low 285.70 8 18 Aug 2026
Cobalt Mini 77 Books Out 289.40 21 7 Aug 2026
Ember Max 78 SKU-0078 Clothing In stock 293.10 34 27 Jul 2026
Quartz Lite 79 SKU-0079 Toys Low 296.80 47 16 Jul 2026
Nimbus Pro 80 SKU-0080 Electronics Out 300.50 60 5 Jul 2026
Showing 76–80 of 80
The code behind this page 3 files

Same model as the Blade page — not a line of filter logic changes. The panel dispatches an event; the results component listens and re-queries.

app/Models/Product.php identical on all three pages
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Laratribe\AdvancedFilters\Concerns\HasFilters;
use Laratribe\AdvancedFilters\Contracts\Filterable;
use Laratribe\AdvancedFilters\Filters\{DateFilter, NumericFilter, SetFilter, TextFilter};

class Product extends Model implements Filterable
{
    use HasFilters;

    protected $guarded = [];

    public $timestamps = false;

    protected function casts(): array
    {
        return ['price' => 'float', 'stock' => 'integer', 'released_at' => 'date'];
    }

    /**
     * The one place that decides what can be filtered — and therefore the allow-list.
     * Anything not declared here can never reach the query, whatever the request says.
     */
    public static function filters(): array
    {
        return [
            TextFilter::make('name', 'Name'),
            TextFilter::make('sku', 'SKU')->nullable(),
            SetFilter::make('category', 'Category')
                ->options([
                    'electronics' => 'Electronics',
                    'books' => 'Books',
                    'clothing' => 'Clothing',
                    'toys' => 'Toys',
                ])
                ->multiple(),
            SetFilter::make('status', 'Status')->options([
                'in_stock' => 'In stock',
                'low' => 'Low stock',
                'out' => 'Out of stock',
            ]),
            NumericFilter::make('price', 'Price'),
            NumericFilter::make('stock', 'Stock'),
            DateFilter::make('released_at', 'Released'),
        ];
    }
}
app/Livewire/ProductResults.php listens for the event
<?php

namespace App\Livewire;

use App\Models\Product;
use Laratribe\AdvancedFilters\Support\FilteredQuery;
use Livewire\Attributes\On;
use Livewire\Component;
use Livewire\WithPagination;

/**
 * The event-driven integration: the package's panel owns the filter rows and dispatches
 * `advanced-filters-updated`; this component listens and re-queries. The two know nothing
 * about each other.
 */
class ProductResults extends Component
{
    use WithPagination;

    /** @var array<int, array<string, mixed>> */
    public array $filters = [];

    #[On('advanced-filters-updated')]
    public function updateFilters(array $filters): void
    {
        $this->filters = $filters;
        $this->resetPage();
    }

    public function render()
    {
        return view('livewire.product-results', [
            // Re-validated here too — a hand-crafted Livewire payload can't smuggle a column in.
            'products' => FilteredQuery::for(Product::class)->withFilters($this->filters)->paginate(15),
        ]);
    }
}
resources/views/livewire-page.blade.php this page
@extends('layout')
@section('title', 'Livewire — Advanced Filters')
@section('heading', 'Livewire')
@section('frontend', 'no page reload — the panel dispatches, the table re-queries')

@push('head')
    @livewireStyles
    {{-- No CDN Alpine here: Livewire ships its own, and loading a second breaks both. --}}
    <script type="module">
        import '{{ asset('vendor/advanced-filters/advanced-filters.js') }}?v={{ filemtime(public_path('vendor/advanced-filters/advanced-filters.js')) }}'
    </script>
@endpush

@section('content')
    <div class="card">
        <livewire:advanced-filters::panel :model="\App\Models\Product::class" />
    </div>

    <livewire:product-results />

    @include('_code', [
        'intro' => 'Same model as the Blade page — <strong>not a line of filter logic changes</strong>.
                    The panel dispatches an event; the results component listens and re-queries.',
        'files' => [
            ['path' => 'app/Models/Product.php', 'note' => 'identical on all three pages'],
            ['path' => 'app/Livewire/ProductResults.php', 'note' => 'listens for the event'],
            ['path' => 'resources/views/livewire-page.blade.php', 'note' => 'this page'],
        ],
    ])
@endsection

@push('scripts')
    @livewireScripts
@endpush