[增添]添加了datasource的setting数据库以及默认值

This commit is contained in:
makotocc0107
2024-08-27 09:57:44 +08:00
parent d111dfaea4
commit 72eb990970
10955 changed files with 978898 additions and 0 deletions

View File

@@ -0,0 +1,176 @@
---
title: Installation
---
**The Table Builder package is pre-installed with the [Panel Builder](/docs/panels).** This guide is for using the Table Builder in a custom TALL Stack application (Tailwind, Alpine, Livewire, Laravel).
## Requirements
Filament requires the following to run:
- PHP 8.1+
- Laravel v10.0+
- Livewire v3.0+
## Installation
Require the Table Builder package using Composer:
```bash
composer require filament/tables:"^3.2" -W
```
## New Laravel projects
To quickly get started with Filament in a new Laravel project, run the following commands to install [Livewire](https://livewire.laravel.com), [Alpine.js](https://alpinejs.dev), and [Tailwind CSS](https://tailwindcss.com):
> Since these commands will overwrite existing files in your application, only run this in a new Laravel project!
```bash
php artisan filament:install --scaffold --tables
npm install
npm run dev
```
## Existing Laravel projects
Run the following command to install the Table Builder assets:
```bash
php artisan filament:install --tables
```
### Installing Tailwind CSS
Run the following command to install Tailwind CSS with the Tailwind Forms and Typography plugins:
```bash
npm install tailwindcss @tailwindcss/forms @tailwindcss/typography postcss postcss-nesting autoprefixer --save-dev
```
Create a new `tailwind.config.js` file and add the Filament `preset` *(includes the Filament color scheme and the required Tailwind plugins)*:
```js
import preset from './vendor/filament/support/tailwind.config.preset'
export default {
presets: [preset],
content: [
'./app/Filament/**/*.php',
'./resources/views/filament/**/*.blade.php',
'./vendor/filament/**/*.blade.php',
],
}
```
### Configuring styles
Add Tailwind's CSS layers to your `resources/css/app.css`:
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```
Create a `postcss.config.js` file in the root of your project and register Tailwind CSS, PostCSS Nesting and Autoprefixer as plugins:
```js
export default {
plugins: {
'tailwindcss/nesting': 'postcss-nesting',
tailwindcss: {},
autoprefixer: {},
},
}
```
### Automatically refreshing the browser
You may also want to update your `vite.config.js` file to refresh the page automatically when Livewire components are updated:
```js
import { defineConfig } from 'vite'
import laravel, { refreshPaths } from 'laravel-vite-plugin'
export default defineConfig({
plugins: [
laravel({
input: ['resources/css/app.css', 'resources/js/app.js'],
refresh: [
...refreshPaths,
'app/Livewire/**',
],
}),
],
})
```
### Compiling assets
Compile your new CSS and Javascript assets using `npm run dev`.
### Configuring your layout
Create a new `resources/views/components/layouts/app.blade.php` layout file for Livewire components:
```blade
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="application-name" content="{{ config('app.name') }}">
<meta name="csrf-token" content="{{ csrf_token() }}">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ config('app.name') }}</title>
<style>
[x-cloak] {
display: none !important;
}
</style>
@filamentStyles
@vite('resources/css/app.css')
</head>
<body class="antialiased">
{{ $slot }}
@filamentScripts
@vite('resources/js/app.js')
</body>
</html>
```
## Publishing configuration
You can publish the package configuration using the following command (optional):
```bash
php artisan vendor:publish --tag=filament-config
```
## Upgrading
> Upgrading from Filament v2? Please review the [upgrade guide](upgrade-guide).
Filament automatically upgrades to the latest non-breaking version when you run `composer update`. After any updates, all Laravel caches need to be cleared, and frontend assets need to be republished. You can do this all at once using the `filament:upgrade` command, which should have been added to your `composer.json` file when you ran `filament:install` the first time:
```json
"post-autoload-dump": [
// ...
"@php artisan filament:upgrade"
],
```
Please note that `filament:upgrade` does not actually handle the update process, as Composer does that already. If you're upgrading manually without a `post-autoload-dump` hook, you can run the command yourself:
```bash
composer update
php artisan filament:upgrade
```

View File

@@ -0,0 +1,177 @@
---
title: Getting started
---
import AutoScreenshot from "@components/AutoScreenshot.astro"
## Overview
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/getting-started) 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/getting-started), or another Filament package, you're ready to go!
## 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/getting-started#available-columns). You can even [create your own custom column types](columns/custom) to display data in whatever way you need.
Columns are stored in an array, as objects within the `$table->columns()` method:
```php
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'),
IconColumn::make('is_featured')
->boolean(),
]);
}
```
<AutoScreenshot name="tables/getting-started/columns" alt="Table with columns" version="3.x" />
In this example, there are 3 columns in the table. The first two display [text](columns/text) - the title and slug of each row in the table. The third column displays an [icon](columns/icon), either a green check or a red cross depending on if the row is featured or not.
### Making columns sortable and searchable
You can easily modify columns by chaining methods onto them. For example, you can make a column [searchable](columns/getting-started#searching) using the `searchable()` method. Now, there will be a search field in the table, and you will be able to filter rows by the value of that column:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('title')
->searchable()
```
<AutoScreenshot name="tables/getting-started/searchable-columns" alt="Table with searchable column" version="3.x" />
You can make multiple columns searchable, and Filament will be able to search for matches within any of them, all at once.
You can also make a column [sortable](columns/getting-started#sorting) using the `sortable()` method. This will add a sort button to the column header, and clicking it will sort the table by that column:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('title')
->sortable()
```
<AutoScreenshot name="tables/getting-started/sortable-columns" alt="Table with sortable column" version="3.x" />
### Accessing related data from columns
You can also display data in a column that belongs to a relationship. For example, if you have a `Post` model that belongs to a `User` model (the author of the post), you can display the user's name in the table:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('author.name')
```
<AutoScreenshot name="tables/getting-started/relationship-columns" alt="Table with relationship column" version="3.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.
## 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:
```php
use Filament\Tables\Filters\Filter;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
public function table(Table $table): Table
{
return $table
->columns([
// ...
])
->filters([
Filter::make('is_featured')
->query(fn (Builder $query) => $query->where('is_featured', true)),
SelectFilter::make('status')
->options([
'draft' => 'Draft',
'reviewing' => 'Reviewing',
'published' => 'Published',
]),
]);
}
```
<AutoScreenshot name="tables/getting-started/filters" alt="Table with filters" version="3.x" />
In this example, we have defined 2 table filters. On the table, there is now a "filter" icon button in the top corner. Clicking it will open a dropdown with the 2 filters we have defined.
The first filter is rendered as a checkbox. When it's checked, only featured rows in the table will be displayed. When it's unchecked, all rows will be displayed.
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).
## 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.
```php
use App\Models\Post;
use Filament\Tables\Actions\Action;
use Filament\Tables\Actions\BulkActionGroup;
use Filament\Tables\Actions\DeleteBulkAction;
public function table(Table $table): Table
{
return $table
->columns([
// ...
])
->actions([
Action::make('feature')
->action(function (Post $record) {
$record->is_featured = true;
$record->save();
})
->hidden(fn (Post $record): bool => $record->is_featured),
Action::make('unfeature')
->action(function (Post $record) {
$record->is_featured = false;
$record->save();
})
->visible(fn (Post $record): bool => $record->is_featured),
])
->bulkActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
]),
]);
}
```
<AutoScreenshot name="tables/getting-started/actions" alt="Table with actions" version="3.x" />
In this example, we define 2 actions for table rows. The first action is a "feature" action. When clicked, it will set the `is_featured` attribute on the record to `true` - which is written within the `action()` method. Using the `hidden()` method, the action will be hidden if the record is already featured. The second action is an "unfeature" action. When clicked, it will set the `is_featured` attribute on the record to `false`. Using the `visible()` method, the action will be hidden if the record is not featured.
We also define a bulk action. When bulk actions are defined, each row in the table will have a checkbox. This bulk action is [built-in to Filament](../actions/prebuilt-actions/delete#bulk-delete), and it will delete all selected records. However, you can [write your own custom bulk actions](actions#bulk-actions) easily too.
<AutoScreenshot name="tables/getting-started/actions-modal" alt="Table with action modal open" version="3.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.
## Next steps with the Table Builder package
Now you've finished reading this guide, where to next? Here are some suggestions:
- [Explore the available columns to display data in your table.](columns/getting-started#available-columns)
- [Deep dive into table actions and start using modals.](actions)
- [Discover how to build complex, responsive table layouts without touching CSS.](layout)
- [Add summaries to your tables, which give an overview of the data inside them.](summaries)
- [Find out about all advanced techniques that you can customize tables to your needs.](advanced)
- [Write automated tests for your tables using our suite of helper methods.](testing)

View File

@@ -0,0 +1,641 @@
---
title: Getting started
---
import AutoScreenshot from "@components/AutoScreenshot.astro"
import LaracastsBanner from "@components/LaracastsBanner.astro"
## Overview
<LaracastsBanner
title="Table Columns"
description="Watch the Rapid Laravel Development with Filament series on Laracasts - it will teach you the basics of adding columns to Filament resource tables."
url="https://laracasts.com/series/rapid-laravel-development-with-filament/episodes/9"
series="rapid-laravel-development"
/>
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:
- [Text column](text)
- [Icon column](icon)
- [Image column](image)
- [Color column](color)
Editable columns allow the user to update data in the database without leaving the table:
- [Select column](select)
- [Toggle column](toggle)
- [Text input column](text-input)
- [Checkbox column](checkbox)
You may also [create your own custom columns](custom) 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:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('title')
->label('Post title')
```
Optionally, you can have the label automatically translated [using Laravel's localization features](https://laravel.com/docs/localization) with the `translateLabel()` method:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('title')
->translateLabel() // Equivalent to `label(__('Title'))`
```
## Sorting
Columns may be sortable, by clicking on the column label. To make a column sortable, you must use the `sortable()` method:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('name')
->sortable()
```
<AutoScreenshot name="tables/columns/sortable" alt="Table with sortable column" version="3.x" />
If you're using an accessor column, you may pass `sortable()` an array of database columns to sort by:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('full_name')
->sortable(['first_name', 'last_name'])
```
You may customize how the sorting is applied to the Eloquent query using a callback:
```php
use Filament\Tables\Columns\TextColumn;
use Illuminate\Database\Eloquent\Builder;
TextColumn::make('full_name')
->sortable(query: function (Builder $query, string $direction): Builder {
return $query
->orderBy('last_name', $direction)
->orderBy('first_name', $direction);
})
```
## 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:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->columns([
// ...
])
->defaultSort('stock', 'desc');
}
```
### Persist sort in session
To persist the sorting in the user's session, use the `persistSortInSession()` method:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->columns([
// ...
])
->persistSortInSession();
}
```
## Searching
Columns may be searchable by using the text input field in the top right of the table. To make a column searchable, you must use the `searchable()` method:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('name')
->searchable()
```
<AutoScreenshot name="tables/columns/searchable" alt="Table with searchable column" version="3.x" />
If you're using an accessor column, you may pass `searchable()` an array of database columns to search within:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('full_name')
->searchable(['first_name', 'last_name'])
```
You may customize how the search is applied to the Eloquent query using a callback:
```php
use Filament\Tables\Columns\TextColumn;
use Illuminate\Database\Eloquent\Builder;
TextColumn::make('full_name')
->searchable(query: function (Builder $query, string $search): Builder {
return $query
->where('first_name', 'like', "%{$search}%")
->orWhere('last_name', 'like', "%{$search}%");
})
```
#### Customizing the table search field placeholder
You may customize the placeholder in the search field using the `searchPlaceholder()` method on the `$table`:
```php
use Filament\Tables\Table;
public static function table(Table $table): Table
{
return $table
->columns([
// ...
])
->searchPlaceholder('Search (ID, Name)');
}
```
### Searching individually
You can choose to enable a per-column search input field using the `isIndividual` parameter:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('name')
->searchable(isIndividual: true)
```
<AutoScreenshot name="tables/columns/individually-searchable" alt="Table with individually searchable column" version="3.x" />
If you use the `isIndividual` parameter, you may still search that column using the main "global" search input field for the entire table.
To disable that functionality while still preserving the individual search functionality, you need the `isGlobal` parameter:
```php
use Filament\Tables\Columns\TextColumn;
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`:
```php
use Filament\Tables\Table;
public static function table(Table $table): Table
{
return $table
->columns([
// ...
])
->searchDebounce('750ms');
}
```
### Searching when the input is blurred
Instead of automatically reloading the table contents while the user is typing their search, which is affected by the [debounce](#customizing-the-table-search-debounce) of the search field, you may change the behavior so that the table is only searched when the user blurs the input (tabs or clicks out of it), using the `searchOnBlur()` method:
```php
use Filament\Tables\Table;
public static function table(Table $table): Table
{
return $table
->columns([
// ...
])
->searchOnBlur();
}
```
### Persist search in session
To persist the table or individual column search in the user's session, use the `persistSearchInSession()` or `persistColumnSearchInSession()` method:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->columns([
// ...
])
->persistSearchInSession()
->persistColumnSearchesInSession();
}
```
## Column actions and URLs
When a cell is clicked, you may run an "action", or open a URL.
### Running actions
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:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('title')
->action(function (Post $record): void {
$this->dispatch('open-post-edit-modal', post: $record->getKey());
})
```
#### Action modals
You may open [action modals](../actions#modals) by passing in an `Action` object to the `action()` method:
```php
use Filament\Tables\Actions\Action;
use Filament\Tables\Columns\TextColumn;
TextColumn::make('title')
->action(
Action::make('select')
->requiresConfirmation()
->action(function (Post $record): void {
$this->dispatch('select-post', post: $record->getKey());
}),
)
```
Action objects passed into the `action()` method must have a unique name to distinguish it from other actions within the table.
### Opening URLs
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:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('title')
->url(fn (Post $record): string => route('posts.edit', ['post' => $record]))
```
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()
```
## Setting a default value
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.
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('description')
->default('No description.')
```
## 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-a-default-value), as the placeholder is always text and not treated as if it were real state.
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('description')
->placeholder('No description.')
```
<AutoScreenshot name="tables/columns/placeholder" alt="Column with a placeholder for empty state" version="3.x" />
## Hiding columns
To hide a column conditionally, you may use the `hidden()` and `visible()` methods, whichever you prefer:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('role')
->hidden(! auth()->user()->isAdmin())
// or
TextColumn::make('role')
->visible(auth()->user()->isAdmin())
```
### Toggling column visibility
Users may hide or show columns themselves in the table. To make a column toggleable, use the `toggleable()` method:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('email')
->toggleable()
```
<AutoScreenshot name="tables/columns/toggleable" alt="Table with toggleable column" version="3.x" />
#### Making toggleable columns hidden by default
By default, toggleable columns are visible. To make them hidden instead:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('id')
->toggleable(isToggledHiddenByDefault: true)
```
#### Customizing the toggle columns dropdown trigger action
To customize the toggle dropdown trigger button, you may use the `toggleColumnsTriggerAction()` method, passing a closure that returns an action. All methods that are available to [customize action trigger buttons](../actions/trigger-button) can be used:
```php
use Filament\Tables\Actions\Action;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->filters([
// ...
])
->toggleColumnsTriggerAction(
fn (Action $action) => $action
->button()
->label('Toggle columns'),
);
}
```
## Calculated state
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="3.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="3.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="3.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="3.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()`:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('slug')
->extraAttributes(['class' => 'bg-gray-200'])
```
These get merged onto the outer `<div>` element of each cell in that column.
## 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 columns [`searchable()`](#searching) and [`toggleable()`](#toggling-column-visibility), you can do it like so:
```php
use Filament\Tables\Columns\Column;
Column::configureUsing(function (Column $column): void {
$column
->toggleable()
->searchable();
});
```
Additionally, you can call this code on specific column types as well:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::configureUsing(function (TextColumn $column): void {
$column
->toggleable()
->searchable();
});
```
Of course, you are still able to overwrite this on each column individually:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('name')
->toggleable(false)
```

View File

