laratribe/laravel-advanced-filters v1.0.0 · 80 products ·
no page reload — the panel dispatches, the table re-queries
Select column
| Name | SKU | Category | Status | Price | Stock | Released |
|---|---|---|---|---|---|---|
| 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 |
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