Merge pull request #17160 from filamentphp/v4-Add-filter-indicator-renderhook

V4 add filter indicator renderhook
This commit is contained in:
Dan Harrin
2025-08-02 13:46:48 +01:00
committed by GitHub
5 changed files with 108 additions and 66 deletions
+22 -1
View File
@@ -132,6 +132,7 @@ All these render hooks [can be scoped](#scoping-render-hooks) to any table Livew
use Filament\Tables\View\TablesRenderHook;
```
- `TablesRenderHook::FILTER_INDICATORS` - Replace the existing filter indicators, receives `filterIndicators` data as `array<Filament\Tables\Filters\Indicator>`
- `TablesRenderHook::SELECTION_INDICATOR_ACTIONS_AFTER` - After the "select all" and "deselect all" action buttons in the selection indicator bar
- `TablesRenderHook::SELECTION_INDICATOR_ACTIONS_BEFORE` - Before the "select all" and "deselect all" action buttons in the selection indicator bar
- `TablesRenderHook::HEADER_AFTER` - After the header container
@@ -220,6 +221,20 @@ FilamentView::registerRenderHook(
);
```
## Passing data to render hooks
Render hooks can receive "data" from when the hook is rendered. To access data from a render hook, you can inject it using an `array $data` parameter to the hook's rendering function:
```php
use Filament\Support\Facades\FilamentView;
use Filament\Tables\View\TablesRenderHook;
FilamentView::registerRenderHook(
TablesRenderHook::FILTER_INDICATORS,
fn (array $data): View => view('filter-indicators', ['indicators' => $data['filterIndicators']]),
);
```
## Rendering hooks
Plugin developers might find it useful to expose render hooks to their users. You do not need to register them anywhere, simply output them in Blade like so:
@@ -228,7 +243,7 @@ Plugin developers might find it useful to expose render hooks to their users. Yo
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::PAGE_START) }}
```
To provide scope your render hook, you can pass it as the second argument to `renderHook()`. For instance, if your hook is inside a Livewire component, you can pass the class of the component using `static::class`:
To provide [scope](#scoping-render-hooks) your render hook, you can pass it as the second argument to `renderHook()`. For instance, if your hook is inside a Livewire component, you can pass the class of the component using `static::class`:
```blade
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::PAGE_START, scopes: $this->getRenderHookScopes()) }}
@@ -239,3 +254,9 @@ You can even pass multiple scopes as an array, and all render hooks that match a
```blade
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::PAGE_START, scopes: [static::class, \App\Filament\Resources\Users\UserResource::class]) }}
```
You can pass [data](#passing-data-to-render-hooks) to a render hook using a `data` argument to the `renderHook()` function:
```blade
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\Tables\View\TablesRenderHook::FILTER_INDICATORS, data: ['filterIndicators' => $filterIndicators]) }}
```
+6 -3
View File
@@ -41,14 +41,15 @@ class ViewManager
/**
* @param string | array<string> | null $scopes
* @param array<string, mixed> $data
*/
public function renderHook(string $name, string | array | null $scopes = null): Htmlable
public function renderHook(string $name, string | array | null $scopes = null, array $data = []): Htmlable
{
$renderedHooks = [];
$scopes = Arr::wrap($scopes);
$renderHook = function (callable $hook) use (&$renderedHooks, $scopes): ?string {
$renderHook = function (callable $hook) use (&$renderedHooks, $scopes, $data): ?string {
$hookId = spl_object_id($hook);
if (in_array($hookId, $renderedHooks)) {
@@ -57,7 +58,9 @@ class ViewManager
$renderedHooks[] = $hookId;
return (string) app()->call($hook, ['scopes' => $scopes]);
$result = app()->call($hook, ['data' => $data, 'scopes' => $scopes]);
return (string) $result;
};
$hooks = array_map(
+65 -59
View File
@@ -2,12 +2,14 @@
use Filament\Support\Enums\Alignment;
use Filament\Support\Enums\VerticalAlignment;
use Filament\Support\Enums\Width;
use Filament\Support\Facades\FilamentView;
use Filament\Tables\Actions\HeaderActionsPosition;
use Filament\Tables\Columns\Column;
use Filament\Tables\Columns\ColumnGroup;
use Filament\Tables\Enums\FiltersLayout;
use Filament\Tables\Enums\RecordActionsPosition;
use Filament\Tables\Enums\RecordCheckboxPosition;
use Filament\Tables\View\TablesRenderHook;
use Illuminate\Support\Str;
use Illuminate\View\ComponentAttributeBag;
@@ -163,7 +165,7 @@
x-show="@js($hasHeader) || @js($hasNonBulkToolbarAction) || (getSelectedRecordsCount() && @js(count($toolbarActions)))"
class="fi-ta-header-ctn"
>
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\Tables\View\TablesRenderHook::HEADER_BEFORE, scopes: static::class) }}
{{ FilamentView::renderHook(TablesRenderHook::HEADER_BEFORE, scopes: static::class) }}
@if ($header)
{{ $header }}
@@ -202,7 +204,7 @@
</div>
@endif
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\Tables\View\TablesRenderHook::HEADER_AFTER, scopes: static::class) }}
{{ FilamentView::renderHook(TablesRenderHook::HEADER_AFTER, scopes: static::class) }}
@if ($hasFiltersAboveContent)
<div
@@ -231,23 +233,23 @@
</div>
@endif
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\Tables\View\TablesRenderHook::TOOLBAR_BEFORE, scopes: static::class) }}
{{ FilamentView::renderHook(TablesRenderHook::TOOLBAR_BEFORE, scopes: static::class) }}
<div
@if (! $hasHeaderToolbar) x-cloak @endif
x-show="@js($hasHeaderToolbar) || @js($hasNonBulkToolbarAction) || (getSelectedRecordsCount() && @js(count($toolbarActions)))"
class="fi-ta-header-toolbar"
>
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\Tables\View\TablesRenderHook::TOOLBAR_START, scopes: static::class) }}
{{ FilamentView::renderHook(TablesRenderHook::TOOLBAR_START, scopes: static::class) }}
<div class="fi-ta-actions fi-align-start fi-wrapped">
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\Tables\View\TablesRenderHook::TOOLBAR_REORDER_TRIGGER_BEFORE, scopes: static::class) }}
{{ FilamentView::renderHook(TablesRenderHook::TOOLBAR_REORDER_TRIGGER_BEFORE, scopes: static::class) }}
@if ($isReorderable)
{{ $reorderRecordsTriggerAction }}
@endif
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\Tables\View\TablesRenderHook::TOOLBAR_REORDER_TRIGGER_AFTER, scopes: static::class) }}
{{ FilamentView::renderHook(TablesRenderHook::TOOLBAR_REORDER_TRIGGER_AFTER, scopes: static::class) }}
@if ((! $isReordering) && count($toolbarActions))
@foreach ($toolbarActions as $action)
@@ -255,7 +257,7 @@
@endforeach
@endif
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\Tables\View\TablesRenderHook::TOOLBAR_GROUPING_SELECTOR_BEFORE, scopes: static::class) }}
{{ FilamentView::renderHook(TablesRenderHook::TOOLBAR_GROUPING_SELECTOR_BEFORE, scopes: static::class) }}
@if ($areGroupingSettingsVisible)
<div
@@ -398,12 +400,12 @@
</div>
@endif
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\Tables\View\TablesRenderHook::TOOLBAR_GROUPING_SELECTOR_AFTER, scopes: static::class) }}
{{ FilamentView::renderHook(TablesRenderHook::TOOLBAR_GROUPING_SELECTOR_AFTER, scopes: static::class) }}
</div>
@if ($isGlobalSearchVisible || $hasFiltersDialog || $hasColumnManagerDropdown)
<div>
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\Tables\View\TablesRenderHook::TOOLBAR_SEARCH_BEFORE, scopes: static::class) }}
{{ FilamentView::renderHook(TablesRenderHook::TOOLBAR_SEARCH_BEFORE, scopes: static::class) }}
@if ($isGlobalSearchVisible)
@php
@@ -417,7 +419,7 @@
/>
@endif
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\Tables\View\TablesRenderHook::TOOLBAR_SEARCH_AFTER, scopes: static::class) }}
{{ FilamentView::renderHook(TablesRenderHook::TOOLBAR_SEARCH_AFTER, scopes: static::class) }}
@if ($hasFiltersDialog || $hasColumnManagerDropdown)
@if ($hasFiltersDialog)
@@ -494,7 +496,7 @@
@endif
@endif
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\Tables\View\TablesRenderHook::TOOLBAR_COLUMN_MANAGER_TRIGGER_BEFORE, scopes: static::class) }}
{{ FilamentView::renderHook(TablesRenderHook::TOOLBAR_COLUMN_MANAGER_TRIGGER_BEFORE, scopes: static::class) }}
@if ($hasColumnManagerDropdown)
@php
@@ -526,15 +528,15 @@
</x-filament::dropdown>
@endif
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\Tables\View\TablesRenderHook::TOOLBAR_COLUMN_MANAGER_TRIGGER_AFTER, scopes: static::class) }}
{{ FilamentView::renderHook(TablesRenderHook::TOOLBAR_COLUMN_MANAGER_TRIGGER_AFTER, scopes: static::class) }}
@endif
</div>
@endif
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\Tables\View\TablesRenderHook::TOOLBAR_END) }}
{{ FilamentView::renderHook(TablesRenderHook::TOOLBAR_END) }}
</div>
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\Tables\View\TablesRenderHook::TOOLBAR_AFTER) }}
{{ FilamentView::renderHook(TablesRenderHook::TOOLBAR_AFTER) }}
</div>
@if ($isReordering)
@@ -578,7 +580,7 @@
@if (! $isSelectionDisabled)
<div>
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\Tables\View\TablesRenderHook::SELECTION_INDICATOR_ACTIONS_BEFORE, scopes: static::class) }}
{{ FilamentView::renderHook(TablesRenderHook::SELECTION_INDICATOR_ACTIONS_BEFORE, scopes: static::class) }}
<div class="fi-ta-selection-indicator-actions-ctn">
<x-filament::link
@@ -601,62 +603,66 @@
</x-filament::link>
</div>
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\Tables\View\TablesRenderHook::SELECTION_INDICATOR_ACTIONS_AFTER, scopes: static::class) }}
{{ FilamentView::renderHook(TablesRenderHook::SELECTION_INDICATOR_ACTIONS_AFTER, scopes: static::class) }}
</div>
@endif
</div>
@endif
@if ($filterIndicators)
<div class="fi-ta-filter-indicators">
<div>
<span class="fi-ta-filter-indicators-label">
{{ __('filament-tables::table.filters.indicator') }}
</span>
@if ($filterIndicatorsView = FilamentView::renderHook(TablesRenderHook::FILTER_INDICATORS, scopes: static::class, data: ['filterIndicators' => $filterIndicators]))
{{ $filterIndicatorsView }}
@else
<div class="fi-ta-filter-indicators">
<div>
<span class="fi-ta-filter-indicators-label">
{{ __('filament-tables::table.filters.indicator') }}
</span>
<div class="fi-ta-filter-indicators-badges-ctn">
@foreach ($filterIndicators as $indicator)
@php
$indicatorColor = $indicator->getColor();
@endphp
<div class="fi-ta-filter-indicators-badges-ctn">
@foreach ($filterIndicators as $indicator)
@php
$indicatorColor = $indicator->getColor();
@endphp
<x-filament::badge :color="$indicatorColor">
{{ $indicator->getLabel() }}
<x-filament::badge :color="$indicatorColor">
{{ $indicator->getLabel() }}
@if ($indicator->isRemovable())
@php
$indicatorRemoveLivewireClickHandler = $indicator->getRemoveLivewireClickHandler();
@endphp
@if ($indicator->isRemovable())
@php
$indicatorRemoveLivewireClickHandler = $indicator->getRemoveLivewireClickHandler();
@endphp
<x-slot
name="deleteButton"
:label="__('filament-tables::table.filters.actions.remove.label')"
:wire:click="$indicatorRemoveLivewireClickHandler"
wire:loading.attr="disabled"
wire:target="removeTableFilter"
></x-slot>
@endif
</x-filament::badge>
@endforeach
<x-slot
name="deleteButton"
:label="__('filament-tables::table.filters.actions.remove.label')"
:wire:click="$indicatorRemoveLivewireClickHandler"
wire:loading.attr="disabled"
wire:target="removeTableFilter"
></x-slot>
@endif
</x-filament::badge>
@endforeach
</div>
</div>
</div>
@if (collect($filterIndicators)->contains(fn (\Filament\Tables\Filters\Indicator $indicator): bool => $indicator->isRemovable()))
<button
type="button"
x-tooltip="{
content: @js(__('filament-tables::table.filters.actions.remove_all.tooltip')),
theme: $store.theme,
}"
wire:click="removeTableFilters"
wire:loading.attr="disabled"
wire:target="removeTableFilters,removeTableFilter"
class="fi-icon-btn fi-size-sm"
>
{{ \Filament\Support\generate_icon_html(\Filament\Support\Icons\Heroicon::XMark, alias: \Filament\Tables\View\TablesIconAlias::FILTERS_REMOVE_ALL_BUTTON, size: \Filament\Support\Enums\IconSize::Small) }}
</button>
@endif
</div>
@if (collect($filterIndicators)->contains(fn (\Filament\Tables\Filters\Indicator $indicator): bool => $indicator->isRemovable()))
<button
type="button"
x-tooltip="{
content: @js(__('filament-tables::table.filters.actions.remove_all.tooltip')),
theme: $store.theme,
}"
wire:click="removeTableFilters"
wire:loading.attr="disabled"
wire:target="removeTableFilters,removeTableFilter"
class="fi-icon-btn fi-size-sm"
>
{{ \Filament\Support\generate_icon_html(\Filament\Support\Icons\Heroicon::XMark, alias: \Filament\Tables\View\TablesIconAlias::FILTERS_REMOVE_ALL_BUTTON, size: \Filament\Support\Enums\IconSize::Small) }}
</button>
@endif
</div>
@endif
@endif
@if (((! $content) && (! $hasColumnsLayout)) || ($records === null) || count($records))
@@ -36,6 +36,8 @@ class TablesRenderHook
const TOOLBAR_START = 'tables::toolbar.start';
const FILTER_INDICATORS = 'tables::filter.indicators';
/**
* @deprecated Use `TOOLBAR_COLUMN_MANAGER_TRIGGER_AFTER` instead.
*/
+13 -3
View File
@@ -28,7 +28,7 @@ test('render hooks can render view files', function (): void {
->toHtml()->toContain('bar');
});
test('render hooks can be scopes:d', function (): void {
test('render hooks can be scoped', function (): void {
FilamentView::registerRenderHook('foo', function (): string {
return Blade::render('bar');
});
@@ -42,7 +42,7 @@ test('render hooks can be scopes:d', function (): void {
->toHtml()->toBe('barbar');
});
test('render hooks can be scopes:d to multiple scopes:s', function (): void {
test('render hooks can be scoped to multiple scoped', function (): void {
FilamentView::registerRenderHook('foo', function (): string {
return Blade::render('bar');
});
@@ -60,7 +60,7 @@ test('render hooks can be scopes:d to multiple scopes:s', function (): void {
->toHtml()->toBe('barbar');
});
test('render hooks can be scopes:d to multiple scopes:s but only ever output once', function (): void {
test('render hooks can be scoped to multiple scoped but only ever output once', function (): void {
FilamentView::registerRenderHook('foo', function (): string {
return Blade::render('bar');
});
@@ -73,3 +73,13 @@ test('render hooks can be scopes:d to multiple scopes:s but only ever output onc
->toBeInstanceOf(HtmlString::class)
->toHtml()->toBe('barbar');
});
test('render hooks can be passed data', function (): void {
FilamentView::registerRenderHook('foo', function ($data): string {
return $data['foo'];
});
expect(FilamentView::renderHook('foo', data: ['foo' => 'bar']))
->toBeInstanceOf(HtmlString::class)
->toHtml()->toBe('bar');
});