@@ -0,0 +1,526 @@
---
title: Text column
---
import AutoScreenshot from "@components/AutoScreenshot.astro"
## Overview
Text columns display simple text from your database:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('title')
```
<AutoScreenshot name="tables/columns/text/simple" alt="Text column" version="3.x" />
## Displaying as a "badge"
By default, the text is quite plain and has no background color. You can make it appear as a "badge" instead using the `badge()` method. A great use case for this is with statuses, where may want to display a badge with a [color](#customizing-the-color) that matches the status:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('status')
->badge()
->color(fn (string $state): string => match ($state) {
'draft' => 'gray',
'reviewing' => 'warning',
'published' => 'success',
'rejected' => 'danger',
})
```
<AutoScreenshot name="tables/columns/text/badge" alt="Text column as badge" version="3.x" />
You may add other things to the badge, like an [icon](#adding-an-icon).
## Displaying a description
Descriptions may be used to easily render additional text above or below the column contents.
You can display a description below the contents of a text column using the `description()` method:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('title')
->description(fn (Post $record): string => $record->description)
```
<AutoScreenshot name="tables/columns/text/description" alt="Text column with description" version="3.x" />
By default, the description is displayed below the main text, but you can move it above using the second parameter:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('title')
->description(fn (Post $record): string => $record->description, position: 'above')
```
<AutoScreenshot name="tables/columns/text/description-above" alt="Text column with description above the content" version="3.x" />
## Date formatting
You may use the `date()` and `dateTime()` methods to format the column's state using [PHP date formatting tokens](https://www.php.net/manual/en/datetime.format.php):
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('created_at')
->dateTime()
```
You may use the `since()` method to format the column's state using [Carbon's `diffForHumans()`](https://carbon.nesbot.com/docs/#api-humandiff):
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('created_at')
->since()
```
## Number formatting
The `numeric()` method allows you to format an entry as a number:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('stock')
->numeric()
```
If you would like to customize the number of decimal places used to format the number with, you can use the `decimalPlaces` argument:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('stock')
->numeric(decimalPlaces: 0)
```
By default, your app's locale will be used to format the number suitably. If you would like to customize the locale used, you can pass it to the `locale` argument:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('stock')
->numeric(locale: 'nl')
```
Alternatively, you can set the default locale used across your app using the `Number::useLocale()` method in the `boot()` method of a service provider:
```php
use Illuminate\Support\Number;
Number::useLocale('nl');
```
## Currency formatting
The `money()` method allows you to easily format monetary values, in any currency:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('price')
->money('EUR')
```
There is also a `divideBy` argument for `money()` that allows you to divide the original value by a number before formatting it. This could be useful if your database stores the price in cents, for example:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('price')
->money('EUR', divideBy: 100)
```
By default, your app's locale will be used to format the money suitably. If you would like to customize the locale used, you can pass it to the `locale` argument:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('price')
->money('EUR', locale: 'nl')
```
Alternatively, you can set the default locale used across your app using the `Number::useLocale()` method in the `boot()` method of a service provider:
```php
use Illuminate\Support\Number;
Number::useLocale('nl');
```
## Limiting text length
You may `limit()` the length of the cell's value:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('description')
->limit(50)
```
You may also reuse the value that is being passed to `limit()`:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('description')
->limit(50)
->tooltip(function (TextColumn $column): ?string {
$state = $column->getState();
if (strlen($state) <= $column->getCharacterLimit()) {
return null;
}
// Only render the tooltip if the column content exceeds the length limit.
return $state;
})
```
## Limiting word count
You may limit the number of `words()` displayed in the cell:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('description')
->words(10)
```
## Limiting text to a specific number of lines
You may want to limit text to a specific number of lines instead of limiting it to a fixed length. Clamping text to a number of lines is useful in responsive interfaces where you want to ensure a consistent experience across all screen sizes. This can be achieved using the `lineClamp()` method:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('description')
->lineClamp(2)
```
## Adding a prefix or suffix
You may add a prefix or suffix to the cell's value:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('domain')
->prefix('https://')
->suffix('.com')
```
## Wrapping content
If you'd like your column's content to wrap if it's too long, you may use the `wrap()` method:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('description')
->wrap()
```
## Listing multiple values
By default, if there are multiple values inside your text column, they will be comma-separated. You may use the `listWithLineBreaks()` method to display them on new lines instead:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('authors.name')
->listWithLineBreaks()
```
### Adding bullet points to the list
You may add a bullet point to each list item using the `bulleted()` method:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('authors.name')
->listWithLineBreaks()
->bulleted()
```
### Limiting the number of values in the list
You can limit the number of values in the list using the `limitList()` method:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('authors.name')
->listWithLineBreaks()
->limitList(3)
```
#### Expanding the limited list
You can allow the limited items to be expanded and collapsed, using the `expandableLimitedList()` method:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('authors.name')
->listWithLineBreaks()
->limitList(3)
->expandableLimitedList()
```
Please note that this is only a feature for `listWithLineBreaks()` or `bulleted()`, where each item is on its own line.
### Using a list separator
If you want to "explode" a text string from your model into multiple list items, you can do so with the `separator()` method. This is useful for displaying comma-separated tags [as badges](#displaying-as-a-badge), for example:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('tags')
->badge()
->separator(',')
```
## Rendering HTML
If your column value is HTML, you may render it using `html()`:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('description')
->html()
```
If you use this method, then the HTML will be sanitized to remove any potentially unsafe content before it is rendered. If you'd like to opt out of this behavior, you can wrap the HTML in an `HtmlString` object by formatting it:
```php
use Filament\Tables\Columns\TextColumn;
use Illuminate\Support\HtmlString;
TextColumn::make('description')
->formatStateUsing(fn (string $state): HtmlString => new HtmlString($state))
```
Or, you can return a `view()` object from the `formatStateUsing()` method, which will also not be sanitized:
```php
use Filament\Tables\Columns\TextColumn;
use Illuminate\Contracts\View\View;
TextColumn::make('description')
->formatStateUsing(fn (string $state): View => view(
'filament.tables.columns.description-entry-content',
['state' => $state],
))
```
### Rendering Markdown as HTML
If your column contains Markdown, you may render it using `markdown()`:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('description')
->markdown()
```
## Custom formatting
You may instead pass a custom formatting callback to `formatStateUsing()`, which accepts the `$state` of the cell, and optionally its `$record`:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('status')
->formatStateUsing(fn (string $state): string => __("statuses.{$state}"))
```
## Customizing the color
You may set a color for the text, either `danger`, `gray`, `info`, `primary`, `success` or `warning`:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('status')
->color('primary')
```
<AutoScreenshot name="tables/columns/text/color" alt="Text column in the primary color" version="3.x" />
## Adding an icon
Text columns may also have an icon:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('email')
->icon('heroicon-m-envelope')
```
<AutoScreenshot name="tables/columns/text/icon" alt="Text column with icon" version="3.x" />
You may set the position of an icon using `iconPosition()`:
```php
use Filament\Support\Enums\IconPosition;
use Filament\Tables\Columns\TextColumn;
TextColumn::make('email')
->icon('heroicon-m-envelope')
->iconPosition(IconPosition::After) // `IconPosition::Before` or `IconPosition::After`
```
<AutoScreenshot name="tables/columns/text/icon-after" alt="Text column with icon after" version="3.x" />
The icon color defaults to the text color, but you may customize the icon color separately using `iconColor()`:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('email')
->icon('heroicon-m-envelope')
->iconColor('primary')
```
<AutoScreenshot name="tables/columns/text/icon-color" alt="Text column with icon in the primary color" version="3.x" />
## Customizing the text size
Text columns have small font size by default, but you may change this to `TextColumnSize::ExtraSmall`, `TextColumnSize::Medium`, or `TextColumnSize::Large`.
For instance, you may make the text larger using `size(TextColumnSize::Large)`:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('title')
->size(TextColumn\TextColumnSize::Large)
```
<AutoScreenshot name="tables/columns/text/large" alt="Text column in a large font size" version="3.x" />
## Customizing the font weight
Text columns have regular font weight by default, but you may change this to any of the following options: `FontWeight::Thin`, `FontWeight::ExtraLight`, `FontWeight::Light`, `FontWeight::Medium`, `FontWeight::SemiBold`, `FontWeight::Bold`, `FontWeight::ExtraBold` or `FontWeight::Black`.
For instance, you may make the font bold using `weight(FontWeight::Bold)`:
```php
use Filament\Support\Enums\FontWeight;
use Filament\Tables\Columns\TextColumn;
TextColumn::make('title')
->weight(FontWeight::Bold)
```
<AutoScreenshot name="tables/columns/text/bold" alt="Text column in a bold font" version="3.x" />
## Customizing the font family
You can change the text font family to any of the following options: `FontFamily::Sans`, `FontFamily::Serif` or `FontFamily::Mono`.
For instance, you may make the font mono using `fontFamily(FontFamily::Mono)`:
```php
use Filament\Support\Enums\FontFamily;
use Filament\Tables\Columns\TextColumn;
TextColumn::make('email')
->fontFamily(FontFamily::Mono)
```
<AutoScreenshot name="tables/columns/text/mono" alt="Text column in a monospaced font" version="3.x" />
## Allowing the text to be copied to the clipboard
You may make the text copyable, such that clicking on the cell copies the text to the clipboard, and optionally specify a custom confirmation message and duration in milliseconds. This feature only works when SSL is enabled for the app.
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('email')
->copyable()
->copyMessage('Email address copied')
->copyMessageDuration(1500)
```
<AutoScreenshot name="tables/columns/text/copyable" alt="Text column with a button to copy it" version="3.x" />
### Customizing the text that is copied to the clipboard
You can customize the text that gets copied to the clipboard using the `copyableState()` method:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('url')
->copyable()
->copyableState(fn (string $state): string => "URL: {$state}")
```
In this function, you can access the whole table row with `$record`:
```php
use App\Models\Post;
use Filament\Tables\Columns\TextColumn;
TextColumn::make('url')
->copyable()
->copyableState(fn (Post $record): string => "URL: {$record->url}")
```
## Displaying the row index
You may want a column to contain the number of the current row in the table:
```php
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Contracts\HasTable;
TextColumn::make('index')->state(
static function (HasTable $livewire, stdClass $rowLoop): string {
return (string) (
$rowLoop->iteration +
($livewire->getTableRecordsPerPage() * (
$livewire->getTablePage() - 1
))
);
}
),
```
As `$rowLoop` is [Laravel Blade's `$loop` object](https://laravel.com/docs/blade#the-loop-variable), you can reference all other `$loop` properties.
As a shortcut, you may use the `rowIndex()` method:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('index')
->rowIndex()
```
To start counting from 0 instead of 1, use `isFromZero: true`:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('index')
->rowIndex(isFromZero: true)
```

View File

@@ -0,0 +1,115 @@
---
title: Icon column
---
import AutoScreenshot from "@components/AutoScreenshot.astro"
## Overview
Icon columns render an [icon](https://blade-ui-kit.com/blade-icons?set=1#search) representing their contents:
```php
use Filament\Tables\Columns\IconColumn;
IconColumn::make('status')
->icon(fn (string $state): string => match ($state) {
'draft' => 'heroicon-o-pencil',
'reviewing' => 'heroicon-o-clock',
'published' => 'heroicon-o-check-circle',
})
```
In the function, `$state` is the value of the column, and `$record` can be used to access the underlying Eloquent record.
<AutoScreenshot name="tables/columns/icon/simple" alt="Icon column" version="3.x" />
## Customizing the color
Icon columns may also have a set of icon colors, using the same syntax. They may be either `danger`, `gray`, `info`, `primary`, `success` or `warning`:
```php
use Filament\Tables\Columns\IconColumn;
IconColumn::make('status')
->color(fn (string $state): string => match ($state) {
'draft' => 'info',
'reviewing' => 'warning',
'published' => 'success',
default => 'gray',
})
```
In the function, `$state` is the value of the column, and `$record` can be used to access the underlying Eloquent record.
<AutoScreenshot name="tables/columns/icon/color" alt="Icon column with color" version="3.x" />
## Customizing the size
The default icon size is `IconColumnSize::Large`, but you may customize the size to be either `IconColumnSize::ExtraSmall`, `IconColumnSize::Small`, `IconColumnSize::Medium`, `IconColumnSize::ExtraLarge` or `IconColumnSize::TwoExtraLarge`:
```php
use Filament\Tables\Columns\IconColumn;
IconColumn::make('status')
->size(IconColumn\IconColumnSize::Medium)
```
<AutoScreenshot name="tables/columns/icon/medium" alt="Medium-sized icon column" version="3.x" />
## Handling booleans
Icon columns can display a check or cross icon based on the contents of the database column, either true or false, using the `boolean()` method:
```php
use Filament\Tables\Columns\IconColumn;
IconColumn::make('is_featured')
->boolean()
```
> If this column in the model class is already cast as a `bool` or `boolean`, Filament is able to detect this, and you do not need to use `boolean()` manually.
<AutoScreenshot name="tables/columns/icon/boolean" alt="Icon column to display a boolean" version="3.x" />
### Customizing the boolean icons
You may customize the icon representing each state. Icons are the name of a Blade component present. By default, [Heroicons](https://heroicons.com) are installed:
```php
use Filament\Tables\Columns\IconColumn;
IconColumn::make('is_featured')
->boolean()
->trueIcon('heroicon-o-check-badge')
->falseIcon('heroicon-o-x-mark')
```
<AutoScreenshot name="tables/columns/icon/boolean-icon" alt="Icon column to display a boolean with custom icons" version="3.x" />
### Customizing the boolean colors
You may customize the icon color representing each state. These may be either `danger`, `gray`, `info`, `primary`, `success` or `warning`:
```php
use Filament\Tables\Columns\IconColumn;
IconColumn::make('is_featured')
->boolean()
->trueColor('info')
->falseColor('warning')
```
<AutoScreenshot name="tables/columns/icon/boolean-color" alt="Icon column to display a boolean with custom colors" version="3.x" />
## Wrapping multiple icons
When displaying multiple icons, they can be set to wrap if they can't fit on one line, using `wrap()`:
```php
use Filament\Tables\Columns\IconColumn;
IconColumn::make('icon')
->wrap()
```
Note: the "width" for wrapping is affected by the column label, so you may need to use a shorter or hidden label to wrap more tightly.

View File

@@ -0,0 +1,239 @@
---
title: Image column
---
import AutoScreenshot from "@components/AutoScreenshot.astro"
## Overview
Images can be easily displayed within your table:
```php
use Filament\Tables\Columns\ImageColumn;
ImageColumn::make('avatar')
```
The column in the database must contain the path to the image, relative to the root directory of its storage disk.
<AutoScreenshot name="tables/columns/image/simple" alt="Image column" version="3.x" />
## Managing the image disk
By default, the `public` disk will be used to retrieve images. You may pass a custom disk name to the `disk()` method:
```php
use Filament\Tables\Columns\ImageColumn;
ImageColumn::make('header_image')
->disk('s3')
```
## Private images
Filament can generate temporary URLs to render private images, you may set the `visibility()` to `private`:
```php
use Filament\Tables\Columns\ImageColumn;
ImageColumn::make('header_image')
->visibility('private')
```
## Customizing the size
You may customize the image size by passing a `width()` and `height()`, or both with `size()`:
```php
use Filament\Tables\Columns\ImageColumn;
ImageColumn::make('header_image')
->width(200)
ImageColumn::make('header_image')
->height(50)
ImageColumn::make('author.avatar')
->size(40)
```
## Square image
You may display the image using a 1:1 aspect ratio:
```php
use Filament\Tables\Columns\ImageColumn;
ImageColumn::make('avatar')
->square()
```
<AutoScreenshot name="tables/columns/image/square" alt="Square image column" version="3.x" />
## Circular image
You may make the image fully rounded, which is useful for rendering avatars:
```php
use Filament\Tables\Columns\ImageColumn;
ImageColumn::make('avatar')
->circular()
```
<AutoScreenshot name="tables/columns/image/circular" alt="Circular image column" version="3.x" />
## Adding a default image URL
You can display a placeholder image if one doesn't exist yet, by passing a URL to the `defaultImageUrl()` method:
```php
use Filament\Tables\Columns\ImageColumn;
ImageColumn::make('avatar')
->defaultImageUrl(url('/images/placeholder.png'))
```
## Stacking images
You may display multiple images as a stack of overlapping images by using `stacked()`:
```php
use Filament\Tables\Columns\ImageColumn;
ImageColumn::make('colleagues.avatar')
->circular()
->stacked()
```
<AutoScreenshot name="tables/columns/image/stacked" alt="Stacked image column" version="3.x" />
### Customizing the stacked ring width
The default ring width is `3`, but you may customize it to be from `0` to `8`:
```php
ImageColumn::make('colleagues.avatar')
->circular()
->stacked()
->ring(5)
```
### Customizing the stacked overlap
The default overlap is `4`, but you may customize it to be from `0` to `8`:
```php
ImageColumn::make('colleagues.avatar')
->circular()
->stacked()
->overlap(2)
```
## Wrapping multiple images
Images can be set to wrap if they can't fit on one line, by setting `wrap()`:
```php
use Filament\Tables\Columns\ImageColumn;
ImageColumn::make('colleagues.avatar')
->circular()
->stacked()
->wrap()
```
Note: the "width" for wrapping is affected by the column label, so you may need to use a shorter or hidden label to wrap more tightly.
## Setting a limit
You may limit the maximum number of images you want to display by passing `limit()`:
```php
use Filament\Tables\Columns\ImageColumn;
ImageColumn::make('colleagues.avatar')
->circular()
->stacked()
->limit(3)
```
<AutoScreenshot name="tables/columns/image/limited" alt="Limited image column" version="3.x" />
### Showing the remaining images count
When you set a limit you may also display the count of remaining images by passing `limitedRemainingText()`.
```php
use Filament\Tables\Columns\ImageColumn;
ImageColumn::make('colleagues.avatar')
->circular()
->stacked()
->limit(3)
->limitedRemainingText()
```
<AutoScreenshot name="tables/columns/image/limited-remaining-text" alt="Limited image column with remaining text" version="3.x" />
#### Showing the limited remaining text separately
By default, `limitedRemainingText()` will display the count of remaining images as a number stacked on the other images. If you prefer to show the count as a number after the images, you may use the `isSeparate: true` parameter:
```php
use Filament\Tables\Columns\ImageColumn;
ImageColumn::make('colleagues.avatar')
->circular()
->stacked()
->limit(3)
->limitedRemainingText(isSeparate: true)
```
<AutoScreenshot name="tables/columns/image/limited-remaining-text-separately" alt="Limited image column with remaining text separately" version="3.x" />
#### Customizing the limited remaining text size
By default, the size of the remaining text is `sm`. You can customize this to be `xs`, `md` or `lg` using the `size` parameter:
```php
use Filament\Tables\Columns\ImageColumn;
ImageColumn::make('colleagues.avatar')
->circular()
->stacked()
->limit(3)
->limitedRemainingText(size: 'lg')
```
## Custom attributes
You may customize the extra HTML attributes of the image using `extraImgAttributes()`:
```php
use Filament\Tables\Columns\ImageColumn;
ImageColumn::make('logo')
->extraImgAttributes(['loading' => 'lazy']),
```
You can access the current record using a `$record` parameter:
```php
use Filament\Tables\Columns\ImageColumn;
ImageColumn::make('logo')
->extraImgAttributes(fn (Company $record): array => [
'alt' => "{$record->name} logo",
]),
```
## Prevent file existence checks
When the table is loaded, it will automatically detect whether the images exist. This is all done on the backend. When using remote storage with many images, this can be time-consuming. You can use the `checkFileExistence(false)` method to disable this feature:
```php
use Filament\Tables\Columns\ImageColumn;
ImageColumn::make('attachment')
->checkFileExistence(false)
```

View File

@@ -0,0 +1,68 @@
---
title: Color column
---
import AutoScreenshot from "@components/AutoScreenshot.astro"
## Overview
The color column allows you to show the color preview from a CSS color definition, typically entered using the color picker field, in one of the supported formats (HEX, HSL, RGB, RGBA).
```php
use Filament\Tables\Columns\ColorColumn;
ColorColumn::make('color')
```
<AutoScreenshot name="tables/columns/color/simple" alt="Color column" version="3.x" />
## Allowing the color to be copied to the clipboard
You may make the color copyable, such that clicking on the preview copies the CSS value to the clipboard, and optionally specify a custom confirmation message and duration in milliseconds. This feature only works when SSL is enabled for the app.
```php
use Filament\Tables\Columns\ColorColumn;
ColorColumn::make('color')
->copyable()
->copyMessage('Color code copied')
->copyMessageDuration(1500)
```
<AutoScreenshot name="tables/columns/color/copyable" alt="Color column with a button to copy it" version="3.x" />
### Customizing the text that is copied to the clipboard
You can customize the text that gets copied to the clipboard using the `copyableState()` method:
```php
use Filament\Tables\Columns\ColorColumn;
ColorColumn::make('color')
->copyable()
->copyableState(fn (string $state): string => "Color: {$state}")
```
In this function, you can access the whole table row with `$record`:
```php
use App\Models\Post;
use Filament\Tables\Columns\ColorColumn;
ColorColumn::make('color')
->copyable()
->copyableState(fn (Post $record): string => "Color: {$record->color}")
```
## Wrapping multiple color blocks
Color blocks can be set to wrap if they can't fit on one line, by setting `wrap()`:
```php
use Filament\Tables\Columns\ColorColumn;
ColorColumn::make('color')
->wrap()
```
Note: the "width" for wrapping is affected by the column label, so you may need to use a shorter or hidden label to wrap more tightly.

View File

@@ -0,0 +1,69 @@
---
title: Select column
---
import AutoScreenshot from "@components/AutoScreenshot.astro"
## Overview
The select column allows you to render a select field inside the table, which can be used to update that database record without needing to open a new page or a modal.
You must pass options to the column:
```php
use Filament\Tables\Columns\SelectColumn;
SelectColumn::make('status')
->options([
'draft' => 'Draft',
'reviewing' => 'Reviewing',
'published' => 'Published',
])
```
<AutoScreenshot name="tables/columns/select/simple" alt="Select column" version="3.x" />
## Validation
You can validate the input by passing any [Laravel validation rules](https://laravel.com/docs/validation#available-validation-rules) in an array:
```php
use Filament\Tables\Columns\SelectColumn;
SelectColumn::make('status')
->options([
'draft' => 'Draft',
'reviewing' => 'Reviewing',
'published' => 'Published',
])
->rules(['required'])
```
## Disabling placeholder selection
You can prevent the placeholder from being selected using the `selectablePlaceholder()` method:
```php
use Filament\Tables\Columns\SelectColumn;
SelectColumn::make('status')
->options([
'draft' => 'Draft',
'reviewing' => 'Reviewing',
'published' => 'Published',
])
->selectablePlaceholder(false)
```
## Lifecycle hooks
Hooks may be used to execute code at various points within the select's lifecycle:
```php
SelectColumn::make()
->beforeStateUpdated(function ($record, $state) {
// Runs before the state is saved to the database.
})
->afterStateUpdated(function ($record, $state) {
// Runs after the state is saved to the database.
})
```

View File

@@ -0,0 +1,30 @@
---
title: Toggle column
---
import AutoScreenshot from "@components/AutoScreenshot.astro"
## Overview
The toggle column allows you to render a toggle button inside the table, which can be used to update that database record without needing to open a new page or a modal:
```php
use Filament\Tables\Columns\ToggleColumn;
ToggleColumn::make('is_admin')
```
<AutoScreenshot name="tables/columns/toggle/simple" alt="Toggle column" version="3.x" />
## Lifecycle hooks
Hooks may be used to execute code at various points within the toggle's lifecycle:
```php
ToggleColumn::make()
->beforeStateUpdated(function ($record, $state) {
// Runs before the state is saved to the database.
})
->afterStateUpdated(function ($record, $state) {
// Runs after the state is saved to the database.
})
```

View File

@@ -0,0 +1,51 @@
---
title: Text input column
---
import AutoScreenshot from "@components/AutoScreenshot.astro"
## Overview
The text input column allows you to render a text input inside the table, which can be used to update that database record without needing to open a new page or a modal:
```php
use Filament\Tables\Columns\TextInputColumn;
TextInputColumn::make('email')
```
<AutoScreenshot name="tables/columns/text-input/simple" alt="Text input column" version="3.x" />
## Validation
You can validate the input by passing any [Laravel validation rules](https://laravel.com/docs/validation#available-validation-rules) in an array:
```php
use Filament\Tables\Columns\TextInputColumn;
TextInputColumn::make('name')
->rules(['required', 'max:255'])
```
## Customizing the HTML input type
You may use the `type()` method to pass a custom [HTML input type](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#input_types):
```php
use Filament\Tables\Columns\TextInputColumn;
TextInputColumn::make('background_color')->type('color')
```
## Lifecycle hooks
Hooks may be used to execute code at various points within the input's lifecycle:
```php
TextInputColumn::make()
->beforeStateUpdated(function ($record, $state) {
// Runs before the state is saved to the database.
})
->afterStateUpdated(function ($record, $state) {
// Runs after the state is saved to the database.
})
```

View File

@@ -0,0 +1,30 @@
---
title: Checkbox column
---
import AutoScreenshot from "@components/AutoScreenshot.astro"
## Overview
The checkbox column allows you to render a checkbox inside the table, which can be used to update that database record without needing to open a new page or a modal:
```php
use Filament\Tables\Columns\CheckboxColumn;
CheckboxColumn::make('is_admin')
```
<AutoScreenshot name="tables/columns/checkbox/simple" alt="Checkbox column" version="3.x" />
## Lifecycle hooks
Hooks may be used to execute code at various points within the checkbox's lifecycle:
```php
CheckboxColumn::make()
->beforeStateUpdated(function ($record, $state) {
// Runs before the state is saved to the database.
})
->afterStateUpdated(function ($record, $state) {
// Runs after the state is saved to the database.
})
```

View File

@@ -0,0 +1,68 @@
---
title: Custom columns
---
import LaracastsBanner from "@components/LaracastsBanner.astro"
<LaracastsBanner
title="Build a Custom Table Column"
description="Watch the Build Advanced Components for Filament series on Laracasts - it will teach you how to build components, and you'll get to know all the internal tools to help you."
url="https://laracasts.com/series/build-advanced-components-for-filament/episodes/10"
series="building-advanced-components"
/>
## View columns
You may render a custom view for a cell using the `view()` method:
```php
use Filament\Tables\Columns\ViewColumn;
ViewColumn::make('status')->view('filament.tables.columns.status-switcher')
```
This assumes that you have a `resources/views/filament/tables/columns/status-switcher.blade.php` file.
## Custom classes
You may create your own custom column classes and cell views, which you can reuse across your project, and even release as a plugin to the community.
> If you're just creating a simple custom column to use once, you could instead use a [view column](#view-columns) to render any custom Blade file.
To create a custom column class and view, you may use the following command:
```bash
php artisan make:table-column StatusSwitcher
```
This will create the following column class:
```php
use Filament\Tables\Columns\Column;
class StatusSwitcher extends Column
{
protected string $view = 'filament.tables.columns.status-switcher';
}
```
It will also create a view file at `resources/views/filament/tables/columns/status-switcher.blade.php`.
## Accessing the state
Inside your view, you may retrieve the state of the cell using the `$getState()` function:
```blade
<div>
{{ $getState() }}
</div>
```
## Accessing the Eloquent record
Inside your view, you may access the Eloquent record using the `$getRecord()` function:
```blade
<div>
{{ $getRecord()->name }}
</div>
```

View File

@@ -0,0 +1,82 @@
---
title: Column relationships
---
## Displaying data from relationships
You may use "dot notation" to access columns within relationships. The name of the relationship comes first, followed by a period, followed by the name of the column to display:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('author.name')
```
## Counting relationships
If you wish to count the number of related records in a column, you may use the `counts()` method:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('users_count')->counts('users')
```
In this example, `users` is the name of the relationship to count from. The name of the column must be `users_count`, as this is the convention that [Laravel uses](https://laravel.com/docs/eloquent-relationships#counting-related-models) for storing the result.
If you'd like to scope the relationship before calculating, you can pass an array to the method, where the key is the relationship name and the value is the function to scope the Eloquent query with:
```php
use Filament\Tables\Columns\TextColumn;
use Illuminate\Database\Eloquent\Builder;
TextColumn::make('users_count')->counts([
'users' => fn (Builder $query) => $query->where('is_active', true),
])
```
## Determining relationship existence
If you simply wish to indicate whether related records exist in a column, you may use the `exists()` method:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('users_exists')->exists('users')
```
In this example, `users` is the name of the relationship to check for existence. The name of the column must be `users_exists`, as this is the convention that [Laravel uses](https://laravel.com/docs/eloquent-relationships#other-aggregate-functions) for storing the result.
If you'd like to scope the relationship before calculating, you can pass an array to the method, where the key is the relationship name and the value is the function to scope the Eloquent query with:
```php
use Filament\Tables\Columns\TextColumn;
use Illuminate\Database\Eloquent\Builder;
TextColumn::make('users_exists')->exists([
'users' => fn (Builder $query) => $query->where('is_active', true),
])
```
## Aggregating relationships
Filament provides several methods for aggregating a relationship field, including `avg()`, `max()`, `min()` and `sum()`. For instance, if you wish to show the average of a field on all related records in a column, you may use the `avg()` method:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('users_avg_age')->avg('users', 'age')
```
In this example, `users` is the name of the relationship, while `age` is the field that is being averaged. The name of the column must be `users_avg_age`, as this is the convention that [Laravel uses](https://laravel.com/docs/eloquent-relationships#other-aggregate-functions) for storing the result.
If you'd like to scope the relationship before calculating, you can pass an array to the method, where the key is the relationship name and the value is the function to scope the Eloquent query with:
```php
use Filament\Tables\Columns\TextColumn;
use Illuminate\Database\Eloquent\Builder;
TextColumn::make('users_avg_age')->avg([
'users' => fn (Builder $query) => $query->where('is_active', true),
], 'age')
```

View File

@@ -0,0 +1,123 @@
---
title: Advanced columns
---
## Table column utility injection
The vast majority of methods used to configure columns accept functions as parameters instead of hardcoded values:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('status')
->color(fn (string $state): string => match ($state) {
'draft' => 'gray',
'reviewing' => 'warning',
'published' => 'success',
'rejected' => 'danger',
})
```
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 a column
If you wish to access the current state (value) of the column, define a `$state` parameter:
```php
function ($state) {
// ...
}
```
### Injecting the current Eloquent record
If you wish to access the current Eloquent record of the column, define a `$record` parameter:
```php
use Illuminate\Database\Eloquent\Model;
function (Model $record) {
// ...
}
```
Be aware that this parameter will be `null` if the column is not bound to an Eloquent record. For instance, the `label()` method of a column will not have access to the record, as the label is not related to any table row.
### Injecting the current column instance
If you wish to access the current column instance, define a `$column` parameter:
```php
use Filament\Tables\Columns\Column;
function (Column $column) {
// ...
}
```
### Injecting the current Livewire component instance
If you wish to access the current Livewire component instance that the table belongs to, define a `$livewire` parameter:
```php
use Filament\Tables\Contracts\HasTable;
function (HasTable $livewire) {
// ...
}
```
### Injecting the current table instance
If you wish to access the current table configuration instance that the column belongs to, define a `$table` parameter:
```php
use Filament\Tables\Table;
function (Table $table) {
// ...
}
```
### Injecting the current table row loop
If you wish to access the current [Laravel Blade loop object](https://laravel.com/docs/blade#the-loop-variable) that the column is rendered part of, define a `$rowLoop` parameter:
```php
function (stdClass $rowLoop) {
// ...
}
```
As `$rowLoop` is [Laravel Blade's `$loop` object](https://laravel.com/docs/blade#the-loop-variable), you can access the current row index using `$rowLoop->index`. Similar to `$record`, this parameter will be `null` if the column is currently being rendered outside a table row.
### Injecting multiple utilities
The parameters are injected dynamically using reflection, so you are able to combine multiple parameters in any order:
```php
use Filament\Tables\Contracts\HasTable;
use Illuminate\Database\Eloquent\Model;
function (HasTable $livewire, Model $record) {
// ...
}
```
### Injecting dependencies from Laravel's container
You may inject anything from Laravel's container like normal, alongside utilities:
```php
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
function (Request $request, Model $record) {
// ...
}
```

View File

@@ -0,0 +1,310 @@
---
title: Getting started
---
import AutoScreenshot from "@components/AutoScreenshot.astro"
import LaracastsBanner from "@components/LaracastsBanner.astro"
## Overview
<LaracastsBanner
title="Table Filters"
description="Watch the Rapid Laravel Development with Filament series on Laracasts - it will teach you the basics of adding filters to Filament resource tables."
url="https://laracasts.com/series/rapid-laravel-development-with-filament/episodes/10"
series="rapid-laravel-development"
/>
Filters allow you to define certain constraints on your data, and allow users to scope it to find the information they need. You put them in the `$table->filters()` method:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->filters([
// ...
]);
}
```
<AutoScreenshot name="tables/filters/simple" alt="Table with filter" version="3.x" />
Filters may be created using the static `make()` method, passing its unique name. You should then pass a callback to `query()` which applies your filter's scope:
```php
use Filament\Tables\Filters\Filter;
use Illuminate\Database\Eloquent\Builder;
Filter::make('is_featured')
->query(fn (Builder $query): Builder => $query->where('is_featured', true))
```
## Available filters
By default, using the `Filter::make()` method will render a checkbox form component. When the checkbox is on, the `query()` will be activated.
- You can also [replace the checkbox with a toggle](#using-a-toggle-button-instead-of-a-checkbox).
- You can use a [ternary filter](ternary) to replace the checkbox with a select field to allow users to pick between 3 states - usually "true", "false" and "blank". This is useful for filtering boolean columns that are nullable.
- The [trashed filter](ternary#filtering-soft-deletable-records) is a pre-built ternary filter that allows you to filter soft-deletable records.
- You may use a [select filter](select) to allow users to select from a list of options, and filter using the selection.
- You may use a [query builder](query-builder) to allow users to create complex sets of filters, with an advanced user interface for combining constraints.
- You may build [custom filters](custom) with other form fields, to do whatever you want.
## Setting a label
By default, the label of the filter, which is displayed in the filter form, is generated from the name of the filter. You may customize this using the `label()` method:
```php
use Filament\Tables\Filters\Filter;
Filter::make('is_featured')
->label('Featured')
```
Optionally, you can have the label automatically translated [using Laravel's localization features](https://laravel.com/docs/localization) with the `translateLabel()` method:
```php
use Filament\Tables\Filters\Filter;
Filter::make('is_featured')
->translateLabel() // Equivalent to `label(__('Is featured'))`
```
## Customizing the filter form
By default, creating a filter with the `Filter` class will render a [checkbox form component](../../forms/fields/checkbox). When the checkbox is checked, the `query()` function will be applied to the table's query, scoping the records in the table. When the checkbox is unchecked, the `query()` function will be removed from the table's query.
Filters are built entirely on Filament's form fields. They can render any combination of form fields, which users can then interact with to filter the table.
### Using a toggle button instead of a checkbox
The simplest example of managing the form field that is used for a filter is to replace the [checkbox](../../forms/fields/checkbox) with a [toggle button](../../forms/fields/toggle), using the `toggle()` method:
```php
use Filament\Tables\Filters\Filter;
Filter::make('is_featured')
->toggle()
```
<AutoScreenshot name="tables/filters/toggle" alt="Table with toggle filter" version="3.x" />
### Applying the filter by default
You may set a filter to be enabled by default, using the `default()` method:
```php
use Filament\Tables\Filters\Filter;
Filter::make('is_featured')
->default()
```
### Customizing the built-in filter form field
Whether you are using a checkbox, a [toggle](#using-a-toggle-button-instead-of-a-checkbox) or a [select](select), you can customize the built-in form field used for the filter, using the `modifyFormFieldUsing()` method. The method accepts a function with a `$field` parameter that gives you access to the form field object to customize:
```php
use Filament\Forms\Components\Checkbox;
use Filament\Tables\Filters\Filter;
Filter::make('is_featured')
->modifyFormFieldUsing(fn (Checkbox $field) => $field->inline(false))
```
## Persist filters in session
To persist the table filters in the user's session, use the `persistFiltersInSession()` method:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->filters([
// ...
])
->persistFiltersInSession();
}
```
## Deferring filters
You can defer filter changes from affecting the table, until the user clicks an "Apply" button. To do this, use the `deferFilters()` method:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->filters([
// ...
])
->deferFilters();
}
```
### Customizing the apply filters action
When deferring filters, you can customize the "Apply" button, using the `filtersApplyAction()` method, passing a closure that returns an action. All methods that are available to [customize action trigger buttons](../../actions/trigger-button) can be used:
```php
use Filament\Tables\Actions\Action;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->filters([
// ...
])
->filtersApplyAction(
fn (Action $action) => $action
->link()
->label('Save filters to table'),
);
}
```
## Deselecting records when filters change
By default, all records will be deselected when the filters change. Using the `deselectAllRecordsWhenFiltered(false)` method, you can disable this behavior:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->filters([
// ...
])
->deselectAllRecordsWhenFiltered(false);
}
```
## Modifying the base query
By default, modifications to the Eloquent query performed in the `query()` method will be applied inside a scoped `where()` clause. This is to ensure that the query does not clash with any other filters that may be applied, especially those that use `orWhere()`.
However, the downside of this is that the `query()` method cannot be used to modify the query in other ways, such as removing global scopes, since the base query needs to be modified directly, not the scoped query.
To modify the base query directly, you may use the `baseQuery()` method, passing a closure that receives the base query:
```php
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
use Filament\Tables\Filters\TernaryFilter;
TernaryFilter::make('trashed')
// ...
->baseQuery(fn (Builder $query) => $query->withoutGlobalScopes([
SoftDeletingScope::class,
]))
```
## Customizing the filters trigger action
To customize the filters trigger buttons, you may use the `filtersTriggerAction()` method, passing a closure that returns an action. All methods that are available to [customize action trigger buttons](../../actions/trigger-button) can be used:
```php
use Filament\Tables\Actions\Action;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->filters([
// ...
])
->filtersTriggerAction(
fn (Action $action) => $action
->button()
->label('Filter'),
);
}
```
<AutoScreenshot name="tables/filters/custom-trigger-action" alt="Table with custom filters trigger action" version="3.x" />
## Table filter utility injection
The vast majority of methods used to configure filters accept functions as parameters instead of hardcoded values:
```php
use App\Models\Author;
use Filament\Tables\Filters\SelectFilter;
SelectFilter::make('author')
->options(fn (): array => Author::query()->pluck('name', 'id')->all())
```
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 filter instance
If you wish to access the current filter instance, define a `$filter` parameter:
```php
use Filament\Tables\Filters\BaseFilter;
function (BaseFilter $filter) {
// ...
}
```
### Injecting the current Livewire component instance
If you wish to access the current Livewire component instance that the table belongs to, define a `$livewire` parameter:
```php
use Filament\Tables\Contracts\HasTable;
function (HasTable $livewire) {
// ...
}
```
### Injecting the current table instance
If you wish to access the current table configuration instance that the filter belongs to, 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 Filament\Tables\Contracts\HasTable;
use Filament\Tables\Table;
function (HasTable $livewire, Table $table) {
// ...
}
```
### Injecting dependencies from Laravel's container
You may inject anything from Laravel's container like normal, alongside utilities:
```php
use Filament\Tables\Table;
use Illuminate\Http\Request;
function (Request $request, Table $table) {
// ...
}
```

View File

@@ -0,0 +1,119 @@
---
title: Select filters
---
import AutoScreenshot from "@components/AutoScreenshot.astro"
import LaracastsBanner from "@components/LaracastsBanner.astro"
## Overview
Often, you will want to use a [select field](../../forms/fields/select) instead of a checkbox. This is especially true when you want to filter a column based on a set of pre-defined options that the user can choose from. To do this, you can create a filter using the `SelectFilter` class:
```php
use Filament\Tables\Filters\SelectFilter;
SelectFilter::make('status')
->options([
'draft' => 'Draft',
'reviewing' => 'Reviewing',
'published' => 'Published',
])
```
The `options()` that are passed to the filter are the same as those that are passed to the [select field](../../forms/fields/select).
## Customizing the column used by a select filter
Select filters do not require a custom `query()` method. The column name used to scope the query is the name of the filter. To customize this, you may use the `attribute()` method:
```php
use Filament\Tables\Filters\SelectFilter;
SelectFilter::make('status')
->options([
'draft' => 'Draft',
'reviewing' => 'Reviewing',
'published' => 'Published',
])
->attribute('status_id')
```
## Multi-select filters
These allow the user to select multiple options to apply the filter to their table. For example, a status filter may present the user with a few status options to pick from and filter the table using. When the user selects multiple options, the table will be filtered to show records that match any of the selected options. You can enable this behavior using the `multiple()` method:
```php
use Filament\Tables\Filters\SelectFilter;
SelectFilter::make('status')
->multiple()
->options([
'draft' => 'Draft',
'reviewing' => 'Reviewing',
'published' => 'Published',
])
```
## Relationship select filters
Select filters are also able to automatically populate themselves based on a relationship. For example, if your table has a `author` relationship with a `name` column, you may use `relationship()` to filter the records belonging to an author:
```php
use Filament\Tables\Filters\SelectFilter;
SelectFilter::make('author')
->relationship('author', 'name')
```
### Preloading the select filter relationship options
If you'd like to populate the searchable options from the database when the page is loaded, instead of when the user searches, you can use the `preload()` method:
```php
use Filament\Tables\Filters\SelectFilter;
SelectFilter::make('author')
->relationship('author', 'name')
->searchable()
->preload()
```
### Customizing the select filter relationship query
You may customize the database query that retrieves options using the third parameter of the `relationship()` method:
```php
use Filament\Tables\Filters\SelectFilter;
use Illuminate\Database\Eloquent\Builder;
SelectFilter::make('author')
->relationship('author', 'name', fn (Builder $query) => $query->withTrashed())
```
### Searching select filter options
You may enable a search input to allow easier access to many options, using the `searchable()` method:
```php
use Filament\Tables\Filters\SelectFilter;
SelectFilter::make('author')
->relationship('author', 'name')
->searchable()
```
## Disable placeholder selection
You can remove the placeholder (null option), which disables the filter so all options are applied, using the `selectablePlaceholder()` method:
```php
use Filament\Tables\Filters\SelectFilter;
SelectFilter::make('status')
->options([
'draft' => 'Draft',
'reviewing' => 'Reviewing',
'published' => 'Published',
])
->default('draft')
->selectablePlaceholder(false)
```

View File

@@ -0,0 +1,82 @@
---
title: Ternary filters
---
## Overview
Ternary filters allow you to easily create a select filter which has three states - usually true, false and blank. To filter a column named `is_admin` to be `true` or `false`, you may use the ternary filter:
```php
use Filament\Tables\Filters\TernaryFilter;
TernaryFilter::make('is_admin')
```
## Using a ternary filter with a nullable column
Another common pattern is to use a nullable column. For example, when filtering verified and unverified users using the `email_verified_at` column, unverified users have a null timestamp in this column. To apply that logic, you may use the `nullable()` method:
```php
use Filament\Tables\Filters\TernaryFilter;
TernaryFilter::make('email_verified_at')
->nullable()
```
## Customizing the column used by a ternary filter
The column name used to scope the query is the name of the filter. To customize this, you may use the `attribute()` method:
```php
use Filament\Tables\Filters\TernaryFilter;
TernaryFilter::make('verified')
->nullable()
->attribute('status_id')
```
## Customizing the ternary filter option labels
You may customize the labels used for each state of the ternary filter. The true option label can be customized using the `trueLabel()` method. The false option label can be customized using the `falseLabel()` method. The blank (default) option label can be customized using the `placeholder()` method:
```php
use Illuminate\Database\Eloquent\Builder;
use Filament\Tables\Filters\TernaryFilter;
TernaryFilter::make('email_verified_at')
->label('Email verification')
->nullable()
->placeholder('All users')
->trueLabel('Verified users')
->falseLabel('Not verified users')
```
## Customizing how a ternary filter modifies the query
You may customize how the query changes for each state of the ternary filter, use the `queries()` method:
```php
use Illuminate\Database\Eloquent\Builder;
use Filament\Tables\Filters\TernaryFilter;
TernaryFilter::make('email_verified_at')
->label('Email verification')
->placeholder('All users')
->trueLabel('Verified users')
->falseLabel('Not verified users')
->queries(
true: fn (Builder $query) => $query->whereNotNull('email_verified_at'),
false: fn (Builder $query) => $query->whereNull('email_verified_at'),
blank: fn (Builder $query) => $query, // In this example, we do not want to filter the query when it is blank.
)
```
## Filtering soft deletable records
The `TrashedFilter` can be used to filter soft deleted records. It is a type of ternary filter that is built-in to Filament. You can use it like so:
```php
use Filament\Tables\Filters\TrashedFilter;
TrashedFilter::make()
```

View File

@@ -0,0 +1,495 @@
---
title: Query builder
---
## Overview
The query builder allows you to define a complex set of conditions to filter the data in your table. It is able to handle unlimited nesting of conditions, which you can group together with "and" and "or" operations.
To use it, you need to define a set of "constraints" that will be used to filter the data. Filament includes some built-in constraints, that follow common data types, but you can also define your own custom constraints.
You can add a query builder to any table using the `QueryBuilder` filter:
```php
use Filament\Tables\Filters\QueryBuilder;
use Filament\Tables\Filters\QueryBuilder\Constraints\BooleanConstraint;
use Filament\Tables\Filters\QueryBuilder\Constraints\DateConstraint;
use Filament\Tables\Filters\QueryBuilder\Constraints\NumberConstraint;
use Filament\Tables\Filters\QueryBuilder\Constraints\RelationshipConstraint;
use Filament\Tables\Filters\QueryBuilder\Constraints\RelationshipConstraint\Operators\IsRelatedToOperator;
use Filament\Tables\Filters\QueryBuilder\Constraints\SelectConstraint;
use Filament\Tables\Filters\QueryBuilder\Constraints\TextConstraint;
QueryBuilder::make()
->constraints([
TextConstraint::make('name'),
BooleanConstraint::make('is_visible'),
NumberConstraint::make('stock'),
SelectConstraint::make('status')
->options([
'draft' => 'Draft',
'reviewing' => 'Reviewing',
'published' => 'Published',
])
->multiple(),
DateConstraint::make('created_at'),
RelationshipConstraint::make('categories')
->multiple()
->selectable(
IsRelatedToOperator::make()
->titleAttribute('name')
->searchable()
->multiple(),
),
NumberConstraint::make('reviewsRating')
->relationship('reviews', 'rating')
->integer(),
])
```
When deeply nesting the query builder, you might need to increase the amount of space that the filters can consume. One way of doing this is to [position the filters above the table content](layout#displaying-filters-above-the-table-content):
```php
use Filament\Tables\Enums\FiltersLayout;
use Filament\Tables\Filters\QueryBuilder;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->filters([
QueryBuilder::make()
->constraints([
// ...
]),
], layout: FiltersLayout::AboveContent);
}
```
## Available constraints
Filament ships with many different constraints that you can use out of the box. You can also [create your own custom constraints](#creating-custom-constraints):
- [Text constraint](#text-constraints)
- [Boolean constraint](#boolean-constraints)
- [Number constraint](#number-constraints)
- [Date constraint](#date-constraints)
- [Select constraint](#select-constraints)
- [Relationship constraint](#relationship-constraints)
### Text constraints
Text constraints allow you to filter text fields. They can be used to filter any text field, including via relationships.
```php
use Filament\Tables\Filters\QueryBuilder\Constraints\TextConstraint;
TextConstraint::make('name') // Filter the `name` column
TextConstraint::make('creatorName')
->relationship(name: 'creator', titleAttribute: 'name') // Filter the `name` column on the `creator` relationship
```
By default, the following operators are available:
- Contains - filters a column to contain the search term
- Does not contain - filters a column to not contain the search term
- Starts with - filters a column to start with the search term
- Does not start with - filters a column to not start with the search term
- Ends with - filters a column to end with the search term
- Does not end with - filters a column to not end with the search term
- Equals - filters a column to equal the search term
- Does not equal - filters a column to not equal the search term
- Is filled - filters a column to not be empty
- Is blank - filters a column to be empty
### Boolean constraints
Boolean constraints allow you to filter boolean fields. They can be used to filter any boolean field, including via relationships.
```php
use Filament\Tables\Filters\QueryBuilder\Constraints\BooleanConstraint;
BooleanConstraint::make('is_visible') // Filter the `is_visible` column
BooleanConstraint::make('creatorIsAdmin')
->relationship(name: 'creator', titleAttribute: 'is_admin') // Filter the `is_admin` column on the `creator` relationship
```
By default, the following operators are available:
- Is true - filters a column to be `true`
- Is false - filters a column to be `false`
### Number constraints
Number constraints allow you to filter numeric fields. They can be used to filter any numeric field, including via relationships.
```php
use Filament\Tables\Filters\QueryBuilder\Constraints\NumberConstraint;
NumberConstraint::make('stock') // Filter the `stock` column
NumberConstraint::make('ordersItemCount')
->relationship(name: 'orders', titleAttribute: 'item_count') // Filter the `item_count` column on the `orders` relationship
```
By default, the following operators are available:
- Is minimum - filters a column to be greater than or equal to the search number
- Is less than - filters a column to be less than the search number
- Is maximum - filters a column to be less than or equal to the search number
- Is greater than - filters a column to be greater than the search number
- Equals - filters a column to equal the search number
- Does not equal - filters a column to not equal the search number
- Is filled - filters a column to not be empty
- Is blank - filters a column to be empty
When using `relationship()` with a number constraint, users also have the ability to "aggregate" related records. This means that they can filter the column to be the sum, average, minimum or maximum of all the related records at once.
#### Integer constraints
By default, number constraints will allow decimal values. If you'd like to only allow integer values, you can use the `integer()` method:
```php
use Filament\Tables\Filters\QueryBuilder\Constraints\NumberConstraint;
NumberConstraint::make('stock')
->integer()
```
### Date constraints
Date constraints allow you to filter date fields. They can be used to filter any date field, including via relationships.
```php
use Filament\Tables\Filters\QueryBuilder\Constraints\DateConstraint;
DateConstraint::make('created_at') // Filter the `created_at` column
DateConstraint::make('creatorCreatedAt')
->relationship(name: 'creator', titleAttribute: 'created_at') // Filter the `created_at` column on the `creator` relationship
```
By default, the following operators are available:
- Is after - filters a column to be after the search date
- Is not after - filters a column to not be after the search date, or to be the same date
- Is before - filters a column to be before the search date
- Is not before - filters a column to not be before the search date, or to be the same date
- Is date - filters a column to be the same date as the search date
- Is not date - filters a column to not be the same date as the search date
- Is month - filters a column to be in the same month as the selected month
- Is not month - filters a column to not be in the same month as the selected month
- Is year - filters a column to be in the same year as the searched year
- Is not year - filters a column to not be in the same year as the searched year
### Select constraints
Select constraints allow you to filter fields using a select field. They can be used to filter any field, including via relationships.
```php
use Filament\Tables\Filters\QueryBuilder\Constraints\SelectConstraint;
SelectConstraint::make('status') // Filter the `status` column
->options([
'draft' => 'Draft',
'reviewing' => 'Reviewing',
'published' => 'Published',
])
SelectConstraint::make('creatorStatus')
->relationship(name: 'creator', titleAttribute: 'department') // Filter the `department` column on the `creator` relationship
->options([
'sales' => 'Sales',
'marketing' => 'Marketing',
'engineering' => 'Engineering',
'purchasing' => 'Purchasing',
])
```
#### Searchable select constraints
By default, select constraints will not allow the user to search the options. If you'd like to allow the user to search the options, you can use the `searchable()` method:
```php
use Filament\Tables\Filters\QueryBuilder\Constraints\SelectConstraint;
SelectConstraint::make('status')
->searchable()
->options([
'draft' => 'Draft',
'reviewing' => 'Reviewing',
'published' => 'Published',
])
```
#### Multi-select constraints
By default, select constraints will only allow the user to select a single option. If you'd like to allow the user to select multiple options, you can use the `multiple()` method:
```php
use Filament\Tables\Filters\QueryBuilder\Constraints\SelectConstraint;
SelectConstraint::make('status')
->multiple()
->options([
'draft' => 'Draft',
'reviewing' => 'Reviewing',
'published' => 'Published',
])
```
When the user selects multiple options, the table will be filtered to show records that match any of the selected options.
### Relationship constraints
Relationship constraints allow you to filter fields using data about a relationship:
```php
use Filament\Tables\Filters\QueryBuilder\Constraints\RelationshipConstraint;
use Filament\Tables\Filters\QueryBuilder\Constraints\RelationshipConstraint\Operators\IsRelatedToOperator;
RelationshipConstraint::make('creator') // Filter the `creator` relationship
->selectable(
IsRelatedToOperator::make()
->titleAttribute('name')
->searchable()
->multiple(),
)
```
The `IsRelatedToOperator` is used to configure the "Is / Contains" and "Is not / Does not contain" operators. It provides a select field which allows the user to filter the relationship by which records are attached to it. The `titleAttribute()` method is used to specify which attribute should be used to identify each related record in the list. The `searchable()` method makes the list searchable. The `multiple()` method allows the user to select multiple related records, and if they do, the table will be filtered to show records that match any of the selected related records.
#### Multiple relationships
By default, relationship constraints only include operators that are appropriate for filtering a singular relationship, like a `BelongsTo`. If you have a relationship such as a `HasMany` or `BelongsToMany`, you may wish to mark the constraint as `multiple()`:
```php
use Filament\Tables\Filters\QueryBuilder\Constraints\RelationshipConstraint;
RelationshipConstraint::make('categories')
->multiple()
```
This will add the following operators to the constraint:
- Has minimum - filters a column to have at least the specified number of related records
- Has less than - filters a column to have less than the specified number of related records
- Has maximum - filters a column to have at most the specified number of related records
- Has more than - filters a column to have more than the specified number of related records
- Has - filters a column to have the specified number of related records
- Does not have - filters a column to not have the specified number of related records
#### Empty relationship constraints
The `RelationshipConstraint` does not support [`nullable()`](#nullable-constraints) in the same way as other constraints.
If the relationship is `multiple()`, then the constraint will show an option to filter out "empty" relationships. This means that the relationship has no related records. If your relationship is singular, then you can use the `emptyable()` method to show an option to filter out "empty" relationships:
```php
use Filament\Tables\Filters\QueryBuilder\Constraints\RelationshipConstraint;
RelationshipConstraint::make('creator')
->emptyable()
```
If you have a `multiple()` relationship that must always have at least 1 related record, then you can use the `emptyable(false)` method to hide the option to filter out "empty" relationships:
```php
use Filament\Tables\Filters\QueryBuilder\Constraints\RelationshipConstraint;
RelationshipConstraint::make('categories')
->emptyable(false)
```
#### Nullable constraints
By default, constraints will not show an option to filter `null` values. If you'd like to show an option to filter `null` values, you can use the `nullable()` method:
```php
use Filament\Tables\Filters\QueryBuilder\Constraints\TextConstraint;
TextConstraint::make('name')
->nullable()
```
Now, the following operators are also available:
- Is filled - filters a column to not be empty
- Is blank - filters a column to be empty
## Scoping relationships
When you use the `relationship()` method on a constraint, you can scope the relationship to filter the related records using the `modifyQueryUsing` argument:
```php
use Filament\Tables\Filters\QueryBuilder\Constraints\TextConstraint;
use Illuminate\Database\Eloquent\Builder;
TextConstraint::make('adminCreatorName')
->relationship(
name: 'creator',
titleAttribute: 'name',
modifyQueryUsing: fn (Builder $query) => $query->where('is_admin', true),
)
```
## Customizing the constraint icon
Each constraint type has a default [icon](https://blade-ui-kit.com/blade-icons?set=1#search), which is displayed next to the label in the picker. You can customize the icon for a constraint by passing its name to the `icon()` method:
```php
use Filament\Tables\Filters\QueryBuilder\Constraints\TextConstraint;
TextConstraint::make('author')
->relationship(name: 'author', titleAttribute: 'name')
->icon('heroicon-m-user')
```
## Overriding the default operators
Each constraint type has a set of default operators, which you can customize by using the `operators()`method:
```php
use Filament\Tables\Filters\QueryBuilder\Constraints\Operators\IsFilledOperator;
use Filament\Tables\Filters\QueryBuilder\Constraints\TextConstraint;
TextConstraint::make('author')
->relationship(name: 'author', titleAttribute: 'name')
->operators([
IsFilledOperator::make(),
])
```
This will remove all operators, and register the `EqualsOperator`.
If you'd like to add an operator to the end of the list, use `pushOperators()` instead:
```php
use Filament\Tables\Filters\QueryBuilder\Constraints\Operators\IsFilledOperator;
use Filament\Tables\Filters\QueryBuilder\Constraints\TextConstraint;
TextConstraint::make('author')
->relationship(name: 'author', titleAttribute: 'name')
->pushOperators([
IsFilledOperator::class,
])
```
If you'd like to add an operator to the start of the list, use `unshiftOperators()` instead:
```php
use Filament\Tables\Filters\QueryBuilder\Constraints\Operators\IsFilledOperator;
use Filament\Tables\Filters\QueryBuilder\Constraints\TextConstraint;
TextConstraint::make('author')
->relationship(name: 'author', titleAttribute: 'name')
->unshiftOperators([
IsFilledOperator::class,
])
```
## Creating custom constraints
Custom constraints can be created "inline" with other constraints using the `Constraint::make()` method. You should also pass an [icon](#customizing-the-constraint-icon) to the `icon()` method:
```php
use Filament\Tables\Filters\QueryBuilder\Constraints\Constraint;
Constraint::make('subscribed')
->icon('heroicon-m-bell')
->operators([
// ...
]),
```
If you want to customize the label of the constraint, you can use the `label()` method:
```php
use Filament\Tables\Filters\QueryBuilder\Constraints\Constraint;
Constraint::make('subscribed')
->label('Subscribed to updates')
->icon('heroicon-m-bell')
->operators([
// ...
]),
```
Now, you must [define operators](#creating-custom-operators) for the constraint. These are options that you can pick from to filter the column. If the column is [nullable](#nullable-constraints), you can also register that built-in operator for your custom constraint:
```php
use Filament\Tables\Filters\QueryBuilder\Constraints\Constraint;
use Filament\Tables\Filters\QueryBuilder\Constraints\Operators\IsFilledOperator;
Constraint::make('subscribed')
->label('Subscribed to updates')
->icon('heroicon-m-bell')
->operators([
// ...
IsFilledOperator::class,
]),
```
### Creating custom operators
Custom operators can be created using the `Operator::make()` method:
```php
use Filament\Tables\Filters\QueryBuilder\Constraints\Operators\Operator;
Operator::make('subscribed')
->label(fn (bool $isInverse): string => $isInverse ? 'Not subscribed' : 'Subscribed')
->summary(fn (bool $isInverse): string => $isInverse ? 'You are not subscribed' : 'You are subscribed')
->baseQuery(fn (Builder $query, bool $isInverse) => $query->{$isInverse ? 'whereDoesntHave' : 'whereHas'}(
'subscriptions.user',
fn (Builder $query) => $query->whereKey(auth()->user()),
)),
```
In this example, the operator is able to filter records based on whether or not the authenticated user is subscribed to the record. A subscription is recorded in the `subscriptions` relationship of the table.
The `baseQuery()` method is used to define the query that will be used to filter the records. The `$isInverse` argument is `false` when the "Subscribed" option is selected, and `true` when the "Not subscribed" option is selected. The function is applied to the base query of the table, where `whereHas()` can be used. If your function does not need to be applied to the base query of the table, like when you are using a simple `where()` or `whereIn()`, you can use the `query()` method instead, which has the bonus of being able to be used inside nested "OR" groups.
The `label()` method is used to render the options in the operator select. Two options are registered for each operator, one for when the operator is not inverted, and one for when it is inverted.
The `summary()` method is used in the header of the constraint when it is applied to the query, to provide an overview of the active constraint.
## Customizing the constraint picker
### Changing the number of columns in the constraint picker
The constraint picker has only 1 column. You may customize it by passing a number of columns to `constraintPickerColumns()`:
```php
use Filament\Tables\Filters\QueryBuilder;
QueryBuilder::make()
->constraintPickerColumns(2)
->constraints([
// ...
])
```
This method can be used in a couple of different ways:
- You can pass an integer like `constraintPickerColumns(2)`. This integer is the number of columns used on the `lg` breakpoint and higher. All smaller devices will have just 1 column.
- You can pass an array, where the key is the breakpoint and the value is the number of columns. For example, `constraintPickerColumns(['md' => 2, 'xl' => 4])` will create a 2 column layout on medium devices, and a 4 column layout on extra large devices. The default breakpoint for smaller devices uses 1 column, unless you use a `default` array key.
Breakpoints (`sm`, `md`, `lg`, `xl`, `2xl`) are defined by Tailwind, and can be found in the [Tailwind documentation](https://tailwindcss.com/docs/responsive-design#overview).
### Increasing the width of the constraint picker
When you [increase the number of columns](#changing-the-number-of-columns-in-the-constraint-picker), the width of the dropdown should increase incrementally to handle the additional columns. If you'd like more control, you can manually set a maximum width for the dropdown using the `constraintPickerWidth()` method. Options correspond to [Tailwind's max-width scale](https://tailwindcss.com/docs/max-width). The options are `xs`, `sm`, `md`, `lg`, `xl`, `2xl`, `3xl`, `4xl`, `5xl`, `6xl`, `7xl`:
```php
use Filament\Tables\Filters\QueryBuilder;
QueryBuilder::make()
->constraintPickerColumns(3)
->constraintPickerWidth('2xl')
->constraints([
// ...
])
```

View File

@@ -0,0 +1,145 @@
---
title: Custom filters
---
import AutoScreenshot from "@components/AutoScreenshot.astro"
import LaracastsBanner from "@components/LaracastsBanner.astro"
## Custom filter forms
<LaracastsBanner
title="Build a Custom Table Filter"
description="Watch the Build Advanced Components for Filament series on Laracasts - it will teach you how to build components, and you'll get to know all the internal tools to help you."
url="https://laracasts.com/series/build-advanced-components-for-filament/episodes/11"
series="building-advanced-components"
/>
You may use components from the [Form Builder](../../forms/fields/getting-started) to create custom filter forms. The data from the custom filter form is available in the `$data` array of the `query()` callback:
```php
use Filament\Forms\Components\DatePicker;
use Filament\Tables\Filters\Filter;
use Illuminate\Database\Eloquent\Builder;
Filter::make('created_at')
->form([
DatePicker::make('created_from'),
DatePicker::make('created_until'),
])
->query(function (Builder $query, array $data): Builder {
return $query
->when(
$data['created_from'],
fn (Builder $query, $date): Builder => $query->whereDate('created_at', '>=', $date),
)
->when(
$data['created_until'],
fn (Builder $query, $date): Builder => $query->whereDate('created_at', '<=', $date),
);
})
```
<AutoScreenshot name="tables/filters/custom-form" alt="Table with custom filter form" version="3.x" />
### Setting default values for custom filter fields
To customize the default value of a field in a custom filter form, you may use the `default()` method:
```php
use Filament\Forms\Components\DatePicker;
use Filament\Tables\Filters\Filter;
Filter::make('created_at')
->form([
DatePicker::make('created_from'),
DatePicker::make('created_until')
->default(now()),
])
```
## Active indicators
When a filter is active, an indicator is displayed above the table content to signal that the table query has been scoped.
<AutoScreenshot name="tables/filters/indicators" alt="Table with filter indicators" version="3.x" />
By default, the label of the filter is used as the indicator. You can override this using the `indicator()` method:
```php
use Filament\Tables\Filters\Filter;
Filter::make('is_admin')
->label('Administrators only?')
->indicator('Administrators')
```
If you are using a [custom filter form](#custom-filter-forms), you should use [`indicateUsing()`](#custom-active-indicators) to display an active indicator.
Please note: if you do not have an indicator for your filter, then the badge-count of how many filters are active in the table will not include that filter.
### Custom active indicators
Not all indicators are simple, so you may need to use `indicateUsing()` to customize which indicators should be shown at any time.
For example, if you have a custom date filter, you may create a custom indicator that formats the selected date:
```php
use Carbon\Carbon;
use Filament\Forms\Components\DatePicker;
use Filament\Tables\Filters\Filter;
Filter::make('created_at')
->form([DatePicker::make('date')])
// ...
->indicateUsing(function (array $data): ?string {
if (! $data['date']) {
return null;
}
return 'Created at ' . Carbon::parse($data['date'])->toFormattedDateString();
})
```
### Multiple active indicators
You may even render multiple indicators at once, by returning an array of `Indicator` objects. If you have different fields associated with different indicators, you should set the field using the `removeField()` method on the `Indicator` object to ensure that the correct field is reset when the filter is removed:
```php
use Carbon\Carbon;
use Filament\Forms\Components\DatePicker;
use Filament\Tables\Filters\Filter;
use Filament\Tables\Filters\Indicator;
Filter::make('created_at')
->form([
DatePicker::make('from'),
DatePicker::make('until'),
])
// ...
->indicateUsing(function (array $data): array {
$indicators = [];
if ($data['from'] ?? null) {
$indicators[] = Indicator::make('Created from ' . Carbon::parse($data['from'])->toFormattedDateString())
->removeField('from');
}
if ($data['until'] ?? null) {
$indicators[] = Indicator::make('Created until ' . Carbon::parse($data['until'])->toFormattedDateString())
->removeField('until');
}
return $indicators;
})
```
### Preventing indicators from being removed
You can prevent users from removing an indicator using `removable(false)` on an `Indicator` object:
```php
use Carbon\Carbon;
use Filament\Tables\Filters\Indicator;
Indicator::make('Created from ' . Carbon::parse($data['from'])->toFormattedDateString())
->removable(false)
```

View File

@@ -0,0 +1,180 @@
---
title: Filter layout
---
import AutoScreenshot from "@components/AutoScreenshot.astro"
## Positioning filters into grid columns
To change the number of columns that filters may occupy, you may use the `filtersFormColumns()` method:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->filters([
// ...
])
->filtersFormColumns(3);
}
```
## Controlling the width of the filters dropdown
To customize the dropdown width, you may use the `filtersFormWidth()` method, and specify a width - `ExtraSmall`, `Small`, `Medium`, `Large`, `ExtraLarge`, `TwoExtraLarge`, `ThreeExtraLarge`, `FourExtraLarge`, `FiveExtraLarge`, `SixExtraLarge` or `SevenExtraLarge`. By default, the width is `ExtraSmall`:
```php
use Filament\Support\Enums\MaxWidth;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->filters([
// ...
])
->filtersFormWidth(MaxWidth::FourExtraLarge);
}
```
## Controlling the maximum height of the filters dropdown
To add a maximum height to the filters' dropdown content, so that they scroll, you may use the `filtersFormMaxHeight()` method, passing a [CSS length](https://developer.mozilla.org/en-US/docs/Web/CSS/length):
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->filters([
// ...
])
->filtersFormMaxHeight('400px');
}
```
## Displaying filters in a modal
To render the filters in a modal instead of in a dropdown, you may use:
```php
use Filament\Tables\Enums\FiltersLayout;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->filters([
// ...
], layout: FiltersLayout::Modal);
}
```
You may use the [trigger action API](getting-started#customizing-the-filters-trigger-action) to [customize the modal](../actions/modals), including [using a `slideOver()`](../actions/modals#using-a-slide-over-instead-of-a-modal).
## Displaying filters above the table content
To render the filters above the table content instead of in a dropdown, you may use:
```php
use Filament\Tables\Enums\FiltersLayout;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->filters([
// ...
], layout: FiltersLayout::AboveContent);
}
```
<AutoScreenshot name="tables/filters/above-content" alt="Table with filters above content" version="3.x" />
### Allowing filters above the table content to be collapsed
To allow the filters above the table content to be collapsed, you may use:
```php
use Filament\Tables\Enums\FiltersLayout;
public function table(Table $table): Table
{
return $table
->filters([
// ...
], layout: FiltersLayout::AboveContentCollapsible);
}
```
## Displaying filters below the table content
To render the filters below the table content instead of in a dropdown, you may use:
```php
use Filament\Tables\Enums\FiltersLayout;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->filters([
// ...
], layout: FiltersLayout::BelowContent);
}
```
<AutoScreenshot name="tables/filters/below-content" alt="Table with filters below content" version="3.x" />
## Hiding the filter indicators
To hide the active filters indicators above the table, you may use `hiddenFilterIndicators()`:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->filters([
// ...
])
->hiddenFilterIndicators();
}
```
## Customizing the filter form schema
You may customize the [form schema](../../forms/layout) of the entire filter form at once, in order to rearrange filters into your desired layout, and use any of the [layout components](../../forms/layout) available to forms. To do this, use the `filterFormSchema()` method, passing a closure function that receives the array of defined `$filters` that you can insert:
```php
use Filament\Forms\Components\Section;
use Filament\Tables\Filters\Filter;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->filters([
Filter::make('is_featured'),
Filter::make('published_at'),
Filter::make('author'),
])
->filtersFormColumns(2)
->filtersFormSchema(fn (array $filters): array => [
Section::make('Visibility')
->description('These filters affect the visibility of the records in the table.')
->schema([
$filters['is_featured'],
$filters['published_at'],
])
->columns(2)
->columnSpanFull(),
$filters['author'],
]);
}
```
In this example, we have put two of the filters inside a [section](../../forms/layout/section) component, and used the `columns()` method to specify that the section should have two columns. We have also used the `columnSpanFull()` method to specify that the section should span the full width of the filter form, which is also 2 columns wide. We have inserted each filter into the form schema by using the filter's name as the key in the `$filters` array.

View File

@@ -0,0 +1,457 @@
---
title: Actions
---
import AutoScreenshot from "@components/AutoScreenshot.astro"
import LaracastsBanner from "@components/LaracastsBanner.astro"
## Overview
<LaracastsBanner
title="Table Actions"
description="Watch the Rapid Laravel Development with Filament series on Laracasts - it will teach you the basics of adding actions to Filament resource tables."
url="https://laracasts.com/series/rapid-laravel-development-with-filament/episodes/11"
series="rapid-laravel-development"
/>
Filament's tables can use [Actions](../actions). They are buttons that can be added to the [end of any table row](#row-actions), or even in the [header](#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](#bulk-actions) can be used to execute code when records in the table are selected. Additionally, actions can be added to any [table column](#column-actions), such that each cell in that column is a trigger for your action.
It's highly advised that you read the documentation about [customizing action trigger buttons](../actions/trigger-button) and [action modals](../actions/modals) to that you are aware of the full capabilities of actions.
## Row actions
Action buttons can be rendered at the end of each table row. You can put them in the `$table->actions()` method:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->actions([
// ...
]);
}
```
Actions may be created using the static `make()` method, passing its unique name.
You can then pass a function to `action()` which executes the task, or a function to `url()` which creates a link:
```php
use App\Models\Post;
use Filament\Tables\Actions\Action;
Action::make('edit')
->url(fn (Post $record): string => route('posts.edit', $record))
->openUrlInNewTab()
Action::make('delete')
->requiresConfirmation()
->action(fn (Post $record) => $record->delete())
```
All methods on the action accept callback functions, where you can access the current table `$record` that was clicked.
<AutoScreenshot name="tables/actions/simple" alt="Table with actions" version="3.x" />
### Positioning row actions before columns
By default, the row actions in your table are rendered in the final cell of each row. You may move them before the columns by using the `position` argument:
```php
use Filament\Tables\Enums\ActionsPosition;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->actions([
// ...
], position: ActionsPosition::BeforeColumns);
}
```
<AutoScreenshot name="tables/actions/before-columns" alt="Table with actions before columns" version="3.x" />
### Positioning row actions before the checkbox column
By default, the row actions in your table are rendered in the final cell of each row. You may move them before the checkbox column by using the `position` argument:
```php
use Filament\Tables\Enums\ActionsPosition;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->actions([
// ...
], position: ActionsPosition::BeforeCells);
}
```
<AutoScreenshot name="tables/actions/before-cells" alt="Table with actions before cells" version="3.x" />
### Accessing the selected table rows
You may want an action to be able to access all the selected rows in the table. Usually, this is done with a [bulk action](#bulk-actions) in the header of the table. However, you may want to do this with a row action, where the selected rows provide context for the action.
For example, you may want to have a row action that copies the row data to all the selected records. To force the table to be selectable, even if there aren't bulk actions defined, you need to use the `selectable()` method. To allow the action to access the selected records, you need to use the `accessSelectedRecords()` method. Then, you can use the `$selectedRecords` parameter in your action to access the selected records:
```php
use Filament\Tables\Table;
use Filament\Tables\Actions\Action;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
public function table(Table $table): Table
{
return $table
->selectable()
->actions([
Action::make('copyToSelected')
->accessSelectedRecords()
->action(function (Model $record, Collection $selectedRecords) {
$selectedRecords->each(
fn (Model $selectedRecord) => $selectedRecord->update([
'is_active' => $record->is_active,
]),
);
}),
]);
}
```
## Bulk actions
Tables also support "bulk actions". These can be used when the user selects rows in the table. Traditionally, when rows are selected, a "bulk actions" button appears in the top left corner of the table. When the user clicks this button, they are presented with a dropdown menu of actions to choose from. You can put them in the `$table->bulkActions()` method:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->bulkActions([
// ...
]);
}
```
Bulk actions may be created using the static `make()` method, passing its unique name. You should then pass a callback to `action()` which executes the task:
```php
use Filament\Tables\Actions\BulkAction;
use Illuminate\Database\Eloquent\Collection;
BulkAction::make('delete')
->requiresConfirmation()
->action(fn (Collection $records) => $records->each->delete())
```
The function allows you to access the current table `$records` that are selected. It is an Eloquent collection of models.
<AutoScreenshot name="tables/actions/bulk" alt="Table with bulk action" version="3.x" />
### Grouping bulk actions
You may use a `BulkActionGroup` object to [group multiple bulk actions together](../actions/grouping-actions) in a dropdown. Any bulk actions that remain outside the `BulkActionGroup` will be rendered next to the dropdown's trigger button:
```php
use Filament\Tables\Actions\BulkAction;
use Filament\Tables\Actions\BulkActionGroup;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->bulkActions([
BulkActionGroup::make([
BulkAction::make('delete')
->requiresConfirmation()
->action(fn (Collection $records) => $records->each->delete()),
BulkAction::make('forceDelete')
->requiresConfirmation()
->action(fn (Collection $records) => $records->each->forceDelete()),
]),
BulkAction::make('export')->button()->action(fn (Collection $records) => ...),
]);
}
```
Alternatively, if all of your bulk actions are grouped, you can use the shorthand `groupedBulkActions()` method:
```php
use Filament\Tables\Actions\BulkAction;
use Filament\Tables\Actions\BulkActionGroup;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->groupedBulkActions([
BulkAction::make('delete')
->requiresConfirmation()
->action(fn (Collection $records) => $records->each->delete()),
BulkAction::make('forceDelete')
->requiresConfirmation()
->action(fn (Collection $records) => $records->each->forceDelete()),
]);
}
```
### Deselecting records once a bulk action has finished
You may deselect the records after a bulk action has been executed using the `deselectRecordsAfterCompletion()` method:
```php
use Filament\Tables\Actions\BulkAction;
use Illuminate\Database\Eloquent\Collection;
BulkAction::make('delete')
->action(fn (Collection $records) => $records->each->delete())
->deselectRecordsAfterCompletion()
```
### Disabling bulk actions for some rows
You may conditionally disable bulk actions for a specific record:
```php
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Model;
public function table(Table $table): Table
{
return $table
->bulkActions([
// ...
])
->checkIfRecordIsSelectableUsing(
fn (Model $record): bool => $record->status === Status::Enabled,
);
}
```
### Preventing bulk-selection of all pages
The `selectCurrentPageOnly()` method can be used to prevent the user from easily bulk-selecting all records in the table at once, and instead only allows them to select one page at a time:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->bulkActions([
// ...
])
->selectCurrentPageOnly();
}
```
## Header actions
Both [row actions](#row-actions) and [bulk actions](#bulk-actions) can be rendered in the header of the table. You can put them in the `$table->headerActions()` method:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->headerActions([
// ...
]);
}
```
This is useful for things like "create" actions, which are not related to any specific table row, or bulk actions that need to be more visible.
<AutoScreenshot name="tables/actions/header" alt="Table with header actions" version="3.x" />
## 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/getting-started#running-actions) in the documentation.
## Prebuilt table actions
Filament includes several prebuilt actions and bulk actions that you can add to a table. Their aim is to simplify the most common Eloquent-related actions:
- [Create](../actions/prebuilt-actions/create)
- [Edit](../actions/prebuilt-actions/edit)
- [View](../actions/prebuilt-actions/view)
- [Delete](../actions/prebuilt-actions/delete)
- [Replicate](../actions/prebuilt-actions/replicate)
- [Force-delete](../actions/prebuilt-actions/force-delete)
- [Restore](../actions/prebuilt-actions/restore)
- [Import](../actions/prebuilt-actions/import)
- [Export](../actions/prebuilt-actions/export)
## Grouping actions
You may use an `ActionGroup` object to group multiple table actions together in a dropdown:
```php
use Filament\Tables\Actions\ActionGroup;
use Filament\Tables\Actions\DeleteAction;
use Filament\Tables\Actions\EditAction;
use Filament\Tables\Actions\ViewAction;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->actions([
ActionGroup::make([
ViewAction::make(),
EditAction::make(),
DeleteAction::make(),
]),
// ...
]);
}
```
<AutoScreenshot name="tables/actions/group" alt="Table with action group" version="3.x" />
### Choosing an action group button style
Out of the box, action group triggers have 3 styles - "button", "link", and "icon button".
"Icon button" triggers are circular buttons with an [icon](#setting-the-action-group-button-icon) and no label. Usually, this is the default button style, but you can use it manually with the `iconButton()` method:
```php
use Filament\Tables\Actions\ActionGroup;
ActionGroup::make([
// ...
])->iconButton()
```
<AutoScreenshot name="tables/actions/group-icon-button" alt="Table with icon button action group" version="3.x" />
"Button" triggers have a background color, label, and optionally an [icon](#setting-the-action-group-button-icon). You can switch to that style with the `button()` method:
```php
use Filament\Tables\Actions\ActionGroup;
ActionGroup::make([
// ...
])
->button()
->label('Actions')
```
<AutoScreenshot name="tables/actions/group-button" alt="Table with button action group" version="3.x" />
"Link" triggers have no background color. They must have a label and optionally an [icon](#setting-the-action-group-button-icon). They look like a link that you might find embedded within text. You can switch to that style with the `link()` method:
```php
use Filament\Tables\Actions\ActionGroup;
ActionGroup::make([
// ...
])
->link()
->label('Actions')
```
<AutoScreenshot name="tables/actions/group-link" alt="Table with link action group" version="3.x" />
### Setting the action group button icon
You may set the [icon](https://blade-ui-kit.com/blade-icons?set=1#search) of the action group button using the `icon()` method:
```php
use Filament\Tables\Actions\ActionGroup;
ActionGroup::make([
// ...
])->icon('heroicon-m-ellipsis-horizontal');
```
<AutoScreenshot name="tables/actions/group-icon" alt="Table with customized action group icon" version="3.x" />
### Setting the action group button color
You may set the color of the action group button using the `color()` method:
```php
use Filament\Tables\Actions\ActionGroup;
ActionGroup::make([
// ...
])->color('info');
```
<AutoScreenshot name="tables/actions/group-color" alt="Table with customized action group color" version="3.x" />
### Setting the action group button size
Buttons come in 3 sizes - `sm`, `md` or `lg`. You may set the size of the action group button using the `size()` method:
```php
use Filament\Support\Enums\ActionSize;
use Filament\Tables\Actions\ActionGroup;
ActionGroup::make([
// ...
])->size(ActionSize::Small);
```
<AutoScreenshot name="tables/actions/group-small" alt="Table with small action group" version="3.x" />
### Setting the action group tooltip
You may set the tooltip of the action group using the `tooltip()` method:
```php
use Filament\Tables\Actions\ActionGroup;
ActionGroup::make([
// ...
])->tooltip('Actions');
```
<AutoScreenshot name="tables/actions/group-tooltip" alt="Table with action group tooltip" version="3.x" />
## Table action utility injection
All actions, not just table actions, have access to [many utilities](../actions/advanced#action-utility-injection) within the vast majority of configuration methods. However, in addition to those, table actions have access to a few more:
### Injecting the current Eloquent record
If you wish to access the current Eloquent record of the action, define a `$record` parameter:
```php
use Illuminate\Database\Eloquent\Model;
function (Model $record) {
// ...
}
```
Be aware that bulk actions, header actions, and empty state actions do not have access to the `$record`, as they are not related to any table row.
### Injecting the current Eloquent model class
If you wish to access the current Eloquent model class of the table, define a `$model` parameter:
```php
function (string $model) {
// ...
}
```
### Injecting the current table instance
If you wish to access the current table configuration instance that the action belongs to, define a `$table` parameter:
```php
use Filament\Tables\Table;
function (Table $table) {
// ...
}
```

468
vendor/filament/tables/docs/06-layout.md vendored Normal file
View File

@@ -0,0 +1,468 @@
---
title: Layout
---
import AutoScreenshot from "@components/AutoScreenshot.astro"
## The problem with traditional table layouts
Traditional tables are notorious for having bad responsiveness. On mobile, there is only so much flexibility you have when rendering content that is horizontally long:
- Allow the user to scroll horizontally to see more table content
- Hide non-important columns on smaller devices
Both of these are possible with Filament. Tables automatically scroll horizontally when they overflow anyway, and you may choose to show and hide columns based on the responsive [breakpoint](https://tailwindcss.com/docs/responsive-design#overview) of the browser. To do this, you may use a `visibleFrom()` or `hiddenFrom()` method:
```php
use Filament\Tables\Columns\TextColumn;
TextColumn::make('slug')
->visibleFrom('md')
```
This is fine, but there is still a glaring issue - **on mobile, the user is unable to see much information in a table row at once without scrolling**.
Thankfully, Filament lets you build responsive table-like interfaces, without touching HTML or CSS. These layouts let you define exactly where content appears in a table row, at each responsive breakpoint.
<AutoScreenshot name="tables/layout/demo" alt="Table with responsive layout" version="3.x" />
<AutoScreenshot name="tables/layout/demo/mobile" alt="Table with responsive layout on mobile" version="3.x" />
## Allowing columns to stack on mobile
Let's introduce a component - `Split`:
```php
use Filament\Support\Enums\FontWeight;
use Filament\Tables\Columns\Layout\Split;
use Filament\Tables\Columns\TextColumn;
Split::make([
ImageColumn::make('avatar')
->circular(),
TextColumn::make('name')
->weight(FontWeight::Bold)
->searchable()
->sortable(),
TextColumn::make('email'),
])
```
<AutoScreenshot name="tables/layout/split" alt="Table with a split layout" version="3.x" />
<AutoScreenshot name="tables/layout/split/mobile" alt="Table with a split layout on mobile" version="3.x" />
A `Split` component is used to wrap around columns, and allow them to stack on mobile.
By default, columns within a split will appear aside each other all the time. However, you may choose a responsive [breakpoint](https://tailwindcss.com/docs/responsive-design#overview) where this behavior starts `from()`. Before this point, the columns will stack on top of each other:
```php
use Filament\Support\Enums\FontWeight;
use Filament\Tables\Columns\Layout\Split;
use Filament\Tables\Columns\ImageColumn;
use Filament\Tables\Columns\TextColumn;
Split::make([
ImageColumn::make('avatar')
->circular(),
TextColumn::make('name')
->weight(FontWeight::Bold)
->searchable()
->sortable(),
TextColumn::make('email'),
])->from('md')
```
In this example, the columns will only appear horizontally aside each other from `md` [breakpoint](https://tailwindcss.com/docs/responsive-design#overview) devices onwards:
<AutoScreenshot name="tables/layout/split-desktop" alt="Table with a split layout on desktop" version="3.x" />
<AutoScreenshot name="tables/layout/split-desktop/mobile" alt="Table with a stacked layout on mobile" version="3.x" />
### Preventing a column from creating whitespace
Splits, like table columns, will automatically adjust their whitespace to ensure that each column has proportionate separation. You can prevent this from happening, using `grow(false)`. In this example, we will make sure that the avatar image will sit tightly against the name column:
```php
use Filament\Support\Enums\FontWeight;
use Filament\Tables\Columns\Layout\Split;
use Filament\Tables\Columns\ImageColumn;
use Filament\Tables\Columns\TextColumn;
Split::make([
ImageColumn::make('avatar')
->circular()
->grow(false),
TextColumn::make('name')
->weight(FontWeight::Bold)
->searchable()
->sortable(),
TextColumn::make('email'),
])
```
The other columns which are allowed to `grow()` will adjust to consume the newly-freed space:
<AutoScreenshot name="tables/layout/grow-disabled" alt="Table with a column that doesn't grow" version="3.x" />
<AutoScreenshot name="tables/layout/grow-disabled/mobile" alt="Table with a column that doesn't grow on mobile" version="3.x" />
### Stacking within a split
Inside a split, you may stack multiple columns on top of each other vertically. This allows you to display more data inside fewer columns on desktop:
```php
use Filament\Support\Enums\FontWeight;
use Filament\Tables\Columns\Layout\Split;
use Filament\Tables\Columns\Layout\Stack;
use Filament\Tables\Columns\ImageColumn;
use Filament\Tables\Columns\TextColumn;
Split::make([
ImageColumn::make('avatar')
->circular(),
TextColumn::make('name')
->weight(FontWeight::Bold)
->searchable()
->sortable(),
Stack::make([
TextColumn::make('phone')
->icon('heroicon-m-phone'),
TextColumn::make('email')
->icon('heroicon-m-envelope'),
]),
])
```
<AutoScreenshot name="tables/layout/stack" alt="Table with a stack" version="3.x" />
<AutoScreenshot name="tables/layout/stack/mobile" alt="Table with a stack on mobile" version="3.x" />
#### Hiding a stack on mobile
Similar to individual columns, you may choose to hide a stack based on the responsive [breakpoint](https://tailwindcss.com/docs/responsive-design#overview) of the browser. To do this, you may use a `visibleFrom()` method:
```php
use Filament\Support\Enums\FontWeight;
use Filament\Tables\Columns\Layout\Split;
use Filament\Tables\Columns\Layout\Stack;
use Filament\Tables\Columns\ImageColumn;
use Filament\Tables\Columns\TextColumn;
Split::make([
ImageColumn::make('avatar')
->circular(),
TextColumn::make('name')
->weight(FontWeight::Bold)
->searchable()
->sortable(),
Stack::make([
TextColumn::make('phone')
->icon('heroicon-m-phone'),
TextColumn::make('email')
->icon('heroicon-m-envelope'),
])->visibleFrom('md'),
])
```
<AutoScreenshot name="tables/layout/stack-hidden-on-mobile" alt="Table with a stack" version="3.x" />
<AutoScreenshot name="tables/layout/stack-hidden-on-mobile/mobile" alt="Table with no stack on mobile" version="3.x" />
#### Aligning stacked content
By default, columns within a stack are aligned to the start. You may choose to align columns within a stack to the `Alignment::Center` or `Alignment::End`:
```php
use Filament\Support\Enums\Alignment;
use Filament\Support\Enums\FontWeight;
use Filament\Tables\Columns\Layout\Split;
use Filament\Tables\Columns\Layout\Stack;
use Filament\Tables\Columns\ImageColumn;
use Filament\Tables\Columns\TextColumn;
Split::make([
ImageColumn::make('avatar')
->circular(),
TextColumn::make('name')
->weight(FontWeight::Bold)
->searchable()
->sortable(),
Stack::make([
TextColumn::make('phone')
->icon('heroicon-m-phone')
->grow(false),
TextColumn::make('email')
->icon('heroicon-m-envelope')
->grow(false),
])
->alignment(Alignment::End)
->visibleFrom('md'),
])
```
Ensure that the columns within the stack have `grow(false)` set, otherwise they will stretch to fill the entire width of the stack and follow their own alignment configuration instead of the stack's.
<AutoScreenshot name="tables/layout/stack-aligned-right" alt="Table with a stack aligned right" version="3.x" />
#### Spacing stacked content
By default, stacked content has no vertical padding between columns. To add some, you may use the `space()` method, which accepts either `1`, `2`, or `3`, corresponding to [Tailwind's spacing scale](https://tailwindcss.com/docs/space):
```php
use Filament\Tables\Columns\Layout\Stack;
use Filament\Tables\Columns\TextColumn;
Stack::make([
TextColumn::make('phone')
->icon('heroicon-m-phone'),
TextColumn::make('email')
->icon('heroicon-m-envelope'),
])->space(1)
```
## Controlling column width using a grid
Sometimes, using a `Split` creates inconsistent widths when columns contain lots of content. This is because it's powered by Flexbox internally and each row individually controls how much space is allocated to content.
Instead, you may use a `Grid` layout, which uses CSS Grid Layout to allow you to control column widths:
```php
use Filament\Tables\Columns\Layout\Grid;
use Filament\Tables\Columns\TextColumn;
Grid::make([
'lg' => 2,
])
->schema([
TextColumn::make('phone')
->icon('heroicon-m-phone'),
TextColumn::make('email')
->icon('heroicon-m-envelope'),
])
```
These columns will always consume equal width within the grid, from the `lg` [breakpoint](https://tailwindcss.com/docs/responsive-design#overview).
You may choose to customize the number of columns within the grid at other breakpoints:
```php
use Filament\Tables\Columns\Layout\Grid;
use Filament\Tables\Columns\Layout\Stack;
use Filament\Tables\Columns\TextColumn;
Grid::make([
'lg' => 2,
'2xl' => 4,
])
->schema([
Stack::make([
TextColumn::make('name'),
TextColumn::make('job'),
]),
TextColumn::make('phone')
->icon('heroicon-m-phone'),
TextColumn::make('email')
->icon('heroicon-m-envelope'),
])
```
And you can even control how many grid columns will be consumed by each component at each [breakpoint](https://tailwindcss.com/docs/responsive-design#overview):
```php
use Filament\Tables\Columns\Layout\Grid;
use Filament\Tables\Columns\Layout\Stack;
use Filament\Tables\Columns\TextColumn;
Grid::make([
'lg' => 2,
'2xl' => 5,
])
->schema([
Stack::make([
TextColumn::make('name'),
TextColumn::make('job'),
])->columnSpan([
'lg' => 'full',
'2xl' => 2,
]),
TextColumn::make('phone')
->icon('heroicon-m-phone')
->columnSpan([
'2xl' => 2,
]),
TextColumn::make('email')
->icon('heroicon-m-envelope'),
])
```
## Collapsible content
When you're using a column layout like split or stack, then you can also add collapsible content. This is very useful for when you don't want to display all data in the table at once, but still want it to be accessible to the user if they need to access it, without navigating away.
Split and stack components can be made `collapsible()`, but there is also a dedicated `Panel` component that provides a pre-styled background color and border radius, to separate the collapsible content from the rest:
```php
use Filament\Support\Enums\FontWeight;
use Filament\Tables\Columns\Layout\Panel;
use Filament\Tables\Columns\Layout\Split;
use Filament\Tables\Columns\Layout\Stack;
use Filament\Tables\Columns\ImageColumn;
use Filament\Tables\Columns\TextColumn;
[
Split::make([
ImageColumn::make('avatar')
->circular(),
TextColumn::make('name')
->weight(FontWeight::Bold)
->searchable()
->sortable(),
]),
Panel::make([
Stack::make([
TextColumn::make('phone')
->icon('heroicon-m-phone'),
TextColumn::make('email')
->icon('heroicon-m-envelope'),
]),
])->collapsible(),
]
```
You can expand a panel by default using the `collapsed(false)` method:
```php
use Filament\Tables\Columns\Layout\Panel;
use Filament\Tables\Columns\Layout\Split;
use Filament\Tables\Columns\TextColumn;
Panel::make([
Split::make([
TextColumn::make('phone')
->icon('heroicon-m-phone'),
TextColumn::make('email')
->icon('heroicon-m-envelope'),
])->from('md'),
])->collapsed(false)
```
<AutoScreenshot name="tables/layout/collapsible" alt="Table with collapsible content" version="3.x" />
<AutoScreenshot name="tables/layout/collapsible/mobile" alt="Table with collapsible content on mobile" version="3.x" />
## Arranging records into a grid
Sometimes, you may find that your data fits into a grid format better than a list. Filament can handle that too!
Simply use the `$table->contentGrid()` method:
```php
use Filament\Tables\Columns\Layout\Stack;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->columns([
Stack::make([
// Columns
]),
])
->contentGrid([
'md' => 2,
'xl' => 3,
]);
}
```
In this example, the rows will be displayed in a grid:
- On mobile, they will be displayed in 1 column only.
- From the `md` [breakpoint](https://tailwindcss.com/docs/responsive-design#overview), they will be displayed in 2 columns.
- From the `xl` [breakpoint](https://tailwindcss.com/docs/responsive-design#overview) onwards, they will be displayed in 3 columns.
These settings are fully customizable, any [breakpoint](https://tailwindcss.com/docs/responsive-design#overview) from `sm` to `2xl` can contain `1` to `12` columns.
<AutoScreenshot name="tables/layout/grid" alt="Table with grid layout" version="3.x" />
<AutoScreenshot name="tables/layout/grid/mobile" alt="Table with grid layout on mobile" version="3.x" />
## Custom HTML
You may add custom HTML to your table using a `View` component. It can even be `collapsible()`:
```php
use Filament\Support\Enums\FontWeight;
use Filament\Tables\Columns\Layout\Split;
use Filament\Tables\Columns\Layout\View;
use Filament\Tables\Columns\ImageColumn;
use Filament\Tables\Columns\TextColumn;
[
Split::make([
ImageColumn::make('avatar')
->circular(),
TextColumn::make('name')
->weight(FontWeight::Bold)
->searchable()
->sortable(),
]),
View::make('users.table.collapsible-row-content')
->collapsible(),
]
```
Now, create a `/resources/views/users/table/collapsible-row-content.blade.php` file, and add in your HTML. You can access the table record using `$getRecord()`:
```blade
<p class="px-4 py-3 bg-gray-100 rounded-lg">
<span class="font-medium">
Email address:
</span>
<span>
{{ $getRecord()->email }}
</span>
</p>
```
### Embedding other components
You could even pass in columns or other layout components to the `components()` method:
```php
use Filament\Support\Enums\FontWeight;
use Filament\Tables\Columns\Layout\Split;
use Filament\Tables\Columns\Layout\View;
use Filament\Tables\Columns\ImageColumn;
use Filament\Tables\Columns\TextColumn;
[
Split::make([
ImageColumn::make('avatar')
->circular(),
TextColumn::make('name')
->weight(FontWeight::Bold)
->searchable()
->sortable(),
]),
View::make('users.table.collapsible-row-content')
->components([
TextColumn::make('email')
->icon('heroicon-m-envelope'),
])
->collapsible(),
]
```
Now, render the components in the Blade file:
```blade
<div class="px-4 py-3 bg-gray-100 rounded-lg">
<x-filament-tables::columns.layout
:components="$getComponents()"
:record="$getRecord()"
:record-key="$recordKey"
/>
</div>
```

View File

@@ -0,0 +1,360 @@
---
title: Summaries
---
import AutoScreenshot from "@components/AutoScreenshot.astro"
## Overview
You may render a "summary" section below your table content. This is great for displaying the results of calculations such as averages, sums, counts, and ranges of the data in your table.
By default, there will be a single summary line for the current page of data, and an additional summary line for the totals for all data if multiple pages are available. You may also add summaries for [groups](grouping) of records, see ["Summarising groups of rows"](#summarising-groups-of-rows).
"Summarizer" objects can be added to any [table column](columns) using the `summarize()` method:
```php
use Filament\Tables\Columns\Summarizers\Average;
use Filament\Tables\Columns\TextColumn;
TextColumn::make('rating')
->summarize(Average::make())
```
Multiple "summarizers" may be added to the same column:
```php
use Filament\Tables\Columns\Summarizers\Average;
use Filament\Tables\Columns\Summarizers\Range;
use Filament\Tables\Columns\TextColumn;
TextColumn::make('rating')
->numeric()
->summarize([
Average::make(),
Range::make(),
])
```
> The first column in a table may not use summarizers. That column is used to render the heading and subheading/s of the summary section.
<AutoScreenshot name="tables/summaries" alt="Table with summaries" version="3.x" />
## Available summarizers
Filament ships with four types of summarizer:
- [Average](#average)
- [Count](#count)
- [Range](#range)
- [Sum](#sum)
You may also [create your own custom summarizers](#custom-summaries) to display data in whatever way you wish.
## Average
Average can be used to calculate the average of all values in the dataset:
```php
use Filament\Tables\Columns\Summarizers\Average;
use Filament\Tables\Columns\TextColumn;
TextColumn::make('rating')
->summarize(Average::make())
```
In this example, all ratings in the table will be added together and divided by the number of ratings.
## Count
Count can be used to find the total number of values in the dataset. Unless you just want to calculate the number of rows, you will probably want to [scope the dataset](#scoping-the-dataset) as well:
```php
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\Summarizers\Count;
use Illuminate\Database\Query\Builder;
IconColumn::make('is_published')
->boolean()
->summarize(
Count::make()->query(fn (Builder $query) => $query->where('is_published', true)),
),
```
In this example, the table will calculate how many posts are published.
### Counting the occurrence of icons
Using a count on an [icon column](columns/icon) allows you to use the `icons()` method, which gives the user a visual representation of how many of each icon are in the table:
```php
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\Summarizers\Count;
use Illuminate\Database\Query\Builder;
IconColumn::make('is_published')
->boolean()
->summarize(Count::make()->icons()),
```
## Range
Range can be used to calculate the minimum and maximum value in the dataset:
```php
use Filament\Tables\Columns\Summarizers\Range;
use Filament\Tables\Columns\TextColumn;
TextColumn::make('price')
->summarize(Range::make())
```
In this example, the minimum and maximum price in the table will be found.
### Date range
You may format the range as dates using the `minimalDateTimeDifference()` method:
```php
use Filament\Tables\Columns\Summarizers\Range;
use Filament\Tables\Columns\TextColumn;
TextColumn::make('created_at')
->dateTime()
->summarize(Range::make()->minimalDateTimeDifference())
```
This method will present the minimal difference between the minimum and maximum date. For example:
- If the minimum and maximum dates are different days, only the dates will be displayed.
- If the minimum and maximum dates are on the same day at different times, both the date and time will be displayed.
- If the minimum and maximum dates and times are identical, they will only appear once.
### Text range
You may format the range as text using the `minimalTextualDifference()` method:
```php
use Filament\Tables\Columns\Summarizers\Range;
use Filament\Tables\Columns\TextColumn;
TextColumn::make('sku')
->summarize(Range::make()->minimalTextualDifference())
```
This method will present the minimal difference between the minimum and maximum. For example:
- If the minimum and maximum start with different letters, only the first letters will be displayed.
- If the minimum and maximum start with the same letter, more of the text will be rendered until a difference is found.
- If the minimum and maximum are identical, they will only appear once.
### Including null values in the range
By default, we will exclude null values from the range. If you would like to include them, you may use the `excludeNull(false)` method:
```php
use Filament\Tables\Columns\Summarizers\Range;
use Filament\Tables\Columns\TextColumn;
TextColumn::make('sku')
->summarize(Range::make()->excludeNull(false))
```
## Sum
Sum can be used to calculate the total of all values in the dataset:
```php
use Filament\Tables\Columns\Summarizers\Sum;
use Filament\Tables\Columns\TextColumn;
TextColumn::make('price')
->summarize(Sum::make())
```
In this example, all prices in the table will be added together.
## Setting a label
You may set a summarizer's label using the `label()` method:
```php
use Filament\Tables\Columns\Summarizers\Sum;
use Filament\Tables\Columns\TextColumn;
TextColumn::make('price')
->summarize(Sum::make()->label('Total'))
```
## Scoping the dataset
You may apply a database query scope to a summarizer's dataset using the `query()` method:
```php
use Filament\Tables\Columns\Summarizers\Average;
use Filament\Tables\Columns\TextColumn;
use Illuminate\Database\Query\Builder;
TextColumn::make('rating')
->summarize(
Average::make()->query(fn (Builder $query) => $query->where('is_published', true)),
),
```
In this example, now only rows where `is_published` is set to `true` will be used to calculate the average.
This feature is especially useful with the [count](#count) summarizer, as it can count how many records in the dataset pass a test:
```php
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\Summarizers\Count;
use Illuminate\Database\Query\Builder;
IconColumn::make('is_published')
->boolean()
->summarize(
Count::make()->query(fn (Builder $query) => $query->where('is_published', true)),
),
```
In this example, the table will calculate how many posts are published.
## Formatting
### Number formatting
The `numeric()` method allows you to format an entry as a number:
```php
use Filament\Tables\Columns\Summarizers\Average;
use Filament\Tables\Columns\TextColumn;
TextColumn::make('rating')
->summarize(Average::make()->numeric())
```
If you would like to customize the number of decimal places used to format the number with, you can use the `decimalPlaces` argument:
```php
use Filament\Tables\Columns\Summarizers\Average;
use Filament\Tables\Columns\TextColumn;
TextColumn::make('rating')
->summarize(Average::make()->numeric(
decimalPlaces: 0,
))
```
By default, your app's locale will be used to format the number suitably. If you would like to customize the locale used, you can pass it to the `locale` argument:
```php
use Filament\Tables\Columns\Summarizers\Average;
use Filament\Tables\Columns\TextColumn;
TextColumn::make('rating')
->summarize(Average::make()->numeric(
locale: 'nl',
))
```
Alternatively, you can set the default locale used across your app using the `Number::useLocale()` method in the `boot()` method of a service provider:
```php
use Illuminate\Support\Number;
Number::useLocale('nl');
```
### Currency formatting
The `money()` method allows you to easily format monetary values, in any currency:
```php
use Filament\Tables\Columns\Summarizers\Sum;
use Filament\Tables\Columns\TextColumn;
TextColumn::make('price')
->summarize(Sum::make()->money('EUR'))
```
There is also a `divideBy` argument for `money()` that allows you to divide the original value by a number before formatting it. This could be useful if your database stores the price in cents, for example:
```php
use Filament\Tables\Columns\Summarizers\Sum;
use Filament\Tables\Columns\TextColumn;
TextColumn::make('price')
->summarize(Sum::make()->money('EUR', divideBy: 100))
```
By default, your app's locale will be used to format the money suitably. If you would like to customize the locale used, you can pass it to the `locale` argument:
```php
use Filament\Tables\Columns\Summarizers\Average;
use Filament\Tables\Columns\TextColumn;
TextColumn::make('price')
->summarize(Sum::make()->money('EUR', locale: 'nl'))
```
Alternatively, you can set the default locale used across your app using the `Number::useLocale()` method in the `boot()` method of a service provider:
```php
use Illuminate\Support\Number;
Number::useLocale('nl');
```
### Limiting text length
You may `limit()` the length of the summary's value:
```php
use Filament\Tables\Columns\Summarizers\Range;
use Filament\Tables\Columns\TextColumn;
TextColumn::make('sku')
->summarize(Range::make()->limit(5))
```
## Custom summaries
You may create a custom summary by returning the value from the `using()` method:
```php
use Filament\Tables\Columns\Summarizers\Summarizer;
use Filament\Tables\Columns\TextColumn;
use Illuminate\Database\Query\Builder;
TextColumn::make('name')
->summarize(Summarizer::make()
->label('First last name')
->using(fn (Builder $query): string => $query->min('last_name')))
```
The callback has access to the database `$query` builder instance to perform calculations with. It should return the value to display in the table.
## Summarising groups of rows
You can use summaries with [groups](grouping) to display a summary of the records inside a group. This works automatically if you choose to add a summariser to a column in a grouped table.
### Hiding the grouped rows and showing the summary only
You may hide the rows inside groups and just show the summary of each group using the `groupsOnly()` method. This is very useful in many reporting scenarios.
```php
use Filament\Tables\Columns\Summarizers\Sum;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('views_count')
->summarize(Sum::make()),
TextColumn::make('likes_count')
->summarize(Sum::make()),
])
->defaultGroup('category')
->groupsOnly();
}
```

View File

@@ -0,0 +1,354 @@
---
title: Grouping rows
---
import AutoScreenshot from "@components/AutoScreenshot.astro"
## Overview
You may allow users to group table rows together using a common attribute. This is useful for displaying lots of data in a more organized way.
Groups can be set up using the name of the attribute to group by (e.g. `'status'`), or a `Group` object which allows you to customize the behavior of that grouping (e.g. `Group::make('status')->collapsible()`).
## Grouping rows by default
You may want to always group posts by a specific attribute. To do this, pass the group to the `defaultGroup()` method:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->defaultGroup('status');
}
```
<AutoScreenshot name="tables/grouping" alt="Table with grouping" version="3.x" />
## Allowing users to choose between groupings
You may also allow users to pick between different groupings, by passing them in an array to the `groups()` method:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->groups([
'status',
'category',
]);
}
```
You can use both `groups()` and `defaultGroup()` together to allow users to choose between different groupings, but have a default grouping set:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->groups([
'status',
'category',
])
->defaultGroup('status');
}
```
## Grouping by a relationship attribute
You can also group by a relationship attribute using dot-syntax. For example, if you have an `author` relationship which has a `name` attribute, you can use `author.name` as the name of the attribute:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->groups([
'author.name',
]);
}
```
## Setting a grouping label
By default, the label of the grouping will be generated based on the attribute. You may customize it with a `Group` object, using the `label()` method:
```php
use Filament\Tables\Grouping\Group;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->groups([
Group::make('author.name')
->label('Author name'),
]);
}
```
## Setting a group title
By default, the title of a group will be the value of the attribute. You may customize it by returning a new title from the `getTitleFromRecordUsing()` method of a `Group` object:
```php
use Filament\Tables\Grouping\Group;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->groups([
Group::make('status')
->getTitleFromRecordUsing(fn (Post $record): string => ucfirst($record->status->getLabel())),
]);
}
```
### Disabling the title label prefix
By default, the title is prefixed with the label of the group. To disable this prefix, utilize the `titlePrefixedWithLabel(false)` method:
```php
use Filament\Tables\Grouping\Group;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->groups([
Group::make('status')
->titlePrefixedWithLabel(false),
]);
}
```
## Setting a group description
You may also set a description for a group, which will be displayed underneath the group title. To do this, use the `getDescriptionFromRecordUsing()` method on a `Group` object:
```php
use Filament\Tables\Grouping\Group;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->groups([
Group::make('status')
->getDescriptionFromRecordUsing(fn (Post $record): string => $record->status->getDescription()),
]);
}
```
<AutoScreenshot name="tables/grouping-descriptions" alt="Table with group descriptions" version="3.x" />
## Setting a group key
By default, the key of a group will be the value of the attribute. It is used internally as a raw identifier of that group, instead of the [title](#setting-a-group-title). You may customize it by returning a new key from the `getKeyFromRecordUsing()` method of a `Group` object:
```php
use Filament\Tables\Grouping\Group;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->groups([
Group::make('status')
->getKeyFromRecordUsing(fn (Post $record): string => $record->status->value),
]);
}
```
## Date groups
When using a date-time column as a group, you may want to group by the date only, and ignore the time. To do this, use the `date()` method on a `Group` object:
```php
use Filament\Tables\Grouping\Group;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->groups([
Group::make('created_at')
->date(),
]);
}
```
## Collapsible groups
You can allow rows inside a group to be collapsed underneath their group title. To enable this, use a `Group` object with the `collapsible()` method:
```php
use Filament\Tables\Grouping\Group;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->groups([
Group::make('author.name')
->collapsible(),
]);
}
```
## Summarising groups
You can use [summaries](summaries) with groups to display a summary of the records inside a group. This works automatically if you choose to add a summariser to a column in a grouped table.
### Hiding the grouped rows and showing the summary only
You may hide the rows inside groups and just show the summary of each group using the `groupsOnly()` method. This is very useful in many reporting scenarios.
```php
use Filament\Tables\Columns\Summarizers\Sum;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('views_count')
->summarize(Sum::make()),
TextColumn::make('likes_count')
->summarize(Sum::make()),
])
->defaultGroup('category')
->groupsOnly();
}
```
## Customizing the Eloquent query ordering behavior
Some features require the table to be able to order an Eloquent query according to a group. You can customize how we do this using the `orderQueryUsing()` method on a `Group` object:
```php
use Filament\Tables\Grouping\Group;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
public function table(Table $table): Table
{
return $table
->groups([
Group::make('status')
->orderQueryUsing(fn (Builder $query, string $direction) => $query->orderBy('status', $direction)),
]);
}
```
## Customizing the Eloquent query scoping behavior
Some features require the table to be able to scope an Eloquent query according to a group. You can customize how we do this using the `scopeQueryByKeyUsing()` method on a `Group` object:
```php
use Filament\Tables\Grouping\Group;
use Illuminate\Database\Eloquent\Builder;
public function table(Table $table): Table
{
return $table
->groups([
Group::make('status')
->scopeQueryByKeyUsing(fn (Builder $query, string $key) => $query->where('status', $key)),
]);
}
```
## Customizing the Eloquent query grouping behavior
Some features require the table to be able to group an Eloquent query according to a group. You can customize how we do this using the `groupQueryUsing()` method on a `Group` object:
```php
use Filament\Tables\Grouping\Group;
use Illuminate\Database\Eloquent\Builder;
public function table(Table $table): Table
{
return $table
->groups([
Group::make('status')
->groupQueryUsing(fn (Builder $query) => $query->groupBy('status')),
]);
}
```
## Customizing the groups dropdown trigger action
To customize the groups dropdown trigger button, you may use the `groupRecordsTriggerAction()` method, passing a closure that returns an action. All methods that are available to [customize action trigger buttons](../actions/trigger-button) can be used:
```php
use Filament\Tables\Actions\Action;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->groups([
// ...
])
->groupRecordsTriggerAction(
fn (Action $action) => $action
->button()
->label('Group records'),
);
}
```
## Using the grouping settings dropdown on desktop
By default, the grouping settings dropdown will only be shown on mobile devices. On desktop devices, the grouping settings are in the header of the table. You can enable the dropdown on desktop devices too by using the `groupingSettingsInDropdownOnDesktop()` method:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->groups([
// ...
])
->groupingSettingsInDropdownOnDesktop();
}
```
## Hiding the grouping settings
You can hide the grouping settings interface using the `groupingSettingsHidden()` method:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->defaultGroup('status')
->groupingSettingsHidden();
}
```
### Hiding the grouping direction setting only
You can hide the grouping direction select interface using the `groupingDirectionSettingHidden()` method:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->defaultGroup('status')
->groupingDirectionSettingHidden();
}
```

