This commit is contained in:
Dan Harrin
2025-03-24 21:16:15 +00:00
parent cddfcbd384
commit b97ed63ba8
31 changed files with 841 additions and 560 deletions
+3 -3
View File
@@ -763,7 +763,7 @@ class TablesDemo extends Component implements HasActions, HasSchemas, HasTable
return $this->filtersTable($table)
->filters([
Filter::make('created_at')
->form([
->schema([
DatePicker::make('created_from'),
DatePicker::make('created_until'),
]),
@@ -791,7 +791,7 @@ class TablesDemo extends Component implements HasActions, HasSchemas, HasTable
SelectFilter::make('status'),
SelectFilter::make('author'),
Filter::make('created_at')
->form([
->schema([
DatePicker::make('created_from'),
DatePicker::make('created_until'),
])
@@ -808,7 +808,7 @@ class TablesDemo extends Component implements HasActions, HasSchemas, HasTable
SelectFilter::make('status'),
SelectFilter::make('author'),
Filter::make('created_at')
->form([
->schema([
DatePicker::make('created_from'),
DatePicker::make('created_until'),
])
+1 -1
View File
@@ -186,7 +186,7 @@ public static function table(Table $table): Table
}
```
Check out the [tables](../../tables/overview) docs to find out how to add table columns, filters, actions and more.
Check out the [tables](../../tables) docs to find out how to add table columns, filters, actions and more.
## Authorization
@@ -50,7 +50,7 @@ Sometimes, you may wish to modify form data before it is finally saved to the da
use Filament\Actions\CreateAction;
CreateAction::make()
->mutateFormDataUsing(function (array $data): array {
->mutateDataUsing(function (array $data): array {
$data['user_id'] = auth()->id();
return $data;
@@ -65,7 +65,7 @@ Sometimes, you may wish to modify form data before it is finally saved to the da
use Filament\Actions\EditAction;
EditAction::make()
->mutateFormDataUsing(function (array $data): array {
->mutateDataUsing(function (array $data): array {
$data['last_edited_by_id'] = auth()->id();
return $data;
+2 -2
View File
@@ -62,8 +62,8 @@ class Action extends ViewComponent implements Arrayable
use Concerns\CanUseDatabaseTransactions;
use Concerns\HasAction;
use Concerns\HasArguments;
use Concerns\HasData;
use Concerns\HasExtraModalWindowAttributes;
use Concerns\HasForm;
use Concerns\HasGroupedIcon;
use Concerns\HasInfolist;
use Concerns\HasKeyBindings;
@@ -460,7 +460,7 @@ class Action extends ViewComponent implements Arrayable
'arguments' => [$this->getArguments()],
'component', 'schemaComponent' => [$this->getSchemaComponent()],
'context', 'operation' => [$this->getSchemaContainer()?->getOperation() ?? $this->getSchemaComponent()?->getContainer()->getOperation()],
'data' => [$this->getFormData()],
'data' => [$this->getData()],
'get' => [$this->getSchemaComponent()->makeGetUtility()],
'livewire' => [$this->getLivewire()],
'model' => [$this->getModel() ?? $this->getSchemaContainer()?->getModel() ?? $this->getSchemaComponent()?->getModel()],
+113
View File
@@ -0,0 +1,113 @@
<?php
namespace Filament\Actions\Concerns;
use Closure;
trait HasData
{
/**
* @var array<string, mixed>
*/
protected array $data = [];
protected ?Closure $mutateDataUsing = null;
public function mutateDataUsing(?Closure $callback): static
{
$this->mutateDataUsing = $callback;
return $this;
}
/**
* @deprecated Use `mutateDataUsing()` instead.
*/
public function mutateFormDataUsing(?Closure $callback): static
{
$this->mutateDataUsing($callback);
return $this;
}
/**
* @param array<string, mixed> $data
*/
public function data(array $data, bool $shouldMutate = true): static
{
if ($shouldMutate && $this->mutateDataUsing) {
$data = $this->evaluate($this->mutateDataUsing, [
'data' => $data,
]);
}
$this->data = $data;
return $this;
}
/**
* @deprecated Use `data()` instead.
*
* @param array<string, mixed> $data
*/
public function formData(array $data, bool $shouldMutate = true): static
{
$this->data($data, $shouldMutate);
return $this;
}
public function resetData(): static
{
$this->data([], shouldMutate: false);
return $this;
}
/**
* @deprecated Use `resetData()` instead.
*/
public function resetFormData(): static
{
$this->resetData();
return $this;
}
/**
* @return array<string, mixed>
*/
public function getData(): array
{
return $this->data;
}
/**
* @deprecated Use `getData()` instead.
*
* @return array<string, mixed>
*/
public function getFormData(): array
{
return $this->getData();
}
/**
* @return array<string, mixed>
*/
public function getRawData(): array
{
return $this->getLivewire()->mountedActions[$this->getNestingIndex()]['data'] ?? [];
}
/**
* @deprecated Use `getRawData()` instead.
*
* @return array<string, mixed>
*/
public function getRawFormData(): array
{
return $this->getRawData();
}
}
-126
View File
@@ -1,126 +0,0 @@
<?php
namespace Filament\Actions\Concerns;
use Closure;
use Filament\Actions\Action;
use Filament\Schemas\Components\Component;
use Filament\Schemas\Schema;
trait HasForm
{
/**
* @var array<string, mixed>
*/
protected array $formData = [];
protected ?Closure $mutateFormDataUsing = null;
protected bool | Closure | null $hasFormWrapper = null;
/**
* @deprecated Use `disabledSchema() instead.
*/
public function disableForm(bool | Closure $condition = true): static
{
$this->disabledSchema($condition);
return $this;
}
/**
* @deprecated Use `disabledSchema() instead.
*/
public function disabledForm(bool | Closure $condition = true): static
{
$this->disabledSchema($condition);
return $this;
}
/**
* @deprecated Use `schema() instead.
*
* @param array<Component| Action> | Closure | null $form
*/
public function form(array | Closure | null $form): static
{
$this->schema($form);
return $this;
}
/**
* @deprecated Use `getSchema()` instead.
*/
public function getForm(Schema $schema): ?Schema
{
return $this->getSchema($schema);
}
public function mutateFormDataUsing(?Closure $callback): static
{
$this->mutateFormDataUsing = $callback;
return $this;
}
/**
* @param array<string, mixed> $data
*/
public function formData(array $data, bool $shouldMutate = true): static
{
if ($shouldMutate && $this->mutateFormDataUsing) {
$data = $this->evaluate($this->mutateFormDataUsing, [
'data' => $data,
]);
}
$this->formData = $data;
return $this;
}
public function resetFormData(): static
{
$this->formData([], shouldMutate: false);
return $this;
}
/**
* @return array<string, mixed>
*/
public function getFormData(): array
{
return $this->formData;
}
/**
* @return array<string, mixed>
*/
public function getRawFormData(): array
{
return $this->getLivewire()->mountedActions[$this->getNestingIndex()]['data'] ?? [];
}
/**
* @deprecated Use `isSchemaDisabled()` instead.
*/
public function isFormDisabled(): bool
{
return $this->isSchemaDisabled();
}
public function formWrapper(bool | Closure | null $condition = true): static
{
$this->hasFormWrapper = $condition;
return $this;
}
public function hasFormWrapper(): bool
{
return (bool) ($this->evaluate($this->hasFormWrapper) ?? (! $this->isWizard()));
}
}
+62 -10
View File
@@ -18,22 +18,14 @@ trait HasSchema
protected bool | Closure $isSchemaDisabled = false;
/**
* @param array<Component | Action | ActionGroup> | Closure | null $schema
*/
public function components(array | Closure | null $schema): static
{
$this->schema = $schema;
return $this;
}
protected bool | Closure | null $hasFormWrapper = null;
/**
* @param array<Component | Action | ActionGroup> | Closure | null $schema
*/
public function schema(array | Closure | null $schema): static
{
$this->components($schema);
$this->schema = $schema;
return $this;
}
@@ -95,4 +87,64 @@ trait HasSchema
return $modifiedSchema;
}
public function formWrapper(bool | Closure | null $condition = true): static
{
$this->hasFormWrapper = $condition;
return $this;
}
public function hasFormWrapper(): bool
{
return (bool) ($this->evaluate($this->hasFormWrapper) ?? (! $this->isWizard()));
}
/**
* @deprecated Use `disabledSchema() instead.
*/
public function disableForm(bool | Closure $condition = true): static
{
$this->disabledSchema($condition);
return $this;
}
/**
* @deprecated Use `disabledSchema() instead.
*/
public function disabledForm(bool | Closure $condition = true): static
{
$this->disabledSchema($condition);
return $this;
}
/**
* @deprecated Use `schema() instead.
*
* @param array<Component| Action> | Closure | null $form
*/
public function form(array | Closure | null $form): static
{
$this->schema($form);
return $this;
}
/**
* @deprecated Use `getSchema()` instead.
*/
public function getForm(Schema $schema): ?Schema
{
return $this->getSchema($schema);
}
/**
* @deprecated Use `isSchemaDisabled()` instead.
*/
public function isFormDisabled(): bool
{
return $this->isSchemaDisabled();
}
}
@@ -215,7 +215,7 @@ trait InteractsWithActions
$schema->getState(afterValidate: function (array $state) use ($action, $schemaState): void {
$action->callAfterFormValidated();
$action->formData([
$action->data([
...$schemaState,
...$state,
]);
@@ -223,7 +223,7 @@ trait InteractsWithActions
$action->callBefore();
});
} else {
$action->formData($schemaState);
$action->data($schemaState);
$action->callBefore();
}
@@ -262,7 +262,7 @@ trait InteractsWithActions
if (! $this->mountedActionShouldOpenModal(mountedAction: $action)) {
$action->resetArguments();
$action->resetFormData();
$action->resetData();
$this->unmountAction();
}
@@ -283,7 +283,7 @@ trait InteractsWithActions
}
$action->resetArguments();
$action->resetFormData();
$action->resetData();
$onlyActionNamesAndContexts = fn (array $actions): array => collect($actions)
->map(fn (array $action): array => Arr::only($action, ['name', 'context']))
@@ -573,7 +573,7 @@ trait InteractsWithActions
return $mountedAction->getSchema(
$this->makeSchema()
->model($mountedAction->getRecord() ?? $mountedAction->getModel() ?? $mountedAction->getSchemaComponent()?->getActionFormModel() ?? $this->getMountedActionSchemaModel())
->model($mountedAction->getRecord() ?? $mountedAction->getModel() ?? $mountedAction->getSchemaComponent()?->getActionSchemaModel() ?? $this->getMountedActionSchemaModel())
->key("mountedActionSchema{$actionNestingIndex}")
->statePath("mountedActions.{$actionNestingIndex}.data")
->operation(
+1 -1
View File
@@ -482,7 +482,7 @@ TextInput::make('name')
## Adding extra content to a field
Fields contain many "slots" where content can be inserted in a child schema. Slots can accept text, [any schema component](../schemas/overview), [actions](../actions) and [action groups](../actions/grouping-actions). Usually, [prime components](../schemas/primes) are used for content.
Fields contain many "slots" where content can be inserted in a child schema. Slots can accept text, [any schema component](../schemas), [actions](../actions) and [action groups](../actions/grouping-actions). Usually, [prime components](../schemas/primes) are used for content.
The following slots are available for all fields:
+2 -2
View File
@@ -1282,13 +1282,13 @@ class Select extends Field implements Contracts\CanDisableOptions, Contracts\Has
/**
* @return Model|class-string<Model>|null
*/
public function getActionFormModel(): Model | string | null
public function getActionSchemaModel(): Model | string | null
{
if ($this->hasRelationship()) {
return $this->getRelationship()->getModel()::class;
}
return parent::getActionFormModel();
return parent::getActionSchemaModel();
}
public function getOptionsLimit(): int
+8 -9
View File
@@ -32,7 +32,7 @@ TextEntry::make('author.name')
## Entry content (state)
Entries may feel a bit magic at first, but they are designed to be simple to use and optimized to display data from an Eloquent record. Despite this, they are flexible and you can display data from any source, not just an Eloquent record.
Entries may feel a bit magic at first, but they are designed to be simple to use and optimized to display data from an Eloquent record. Despite this, they are flexible and you can display data from any source, not just an Eloquent record attribute.
The data that an entry displays is called its "state". When using a [panel resource](../resources), the infolist is aware of the record it is displaying. This means that the state of the entry is set based on the value of the attribute on the record. For example, if the entry is used in the infolist of a `PostResource`, then the `title` attribute value of the current post will be displayed.
@@ -484,7 +484,7 @@ TextEntry::make('title')
## Adding extra content to an entry
Entries contain many "slots" where content can be inserted in a child schema. Slots can accept text, [any schema component](../schemas/overview), [actions](../actions) and [action groups](../actions/grouping-actions). Usually, [prime components](../schemas/primes) are used for content.
Entries contain many "slots" where content can be inserted in a child schema. Slots can accept text, [any schema component](../schemas), [actions](../actions) and [action groups](../actions/grouping-actions). Usually, [prime components](../schemas/primes) are used for content.
The following slots are available for all entries:
@@ -808,7 +808,7 @@ These injected utilities require specific parameter names to be used. Otherwise,
### Injecting the current state of the entry
If you wish to access the current value (state) of the entry, define a `$state` parameter:
If you wish to access the current [value (state)](#entry-content-state) of the entry, define a `$state` parameter:
```php
function ($state) {
@@ -888,11 +888,11 @@ function (Entry $component) {
The parameters are injected dynamically using reflection, so you are able to combine multiple parameters in any order:
```php
use App\Models\User;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Components\Utilities\Set;
use Livewire\Component as Livewire;
function (Livewire $livewire, Get $get, Set $set) {
function (Livewire $livewire, Get $get, User $record) {
// ...
}
```
@@ -902,10 +902,10 @@ function (Livewire $livewire, Get $get, Set $set) {
You may inject anything from Laravel's container like normal, alongside utilities:
```php
use Filament\Schemas\Components\Utilities\Set;
use App\Models\User;
use Illuminate\Http\Request;
function (Request $request, Set $set) {
function (Request $request, User $record) {
// ...
}
```
@@ -918,8 +918,7 @@ If you wish to change the default behavior of all entries globally, then you can
use Filament\Infolists\Components\TextEntry;
TextEntry::configureUsing(function (TextEntry $entry): void {
$entry
->words(10);
$entry->words(10);
});
```
+1 -1
View File
@@ -133,7 +133,7 @@ Section::make('Cart')
<UtilityInjection set="schemaComponents" version="4.x">As well as allowing static values, the `collapsible()` and `collapsed()` methods also accept functions to dynamically calculate it. You can inject various utilities into the function as parameters.</UtilityInjection>
### Persisting collapsed sections
### Persisting collapsed sections in the user's session
You can persist whether a section is collapsed in local storage using the `persistCollapsed()` method, so it will remain collapsed when the user refreshes the page:
+1 -1
View File
@@ -205,7 +205,7 @@ Tabs::make('Tabs')
<UtilityInjection set="schemaComponents" version="4.x">As well as allowing a static value, the `contained()` method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters.</UtilityInjection>
## Persisting the current tab
## Persisting the current tab in the user's session
By default, the current tab is not persisted in the browser's local storage. You can change this behavior using the `persistTab()` method. You must also pass in a unique `id()` for the tabs component, to distinguish it from all other sets of tabs in the app. This ID will be used as the key in the local storage to store the current tab:
@@ -24,7 +24,7 @@ trait HasActions
/**
* @var Model|class-string<Model>|null
*/
protected Model | string | null $actionFormModel = null;
protected Model | string | null $actionSchemaModel = null;
protected ?Action $action = null;
@@ -138,9 +138,9 @@ trait HasActions
/**
* @param Model|class-string<Model>|null $model
*/
public function actionFormModel(Model | string | null $model): static
public function actionSchemaModel(Model | string | null $model): static
{
$this->actionFormModel = $model;
$this->actionSchemaModel = $model;
return $this;
}
@@ -148,9 +148,9 @@ trait HasActions
/**
* @return Model|class-string<Model>|null
*/
public function getActionFormModel(): Model | string | null
public function getActionSchemaModel(): Model | string | null
{
return $this->actionFormModel ?? $this->getRecord() ?? $this->getModel();
return $this->actionSchemaModel ?? $this->getRecord() ?? $this->getModel();
}
public function hasAction(string $name): bool
+66 -112
View File
@@ -1,19 +1,18 @@
---
title: Overview
---
import Aside from "@components/Aside.astro"
import AutoScreenshot from "@components/AutoScreenshot.astro"
## Introduction
Filament's Table Builder package allows you to [add an interactive datatable to any Livewire component](adding-a-table-to-a-livewire-component). It's also used within other Filament packages, such as the [Panel Builder](../panels) for displaying [resources](../panels/resources) and [relation managers](../panels/resources/relation-managers), as well as for the [table widget](../panels/dashboard#table-widgets). Learning the features of the Table Builder will be incredibly time-saving when both building your own custom Livewire tables and using Filament's other packages.
This guide will walk you through the basics of building tables with Filament's table package. If you're planning to add a new table to your own Livewire component, you should [do that first](adding-a-table-to-a-livewire-component) and then come back. If you're adding a table to an [app resource](../panels/resources), or another Filament package, you're ready to go!
Tables are a common UI pattern for displaying lists of records in web applications. Filament provides a PHP-based API for defining tables with many features, while also being incredibly customizable.
## Defining table columns
The basis of any table is rows and columns. Filament uses Eloquent to get the data for rows in the table, and you are responsible for defining the columns that are used in that row.
Filament includes many column types prebuilt for you, and you can [view a full list here](columns/overview#available-columns). You can even [create your own custom column types](columns/custom) to display data in whatever way you need.
Filament includes many column types prebuilt for you, and you can [view a full list here](columns/overview). You can even [create your own custom column types](columns/custom-columns) to display data in whatever way you need.
Columns are stored in an array, as objects within the `$table->columns()` method:
@@ -76,11 +75,13 @@ TextColumn::make('author.name')
<AutoScreenshot name="tables/overview/relationship-columns" alt="Table with relationship column" version="4.x" />
In this case, Filament will search for an `author` relationship on the `Post` model, and then display the `name` attribute of that relationship. We call this "dot notation" - you can use it to display any attribute of any relationship, even nested distant relationships. Filament uses this dot notation to eager-load the results of that relationship for you.
In this case, Filament will search for an `author` relationship on the `Post` model, and then display the `name` attribute of that relationship. We call this "dot notation", and you can use it to display any attribute of any relationship, even nested relationships. Filament uses this dot notation to eager-load the results of that relationship for you.
For more information about column relationships, visit the [Relationships section](columns/relationships).
## Defining table filters
As well as making columns `searchable()`, you can allow the users to filter rows in the table in other ways. We call these components "filters", and they are defined in the `$table->filters()` method:
As well as making columns `searchable()`, which allows the user to filter the table by searching the content of columns, you can also allow the users to filter rows in the table in other ways. [Filters](filters) can be defined in the `$table->filters()` method:
```php
use Filament\Tables\Filters\Filter;
@@ -115,11 +116,11 @@ The first filter is rendered as a checkbox. When it's checked, only featured row
The second filter is rendered as a select dropdown. When a user selects an option, only rows with that status will be displayed. When no option is selected, all rows will be displayed.
It's possible to define as many filters as you need, and use any component from the [Form Builder package](../forms) to create a UI. For example, you could create [a custom date range filter](../filters/custom).
You can use any [schema component](../schemas) to build the UI for a filter. For example, you could create [a custom date range filter](filters/custom).
## Defining table actions
Filament's tables can use [Actions](../actions/overview). They are buttons that can be added to the [end of any table row](actions#row-actions), or even in the [header](actions#header-actions) of a table. For instance, you may want an action to "create" a new record in the header, and then "edit" and "delete" actions on each row. [Bulk actions](actions#bulk-actions) can be used to execute code when records in the table are selected.
Filament's tables can use [actions](../actions/overview). They are buttons that can be added to the [end of any table row](actions#row-actions), or even in the [header](actions#header-actions) of a table. For instance, you may want an action to "create" a new record in the header, and then "edit" and "delete" actions on each row. [Bulk actions](actions#bulk-actions) can be used to execute code when records in the table are selected.
```php
use App\Models\Post;
@@ -163,23 +164,11 @@ We also define a bulk action. When bulk actions are defined, each row in the tab
<AutoScreenshot name="tables/overview/actions-modal" alt="Table with action modal open" version="4.x" />
Actions can also open modals to request confirmation from the user, as well as render forms inside to collect extra data. It's a good idea to read the [Actions documentation](../actions/overview) to learn more about their extensive capabilities throughout Filament.
Actions can also open modals to request confirmation from the user, as well as render forms inside to collect extra data. It's a good idea to read the [Actions documentation](../actions) to learn more about their extensive capabilities throughout Filament.
## Pagination
### Disabling pagination
By default, tables will be paginated. To disable this, you should use the `$table->paginated(false)` method:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->paginated(false);
}
```
By default, Filament tables will be paginated. The user can choose between 5, 10, 25, and 50 records per page. If there are more records than the selected number, the user can navigate between pages using the pagination buttons.
### Customizing the pagination options
@@ -195,7 +184,9 @@ public function table(Table $table): Table
}
```
Be aware when using `all` as it will cause performance issues when dealing with a large number of records.
<Aside variant="warning">
Be aware when using very high numbers and `all` as large number of records can cause performance issues.
</Aside>
### Customizing the default pagination page option
@@ -211,21 +202,9 @@ public function table(Table $table): Table
}
```
### Preventing query string conflicts with the pagination page
By default, Livewire stores the pagination state in a `page` parameter of the URL query string. If you have multiple tables on the same page, this will mean that the pagination state of one table may be overwritten by the state of another table.
To fix this, you may define a `$table->queryStringIdentifier()`, to return a unique query string identifier for that table:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->queryStringIdentifier('users');
}
```
<Aside variant="info">
Make sure that the default pagination page option is included in the [pagination options](#customizing-the-pagination-options).
</Aside>
### Displaying links to the first and the last pagination page
@@ -271,6 +250,36 @@ public function table(Table $table): Table
}
```
### Preventing query string conflicts with the pagination page
By default, Livewire stores the pagination state in a `page` parameter of the URL query string. If you have multiple tables on the same page, this will mean that the pagination state of one table may be overwritten by the state of another table.
To fix this, you may define a `$table->queryStringIdentifier()`, to return a unique query string identifier for that table:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->queryStringIdentifier('users');
}
```
### Disabling pagination
By default, tables will be paginated. To disable this, you should use the `$table->paginated(false)` method:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->paginated(false);
}
```
## Record URLs (clickable rows)
You may allow table rows to be completely clickable by using the `$table->recordUrl()` method:
@@ -288,7 +297,11 @@ public function table(Table $table): Table
}
```
In this example, clicking on each post will take you to the `posts.edit` route.
When using a [resource](../resources) table, the URL for each row is usually already set up for you, but this method can be called to override the default URL for each row.
<Aside variant="tip">
You can also [override the URL](columns/overview#opening-urls) for a specific column, or [trigger an action](columns/overview#triggering-actions) when a column is clicked.
</Aside>
You may also open the URL in a new tab:
@@ -302,8 +315,6 @@ public function table(Table $table): Table
}
```
If you'd like to [override the URL](columns/overview#opening-urls) for a specific column, or instead [run an action](columns/overview#running-actions) when a column is clicked, see the [columns documentation](columns/overview#opening-urls).
## Reordering records
To allow the user to reorder records using drag and drop in your table, you can use the `$table->reorderable()` method:
@@ -350,7 +361,7 @@ public function table(Table $table): Table
### Enabling pagination while reordering
Pagination will be disabled in reorder mode to allow you to move records between pages. It is generally bad UX to re-enable pagination while reordering, but if you are sure then you can use `$table->paginatedWhileReordering()`:
Pagination will be disabled in reorder mode to allow you to move records between pages. It is generally a bad experience to have pagination while reordering, but if would like to override this use `$table->paginatedWhileReordering()`:
```php
use Filament\Tables\Table;
@@ -414,7 +425,7 @@ public function table(Table $table): Table
]);
```
You can pass a view to the `$table->header()` method to customize the entire header:
You can pass a view to the `$table->header()` method to customize the entire header HTML:
```php
use Filament\Tables\Table;
@@ -460,29 +471,20 @@ public function table(Table $table): Table
## Searching records with Laravel Scout
While Filament doesn't provide a direct integration with [Laravel Scout](https://laravel.com/docs/scout), you may override methods to integrate it.
Use a `whereIn()` clause to filter the query for Scout results:
While Filament doesn't provide a direct integration with [Laravel Scout](https://laravel.com/docs/scout), you may use the `searchUsing()` method with a `whereKey()` clause to filter the query for Scout results:
```php
use App\Models\Post;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
protected function applySearchToTableQuery(Builder $query): Builder
public function table(Table $table): Table
{
$this->applyColumnSearchesToTableQuery($query);
if (filled($search = $this->getTableSearch())) {
$query->whereIn('id', Post::search($search)->keys());
}
return $query;
}
return $table
->searchUsing(fn (Builder $query, string $search) => $query->whereKey(Post::search($search)->keys()));
```
Scout uses this `whereIn()` method to retrieve results internally, so there is no performance penalty for using it.
The `applyColumnSearchesToTableQuery()` method ensures that searching individual columns will still work. You can replace that method with your own implementation if you want to use Scout for those search inputs as well.
Under normal circumstances Scout uses the `whereKey()` (`whereIn()`) method to retrieve results internally, so there is no performance penalty for using it.
For the global search input to show, at least one column in the table needs to be `searchable()`. Alternatively, if you are using Scout to control which columns are searchable already, you can simply pass `searchable()` to the entire table instead:
@@ -496,45 +498,6 @@ public function table(Table $table): Table
}
```
## Query string
Livewire ships with a feature to store data in the URL's query string, to access across requests.
With Filament, this allows you to store your table's filters, sort, search and pagination state in the URL.
To store the filters, sorting, and search state of your table in the query string:
```php
use Livewire\Attributes\Url;
#[Url]
public bool $isTableReordering = false;
/**
* @var array<string, mixed> | null
*/
#[Url]
public ?array $tableFilters = null;
#[Url]
public ?string $tableGrouping = null;
#[Url]
public ?string $tableGroupingDirection = null;
/**
* @var ?string
*/
#[Url]
public $tableSearch = '';
#[Url]
public ?string $tableSortColumn = null;
#[Url]
public ?string $tableSortDirection = null;
```
## Styling table rows
### Striped table rows
@@ -558,6 +521,7 @@ public function table(Table $table): Table
You may want to conditionally style rows based on the record data. This can be achieved by specifying a string or array of CSS classes to be applied to the row using the `$table->recordClasses()` method:
```php
use App\Models\Post;
use Closure;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Model;
@@ -565,28 +529,18 @@ use Illuminate\Database\Eloquent\Model;
public function table(Table $table): Table
{
return $table
->recordClasses(fn (Model $record) => match ($record->status) {
'draft' => 'opacity-30',
'reviewing' => 'border-s-2 border-orange-600 dark:border-orange-300',
'published' => 'border-s-2 border-green-600 dark:border-green-300',
->recordClasses(fn (Post $record) => match ($record->status) {
'draft' => 'draft-post-table-row',
'reviewing' => 'reviewing-post-table-row',
'published' => 'published-post-table-row',
default => null,
});
}
```
These classes are not automatically compiled by Tailwind CSS. If you want to apply Tailwind CSS classes that are not already used in Blade files, you should update your `content` configuration in `tailwind.config.js` to also scan for classes inside your directory: `'./app/Filament/**/*.php'`
## Resetting the table
If you make changes to the table definition during a Livewire request, for example, when consuming a public property in the `table()` method, you may need to reset the table to ensure that the changes are applied. To do this, you can call the `resetTable()` method on the Livewire component:
```php
$this->resetTable();
```
## Global settings
To customize the default configuration that is used for all tables, you can call the static `configureUsing()` method from the `boot()` method of a service provider. The function will be run for each table that gets created:
To customize the default configuration used for all tables, you can call the static `configureUsing()` method from the `boot()` method of a service provider. The function will be run for each table that gets created:
```php
use Filament\Tables\Enums\FiltersLayout;
+494 -258
View File
@@ -1,39 +1,13 @@
---
title: Overview
---
import Aside from "@components/Aside.astro"
import AutoScreenshot from "@components/AutoScreenshot.astro"
import UtilityInjection from "@components/UtilityInjection.astro"
## Introduction
Column classes can be found in the `Filament\Tables\Columns` namespace. You can put them inside the `$table->columns()` method:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->columns([
// ...
]);
}
```
Columns may be created using the static `make()` method, passing its unique name. The name of the column should correspond to a column or accessor on your model. You may use "dot notation" to access columns within relationships.
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('title')
TextColumn::make('author.name')
```
## Available columns
Filament ships with two main types of columns - static and editable.
Static columns display data to the user:
Column classes can be found in the `Filament\Tables\Columns` namespace. They reside within the `$table->columns()` method. Filament includes a number of columns built-in:
- [Text column](text)
- [Icon column](icon)
@@ -47,17 +21,103 @@ Editable columns allow the user to update data in the database without leaving t
- [Text input column](text-input)
- [Checkbox column](checkbox)
You may also [create your own custom columns](custom) to display data however you wish.
You may also [create your own custom columns](custom-columns) to display data however you wish.
## Setting a label
By default, the label of the column, which is displayed in the header of the table, is generated from the name of the column. You may customize this using the `label()` method:
Entries may be created using the static `make()` method, passing its unique name. Usually, the name of an entry corresponds to the name of an attribute on an Eloquent model. You may use "dot notation" to access attributes within relationships:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('title')
->label('Post title')
TextColumn::make('author.name')
```
## Column content (state)
Columns may feel a bit magic at first, but they are designed to be simple to use and optimized to display data from an Eloquent record. Despite this, they are flexible and you can display data from any source, not just an Eloquent record attribute.
The data that a column displays is called its "state". When using a [panel resource](../resources), the table is aware of the records it is displaying. This means that the state of the column is set based on the value of the attribute on the record. For example, if the column is used in the table of a `PostResource`, then the `title` attribute value of the current post will be displayed.
```php
use Filament\Tables\Components\TextColumn;
TextColumn::make('title')
```
If you want to access the value stored in a relationship, you can use "dot notation". The name of the relationship that you would like to access data from comes first, followed by a dot, and then the name of the attribute:
```php
use Filament\Tables\Components\TextColumn;
TextColumn::make('author.name')
```
You can also use "dot notation" to access values within a JSON / array column on an Eloquent model. The name of the attribute comes first, followed by a dot, and then the key of the JSON object you want to read from:
```php
use Filament\Tables\Components\TextColumn;
TextColumn::make('meta.title')
```
### Setting the state of a column
You can pass your own state to a column by using the `state()` method:
```php
use Filament\Tables\Components\TextColumn;
TextColumn::make('title')
->state('Hello, world!')
```
<UtilityInjection set="tableColumns" version="4.x">The `state()` method also accepts a function to dynamically calculate the state. You can inject various utilities into the function as parameters.</UtilityInjection>
### Setting the default state of a column
When a column is empty (its state is `null`), you can use the `default()` method to define alternative state to use instead. This method will treat the default state as if it were real, so columns like [image](image) or [color](color) will display the default image or color.
```php
use Filament\Tables\Components\TextColumn;
TextColumn::make('title')
->default('Untitled')
```
#### Adding placeholder text if a column is empty
Sometimes you may want to display placeholder text for columns with an empty state, which is styled as a lighter gray text. This differs from the [default value](#setting-the-default-state-of-an-column), as the placeholder is always text and not treated as if it were real state.
```php
use Filament\Tables\Components\TextColumn;
TextColumn::make('title')
->placeholder('Untitled')
```
<AutoScreenshot name="tables/columns/placeholder" alt="Column with a placeholder for empty state" version="4.x" />
## Setting a column's label
By default, the label of the column, which is displayed in the header of the table, is generated from the name of the column. You may customize this using the `label()` method:
```php
use Filament\Tables\Components\TextColumn;
TextColumn::make('name')
->label('Full name')
```
<UtilityInjection set="tableColumns" version="4.x">As well as allowing a static value, the `label()` method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters.</UtilityInjection>
Customizing the label in this way is useful if you wish to use a [translation string for localization](https://laravel.com/docs/localization#retrieving-translation-strings):
```php
use Filament\Tables\Components\TextColumn;
TextColumn::make('name')
->label(__('columns.name'))
```
## Sorting
@@ -73,7 +133,11 @@ TextColumn::make('name')
<AutoScreenshot name="tables/columns/sortable" alt="Table with sortable column" version="4.x" />
If you're using an accessor column, you may pass `sortable()` an array of database columns to sort by:
Using the name of the column, Filament will apply an `orderBy()` clause to the Eloquent query. This is useful for simple cases where the column name matches the database column name. It can also handle [relationships](relationships).
However, many columns are not as simple. The [state](#column-content-state) of the column might be customized, or using an [Eloquent accessor](https://laravel.com/docs/12.x/eloquent-mutators#accessors-and-mutators). In this case, you may need to customize the sorting behaviour.
You can pass an array of real database columns in the table to sort the column with:
```php
use Filament\Tables\Columns\TextColumn;
@@ -82,7 +146,9 @@ TextColumn::make('full_name')
->sortable(['first_name', 'last_name'])
```
You may customize how the sorting is applied to the Eloquent query using a callback:
In this instance, the `full_name` column is not a real column in the database, but the `first_name` and `last_name` columns are. When the `full_name` column is sorted, Filament will sort the table by the `first_name` and `last_name` columns. The reason why two columns are passed is that if two records have the same `first_name`, the `last_name` will be used to sort them. If your use case does not require this, you can pass only one column in the array if you wish.
You may also directly interact with the Eloquent query to customize how sorting is applied for that column:
```php
use Filament\Tables\Columns\TextColumn;
@@ -96,7 +162,9 @@ TextColumn::make('full_name')
})
```
## Sorting by default
<UtilityInjection set="tableColumns" version="4.x" extras="Direction;;string;;$direction;;The direction that the column is currently being sorted on, either <code>'asc'</code> or <code>'desc'</code>.||Eloquent query builder;;Illuminate\Database\Eloquent\Builder;;$query;;The query builder to modify.">The `query` parameter's function can inject various utilities as parameters.</UtilityInjection>
### Sorting by default
You may choose to sort a table by default if no other sort is applied. You can use the `defaultSort()` method for this:
@@ -109,11 +177,31 @@ public function table(Table $table): Table
->columns([
// ...
])
->defaultSort('stock', 'desc');
->defaultSort('stock', direction: 'desc');
}
```
### Persist sort in session
The second parameter is optional and defaults to `'asc'`.
If you pass the name of a table column as the first parameter, Filament will use that column's sorting behavior (custom sorting columns or query function). However, if you need to sort by a column that does not exist in the table or in the database, you should pass a query function instead:
```php
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
public function table(Table $table): Table
{
return $table
->columns([
// ...
])
->defaultSort(query: function (Builder $query): Builder {
return $query->orderBy('stock');
});
}
```
### Persisting the sort in the user's session
To persist the sorting in the user's session, use the `persistSortInSession()` method:
@@ -160,7 +248,11 @@ TextColumn::make('name')
<AutoScreenshot name="tables/columns/searchable" alt="Table with searchable column" version="4.x" />
If you're using an accessor column, you may pass `searchable()` an array of database columns to search within:
By default, Filament will apply a `where` clause to the Eloquent query, searching for the column name. This is useful for simple cases where the column name matches the database column name. It can also handle [relationships](relationships).
However, many columns are not as simple. The [state](#column-content-state) of the column might be customized, or using an [Eloquent accessor](https://laravel.com/docs/12.x/eloquent-mutators#accessors-and-mutators). In this case, you may need to customize the search behaviour.
You can pass an array of real database columns in the table to search the column with:
```php
use Filament\Tables\Columns\TextColumn;
@@ -169,7 +261,9 @@ TextColumn::make('full_name')
->searchable(['first_name', 'last_name'])
```
You may customize how the search is applied to the Eloquent query using a callback:
In this instance, the `full_name` column is not a real column in the database, but the `first_name` and `last_name` columns are. When the `full_name` column is searched, Filament will search the table by the `first_name` and `last_name` columns.
You may also directly interact with the Eloquent query to customize how searching is applied for that column:
```php
use Filament\Tables\Columns\TextColumn;
@@ -183,7 +277,9 @@ TextColumn::make('full_name')
})
```
#### Adding extra searchable columns to the table
<UtilityInjection set="tableColumns" version="4.x" extras="Search;;string;;$search;;The current search input value.||Eloquent query builder;;Illuminate\Database\Eloquent\Builder;;$query;;The query builder to modify.">The `query` parameter's function can inject various utilities as parameters.</UtilityInjection>
### Adding extra searchable columns to the table
You may allow the table to search with extra columns that are not present in the table by passing an array of column names to the `searchable()` method:
@@ -240,7 +336,7 @@ public function table(Table $table): Table
}
```
#### Customizing the table search field placeholder
### Customizing the table search field placeholder
You may customize the placeholder in the search field using the `searchPlaceholder()` method on the `$table`:
@@ -281,21 +377,9 @@ TextColumn::make('title')
->searchable(isIndividual: true, isGlobal: false)
```
You may optionally persist the searches in the query string:
```php
use Livewire\Attributes\Url;
/**
* @var array<string, string | array<string, string | null> | null>
*/
#[Url]
public array $tableColumnSearches = [];
```
### Customizing the table search debounce
You may customize the debounce time in all table search fields using the `searchDebounce()` method on the `$table`. By default it is set to `500ms`:
You may customize the debounce time in all table search fields using the `searchDebounce()` method on the `$table`. By default, it is set to `500ms`:
```php
use Filament\Tables\Table;
@@ -327,7 +411,7 @@ public static function table(Table $table): Table
}
```
### Persist search in session
### Persisting the search in the user's session
To persist the table or individual column search in the user's session, use the `persistSearchInSession()` or `persistColumnSearchInSession()` method:
@@ -362,13 +446,42 @@ public function table(Table $table): Table
}
```
## Column actions and URLs
## Clickable cell content
When a cell is clicked, you may run an "action", or open a URL.
When a cell is clicked, you may open a URL or trigger an "action".
### Running actions
### Opening URLs
To run an action, you may use the `action()` method, passing a callback or the name of a Livewire method to run. Each method accepts a `$record` parameter which you may use to customize the behavior of the action:
To open a URL, you may use the `url()` method:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('title')
->url(fn (Post $record): string => route('posts.edit', ['post' => $record]))
```
<UtilityInjection set="tableColumns" version="4.x">The `url()` method also accepts a function to dynamically calculate the value. You can inject various utilities into the function as parameters.</UtilityInjection>
<Aside variant="tip">
You can also pick a URL for the entire row to open, not just a singular column. Please see the [Record URLs section](../overview#record-urls-clickable-rows).
When using a record URL and a column URL, the column URL will override the record URL for those cells only.
</Aside>
You may also choose to open the URL in a new tab:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('title')
->url(fn (Post $record): string => route('posts.edit', ['post' => $record]))
->openUrlInNewTab()
```
### Triggering actions
To run a function when a cell is clicked, you may use the `action()` method. Each method accepts a `$record` parameter which you may use to customize the behavior of the action:
```php
use Filament\Tables\Columns\TextColumn;
@@ -381,7 +494,7 @@ TextColumn::make('title')
#### Action modals
You may open [action modals](../actions#modals) by passing in an `Action` object to the `action()` method:
You may open [action modals](../../actions#modals) by passing in an `Action` object to the `action()` method:
```php
use Filament\Actions\Action;
@@ -399,63 +512,212 @@ TextColumn::make('title')
Action objects passed into the `action()` method must have a unique name to distinguish it from other actions within the table.
### Opening URLs
#### Preventing cells from being clicked
To open a URL, you may use the `url()` method, passing a callback or static URL to open. Callbacks accept a `$record` parameter which you may use to customize the URL:
You may prevent a cell from being clicked by using the `disabledClick()` method:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('title')
->url(fn (Post $record): string => route('posts.edit', ['post' => $record]))
->disabledClick()
```
You may also choose to open the URL in a new tab:
If [row URLs](../overview#record-urls-clickable-rows) are enabled, the cell will not be clickable.
## Adding a tooltip to a column
You may specify a tooltip to display when you hover over a cell:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('title')
->url(fn (Post $record): string => route('posts.edit', ['post' => $record]))
->openUrlInNewTab()
->tooltip('Title')
```
## Setting a default value
<UtilityInjection set="tableColumns" version="4.x">As well as allowing a static value, the `tooltip()` method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters.</UtilityInjection>
To set a default value for columns with an empty state, you may use the `default()` method. This method will treat the default state as if it were real, so columns like [image](image) or [color](color) will display the default image or color.
<AutoScreenshot name="tables/columns/tooltips" alt="Table with column triggering a tooltip" version="4.x" />
## Aligning column content
### Horizontally aligning column content
You may align the content of an column to the start (left in left-to-right interfaces, right in right-to-left interfaces), center, or end (right in left-to-right interfaces, left in right-to-left interfaces) using the `alignStart()`, `alignCenter()` or `alignEnd()` methods:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('description')
->default('No description.')
TextColumn::make('email')
->alignStart() // This is the default alignment.
TextColumn::make('email')
->alignCenter()
TextColumn::make('email')
->alignEnd()
```
## Adding placeholder text if a column is empty
Alternatively, you may pass an `Alignment` enum to the `alignment()` method:
Sometimes you may want to display placeholder text for columns with an empty state, which is styled as a lighter gray text. This differs from the [default value](#setting-a-default-value), as the placeholder is always text and not treated as if it were real state.
```php
use Filament\Support\Enums\Alignment;
use Filament\Tables\Columns\TextColumn;
TextColumn::make('email')
->label('Email address')
->alignment(Alignment::End)
```
<UtilityInjection set="tableColumns" version="4.x">As well as allowing a static value, the `alignment()` method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters.</UtilityInjection>
<AutoScreenshot name="tables/columns/alignment" alt="Table with column aligned to the end" version="4.x" />
### Vertically aligning column content
You may align the content of a column to the start, center, or end using the `verticallyAlignStart()`, `verticallyAlignCenter()` or `verticallyAlignEnd()` methods:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('description')
->placeholder('No description.')
TextColumn::make('name')
->verticallyAlignStart()
TextColumn::make('name')
->verticallyAlignCenter() // This is the default alignment.
TextColumn::make('name')
->verticallyAlignEnd()
```
<AutoScreenshot name="tables/columns/placeholder" alt="Column with a placeholder for empty state" version="4.x" />
Alternatively, you may pass a `VerticalAlignment` enum to the `verticalAlignment()` method:
```php
use Filament\Support\Enums\VerticalAlignment;
use Filament\Tables\Columns\TextColumn;
TextColumn::make('name')
->verticalAlignment(VerticalAlignment::Start)
```
<UtilityInjection set="tableColumns" version="4.x">As well as allowing a static value, the `verticalAlignment()` method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters.</UtilityInjection>
<AutoScreenshot name="tables/columns/vertical-alignment" alt="Table with column vertically aligned to the start" version="4.x" />
## Allowing column headers to wrap
By default, column headers will not wrap onto multiple lines if they need more space. You may allow them to wrap using the `wrapHeader()` method:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('name')
->wrapHeader()
```
Optionally, you may pass a boolean value to control if the header should wrap:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('name')
->wrapHeader(FeatureFlag::active())
```
<UtilityInjection set="tableColumns" version="4.x">The `wrapHeader()` method also accepts a function to dynamically calculate the value. You can inject various utilities into the function as parameters.</UtilityInjection>
## Controlling the width of columns
By default, columns will take up as much space as they need. You may allow some columns to consume more space than others by using the `grow()` method:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('name')
->grow()
```
Alternatively, you can define a width for the column, which is passed to the header cell using the `style` attribute, so you can use any valid CSS value:
```php
use Filament\Tables\Columns\IconColumn;
IconColumn::make('is_paid')
->label('Paid')
->boolean()
->width('1%')
```
<UtilityInjection set="tableColumns" version="4.x">The `width()` method also accepts a function to dynamically calculate the value. You can inject various utilities into the function as parameters.</UtilityInjection>
## Grouping columns
You group multiple columns together underneath a single heading using a `ColumnGroup` object:
```php
use Filament\Tables\Columns\ColumnGroup;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('title'),
TextColumn::make('slug'),
ColumnGroup::make('Visibility', [
TextColumn::make('status'),
IconColumn::make('is_featured'),
]),
TextColumn::make('author.name'),
]);
}
```
The first argument is the label of the group, and the second is an array of column objects that belong to that group.
<AutoScreenshot name="tables/columns/grouping" alt="Table with grouped columns" version="4.x" />
You can also control the group header [alignment](#horizontally-aligning-column-content) and [wrapping](#allowing-column-headers-to-wrap) on the `ColumnGroup` object. To improve the multi-line fluency of the API, you can chain the `columns()` onto the object instead of passing it as the second argument:
```php
use Filament\Support\Enums\Alignment;
use Filament\Tables\Columns\ColumnGroup;
ColumnGroup::make('Website visibility')
->columns([
// ...
])
->alignCenter()
->wrapHeader()
```
## Hiding columns
To hide a column conditionally, you may use the `hidden()` and `visible()` methods, whichever you prefer:
You may hide a column by using the `hidden()` or `visible()` method:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('email')
->hidden()
TextColumn::make('email')
->visible()
```
To hide a column conditionally, you may pass a boolean value to either method:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('role')
->hidden(! auth()->user()->isAdmin())
// or
->hidden(FeatureFlag::active())
TextColumn::make('role')
->visible(auth()->user()->isAdmin())
->visible(FeatureFlag::active())
```
### Toggling column visibility
@@ -504,204 +766,178 @@ public function table(Table $table): Table
}
```
## Calculated state
## Adding extra HTML attributes to a column content
Sometimes you need to calculate the state of a column, instead of directly reading it from a database column.
By passing a callback function to the `state()` method, you can customize the returned state for that column based on the `$record`:
```php
use App\Models\Order;
use Filament\Tables\Columns\TextColumn;
TextColumn::make('amount_including_vat')
->state(function (Order $record): float {
return $record->amount * (1 + $record->vat_rate);
})
```
## Tooltips
You may specify a tooltip to display when you hover over a cell:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('title')
->tooltip('Title')
```
<AutoScreenshot name="tables/columns/tooltips" alt="Table with column triggering a tooltip" version="4.x" />
This method also accepts a closure that can access the current table record:
```php
use Filament\Tables\Columns\TextColumn;
use Illuminate\Database\Eloquent\Model;
TextColumn::make('title')
->tooltip(fn (Model $record): string => "By {$record->author->name}")
```
## Horizontally aligning column content
Table columns are aligned to the start (left in LTR interfaces or right in RTL interfaces) by default. You may change the alignment using the `alignment()` method, and passing it `Alignment::Start`, `Alignment::Center`, `Alignment::End` or `Alignment::Justify` options:
```php
use Filament\Support\Enums\Alignment;
use Filament\Tables\Columns\TextColumn;
TextColumn::make('email')
->alignment(Alignment::End)
```
<AutoScreenshot name="tables/columns/alignment" alt="Table with column aligned to the end" version="4.x" />
Alternatively, you may use shorthand methods like `alignEnd()`:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('name')
->alignEnd()
```
## Vertically aligning column content
Table column content is vertically centered by default. You may change the vertical alignment using the `verticalAlignment()` method, and passing it `VerticalAlignment::Start`, `VerticalAlignment::Center` or `VerticalAlignment::End` options:
```php
use Filament\Support\Enums\VerticalAlignment;
use Filament\Tables\Columns\TextColumn;
TextColumn::make('name')
->verticalAlignment(VerticalAlignment::Start)
```
<AutoScreenshot name="tables/columns/vertical-alignment" alt="Table with column vertically aligned to the start" version="4.x" />
Alternatively, you may use shorthand methods like `verticallyAlignStart()`:
```php
use Filament\Support\Enums\VerticalAlignment;
use Filament\Tables\Columns\TextColumn;
TextColumn::make('name')
->verticallyAlignStart()
```
## Allowing column headers to wrap
By default, column headers will not wrap onto multiple lines, if they need more space. You may allow them to wrap using the `wrapHeader()` method:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('name')
->wrapHeader()
```
## Controlling the width of columns
By default, columns will take up as much space as they need. You may allow some columns to consume more space than others by using the `grow()` method:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('name')
->grow()
```
Alternatively, you can define a width for the column, which is passed to the header cell using the `style` attribute, so you can use any valid CSS value:
```php
use Filament\Tables\Columns\IconColumn;
IconColumn::make('is_paid')
->label('Paid')
->boolean()
->width('1%')
```
## Grouping columns
You group multiple columns together underneath a single heading using a `ColumnGroup` object:
```php
use Filament\Tables\Columns\ColumnGroup;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('title'),
TextColumn::make('slug'),
ColumnGroup::make('Visibility', [
TextColumn::make('status'),
IconColumn::make('is_featured'),
]),
TextColumn::make('author.name'),
]);
}
```
The first argument is the label of the group, and the second is an array of column objects that belong to that group.
<AutoScreenshot name="tables/columns/grouping" alt="Table with grouped columns" version="4.x" />
You can also control the group header [alignment](#horizontally-aligning-column-content) and [wrapping](#allowing-column-headers-to-wrap) on the `ColumnGroup` object. To improve the multi-line fluency of the API, you can chain the `columns()` onto the object instead of passing it as the second argument:
```php
use Filament\Support\Enums\Alignment;
use Filament\Tables\Columns\ColumnGroup;
ColumnGroup::make('Website visibility')
->columns([
// ...
])
->alignment(Alignment::Center)
->wrapHeader()
```
## Custom attributes
The HTML of columns can be customized, by passing an array of `extraAttributes()`:
You can pass extra HTML attributes to the column content via the `extraAttributes()` method, which will be merged onto its outer HTML element. The attributes should be represented by an array, where the key is the attribute name and the value is the attribute value:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('slug')
->extraAttributes(['class' => 'bg-gray-200'])
->extraAttributes(['class' => 'slug-column'])
```
These get merged onto the outer `<div>` element of each cell in that column.
<UtilityInjection set="tableColumns" version="4.x">As well as allowing a static value, the `extraAttributes()` method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters.</UtilityInjection>
## Global settings
By default, calling `extraAttributes()` multiple times will overwrite the previous attributes. If you wish to merge the attributes instead, you can pass `merge: true` to the method.
If you wish to change the default behavior of all columns globally, then you can call the static `configureUsing()` method inside a service provider's `boot()` method, to which you pass a Closure to modify the columns using. For example, if you wish to make all columns [`searchable()`](#searching) and [`toggleable()`](#toggling-column-visibility), you can do it like so:
### Adding extra HTML attributes to the cell
You can also pass extra HTML attributes to the table cell which surrounds the content of the column:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('slug')
->extraCellAttributes(['class' => 'slug-cell'])
```
<UtilityInjection set="tableColumns" version="4.x">As well as allowing a static value, the `extraCellAttributes()` method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters.</UtilityInjection>
By default, calling `extraCellAttributes()` multiple times will overwrite the previous attributes. If you wish to merge the attributes instead, you can pass `merge: true` to the method.
### Adding extra attributes to the header cell
You can pass extra HTML attributes to the table header cell which surrounds the content of the column:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('slug')
->extraHeaderAttributes(['class' => 'slug-header-cell'])
```
<UtilityInjection set="tableColumns" version="4.x">As well as allowing a static value, the `extraHeaderAttributes()` method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters.</UtilityInjection>
By default, calling `extraHeaderAttributes()` multiple times will overwrite the previous attributes. If you wish to merge the attributes instead, you can pass `merge: true` to the method.
## Column utility injection
The vast majority of methods used to configure columns accept functions as parameters instead of hardcoded values:
```php
use App\Models\User;
use Filament\Tables\Columns\TextColumn;
TextColumn::make('email')
->placeholder(fn (User $record): string => "No email for {$record->name}")
TextColumn::make('role')
->hidden(fn (User $record): bool => $record->role === 'admin')
TextColumn::make('name')
->extraAttributes(fn (User $record): array => ['class' => "{$record->getKey()}-name-column"])
```
This alone unlocks many customization possibilities.
The package is also able to inject many utilities to use inside these functions, as parameters. All customization methods that accept functions as arguments can inject utilities.
These injected utilities require specific parameter names to be used. Otherwise, Filament doesn't know what to inject.
### Injecting the current state of the column
If you wish to access the current [value (state)](#column-content-state) of the column, define a `$state` parameter:
```php
function ($state) {
// ...
}
```
### Injecting the current Eloquent record
You may retrieve the Eloquent record for the current schema using a `$record` parameter:
```php
use Illuminate\Database\Eloquent\Model;
function (?Model $record) {
// ...
}
```
### Injecting the row loop
To access the [row loop](https://laravel.com/docs/blade#the-loop-variable) object for the current table row, define a `$rowLoop` parameter:
```php
function (stdClass $rowLoop) {
// ...
}
```
### Injecting the current Livewire component instance
If you wish to access the current Livewire component instance, define a `$livewire` parameter:
```php
use Livewire\Component;
function (Component $livewire) {
// ...
}
```
### Injecting the current column instance
If you wish to access the current component instance, define a `$component` parameter:
```php
use Filament\Tables\Columns\Column;
Column::configureUsing(function (Column $column): void {
$column
->toggleable()
->searchable();
});
function (Column $component) {
// ...
}
```
Additionally, you can call this code on specific column types as well:
### Injecting the current table instance
If you wish to access the current table instance, define a `$table` parameter:
```php
use Filament\Tables\Table;
function (Table $table) {
// ...
}
```
### Injecting multiple utilities
The parameters are injected dynamically using reflection, so you are able to combine multiple parameters in any order:
```php
use App\Models\User;
use Livewire\Component as Livewire;
function (Livewire $livewire, mixed $state, User $record) {
// ...
}
```
### Injecting dependencies from Laravel's container
You may inject anything from Laravel's container like normal, alongside utilities:
```php
use App\Models\User;
use Illuminate\Http\Request;
function (Request $request, User $record) {
// ...
}
```
## Global settings
If you wish to change the default behavior of all columns globally, then you can call the static `configureUsing()` method inside a service provider's `boot()` method, to which you pass a Closure to modify the columns using. For example, if you wish to make all `TextColumn` columns [`toggleable()`](#toggling-column-visibility), you can do it like so:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::configureUsing(function (TextColumn $column): void {
$column
->toggleable()
->searchable();
$column->toggleable();
});
```
@@ -95,7 +95,7 @@ Filter::make('is_featured')
->modifyFormFieldUsing(fn (Checkbox $field) => $field->inline(false))
```
## Persist filters in session
## Persisting filters in the user's session
To persist the table filters in the user's session, use the `persistFiltersInSession()` method:
+4 -4
View File
@@ -13,7 +13,7 @@ use Filament\Tables\Filters\Filter;
use Illuminate\Database\Eloquent\Builder;
Filter::make('created_at')
->form([
->schema([
DatePicker::make('created_from'),
DatePicker::make('created_until'),
])
@@ -41,7 +41,7 @@ use Filament\Forms\Components\DatePicker;
use Filament\Tables\Filters\Filter;
Filter::make('created_at')
->form([
->schema([
DatePicker::make('created_from'),
DatePicker::make('created_until')
->default(now()),
@@ -80,7 +80,7 @@ use Filament\Forms\Components\DatePicker;
use Filament\Tables\Filters\Filter;
Filter::make('created_at')
->form([DatePicker::make('date')])
->schema([DatePicker::make('date')])
// ...
->indicateUsing(function (array $data): ?string {
if (! $data['date']) {
@@ -102,7 +102,7 @@ use Filament\Tables\Filters\Filter;
use Filament\Tables\Filters\Indicator;
Filter::make('created_at')
->form([
->schema([
DatePicker::make('from'),
DatePicker::make('until'),
])
+1 -1
View File
@@ -263,7 +263,7 @@ This is useful for things like "create" actions, which are not related to any sp
## Column actions
Actions can be added to columns, such that when a cell in that column is clicked, it acts as the trigger for an action. You can learn more about [column actions](columns/overview#running-actions) in the documentation.
Actions can be added to columns, such that when a cell in that column is clicked, it acts as the trigger for an action. You can learn more about [column actions](columns/overview#triggering-actions) in the documentation.
## Prebuilt table actions
@@ -138,6 +138,12 @@ trait CanSearchRecords
return $query;
}
if ($this->getTable()->hasSearchUsingCallback()) {
$this->getTable()->callSearchUsing($query, $search);
return $query;
}
if (! $this->getTable()->shouldSplitSearchTerms()) {
$query->where(function (Builder $query) use ($search): void {
$isFirst = true;
+1 -1
View File
@@ -13,10 +13,10 @@ class BaseFilter extends Component
use Concerns\CanSpanColumns;
use Concerns\HasColumns;
use Concerns\HasDefaultState;
use Concerns\HasFormSchema;
use Concerns\HasIndicators;
use Concerns\HasLabel;
use Concerns\HasName;
use Concerns\HasSchema;
use Concerns\InteractsWithTableQuery;
protected string $evaluationIdentifier = 'filter';
@@ -9,21 +9,33 @@ use Filament\Forms\Components\Field;
use Filament\Schemas\Components\Component;
use Filament\Schemas\Schema;
trait HasFormSchema
trait HasSchema
{
/**
* @var array<Component | Action | ActionGroup> | Closure | null
*/
protected array | Closure | null $formSchema = null;
protected array | Closure | null $schema = null;
protected ?Closure $modifyFormFieldUsing = null;
/**
* @param array<Component | Action | ActionGroup> | Closure | null $schema
*/
public function schema(array | Closure | null $schema): static
{
$this->schema = $schema;
return $this;
}
/**
* @deprecated Use `schema()` instead.
*
* @param array<Component | Action | ActionGroup> | Closure | null $schema
*/
public function form(array | Closure | null $schema): static
{
$this->formSchema = $schema;
$this->schema($schema);
return $this;
}
@@ -38,9 +50,9 @@ trait HasFormSchema
/**
* @return array<Component | Action | ActionGroup>
*/
public function getFormSchema(): array
public function getSchemaComponents(): array
{
$schema = $this->evaluate($this->formSchema);
$schema = $this->evaluate($this->schema);
if ($schema !== null) {
return $schema;
@@ -67,9 +79,19 @@ trait HasFormSchema
return [$field];
}
public function hasFormSchema(): bool
/**
* @deprecated Use `getSchema()` instead.
*
* @return array<Component | Action | ActionGroup>
*/
public function getFormSchema(): array
{
return $this->evaluate($this->formSchema) !== null;
return $this->getSchemaComponents();
}
public function hasSchema(): bool
{
return $this->evaluate($this->schema) !== null;
}
public function getFormField(): ?Field
@@ -77,7 +99,7 @@ trait HasFormSchema
return null;
}
public function getForm(): Schema
public function getSchema(): Schema
{
return $this->getLivewire()
->getTableFiltersForm()
+1 -1
View File
@@ -67,7 +67,7 @@ class Filter extends BaseFilter
*/
public function getResetState(): array
{
if ($this->hasFormSchema()) {
if ($this->hasSchema()) {
return parent::getResetState();
}
+2 -2
View File
@@ -29,7 +29,7 @@ class QueryBuilder extends BaseFilter
$this->label(__('filament-tables::filters/query-builder.label'));
$this->form(fn (QueryBuilder $filter): array => [
$this->schema(fn (QueryBuilder $filter): array => [
RuleBuilder::make('rules')
->label($filter->getLabel())
->constraints($filter->getConstraints())
@@ -214,7 +214,7 @@ class QueryBuilder extends BaseFilter
protected function getRuleBuilder(): RuleBuilder
{
$builder = $this->getForm()->getComponent(fn (Component $component): bool => $component instanceof RuleBuilder);
$builder = $this->getSchema()->getComponent(fn (Component $component): bool => $component instanceof RuleBuilder);
if (! ($builder instanceof RuleBuilder)) {
throw new Exception('No rule builder component found.');
@@ -33,6 +33,8 @@ trait CanSearchRecords
protected bool | Closure $shouldSplitSearchTerms = true;
protected ?Closure $searchUsing = null;
public function persistSearchInSession(bool | Closure $condition = true): static
{
$this->persistsSearchInSession = $condition;
@@ -270,4 +272,24 @@ trait CanSearchRecords
{
return (bool) $this->evaluate($this->shouldSplitSearchTerms);
}
public function searchUsing(?Closure $searchUsing): static
{
$this->searchUsing = $searchUsing;
return $this;
}
public function hasSearchUsingCallback(): bool
{
return filled($this->searchUsing);
}
public function callSearchUsing(Builder $query, string $search): void
{
$this->evaluate($this->searchUsing, [
'query' => $query,
'search' => $search,
]);
}
}
@@ -184,7 +184,7 @@ trait HasFilters
foreach ($this->getFilters() as $filterName => $filter) {
$filters[$filterName] = Group::make()
->schema($filter->getFormSchema())
->schema($filter->getSchemaComponents())
->statePath($filterName)
->key($filterName)
->columnSpan($filter->getColumnSpan())
+3
View File
@@ -3,6 +3,7 @@
use Filament\Actions\Action;
use Filament\Schemas\Components\Component;
use Filament\Schemas\Schema;
use Filament\Tables\Filters\BaseFilter;
use Filament\Upgrade\Rector;
use Rector\Config\RectorConfig;
use Rector\Renaming\Rector\MethodCall\RenameMethodRector;
@@ -271,8 +272,10 @@ return static function (RectorConfig $rectorConfig): void {
$rectorConfig->ruleWithConfiguration(RenameMethodRector::class, [
new MethodCallRename(Action::class, 'infolist', 'schema'),
new MethodCallRename(Action::class, 'form', 'schema'),
new MethodCallRename(Action::class, 'mutateFormDataUsing', 'mutateDataUsing'),
new MethodCallRename(Component::class, 'getChildComponentContainer', 'getChildSchema'),
new MethodCallRename(Component::class, 'getChildComponentContainers', 'getChildSchemas'),
new MethodCallRename(BaseFilter::class, 'form', 'schema'),
new MethodCallRename(Schema::class, 'schema', 'components'),
]);
};
+2 -2
View File
@@ -14,7 +14,7 @@ Filament ships with these widgets:
- [Stats overview](../widgets/stats-overview) widgets display any data, often numeric data, as stats in a row.
- [Chart](../widgets/charts) widgets display numeric data in a visual chart.
- [Table](#table-widgets) widgets which display a [table](../tables/overview) on your dashboard.
- [Table](#table-widgets) widgets which display a [table](../tables) on your dashboard.
You may also [create your own custom widgets](#custom-widgets) which can then have a consistent design with Filament's prebuilt widgets.
@@ -97,7 +97,7 @@ You may easily add tables to your dashboard. Start by creating a widget with the
php artisan make:filament-widget LatestOrders --table
```
You may now [customize the table](../tables/overview) by editing the widget file.
You may now [customize the table](../tables) by editing the widget file.
## Custom widgets
+1 -1
View File
@@ -33,7 +33,7 @@ class Actions extends Page
TextInput::make('payload')->required(),
])
->before(function (Action $action): void {
$this->dispatch('before-hook-called', data: $action->getFormData());
$this->dispatch('before-hook-called', data: $action->getData());
}),
Action::make('arguments')
->requiresConfirmation()