Livewire

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

NameSKUCategoryStatus PriceStockReleased
Cobalt Mini 61 SKU-0061 Books Low 230.20 93 26 Dec 2025
Ember Max 62 SKU-0062 Clothing Out 233.90 106 15 Dec 2025
Quartz Lite 63 Toys In stock 237.60 119 4 Dec 2025
Nimbus Pro 64 SKU-0064 Electronics Low 241.30 132 23 Nov 2025
Vertex Mini 65 SKU-0065 Books Out 245.00 5 12 Nov 2025
Lumen Max 66 SKU-0066 Clothing In stock 248.70 18 1 Nov 2025
Atlas Lite 67 SKU-0067 Toys Low 252.40 31 21 Oct 2025
Orbit Pro 68 SKU-0068 Electronics Out 256.10 44 10 Oct 2025
Cobalt Mini 69 SKU-0069 Books In stock 259.80 57 29 Sep 2025
Ember Max 70 Clothing Low 263.50 70 18 Sep 2025
Quartz Lite 71 SKU-0071 Toys Out 267.20 83 7 Sep 2025
Nimbus Pro 72 SKU-0072 Electronics In stock 270.90 96 27 Aug 2025
Vertex Mini 73 SKU-0073 Books Low 274.60 109 20 Sep 2026
Lumen Max 74 SKU-0074 Clothing Out 278.30 122 9 Sep 2026
Atlas Lite 75 SKU-0075 Toys In stock 282.00 135 29 Aug 2026
Showing 61–75 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