View File

@@ -0,0 +1,96 @@
---
title: Empty state
---
import AutoScreenshot from "@components/AutoScreenshot.astro"
## Overview
The table's "empty state" is rendered when there are no rows in the table.
<AutoScreenshot name="tables/empty-state" alt="Table with empty state" version="3.x" />
## Setting the empty state heading
To customize the heading of the empty state, use the `emptyStateHeading()` method:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->emptyStateHeading('No posts yet');
}
```
<AutoScreenshot name="tables/empty-state-heading" alt="Table with customized empty state heading" version="3.x" />
## Setting the empty state description
To customize the description of the empty state, use the `emptyStateDescription()` method:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->emptyStateDescription('Once you write your first post, it will appear here.');
}
```
<AutoScreenshot name="tables/empty-state-description" alt="Table with empty state description" version="3.x" />
## Setting the empty state icon
To customize the [icon](https://blade-ui-kit.com/blade-icons?set=1#search) of the empty state, use the `emptyStateIcon()` method:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->emptyStateIcon('heroicon-o-bookmark');
}
```
<AutoScreenshot name="tables/empty-state-icon" alt="Table with customized empty state icon" version="3.x" />
## Adding empty state actions
You can add [Actions](actions) to the empty state to prompt users to take action. Pass these to the `emptyStateActions()` method:
```php
use Filament\Tables\Actions\Action;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->emptyStateActions([
Action::make('create')
->label('Create post')
->url(route('posts.create'))
->icon('heroicon-m-plus')
->button(),
]);
}
```
<AutoScreenshot name="tables/empty-state-actions" alt="Table with empty state actions" version="3.x" />
## Using a custom empty state view
You may use a completely custom empty state view by passing it to the `emptyState()` method:
```php
use Filament\Tables\Actions\Action;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->emptyState(view('tables.posts.empty-state'));
}
```

View File

@@ -0,0 +1,438 @@
---
title: Advanced
---
import AutoScreenshot from "@components/AutoScreenshot.astro"
## 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);
}
```
### Customizing the pagination options
You may customize the options for the paginated records per page select by passing them to the `paginated()` method:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->paginated([10, 25, 50, 100, 'all']);
}
```
### Customizing the default pagination page option
To customize the default number of records shown use the `defaultPaginationPageOption()` method:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->defaultPaginationPageOption(25);
}
```
### 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');
}
```
### Displaying links to the first and the last pagination page
To add "extreme" links to the first and the last page using the `extremePaginationLinks()` method:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->extremePaginationLinks();
}
```
### Using simple pagination
You may use simple pagination by overriding `paginateTableQuery()` method.
First, locate your Livewire component. If you're using a resource from the Panel Builder and you want to add simple pagination to the List page, you'll want to open the `Pages/List.php` file in the resource, not the resource class itself.
```php
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Builder;
protected function paginateTableQuery(Builder $query): Paginator
{
return $query->simplePaginate(($this->getTableRecordsPerPage() === 'all') ? $query->count() : $this->getTableRecordsPerPage());
}
```
### Using cursor pagination
You may use cursor pagination by overriding `paginateTableQuery()` method.
First, locate your Livewire component. If you're using a resource from the Panel Builder and you want to add simple pagination to the List page, you'll want to open the `Pages/List.php` file in the resource, not the resource class itself.
```php
use Illuminate\Contracts\Pagination\CursorPaginator;
use Illuminate\Database\Eloquent\Builder;
protected function paginateTableQuery(Builder $query): CursorPaginator
{
return $query->cursorPaginate(($this->getTableRecordsPerPage() === 'all') ? $query->count() : $this->getTableRecordsPerPage());
}
```
## Record URLs (clickable rows)
You may allow table rows to be completely clickable by using the `$table->recordUrl()` method:
```php
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Model;
public function table(Table $table): Table
{
return $table
->recordUrl(
fn (Model $record): string => route('posts.edit', ['record' => $record]),
);
}
```
In this example, clicking on each post will take you to the `posts.edit` route.
You may also open the URL in a new tab:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->openRecordUrlInNewTab();
}
```
If you'd like to [override the URL](columns/getting-started#opening-urls) for a specific column, or instead [run an action](columns/getting-started#running-actions) when a column is clicked, see the [columns documentation](columns/getting-started#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:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->reorderable('sort');
}
```
If you're using mass assignment protection on your model, you will also need to add the `sort` attribute to the `$fillable` array there.
When making the table reorderable, a new button will be available on the table to toggle reordering.
<AutoScreenshot name="tables/reordering" alt="Table with reorderable rows" version="3.x" />
The `reorderable()` method accepts the name of a column to store the record order in. If you use something like [`spatie/eloquent-sortable`](https://github.com/spatie/eloquent-sortable) with an order column such as `order_column`, you may use this instead:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->reorderable('order_column');
}
```
The `reorderable()` method also accepts a boolean condition as its second parameter, allowing you to conditionally enable reordering:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->reorderable('sort', auth()->user()->isAdmin());
}
```
### 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()`:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->paginatedWhileReordering();
}
```
### Customizing the reordering trigger action
To customize the reordering trigger button, you may use the `reorderRecordsTriggerAction()` method, passing a closure that returns an action. All methods that are available to [customize action trigger buttons](../actions/trigger-button) can be used:
```php
use Filament\Tables\Actions\Action;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->reorderRecordsTriggerAction(
fn (Action $action, bool $isReordering) => $action
->button()
->label($isReordering ? 'Disable reordering' : 'Enable reordering'),
);
}
```
<AutoScreenshot name="tables/reordering/custom-trigger-action" alt="Table with reorderable rows and a custom trigger action" version="3.x" />
## Customizing the table header
You can add a heading to a table using the `$table->heading()` method:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->heading('Clients')
->columns([
// ...
]);
```
You can also add a description below the heading using the `$table->description()` method:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->heading('Clients')
->description('Manage your clients here.')
->columns([
// ...
]);
```
You can pass a view to the `$table->header()` method to customize the entire header:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->header(view('tables.header', [
'heading' => 'Clients',
]))
->columns([
// ...
]);
```
## Polling table content
You may poll table content so that it refreshes at a set interval, using the `$table->poll()` method:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->poll('10s');
}
```
## Deferring loading
Tables with lots of data might take a while to load, in which case you can load the table data asynchronously using the `deferLoading()` method:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->deferLoading();
}
```
## 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:
```php
use App\Models\Post;
use Illuminate\Database\Eloquent\Builder;
protected function applySearchToTableQuery(Builder $query): Builder
{
$this->applyColumnSearchesToTableQuery($query);
if (filled($search = $this->getTableSearch())) {
$query->whereIn('id', Post::search($search)->keys());
}
return $query;
}
```
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.
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:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->searchable();
}
```
## 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
To enable striped table rows, you can use the `striped()` method:
```php
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->striped();
}
```
<AutoScreenshot name="tables/striped" alt="Table with striped rows" version="3.x" />
### Custom row classes
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 Closure;
use Filament\Tables\Table;
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',
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:
```php
use Filament\Tables\Enums\FiltersLayout;
use Filament\Tables\Table;
Table::configureUsing(function (Table $table): void {
$table
->filtersLayout(FiltersLayout::AboveContentCollapsible)
->paginationPageOptions([10, 25, 50]);
});
```

View File

@@ -0,0 +1,137 @@
---
title: Adding a table to a Livewire component
---
## Setting up the Livewire component
First, generate a new Livewire component:
```bash
php artisan make:livewire ListProducts
```
Then, render your Livewire component on the page:
```blade
@livewire('list-products')
```
Alternatively, you can use a full-page Livewire component:
```php
use App\Livewire\ListProducts;
use Illuminate\Support\Facades\Route;
Route::get('products', ListProducts::class);
```
## Adding the table
There are 3 tasks when adding a table to a Livewire component class:
1) Implement the `HasTable` and `HasForms` interfaces, and use the `InteractsWithTable` and `InteractsWithForms` traits.
2) Add a `table()` method, which is where you configure the table. [Add the table's columns, filters, and actions](getting-started#columns).
3) Make sure to define the base query that will be used to fetch rows in the table. For example, if you're listing products from your `Product` model, you will want to return `Product::query()`.
```php
<?php
namespace App\Livewire;
use App\Models\Shop\Product;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Concerns\InteractsWithTable;
use Filament\Tables\Contracts\HasTable;
use Filament\Tables\Table;
use Illuminate\Contracts\View\View;
use Livewire\Component;
class ListProducts extends Component implements HasForms, HasTable
{
use InteractsWithTable;
use InteractsWithForms;
public function table(Table $table): Table
{
return $table
->query(Product::query())
->columns([
TextColumn::make('name'),
])
->filters([
// ...
])
->actions([
// ...
])
->bulkActions([
// ...
]);
}
public function render(): View
{
return view('livewire.list-products');
}
}
```
Finally, in your Livewire component's view, render the table:
```blade
<div>
{{ $this->table }}
</div>
```
Visit your Livewire component in the browser, and you should see the table.
## Building a table for an Eloquent relationship
If you want to build a table for an Eloquent relationship, you can use the `relationship()` and `inverseRelationship()` methods on the `$table` instead of passing a `query()`. `HasMany`, `HasManyThrough`, `BelongsToMany`, `MorphMany` and `MorphToMany` relationships are compatible:
```php
use App\Models\Category;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
public Category $category;
public function table(Table $table): Table
{
return $table
->relationship(fn (): BelongsToMany => $this->category->products())
->inverseRelationship('categories')
->columns([
TextColumn::make('name'),
]);
}
```
In this example, we have a `$category` property which holds a `Category` model instance. The category has a relationship named `products`. We use a function to return the relationship instance. This is a many-to-many relationship, so the inverse relationship is called `categories`, and is defined on the `Product` model. We just need to pass the name of this relationship to the `inverseRelationship()` method, not the whole instance.
Now that the table is using a relationship instead of a plain Eloquent query, all actions will be performed on the relationship instead of the query. For example, if you use a [`CreateAction`](../actions/prebuilt-actions/create), the new product will be automatically attached to the category.
If your relationship uses a pivot table, you can use all pivot columns as if they were normal columns on your table, as long as they are listed in the `withPivot()` method of the relationship *and* inverse relationship definition.
Relationship tables are used in the Panel Builder as ["relation managers"](../panels/resources/relation-managers#creating-a-relation-manager). Most of the documented features for relation managers are also available for relationship tables. For instance, [attaching and detaching](../panels/resources/relation-managers#attaching-and-detaching-records) and [associating and dissociating](../panels/resources/relation-managers#associating-and-dissociating-records) actions.
## Generating table Livewire components with the CLI
It's advised that you learn how to set up a Livewire component with the Table Builder manually, but once you are confident, you can use the CLI to generate a table for you.
```bash
php artisan make:livewire-table Products/ListProducts
```
This will ask you for the name of a prebuilt model, for example `Product`. Finally, it will generate a new `app/Livewire/Products/ListProducts.php` component, which you can customize.
### Automatically generating table columns
Filament is also able to guess which table columns you want in the table, based on the model's database columns. You can use the `--generate` flag when generating your table:
```bash
php artisan make:livewire-table Products/ListProducts --generate
```

View File

@@ -0,0 +1,683 @@
---
title: Testing
---
## Overview
All examples in this guide will be written using [Pest](https://pestphp.com). To use Pest's Livewire plugin for testing, you can follow the installation instructions in the Pest documentation on plugins: [Livewire plugin for Pest](https://pestphp.com/docs/plugins#livewire). However, you can easily adapt this to PHPUnit.
Since the Table Builder works on Livewire components, you can use the [Livewire testing helpers](https://livewire.laravel.com/docs/testing). However, we have many custom testing helpers that you can use for tables:
## Render
To ensure a table component renders, use the `assertSuccessful()` Livewire helper:
```php
use function Pest\Livewire\livewire;
it('can render page', function () {
livewire(ListPosts::class)->assertSuccessful();
});
```
To test which records are shown, you can use `assertCanSeeTableRecords()`, `assertCanNotSeeTableRecords()` and `assertCountTableRecords()`:
```php
use function Pest\Livewire\livewire;
it('cannot display trashed posts by default', function () {
$posts = Post::factory()->count(4)->create();
$trashedPosts = Post::factory()->trashed()->count(6)->create();
livewire(PostResource\Pages\ListPosts::class)
->assertCanSeeTableRecords($posts)
->assertCanNotSeeTableRecords($trashedPosts)
->assertCountTableRecords(4);
});
```
> If your table uses pagination, `assertCanSeeTableRecords()` will only check for records on the first page. To switch page, call `call('gotoPage', 2)`.
> If your table uses `deferLoading()`, you should call `loadTable()` before `assertCanSeeTableRecords()`.
## Columns
To ensure that a certain column is rendered, pass the column name to `assertCanRenderTableColumn()`:
```php
use function Pest\Livewire\livewire;
it('can render post titles', function () {
Post::factory()->count(10)->create();
livewire(PostResource\Pages\ListPosts::class)
->assertCanRenderTableColumn('title');
});
```
This helper will get the HTML for this column, and check that it is present in the table.
For testing that a column is not rendered, you can use `assertCanNotRenderTableColumn()`:
```php
use function Pest\Livewire\livewire;
it('can not render post comments', function () {
Post::factory()->count(10)->create()
livewire(PostResource\Pages\ListPosts::class)
->assertCanNotRenderTableColumn('comments');
});
```
This helper will assert that the HTML for this column is not shown by default in the present table.
### Sorting
To sort table records, you can call `sortTable()`, passing the name of the column to sort by. You can use `'desc'` in the second parameter of `sortTable()` to reverse the sorting direction.
Once the table is sorted, you can ensure that the table records are rendered in order using `assertCanSeeTableRecords()` with the `inOrder` parameter:
```php
use function Pest\Livewire\livewire;
it('can sort posts by title', function () {
$posts = Post::factory()->count(10)->create();
livewire(PostResource\Pages\ListPosts::class)
->sortTable('title')
->assertCanSeeTableRecords($posts->sortBy('title'), inOrder: true)
->sortTable('title', 'desc')
->assertCanSeeTableRecords($posts->sortByDesc('title'), inOrder: true);
});
```
### Searching
To search the table, call the `searchTable()` method with your search query.
You can then use `assertCanSeeTableRecords()` to check your filtered table records, and use `assertCanNotSeeTableRecords()` to assert that some records are no longer in the table:
```php
use function Pest\Livewire\livewire;
it('can search posts by title', function () {
$posts = Post::factory()->count(10)->create();
$title = $posts->first()->title;
livewire(PostResource\Pages\ListPosts::class)
->searchTable($title)
->assertCanSeeTableRecords($posts->where('title', $title))
->assertCanNotSeeTableRecords($posts->where('title', '!=', $title));
});
```
To search individual columns, you can pass an array of searches to `searchTableColumns()`:
```php
use function Pest\Livewire\livewire;
it('can search posts by title column', function () {
$posts = Post::factory()->count(10)->create();
$title = $posts->first()->title;
livewire(PostResource\Pages\ListPosts::class)
->searchTableColumns(['title' => $title])
->assertCanSeeTableRecords($posts->where('title', $title))
->assertCanNotSeeTableRecords($posts->where('title', '!=', $title));
});
```
### State
To assert that a certain column has a state or does not have a state for a record you can use `assertTableColumnStateSet()` and `assertTableColumnStateNotSet()`:
```php
use function Pest\Livewire\livewire;
it('can get post author names', function () {
$posts = Post::factory()->count(10)->create();
$post = $posts->first();
livewire(PostResource\Pages\ListPosts::class)
->assertTableColumnStateSet('author.name', $post->author->name, record: $post)
->assertTableColumnStateNotSet('author.name', 'Anonymous', record: $post);
});
```
To assert that a certain column has a formatted state or does not have a formatted state for a record you can use `assertTableColumnFormattedStateSet()` and `assertTableColumnFormattedStateNotSet()`:
```php
use function Pest\Livewire\livewire;
it('can get post author names', function () {
$post = Post::factory(['name' => 'John Smith'])->create();
livewire(PostResource\Pages\ListPosts::class)
->assertTableColumnFormattedStateSet('author.name', 'Smith, John', record: $post)
->assertTableColumnFormattedStateNotSet('author.name', $post->author->name, record: $post);
});
```
### Existence
To ensure that a column exists, you can use the `assertTableColumnExists()` method:
```php
use function Pest\Livewire\livewire;
it('has an author column', function () {
livewire(PostResource\Pages\ListPosts::class)
->assertTableColumnExists('author');
});
```
You may pass a function as an additional argument in order to assert that a column passes a given "truth test". This is useful for asserting that a column has a specific configuration. You can also pass in a record as the third parameter, which is useful if your check is dependant on which table row is being rendered:
```php
use function Pest\Livewire\livewire;
use Filament\Tables\Columns\TextColumn;
it('has an author column', function () {
$post = Post::factory()->create();
livewire(PostResource\Pages\ListPosts::class)
->assertTableColumnExists('author', function (TextColumn $column): bool {
return $column->getDescriptionBelow() === $post->subtitle;
}, $post);
});
```
### Authorization
To ensure that a particular user cannot see a column, you can use the `assertTableColumnVisible()` and `assertTableColumnHidden()` methods:
```php
use function Pest\Livewire\livewire;
it('shows the correct columns', function () {
livewire(PostResource\Pages\ListPosts::class)
->assertTableColumnVisible('created_at')
->assertTableColumnHidden('author');
});
```
### Descriptions
To ensure a column has the correct description above or below you can use the `assertTableColumnHasDescription()` and `assertTableColumnDoesNotHaveDescription()` methods:
```php
use function Pest\Livewire\livewire;
it('has the correct descriptions above and below author', function () {
$post = Post::factory()->create();
livewire(PostsTable::class)
->assertTableColumnHasDescription('author', 'Author! ↓↓↓', $post, 'above')
->assertTableColumnHasDescription('author', 'Author! ↑↑↑', $post)
->assertTableColumnDoesNotHaveDescription('author', 'Author! ↑↑↑', $post, 'above')
->assertTableColumnDoesNotHaveDescription('author', 'Author! ↓↓↓', $post);
});
```
### Extra Attributes
To ensure that a column has the correct extra attributes, you can use the `assertTableColumnHasExtraAttributes()` and `assertTableColumnDoesNotHaveExtraAttributes()` methods:
```php
use function Pest\Livewire\livewire;
it('displays author in red', function () {
$post = Post::factory()->create();
livewire(PostsTable::class)
->assertTableColumnHasExtraAttributes('author', ['class' => 'text-danger-500'], $post)
->assertTableColumnDoesNotHaveExtraAttributes('author', ['class' => 'text-primary-500'], $post);
});
```
### Select Columns
If you have a select column, you can ensure it has the correct options with `assertTableSelectColumnHasOptions()` and `assertTableSelectColumnDoesNotHaveOptions()`:
```php
use function Pest\Livewire\livewire;
it('has the correct statuses', function () {
$post = Post::factory()->create();
livewire(PostsTable::class)
->assertTableSelectColumnHasOptions('status', ['unpublished' => 'Unpublished', 'published' => 'Published'], $post)
->assertTableSelectColumnDoesNotHaveOptions('status', ['archived' => 'Archived'], $post);
});
```
## Filters
To filter the table records, you can use the `filterTable()` method, along with `assertCanSeeTableRecords()` and `assertCanNotSeeTableRecords()`:
```php
use function Pest\Livewire\livewire;
it('can filter posts by `is_published`', function () {
$posts = Post::factory()->count(10)->create();
livewire(PostResource\Pages\ListPosts::class)
->assertCanSeeTableRecords($posts)
->filterTable('is_published')
->assertCanSeeTableRecords($posts->where('is_published', true))
->assertCanNotSeeTableRecords($posts->where('is_published', false));
});
```
For a simple filter, this will just enable the filter.
If you'd like to set the value of a `SelectFilter` or `TernaryFilter`, pass the value as a second argument:
```php
use function Pest\Livewire\livewire;
it('can filter posts by `author_id`', function () {
$posts = Post::factory()->count(10)->create();
$authorId = $posts->first()->author_id;
livewire(PostResource\Pages\ListPosts::class)
->assertCanSeeTableRecords($posts)
->filterTable('author_id', $authorId)
->assertCanSeeTableRecords($posts->where('author_id', $authorId))
->assertCanNotSeeTableRecords($posts->where('author_id', '!=', $authorId));
});
```
### Resetting filters
To reset all filters to their original state, call `resetTableFilters()`:
```php
use function Pest\Livewire\livewire;
it('can reset table filters', function () {
$posts = Post::factory()->count(10)->create();
livewire(PostResource\Pages\ListPosts::class)
->resetTableFilters();
});
```
### Removing Filters
To remove a single filter you can use `removeTableFilter()`:
```php
use function Pest\Livewire\livewire;
it('filters list by published', function () {
$posts = Post::factory()->count(10)->create();
$unpublishedPosts = $posts->where('is_published', false)->get();
livewire(PostsTable::class)
->filterTable('is_published')
->assertCanNotSeeTableRecords($unpublishedPosts)
->removeTableFilter('is_published')
->assertCanSeeTableRecords($posts);
});
```
To remove all filters you can use `removeTableFilters()`:
```php
use function Pest\Livewire\livewire;
it('can remove all table filters', function () {
$posts = Post::factory()->count(10)->forAuthor()->create();
$unpublishedPosts = $posts
->where('is_published', false)
->where('author_id', $posts->first()->author->getKey());
livewire(PostsTable::class)
->filterTable('is_published')
->filterTable('author', $author)
->assertCanNotSeeTableRecords($unpublishedPosts)
->removeTableFilters()
->assertCanSeeTableRecords($posts);
});
```
## Actions
### Calling actions
You can call an action by passing its name or class to `callTableAction()`:
```php
use function Pest\Livewire\livewire;
it('can delete posts', function () {
$post = Post::factory()->create();
livewire(PostResource\Pages\ListPosts::class)
->callTableAction(DeleteAction::class, $post);
$this->assertModelMissing($post);
});
```
This example assumes that you have a `DeleteAction` on your table. If you have a custom `Action::make('reorder')`, you may use `callTableAction('reorder')`.
For column actions, you may do the same, using `callTableColumnAction()`:
```php
use function Pest\Livewire\livewire;
it('can copy posts', function () {
$post = Post::factory()->create();
livewire(PostResource\Pages\ListPosts::class)
->callTableColumnAction('copy', $post);
$this->assertDatabaseCount((new Post)->getTable(), 2);
});
```
For bulk actions, you may do the same, passing in multiple records to execute the bulk action against with `callTableBulkAction()`:
```php
use function Pest\Livewire\livewire;
it('can bulk delete posts', function () {
$posts = Post::factory()->count(10)->create();
livewire(PostResource\Pages\ListPosts::class)
->callTableBulkAction(DeleteBulkAction::class, $posts);
foreach ($posts as $post) {
$this->assertModelMissing($post);
}
});
```
To pass an array of data into an action, use the `data` parameter:
```php
use function Pest\Livewire\livewire;
it('can edit posts', function () {
$post = Post::factory()->create();
livewire(PostResource\Pages\ListPosts::class)
->callTableAction(EditAction::class, $post, data: [
'title' => $title = fake()->words(asText: true),
])
->assertHasNoTableActionErrors();
expect($post->refresh())
->title->toBe($title);
});
```
### Execution
To check if an action or bulk action has been halted, you can use `assertTableActionHalted()` / `assertTableBulkActionHalted()`:
```php
use function Pest\Livewire\livewire;
it('will halt delete if post is flagged', function () {
$posts= Post::factory()->count(2)->flagged()->create();
livewire(PostResource\Pages\ListPosts::class)
->callTableAction('delete', $posts->first())
->callTableBulkAction('delete', $posts)
->assertTableActionHalted('delete')
->assertTableBulkActionHalted('delete');
$this->assertModelExists($post);
});
```
### Errors
`assertHasNoTableActionErrors()` is used to assert that no validation errors occurred when submitting the action form.
To check if a validation error has occurred with the data, use `assertHasTableActionErrors()`, similar to `assertHasErrors()` in Livewire:
```php
use function Pest\Livewire\livewire;
it('can validate edited post data', function () {
$post = Post::factory()->create();
livewire(PostResource\Pages\ListPosts::class)
->callTableAction(EditAction::class, $post, data: [
'title' => null,
])
->assertHasTableActionErrors(['title' => ['required']]);
});
```
For bulk actions these methods are called `assertHasTableBulkActionErrors()` and `assertHasNoTableBulkActionErrors()`.
### Pre-filled data
To check if an action or bulk action is pre-filled with data, you can use the `assertTableActionDataSet()` or `assertTableBulkActionDataSet()` method:
```php
use function Pest\Livewire\livewire;
it('can load existing post data for editing', function () {
$post = Post::factory()->create();
livewire(PostResource\Pages\ListPosts::class)
->mountTableAction(EditAction::class, $post)
->assertTableActionDataSet([
'title' => $post->title,
])
->setTableActionData([
'title' => $title = fake()->words(asText: true),
])
->callMountedTableAction()
->assertHasNoTableActionErrors();
expect($post->refresh())
->title->toBe($title);
});
```
### Action state
To ensure that an action or bulk action exists or doesn't in a table, you can use the `assertTableActionExists()` / `assertTableActionDoesNotExist()` or `assertTableBulkActionExists()` / `assertTableBulkActionDoesNotExist()` method:
```php
use function Pest\Livewire\livewire;
it('can publish but not unpublish posts', function () {
livewire(PostResource\Pages\ListPosts::class)
->assertTableActionExists('publish')
->assertTableActionDoesNotExist('unpublish')
->assertTableBulkActionExists('publish')
->assertTableBulkActionDoesNotExist('unpublish');
});
```
To ensure different sets of actions exist in the correct order, you can use the various "InOrder" assertions
```php
use function Pest\Livewire\livewire;
it('has all actions in expected order', function () {
livewire(PostResource\Pages\ListPosts::class)
->assertTableActionsExistInOrder(['edit', 'delete'])
->assertTableBulkActionsExistInOrder(['restore', 'forceDelete'])
->assertTableHeaderActionsExistInOrder(['create', 'attach'])
->assertTableEmptyStateActionsExistInOrder(['create', 'toggle-trashed-filter'])
});
```
To ensure that an action or bulk action is enabled or disabled for a user, you can use the `assertTableActionEnabled()` / `assertTableActionDisabled()` or `assertTableBulkActionEnabled()` / `assertTableBulkActionDisabled()` methods:
```php
use function Pest\Livewire\livewire;
it('can not publish, but can delete posts', function () {
$post = Post::factory()->create();
livewire(PostResource\Pages\ListPosts::class)
->assertTableActionDisabled('publish', $post)
->assertTableActionEnabled('delete', $post)
->assertTableBulkActionDisabled('publish')
->assertTableBulkActionEnabled('delete');
});
```
To ensure that an action or bulk action is visible or hidden for a user, you can use the `assertTableActionVisible()` / `assertTableActionHidden()` or `assertTableBulkActionVisible()` / `assertTableBulkActionHidden()` methods:
```php
use function Pest\Livewire\livewire;
it('can not publish, but can delete posts', function () {
$post = Post::factory()->create();
livewire(PostResource\Pages\ListPosts::class)
->assertTableActionHidden('publish', $post)
->assertTableActionVisible('delete', $post)
->assertTableBulkActionHidden('publish')
->assertTableBulkActionVisible('delete');
});
```
### Button Style
To ensure an action or bulk action has the correct label, you can use `assertTableActionHasLabel()` / `assertTableBulkActionHasLabel()` and `assertTableActionDoesNotHaveLabel()` / `assertTableBulkActionDoesNotHaveLabel()`:
```php
use function Pest\Livewire\livewire;
it('delete actions have correct labels', function () {
$post = Post::factory()->create();
livewire(PostResource\Pages\ListPosts::class)
->assertTableActionHasLabel('delete', 'Archive Post')
->assertTableActionDoesNotHaveLabel('delete', 'Delete');
->assertTableBulkActionHasLabel('delete', 'Archive Post')
->assertTableBulkActionDoesNotHaveLabel('delete', 'Delete');
});
```
To ensure an action or bulk action's button is showing the correct icon, you can use `assertTableActionHasIcon()` / `assertTableBulkActionHasIcon()` or `assertTableActionDoesNotHaveIcon()` / `assertTableBulkActionDoesNotHaveIcon()`:
```php
use function Pest\Livewire\livewire;
it('delete actions have correct icons', function () {
$post = Post::factory()->create();
livewire(PostResource\Pages\ListPosts::class)
->assertTableActionHasIcon('delete', 'heroicon-m-archive-box')
->assertTableActionDoesNotHaveIcon('delete', 'heroicon-m-trash');
->assertTableBulkActionHasIcon('delete', 'heroicon-m-archive-box')
->assertTableBulkActionDoesNotHaveIcon('delete', 'heroicon-m-trash');
});
```
To ensure that an action or bulk action's button is displaying the right color, you can use `assertTableActionHasColor()` / `assertTableBulkActionHasColor()` or `assertTableActionDoesNotHaveColor()` / `assertTableBulkActionDoesNotHaveColor()`:
```php
use function Pest\Livewire\livewire;
it('delete actions have correct colors', function () {
$post = Post::factory()->create();
livewire(PostResource\Pages\ListPosts::class)
->assertTableActionHasColor('delete', 'warning')
->assertTableActionDoesNotHaveColor('delete', 'danger');
->assertTableBulkActionHasColor('delete', 'warning')
->assertTableBulkActionDoesNotHaveColor('delete', 'danger');
});
```
### URL
To ensure an action or bulk action has the correct URL traits, you can use `assertTableActionHasUrl()`, `assertTableActionDoesNotHaveUrl()`, `assertTableActionShouldOpenUrlInNewTab()`, and `assertTableActionShouldNotOpenUrlInNewTab()`:
```php
use function Pest\Livewire\livewire;
it('links to the correct Filament sites', function () {
$post = Post::factory()->create();
livewire(PostResource\Pages\ListPosts::class)
->assertTableActionHasUrl('filament', 'https://filamentphp.com/')
->assertTableActionDoesNotHaveUrl('filament', 'https://github.com/filamentphp/filament')
->assertTableActionShouldOpenUrlInNewTab('filament')
->assertTableActionShouldNotOpenUrlInNewTab('github');
});
```
## Summaries
To test that a summary calculation is working, you may use the `assertTableColumnSummarySet()` method:
```php
use function Pest\Livewire\livewire;
it('can average values in a column', function () {
$posts = Post::factory()->count(10)->create();
livewire(PostResource\Pages\ListPosts::class)
->assertCanSeeTableRecords($posts)
->assertTableColumnSummarySet('rating', 'average', $posts->avg('rating'));
});
```
The first argument is the column name, the second is the summarizer ID, and the third is the expected value.
You may set a summarizer ID by passing it to the `make()` method:
```php
use Filament\Tables\Columns\Summarizers\Average;
use Filament\Tables\Columns\TextColumn;
TextColumn::make('rating')
->summarize(Average::make('average'))
```
The ID should be unique between summarizers in that column.
### Summarizing only one pagination page
To calculate the average for only one pagination page, use the `isCurrentPaginationPageOnly` argument:
```php
use function Pest\Livewire\livewire;
it('can average values in a column', function () {
$posts = Post::factory()->count(20)->create();
livewire(PostResource\Pages\ListPosts::class)
->assertCanSeeTableRecords($posts->take(10))
->assertTableColumnSummarySet('rating', 'average', $posts->take(10)->avg('rating'), isCurrentPaginationPageOnly: true);
});
```
### Testing a range summarizer
To test a range, pass the minimum and maximum value into a tuple-style `[$minimum, $maximum]` array:
```php
use function Pest\Livewire\livewire;
it('can average values in a column', function () {
$posts = Post::factory()->count(10)->create();
livewire(PostResource\Pages\ListPosts::class)
->assertCanSeeTableRecords($posts)
->assertTableColumnSummarySet('rating', 'range', [$posts->min('rating'), $posts->max('rating')]);
});
```

View File

@@ -0,0 +1,82 @@
---
title: Upgrading from v2.x
---
> If you see anything missing from this guide, please do not hesitate to [make a pull request](https://github.com/filamentphp/filament/edit/3.x/packages/tables/docs/13-upgrade-guide.md) to our repository! Any help is appreciated!
## New requirements
- Laravel v10.0+
- Livewire v3.0+
Please upgrade Filament before upgrading to Livewire v3. Instructions on how to upgrade Livewire can be found [here](https://livewire.laravel.com/docs/upgrading).
## Upgrading automatically
The easiest way to upgrade your app is to run the automated upgrade script. This script will automatically upgrade your application to the latest version of Filament, and make changes to your code which handle most breaking changes.
```bash
composer require filament/upgrade:"^3.2" -W --dev
vendor/bin/filament-v3
```
Make sure to carefully follow the instructions, and review the changes made by the script. You may need to make some manual changes to your code afterwards, but the script should handle most of the repetitive work for you.
Finally, you must run `php artisan filament:install` to finalize the Filament v3 installation. This command must be run for all new Filament projects.
You can now `composer remove filament/upgrade` as you don't need it anymore.
> Some plugins you're using may not be available in v3 just yet. You could temporarily remove them from your `composer.json` file until they've been upgraded, replace them with a similar plugins that are v3-compatible, wait for the plugins to be upgraded before upgrading your app, or even write PRs to help the authors upgrade them.
## Upgrading manually
After upgrading the dependency via Composer, you should execute `php artisan filament:upgrade` in order to clear any Laravel caches and publish the new frontend assets.
### High-impact changes
#### Config file renamed and combined with other Filament packages
Only one config file is now used for all Filament packages. Most configuration has been moved into other parts of the codebase, and little remains. You should use the v3 documentation as a reference when replace the configuration options you did modify. To publish the new configuration file and remove the old one, run:
```bash
php artisan vendor:publish --tag=filament-config --force
rm config/tables.php
```
#### `TABLES_FILESYSTEM_DRIVER` .env variable
The `TABLES_FILESYSTEM_DRIVER` .env variable has been renamed to `FILAMENT_FILESYSTEM_DISK`. This is to make it more consistent with Laravel, as Laravel v9 introduced this change as well. Please ensure that you update your .env files accordingly, and don't forget production!
#### New `@filamentScripts` and `@filamentStyles` Blade directives
The `@filamentScripts` and `@filamentStyles` Blade directives must be added to your Blade layout file/s. Since Livewire v3 no longer uses similar directives, you can replace `@livewireScripts` with `@filamentScripts` and `@livewireStyles` with `@filamentStyles`.
#### CSS file removed
The CSS file for form components, `module.esm.css`, has been removed. Check `resources/css/app.css`. That CSS is now automatically loaded by `@filamentStyles`.
#### JavaScript files removed
You no longer need to import the `FormsAlpinePlugin` in your JavaScript files. Alpine plugins are now automatically loaded by `@filamentScripts`.
#### Heroicons have been updated to v2
The Heroicons library has been updated to v2. This means that any icons you use in your app may have changed names. You can find a list of changes [here](https://github.com/tailwindlabs/heroicons/releases/tag/v2.0.0).
### Medium-impact changes
#### Secondary color
Filament v2 had a `secondary` color for many components which was gray. All references to `secondary` should be replaced with `gray` to preserve the same appearance. This frees `secondary` to be registered to a new custom color of your choice.
#### `BadgeColumn::enum()` removed
You can use a `formatStateUsing()` function to transform text.
#### Enum classes moved
The following enum classes have moved:
- `Filament\Tables\Actions\Position` has moved to `Filament\Tables\Enums\ActionsPosition`.
- `Filament\Tables\Actions\RecordCheckboxPosition` has moved to `Filament\Tables\Enums\RecordCheckboxPosition`.
- `Filament\Tables\Filters\Layout` has moved to `Filament\Tables\Enums\FiltersLayout`.