🆙 Add cms i using 🆙

This commit is contained in:
Remco
2025-11-25 22:42:56 +01:00
parent 94704e0925
commit d44196149e
35591 changed files with 3601123 additions and 0 deletions
@@ -0,0 +1,17 @@
<?php
namespace Filament\Tables\Concerns;
/**
* @deprecated Override the `table()` method to configure the table.
*/
trait CanBeStriped
{
/**
* @deprecated Override the `table()` method to configure the table.
*/
protected function isTableStriped(): bool
{
return false;
}
}
@@ -0,0 +1,30 @@
<?php
namespace Filament\Tables\Concerns;
trait CanDeferLoading
{
public bool $isTableLoaded = false;
/**
* @deprecated Override the `table()` method to configure the table.
*/
public function isTableLoadingDeferred(): bool
{
return false;
}
public function loadTable(): void
{
$this->isTableLoaded = true;
}
public function isTableLoaded(): bool
{
if (! $this->getTable()->isLoadingDeferred()) {
return true;
}
return $this->isTableLoaded;
}
}
@@ -0,0 +1,68 @@
<?php
namespace Filament\Tables\Concerns;
use Filament\Tables\Grouping\Group;
use Illuminate\Database\Eloquent\Builder;
trait CanGroupRecords
{
public ?string $tableGrouping = null;
public function getTableGrouping(): ?Group
{
if ($this->isTableReordering()) {
return null;
}
if (
filled($this->tableGrouping) &&
($group = $this->getTable()->getGroup((string) str($this->tableGrouping)->before(':')))
) {
return $group;
}
if ($this->getTable()->isDefaultGroupSelectable()) {
return null;
}
return $this->getTable()->getDefaultGroup();
}
public function updatedTableGroupColumn(): void
{
$this->resetPage();
}
public function getTableGroupingDirection(): ?string
{
if (blank($this->tableGrouping)) {
return null;
}
if (! str($this->tableGrouping)->contains(':')) {
return 'asc';
}
return match ((string) str($this->tableGrouping)->after(':')) {
'asc' => 'asc',
'desc' => 'desc',
default => null,
};
}
protected function applyGroupingToTableQuery(Builder $query): Builder
{
$group = $this->getTableGrouping();
if (! $group) {
return $query;
}
$group->applyEagerLoading($query);
$group->orderQuery($query, $this->getTableGroupingDirection() ?? 'asc');
return $query;
}
}
@@ -0,0 +1,118 @@
<?php
namespace Filament\Tables\Concerns;
use Filament\Tables\Enums\PaginationMode;
use Illuminate\Contracts\Pagination\CursorPaginator;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Pagination\LengthAwarePaginator;
trait CanPaginateRecords
{
/**
* @var int | string | null
*/
public $tableRecordsPerPage = null;
protected int | string | null $defaultTableRecordsPerPageSelectOption = null;
public function updatedTableRecordsPerPage(): void
{
session()->put([
$this->getTablePerPageSessionKey() => $this->getTableRecordsPerPage(),
]);
$this->resetPage();
}
protected function paginateTableQuery(Builder $query): Paginator | CursorPaginator
{
$perPage = $this->getTableRecordsPerPage();
$mode = $this->getTable()->getPaginationMode();
if ($mode === PaginationMode::Simple) {
return $query->simplePaginate(
perPage: ($perPage === 'all') ? $query->toBase()->getCountForPagination() : $perPage,
pageName: $this->getTablePaginationPageName(),
);
}
if ($mode === PaginationMode::Cursor) {
return $query->cursorPaginate(
perPage: ($perPage === 'all') ? $query->toBase()->getCountForPagination() : $perPage,
cursorName: $this->getTablePaginationPageName(),
);
}
$total = $query->toBase()->getCountForPagination();
/** @var LengthAwarePaginator $records */
$records = $query->paginate(
perPage: ($perPage === 'all') ? $total : $perPage,
pageName: $this->getTablePaginationPageName(),
total: $total,
);
return $records->onEachSide(0);
}
public function getTableRecordsPerPage(): int | string | null
{
return $this->tableRecordsPerPage;
}
public function getTablePage(): int | string
{
return $this->getPage($this->getTablePaginationPageName());
}
public function getDefaultTableRecordsPerPageSelectOption(): int | string
{
$option = session()->get(
$this->getTablePerPageSessionKey(),
$this->defaultTableRecordsPerPageSelectOption ?? $this->getTable()->getDefaultPaginationPageOption(),
);
$pageOptions = $this->getTable()->getPaginationPageOptions();
if (in_array($option, $pageOptions)) {
return $option;
}
session()->remove($this->getTablePerPageSessionKey());
return $pageOptions[0];
}
public function getTablePaginationPageName(): string
{
return $this->getIdentifiedTableQueryStringPropertyNameFor('page');
}
public function getTablePerPageSessionKey(): string
{
$table = md5($this::class);
return "tables.{$table}_per_page";
}
/**
* @deprecated Override the `table()` method to configure the table.
*
* @return array<int | string> | null
*/
protected function getTableRecordsPerPageSelectOptions(): ?array
{
return null;
}
/**
* @deprecated Override the `table()` method to configure the table.
*/
protected function isTablePaginationEnabled(): bool
{
return true;
}
}
@@ -0,0 +1,17 @@
<?php
namespace Filament\Tables\Concerns;
/**
* @deprecated Override the `table()` method to configure the table.
*/
trait CanPollRecords
{
/**
* @deprecated Override the `table()` method to configure the table.
*/
protected function getTablePollingInterval(): ?string
{
return null;
}
}
@@ -0,0 +1,82 @@
<?php
namespace Filament\Tables\Concerns;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Query\Expression;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
trait CanReorderRecords
{
public bool $isTableReordering = false;
/**
* @param array<int | string> $order
*/
public function reorderTable(array $order, int | string | null $draggedRecordKey = null): void
{
if (! $this->getTable()->isReorderable()) {
return;
}
$orderColumn = (string) str($this->getTable()->getReorderColumn())->afterLast('.');
DB::transaction(function () use ($order, $orderColumn): void {
if (
(($relationship = $this->getTable()->getRelationship()) instanceof BelongsToMany) &&
in_array($orderColumn, $relationship->getPivotColumns())
) {
foreach ($order as $index => $recordKey) {
$this->getTableRecord($recordKey)->getRelationValue($relationship->getPivotAccessor())->update([
$orderColumn => $index + 1,
]);
}
return;
}
$model = app($this->getTable()->getModel());
$modelKeyName = $model->getKeyName();
$wrappedModelKeyName = $model->getConnection()?->getQueryGrammar()?->wrap($modelKeyName) ?? $modelKeyName;
$model
->newModelQuery()
->whereIn($modelKeyName, array_values($order))
->update([
$orderColumn => new Expression(
'case ' . collect($order)
->when($this->getTable()->getReorderDirection() === 'desc', fn (Collection $order) => $order->reverse()->values())
->map(fn ($recordKey, int $recordIndex): string => 'when ' . $wrappedModelKeyName . ' = ' . DB::getPdo()->quote($recordKey) . ' then ' . ($recordIndex + 1))
->implode(' ') . ' end'
),
]);
});
}
public function toggleTableReordering(): void
{
$this->isTableReordering = ! $this->isTableReordering;
}
public function isTableReordering(): bool
{
return $this->getTable()->isReorderable() && $this->isTableReordering;
}
/**
* @deprecated Override the `table()` method to configure the table.
*/
protected function isTablePaginationEnabledWhileReordering(): bool
{
return false;
}
/**
* @deprecated Override the `table()` method to configure the table.
*/
protected function getTableReorderColumn(): ?string
{
return null;
}
}
@@ -0,0 +1,352 @@
<?php
namespace Filament\Tables\Concerns;
use Filament\Tables\Filters\Indicator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use RecursiveArrayIterator;
use RecursiveIteratorIterator;
trait CanSearchRecords
{
/**
* @var array<string, string | array<string, string | null> | null>
*/
public array $tableColumnSearches = [];
/**
* @var ?string
*/
public $tableSearch = '';
public function updatedTableSearch(): void
{
if ($this->getTable()->persistsSearchInSession()) {
session()->put(
$this->getTableSearchSessionKey(),
$this->tableSearch,
);
}
if ($this->getTable()->shouldDeselectAllRecordsWhenFiltered()) {
$this->deselectAllTableRecords();
}
$this->resetPage();
}
/**
* @param string | null $value
*/
public function updatedTableColumnSearches($value = null, ?string $key = null): void
{
if (blank($value) && filled($key)) {
Arr::forget($this->tableColumnSearches, $key);
}
if ($this->getTable()->persistsColumnSearchesInSession()) {
session()->put(
$this->getTableColumnSearchesSessionKey(),
$this->tableColumnSearches,
);
}
if ($this->getTable()->shouldDeselectAllRecordsWhenFiltered()) {
$this->deselectAllTableRecords();
}
$this->resetPage();
}
protected function applySearchToTableQuery(Builder $query): Builder
{
$this->applyColumnSearchesToTableQuery($query);
$this->applyGlobalSearchToTableQuery($query);
return $query;
}
protected function applyColumnSearchesToTableQuery(Builder $query): Builder
{
$table = $this->getTable();
$shouldSplitSearchTerms = $table->shouldSplitSearchTerms();
foreach ($this->getTableColumnSearches() as $column => $search) {
if (blank($search)) {
continue;
}
$column = $table->getColumn($column);
if (! $column) {
continue;
}
if ($column->isHidden()) {
continue;
}
if (! $column->isIndividuallySearchable()) {
continue;
}
if (! $shouldSplitSearchTerms) {
$isFirst = true;
$column->applySearchConstraint(
$query,
$search,
$isFirst,
);
continue;
}
foreach ($this->extractTableSearchWords($search) as $searchWord) {
$query->where(function (Builder $query) use ($column, $searchWord): void {
$isFirst = true;
$column->applySearchConstraint(
$query,
$searchWord,
$isFirst,
);
});
}
}
return $query;
}
/**
* @return array<string>
*/
protected function extractTableSearchWords(string $search): array
{
return array_filter(
str_getcsv(preg_replace('/\s+/', ' ', $search), separator: ' ', escape: '\\'),
fn ($word): bool => filled($word),
);
}
protected function applyGlobalSearchToTableQuery(Builder $query): Builder
{
$search = $this->getTableSearch();
if (blank($search)) {
return $query;
}
if ($this->getTable()->hasSearchUsingCallback()) {
$this->getTable()->callSearchUsing($query, $search);
return $query;
}
if (! $this->getTable()->shouldSplitSearchTerms()) {
$query->where(function (Builder $query) use ($search): void {
$isFirst = true;
foreach ($this->getTable()->getColumns() as $column) {
if ($column->isHidden()) {
continue;
}
if (! $column->isGloballySearchable()) {
continue;
}
$column->applySearchConstraint(
$query,
$search,
$isFirst,
);
}
$this->getTable()->applyExtraSearchConstraints($query, $search, $isFirst);
});
return $query;
}
foreach ($this->extractTableSearchWords($search) as $searchWord) {
$query->where(function (Builder $query) use ($searchWord): void {
$isFirst = true;
foreach ($this->getTable()->getColumns() as $column) {
if ($column->isHidden()) {
continue;
}
if (! $column->isGloballySearchable()) {
continue;
}
$column->applySearchConstraint(
$query,
$searchWord,
$isFirst,
);
}
$this->getTable()->applyExtraSearchConstraints($query, $searchWord, $isFirst);
});
}
return $query;
}
public function getTableSearch(): ?string
{
return filled($this->tableSearch) ? trim(strval($this->tableSearch)) : null;
}
public function hasTableSearch(): bool
{
return filled($this->tableSearch);
}
public function resetTableSearch(): void
{
$this->tableSearch = '';
$this->updatedTableSearch();
}
public function resetTableColumnSearch(string $column): void
{
$this->updatedTableColumnSearches(null, $column);
}
public function resetTableColumnSearches(): void
{
$this->tableColumnSearches = [];
$this->updatedTableColumnSearches();
}
public function getTableSearchIndicator(): Indicator
{
return Indicator::make(__('filament-tables::table.fields.search.indicator') . ': ' . $this->getTableSearch())
->removeLivewireClickHandler('resetTableSearch');
}
/**
* @return array<Indicator>
*/
public function getTableColumnSearchIndicators(): array
{
$indicators = [];
foreach ($this->getTable()->getColumns() as $column) {
if ($column->isHidden()) {
continue;
}
if (! $column->isIndividuallySearchable()) {
continue;
}
$columnName = $column->getName();
$search = Arr::get($this->tableColumnSearches, $columnName);
if (blank($search)) {
continue;
}
$indicators[] = Indicator::make("{$column->getLabel()}: {$search}")
->removeLivewireClickHandler("resetTableColumnSearch('{$columnName}')");
}
return $indicators;
}
/**
* @param array<string, string | array<string, string | null> | null> $searches
* @return array<string, string | array<string, string | null> | null>
*/
protected function castTableColumnSearches(array $searches): array
{
return array_map(
fn ($search): array | string => is_array($search) ?
$this->castTableColumnSearches($search) :
strval($search),
$searches,
);
}
/**
* @return array<string, string | null>
*/
public function getTableColumnSearches(): array
{
// Example input of `$this->tableColumnSearches`:
// [
// 'number' => '12345 ',
// 'customer' => [
// 'name' => ' john Smith',
// ],
// ]
// The `$this->tableColumnSearches` array is potentially nested.
// So, we iterate through it deeply:
$iterator = new RecursiveIteratorIterator(
new RecursiveArrayIterator($this->tableColumnSearches), /** @phpstan-ignore argument.type */
RecursiveIteratorIterator::SELF_FIRST
);
$searches = [];
$path = [];
foreach ($iterator as $key => $value) {
$path[$iterator->getDepth()] = $key;
if (is_array($value)) {
continue;
}
// Nested array keys are flattened into `dot.syntax`.
$searches[
implode('.', array_slice($path, 0, $iterator->getDepth() + 1))
] = trim(strval($value));
}
return $searches;
// Example output:
// [
// 'number' => '12345',
// 'customer.name' => 'john smith',
// ]
}
public function getTableSearchSessionKey(): string
{
$table = md5($this::class);
return "tables.{$table}_search";
}
public function getTableColumnSearchesSessionKey(): string
{
$table = md5($this::class);
return "tables.{$table}_column_search";
}
/**
* @deprecated Override the `table()` method to configure the table.
*/
protected function shouldPersistTableSearchInSession(): bool
{
return false;
}
/**
* @deprecated Override the `table()` method to configure the table.
*/
protected function shouldPersistTableColumnSearchInSession(): bool
{
return false;
}
}
@@ -0,0 +1,169 @@
<?php
namespace Filament\Tables\Concerns;
use Illuminate\Database\Eloquent\Builder;
trait CanSortRecords
{
public ?string $tableSort = null;
public function sortTable(?string $column = null, ?string $direction = null): void
{
if ($column === $this->getTableSortColumn()) {
$direction ??= match ($this->getTableSortDirection()) {
'asc' => 'desc',
'desc' => null,
default => 'asc',
};
} else {
$direction ??= 'asc';
}
$this->tableSort = $direction ? "{$column}:{$direction}" : null;
$this->updatedTableSort();
}
public function getTableSortColumn(): ?string
{
if (blank($this->tableSort)) {
return null;
}
return (string) str($this->tableSort)->before(':');
}
public function getTableSortDirection(): ?string
{
if (blank($this->tableSort)) {
return null;
}
if (! str($this->tableSort)->contains(':')) {
return 'asc';
}
return match ((string) str($this->tableSort)->after(':')) {
'asc' => 'asc',
'desc' => 'desc',
default => null,
};
}
public function updatedTableSort(): void
{
if ($this->getTable()->persistsSortInSession()) {
session()->put(
$this->getTableSortSessionKey(),
$this->tableSort,
);
}
$this->resetPage();
}
public function updatedTableSortDirection(): void
{
if ($this->getTable()->persistsSortInSession()) {
session()->put(
$this->getTableSortSessionKey(),
$this->tableSort,
);
}
$this->resetPage();
}
protected function applySortingToTableQuery(Builder $query): Builder
{
if ($this->getTable()->isGroupsOnly()) {
return $query;
}
if ($this->isTableReordering()) {
return $query->orderBy($this->getTable()->getReorderColumn(), $this->getTable()->getReorderDirection());
}
if (
$this->getTableSortColumn() &&
$column = $this->getTable()->getSortableVisibleColumn($this->getTableSortColumn())
) {
$sortDirection = $this->getTableSortDirection() === 'desc' ? 'desc' : 'asc';
$column->applySort($query, $sortDirection);
}
$sortDirection = ($this->getTable()->getDefaultSortDirection() ?? $this->getTableSortDirection()) === 'desc' ? 'desc' : 'asc';
$defaultSort = $this->getTable()->getDefaultSort($query, $sortDirection);
if (
is_string($defaultSort) &&
($defaultSort !== $this->getTableSortColumn()) &&
($sortColumn = $this->getTable()->getSortableVisibleColumn($defaultSort))
) {
$sortColumn->applySort($query, $sortDirection);
} elseif (is_string($defaultSort)) {
$query->orderBy($defaultSort, $sortDirection);
}
if ($defaultSort instanceof Builder) {
$query = $defaultSort;
}
if (! $this->getTable()->hasDefaultKeySort()) {
return $query;
}
$qualifiedKeyName = $query->getModel()->getQualifiedKeyName();
foreach ($query->getQuery()->orders ?? [] as $order) {
if (($order['column'] ?? null) === $qualifiedKeyName) {
return $query;
}
if (
is_string($order['column'] ?? null) &&
str($order['column'] ?? null)->contains('.') &&
str($order['column'] ?? null)->afterLast('.')->is(
str($qualifiedKeyName)->afterLast('.')
)
) {
return $query;
}
}
return $query->orderBy($qualifiedKeyName, $sortDirection);
}
/**
* @deprecated Override the `table()` method to configure the table.
*/
protected function getDefaultTableSortColumn(): ?string
{
return null;
}
/**
* @deprecated Override the `table()` method to configure the table.
*/
protected function getDefaultTableSortDirection(): ?string
{
return null;
}
public function getTableSortSessionKey(): string
{
$table = md5($this::class);
return "tables.{$table}_sort";
}
/**
* @deprecated Override the `table()` method to configure the table.
*/
protected function shouldPersistTableSortInSession(): bool
{
return false;
}
}
@@ -0,0 +1,119 @@
<?php
namespace Filament\Tables\Concerns;
use Closure;
use Filament\Support\Services\RelationshipJoiner;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\Expression;
use Illuminate\Support\Str;
use stdClass;
trait CanSummarizeRecords
{
public function getAllTableSummaryQuery(): ?Builder
{
return $this->getFilteredTableQuery();
}
public function getPageTableSummaryQuery(): ?Builder
{
return $this->getFilteredSortedTableQuery()?->forPage(
page: $this->getTableRecords()->currentPage(),
perPage: $this->getTableRecords()->perPage(),
);
}
/**
* @return array<string, mixed>
*/
public function getTableSummarySelectedState(?Builder $query = null, ?Closure $modifyQueryUsing = null): array
{
if (! $query) {
return [];
}
$selects = [];
foreach ($this->getTable()->getVisibleColumns() as $column) {
$summarizers = $column->getSummarizers($query);
if (! count($summarizers)) {
continue;
}
if ($column->hasRelationship($query->getModel())) {
continue;
}
$qualifiedAttribute = $query->getModel()->qualifyColumn($column->getName());
foreach ($summarizers as $summarizer) {
if ($summarizer->hasQueryModification()) {
continue;
}
$selectStatements = $summarizer
->query($query)
->getSelectStatements($qualifiedAttribute);
foreach ($selectStatements as $alias => $statement) {
$selects[] = "{$statement} as \"{$alias}\"";
}
}
}
if (! count($selects)) {
return [];
}
$queryToJoin = $query->clone();
$joins = [];
$query = $query->getModel()->resolveConnection($query->getModel()->getConnectionName())
->table($query->toBase(), $query->getModel()->getTable());
if ($modifyQueryUsing) {
$query = $modifyQueryUsing($query) ?? $query;
}
$group = $query->groups[0] ?? null;
$groupSelectAlias = null;
if ($group !== null) {
$groupSelectAlias = Str::random();
if ($group instanceof Expression) {
$group = $group->getValue($query->getGrammar());
}
$selects[] = "{$group} as \"{$groupSelectAlias}\"";
if (filled($groupingRelationshipName = $this->getTableGrouping()?->getRelationshipName())) {
$joins = app(RelationshipJoiner::class)->getLeftJoinsForRelationship(
query: $queryToJoin,
relationship: $groupingRelationshipName,
);
}
}
$query->joins = [
...($query->joins ?? []),
...$joins,
];
return $query
->selectRaw(implode(', ', $selects))
->get()
->mapWithKeys(function (stdClass $state, $key) use ($groupSelectAlias): array {
if ($groupSelectAlias !== null) {
$key = $state->{$groupSelectAlias};
unset($state->{$groupSelectAlias});
}
return [$key => (array) $state];
})
->all();
}
}
@@ -0,0 +1,113 @@
<?php
namespace Filament\Tables\Concerns;
use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Schemas\Schema;
use Illuminate\Database\Eloquent\Model;
trait HasActions
{
/**
* @deprecated Use the `callMountedAction()` method instead.
*
* @param array<string, mixed> $arguments
*/
public function callMountedTableAction(array $arguments = []): mixed
{
return $this->callMountedAction($arguments);
}
/**
* @deprecated Use `mountAction()` instead.
*
* @param array<string, mixed> $arguments
*/
public function mountTableAction(string $name, ?string $record = null, array $arguments = []): mixed
{
return $this->mountAction($name, $arguments, context: [
'table' => true,
'recordKey' => $record,
]);
}
/**
* @deprecated Use `mountAction()` instead.
*
* @param array<string, mixed> $arguments
*/
public function replaceMountedTableAction(string $name, ?string $record = null, array $arguments = []): void
{
$this->mountAction($name, $arguments, context: [
'table' => true,
'recordKey' => $record,
]);
}
/**
* @deprecated Use `mountedActionShouldOpenModal()` instead.
*/
public function mountedTableActionShouldOpenModal(?Action $mountedAction = null): bool
{
return $this->mountedActionShouldOpenModal($mountedAction);
}
/**
* @deprecated Use `mountedActionHasSchema()` instead.
*/
public function mountedTableActionHasForm(?Action $mountedAction = null): bool
{
return $this->mountedActionHasSchema($mountedAction);
}
/**
* @deprecated Use `getMountedAction()` instead.
*/
public function getMountedTableAction(): ?Action
{
return $this->getMountedAction();
}
/**
* @deprecated Use `getMountedActionSchema()` instead.
*/
public function getMountedTableActionForm(?Action $mountedAction = null): ?Schema
{
return $this->getMountedActionSchema(0, $mountedAction);
}
/**
* @deprecated Use `getMountedAction()?->getRecord()` instead.
*/
public function getMountedTableActionRecord(): ?Model
{
return $this->getMountedAction()?->getRecord();
}
/**
* @deprecated Use `unmountAction()` instead.
*/
public function unmountTableAction(bool $shouldCancelParentActions = true): void
{
$this->unmountAction($shouldCancelParentActions);
}
/**
* @deprecated Override the `table()` method to configure the table.
*
* @return array<Action | ActionGroup>
*/
protected function getTableActions(): array
{
return [];
}
/**
* @deprecated Override the `table()` method to configure the table.
*/
protected function getTableActionsColumnLabel(): ?string
{
return null;
}
}
@@ -0,0 +1,359 @@
<?php
namespace Filament\Tables\Concerns;
use Filament\Actions\Action;
use Filament\Actions\BulkAction;
use Filament\Schemas\Schema;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\LazyCollection;
use LogicException;
use function Livewire\invade;
trait HasBulkActions
{
/**
* @var array<int | string>
*/
public array $selectedTableRecords = [];
/**
* @var array<int | string>
*/
public array $deselectedTableRecords = [];
public bool $isTrackingDeselectedTableRecords = false;
protected EloquentCollection | Collection | LazyCollection $cachedSelectedTableRecords;
/**
* @deprecated Use the `callMountedAction()` method instead.
*
* @param array<string, mixed> $arguments
*/
public function callMountedTableBulkAction(array $arguments = []): mixed
{
return $this->callMountedAction($arguments);
}
/**
* @deprecated Use the `mountAction()` method instead.
*
* @param array<int | string> | null $selectedRecords
*/
public function mountTableBulkAction(string $name, ?array $selectedRecords = null): mixed
{
if ($selectedRecords !== null) {
$this->selectedTableRecords = $selectedRecords;
}
return $this->mountAction($name, context: ['table' => true, 'bulk' => true]);
}
/**
* @deprecated Use the `replaceMountedAction()` method instead.
*
* @param array<int | string> | null $selectedRecords
*/
public function replaceMountedTableBulkAction(string $name, ?array $selectedRecords = null): void
{
if ($selectedRecords !== null) {
$this->selectedTableRecords = $selectedRecords;
}
$this->replaceMountedAction($name, context: ['table' => true, 'bulk' => true]);
}
/**
* @deprecated Use the `mountedActionShouldOpenModal()` method instead.
*/
public function mountedTableBulkActionShouldOpenModal(?BulkAction $mountedBulkAction = null): bool
{
return $this->mountedActionShouldOpenModal($mountedBulkAction);
}
/**
* @deprecated Use the `mountedActionHasSchema()` method instead.
*/
public function mountedTableBulkActionHasForm(?BulkAction $mountedBulkAction = null): bool
{
return $this->mountedActionHasSchema($mountedBulkAction);
}
public function deselectAllTableRecords(): void
{
$this->dispatch('deselectAllTableRecords')->self();
}
/**
* @return array<string>
*/
public function getAllSelectableTableRecordKeys(): array
{
$query = $this->getFilteredTableQuery();
if (! $this->getTable()->checksIfRecordIsSelectable()) {
if (! $this->getTable()->hasQuery()) {
/** @phpstan-ignore-next-line */
return $this->getTableRecords()->keys()->all();
}
$records = $this->getTable()->selectsCurrentPageOnly() ?
$this->getTableRecords()->pluck($query->getModel()->getKeyName()) :
$query->toBase()->pluck($query->getModel()->getQualifiedKeyName());
/** @phpstan-ignore-next-line */
return $records->map(fn ($key): string => (string) $key)->all();
}
$records = $this->getTable()->selectsCurrentPageOnly() ?
$this->getTableRecords() :
$query->get();
return $records->reduce(
function (array $carry, Model | array $record, string $key): array {
if (! $this->getTable()->isRecordSelectable($record)) {
return $carry;
}
$carry[] = ($record instanceof Model) ? ((string) $record->getKey()) : $key;
return $carry;
},
initial: [],
);
}
/**
* @return array<string>
*/
public function getGroupedSelectableTableRecordKeys(?string $group): array
{
$query = $this->getFilteredTableQuery();
$tableGrouping = $this->getTableGrouping();
$tableGrouping->scopeQueryByKey($query, $group);
if (! $this->getTable()->checksIfRecordIsSelectable()) {
$records = $this->getTable()->selectsCurrentPageOnly() ?
/** @phpstan-ignore-next-line */
$this->getTableRecords()
->filter(fn (Model $record): bool => $tableGrouping->getStringKey($record) === $group)
->pluck($query->getModel()->getKeyName()) :
$query->toBase()->pluck($query->getModel()->getQualifiedKeyName());
return $records
->map(fn ($key): string => (string) $key)
->all();
}
$records = $this->getTable()->selectsCurrentPageOnly() ?
$this->getTableRecords()->filter(
fn (Model $record) => $tableGrouping->getStringKey($record) === $group,
) :
$query->get();
return $records->reduce(
function (array $carry, Model $record): array {
if (! $this->getTable()->isRecordSelectable($record)) {
return $carry;
}
$carry[] = (string) $record->getKey();
return $carry;
},
initial: [],
);
}
public function getAllSelectableTableRecordsCount(): int
{
if ($this->getTable()->checksIfRecordIsSelectable()) {
/** @var Collection $records */
$records = $this->getTable()->selectsCurrentPageOnly() ?
$this->getTableRecords() :
$this->getFilteredTableQuery()->get();
return $records
->filter(fn (Model | array $record): bool => $this->getTable()->isRecordSelectable($record))
->count();
}
if ($this->getTable()->selectsCurrentPageOnly()) {
return $this->cachedTableRecords->count();
}
if ($this->cachedTableRecords instanceof LengthAwarePaginator) {
return $this->cachedTableRecords->total();
}
return $this->getFilteredTableQuery()?->count() ?? $this->cachedTableRecords->count();
}
public function getSelectedTableRecords(bool $shouldFetchSelectedRecords = true, ?int $chunkSize = null): EloquentCollection | Collection | LazyCollection
{
if (isset($this->cachedSelectedTableRecords)) {
return $this->cachedSelectedTableRecords;
}
$table = $this->getTable();
if (! $table->hasQuery()) {
$resolveSelectedRecords = $table->getResolveSelectedRecordsCallback();
$resolvedSelectedRecords = $resolveSelectedRecords ?
$table->evaluate($resolveSelectedRecords, [
'keys' => $this->selectedTableRecords,
'records' => $this->selectedTableRecords,
'deselectedKeys' => $this->deselectedTableRecords,
'deselectedRecords' => $this->deselectedTableRecords,
'isTrackingDeselectedKeys' => $this->isTrackingDeselectedTableRecords,
'isTrackingDeselectedRecords' => $this->isTrackingDeselectedTableRecords,
]) :
($this->isTrackingDeselectedTableRecords ? $this->getTableRecords()->except($this->deselectedTableRecords) : $this->getTableRecords()->only($this->selectedTableRecords));
$maxSelectableRecords = $table->getMaxSelectableRecords();
if ($maxSelectableRecords && ($resolvedSelectedRecords->count() > $maxSelectableRecords)) {
throw new LogicException("The total count of selected records [{$resolvedSelectedRecords->count()}] must not exceed the maximum selectable records limit [{$maxSelectableRecords}].");
}
return $this->cachedSelectedTableRecords = $resolvedSelectedRecords;
}
$query = $this->getSelectedTableRecordsQuery($shouldFetchSelectedRecords, $chunkSize);
if (! $chunkSize) {
$this->applySortingToTableQuery($query);
}
if (! $shouldFetchSelectedRecords) {
return $this->cachedSelectedTableRecords = $query->toBase()->pluck($query->getModel()->getQualifiedKeyName());
}
if ($chunkSize && $table->getRelationship() instanceof BelongsToMany && ! $table->allowsDuplicates()) {
$invadedRelationship = invade($table->getRelationship());
return $this->cachedSelectedTableRecords = $query->lazyById($chunkSize)
->tapEach(fn (Model $record) => $invadedRelationship->hydratePivotRelation([$record]));
}
if ($chunkSize) {
return $this->cachedSelectedTableRecords = $query->lazyById($chunkSize);
}
return $this->cachedSelectedTableRecords = $this->hydratePivotRelationForTableRecords($query->get());
}
public function getSelectedTableRecordsQuery(bool $shouldFetchSelectedRecords = true, ?int $chunkSize = null): Builder
{
$table = $this->getTable();
$maxSelectableRecords = $table->getMaxSelectableRecords();
if (! ($table->getRelationship() instanceof BelongsToMany && $table->allowsDuplicates())) {
if ($this->isTrackingDeselectedTableRecords) {
$query = $table->getQuery()->whereKeyNot($this->deselectedTableRecords);
} else {
$query = $table->getQuery()->whereKey($this->selectedTableRecords);
}
if ($maxSelectableRecords) {
$query->limit($maxSelectableRecords);
}
if (! $chunkSize) {
$this->applySortingToTableQuery($query);
}
if ($shouldFetchSelectedRecords) {
foreach ($this->getTable()->getColumns() as $column) {
$column->applyEagerLoading($query);
$column->applyRelationshipAggregates($query);
}
}
if ($table->shouldDeselectAllRecordsWhenFiltered()) {
$this->filterTableQuery($query);
}
return $query;
}
/** @var BelongsToMany $relationship */
$relationship = $table->getRelationship();
$pivotClass = $relationship->getPivotClass();
$pivotKeyName = app($pivotClass)->getKeyName();
if ($this->isTrackingDeselectedTableRecords) {
$relationship->wherePivotNotIn($pivotKeyName, $this->deselectedTableRecords);
} else {
$relationship->wherePivotIn($pivotKeyName, $this->selectedTableRecords);
}
if ($maxSelectableRecords) {
$relationship->limit($maxSelectableRecords);
}
if ($shouldFetchSelectedRecords) {
foreach ($this->getTable()->getColumns() as $column) {
$column->applyEagerLoading($relationship);
$column->applyRelationshipAggregates($relationship);
}
}
$relationship = $table->selectPivotDataInQuery($relationship);
return $relationship->getQuery();
}
/**
* @deprecated Override the `table()` method to configure the table.
*/
public function shouldSelectCurrentPageOnly(): bool
{
return false;
}
/**
* @deprecated Override the `table()` method to configure the table.
*/
public function shouldDeselectAllRecordsWhenTableFiltered(): bool
{
return true;
}
/**
* @deprecated Use the `getMountedAction()` method instead.
*/
public function getMountedTableBulkAction(): ?Action
{
return $this->getMountedAction();
}
/**
* @deprecated Use the `getMountedActionSchema()` method instead.
*/
public function getMountedTableBulkActionForm(?BulkAction $mountedBulkAction = null): ?Schema
{
return $this->getMountedActionSchema(0, $mountedBulkAction);
}
/**
* @deprecated Override the `table()` method to configure the table.
*
* @return array<BulkAction>
*/
protected function getTableBulkActions(): array
{
return [];
}
}
@@ -0,0 +1,485 @@
<?php
namespace Filament\Tables\Concerns;
use Filament\Schemas\Schema;
use Filament\Support\Components\Component;
use Filament\Tables\Columns\Column;
use Filament\Tables\Columns\ColumnGroup;
use LogicException;
/**
* @property-read Schema $toggleTableColumnForm
*/
trait HasColumnManager
{
public const TABLE_COLUMN_MANAGER_GROUP_TYPE = 'group';
public const TABLE_COLUMN_MANAGER_COLUMN_TYPE = 'column';
/**
* @var array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool, columns?: array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool}>}>
*/
public array $tableColumns = [];
/**
* @var ?array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool,columns?: array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool}>}>
*/
protected ?array $cachedDefaultTableColumnState = null;
protected ?bool $hasReorderableTableColumns = null;
public function initTableColumnManager(): void
{
if ($this->getTable()->hasColumnsLayout()) {
return;
}
if (blank($this->tableColumns)) {
$this->tableColumns = $this->loadTableColumnsFromSession();
}
$this->applyTableColumnManager();
}
/**
* @return array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool, columns?: array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool}>}>
*/
public function getDefaultTableColumnState(): array
{
return $this->cachedDefaultTableColumnState ??= collect($this->getTable()->getColumnsLayout())
->map(fn (Component $component): ?array => match (true) {
$component instanceof ColumnGroup => $this->mapTableColumnGroupToArray($component),
$component instanceof Column => $this->mapTableColumnToArray($component),
default => null,
})
->filter()
->values()
->all();
}
/**
* @deprecated Use `applyTableColumnManager()` instead.
*/
public function updatedToggledTableColumns(): void
{
$this->applyTableColumnManager();
}
/**
* @param array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool, columns?: array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool}>}>|null $state
*/
public function applyTableColumnManager(?array $state = null, bool $wasReordered = false): void
{
if (filled($state)) {
$this->tableColumns = $state;
if ($this->hasReorderableTableColumns()) {
$this->persistHasReorderedTableColumns($wasReordered);
}
}
$this->hasReorderableTableColumns() && session()->get($this->getHasReorderedTableColumnsSessionKey())
? $this->syncReorderableColumnsFromDefaultTableColumnState()
: $this->syncStaticColumnsFromTableColumnState();
$this->persistTableColumns();
}
public function resetTableColumnManager(): void
{
$this->tableColumns = $this->getDefaultTableColumnState();
if ($this->hasReorderableTableColumns()) {
$this->updateTableColumns();
$this->persistHasReorderedTableColumns();
}
$this->persistTableColumns();
}
public function isTableColumnToggledHidden(string $name): bool
{
foreach ($this->tableColumns as $item) {
if ($item['type'] === self::TABLE_COLUMN_MANAGER_COLUMN_TYPE && $item['name'] === $name) {
return ! $item['isToggled'];
}
if ($item['type'] === self::TABLE_COLUMN_MANAGER_GROUP_TYPE && isset($item['columns'])) {
foreach ($item['columns'] as $column) {
if ($column['name'] === $name) {
return ! $column['isToggled'];
}
}
}
}
return true;
}
/**
* @deprecated Use `getTableColumnManagerSessionKey()` instead.
*/
protected function getToggledTableColumnsSessionKey(): string
{
return $this->getTableColumnsSessionKey();
}
public function getTableColumnsSessionKey(): string
{
$table = md5($this::class);
return "tables.{$table}_columns";
}
public function getHasReorderedTableColumnsSessionKey(): string
{
$table = md5($this::class);
return "tables.{$table}_has_reordered_columns";
}
/**
* @return array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool, columns?: array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool}>}>
*/
protected function loadTableColumnsFromSession(): array
{
return session()->get(
$this->getTableColumnsSessionKey(),
$this->getDefaultTableColumnState(),
);
}
protected function persistTableColumns(): void
{
session()->put(
$this->getTableColumnsSessionKey(),
$this->tableColumns
);
}
protected function persistHasReorderedTableColumns(bool $wasReordered = false): void
{
session()->put(
$this->getHasReorderedTableColumnsSessionKey(),
$wasReordered || $this->hasReorderedTableColumns()
);
}
/**
* @deprecated Override the `table()` method to configure the table.
*
* @return int | array<string, int | null>
*/
protected function getTableColumnToggleFormColumns(): int | array
{
return 1;
}
/**
* @deprecated Override the `table()` method to configure the table.
*/
protected function getTableColumnToggleFormWidth(): ?string
{
return null;
}
/**
* @deprecated Override the `table()` method to configure the table.
*/
protected function getTableColumnToggleFormMaxHeight(): ?string
{
return null;
}
/**
* @return array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool, columns: array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool}>}
*/
protected function mapTableColumnGroupToArray(ColumnGroup $group): array
{
$label = e($group->getLabel());
return [
'type' => self::TABLE_COLUMN_MANAGER_GROUP_TYPE,
'name' => $label,
'label' => $label,
'isHidden' => empty(array_filter($group->getColumns(), fn (Column $column): bool => ! $column->isHidden())),
'isToggled' => true,
'isToggleable' => true,
'isToggledHiddenByDefault' => null,
'columns' => array_values(
array_map(
fn (Column $column): array => $this->mapTableColumnToArray($column),
$group->getColumns()
)
),
];
}
/**
* @return array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool}
*/
protected function mapTableColumnToArray(Column $column): array
{
$label = e($column->getLabel());
if (blank($label) && $this->hasReorderableTableColumns()) {
throw new LogicException("The table column [{$column->getName()}] has a blank label. All columns must have labels when they are reorderable.");
}
return [
'type' => self::TABLE_COLUMN_MANAGER_COLUMN_TYPE,
'name' => $column->getName(),
'label' => $label,
'isHidden' => $column->isHidden(),
'isToggled' => ! $column->isToggleable() || ! $column->isToggledHiddenByDefault(),
'isToggleable' => $column->isToggleable(),
'isToggledHiddenByDefault' => $column->isToggleable() ? $column->isToggledHiddenByDefault() : null,
];
}
protected function syncReorderableColumnsFromDefaultTableColumnState(): void
{
$defaultColumnState = $this->getDefaultTableColumnState();
$this->tableColumns = collect($this->tableColumns)
->map(fn (array $item) => $this->syncItemFromDefaultTableColumnState($item, $defaultColumnState))
->filter()
->values()
->merge($this->getNewDefaultColumnStateItems($defaultColumnState))
->all();
$this->updateTableColumns();
}
protected function updateTableColumns(): void
{
$reorderedColumns = collect($this->tableColumns)
->map(function (array $item): Column | ColumnGroup | null {
if ($item['type'] === self::TABLE_COLUMN_MANAGER_COLUMN_TYPE) {
return $this->getTable()->getColumn($item['name']);
}
if ($item['type'] !== self::TABLE_COLUMN_MANAGER_GROUP_TYPE || ! isset($item['columns'])) {
return null;
}
$columns = collect($item['columns'])
->map(fn (array $column): ?Column => $this->getTable()->getColumn($column['name']))
->filter()
->all();
if (empty($columns)) {
return null;
}
return $this->getTable()
->getColumnGroup($item['name'])
->columns($columns);
})
->filter()
->all();
$this->getTable()->columns($reorderedColumns);
}
protected function syncStaticColumnsFromTableColumnState(): void
{
$this->tableColumns = collect($this->getDefaultTableColumnState())
->map(fn (array $item) => $this->syncItemFromTableColumnState($item, $this->tableColumns))
->all();
}
/**
* @param array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool, columns?: array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool}>} $item
* @param array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool, columns?: array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool}>}> $defaultColumnState
* @return array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool, columns?: array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool}>}|null
*/
protected function syncItemFromDefaultTableColumnState(array $item, array $defaultColumnState): ?array
{
$matchingItem = $this->findMatchingTableColumnStateItem($item, $defaultColumnState);
if ($matchingItem === null) {
return null;
}
$syncedItem = $this->syncTableColumnStateItemAttributes($item, $matchingItem);
if ($syncedItem['type'] !== self::TABLE_COLUMN_MANAGER_GROUP_TYPE || ! isset($syncedItem['columns'])) {
return $syncedItem;
}
$syncedItem['columns'] = $this->syncGroupFromDefaultTableColumnState(
$syncedItem['columns'],
$matchingItem['columns'] ?? []
);
if (empty($syncedItem['columns'])) {
return null;
}
return $syncedItem;
}
/**
* @param array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool, columns?: array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool}>} $item
* @param array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool, columns?: array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool}>}> $tableColumnState
* @return array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool, columns?: array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool}>}
*/
protected function syncItemFromTableColumnState(array $item, array $tableColumnState): array
{
$matchingItem = $this->findMatchingTableColumnStateItem($item, $tableColumnState);
if ($matchingItem === null) {
return $item;
}
$syncedItem = $this->syncTableColumnStateItemAttributes($matchingItem, $item);
if ($syncedItem['type'] !== self::TABLE_COLUMN_MANAGER_GROUP_TYPE || ! isset($syncedItem['columns'])) {
return $syncedItem;
}
$syncedItem['columns'] = collect($item['columns'])
->map(fn (array $item) => $this->syncItemFromTableColumnState(
$item,
$matchingItem['columns'] ?? []
))
->all();
return $syncedItem;
}
/**
* @param array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool}> $existingColumns
* @param array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool}> $defaultColumns
* @return array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool}>
*/
protected function syncGroupFromDefaultTableColumnState(array $existingColumns, array $defaultColumns): array
{
$updatedColumns = collect($existingColumns)
->map(function (array $column) use ($defaultColumns): ?array {
$matchingDefault = $this->findMatchingTableColumnStateItem($column, $defaultColumns);
if ($matchingDefault === null) {
return null;
}
return $this->syncTableColumnStateItemAttributes($column, $matchingDefault);
})
->filter()
->values();
$existingNames = $updatedColumns
->pluck('name')
->all();
$newColumnsToAdd = collect($defaultColumns)
->reject(fn (array $column) => in_array($column['name'], $existingNames))
->values();
return $updatedColumns
->merge($newColumnsToAdd)
->all();
}
/**
* @param array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool, columns?: array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool}>} $item
* @param array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool, columns?: array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool}>} $default
* @return array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool, columns?: array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool}>}
*/
protected function syncTableColumnStateItemAttributes(array $item, array $default): array
{
$item['label'] = $default['label'];
$item['isToggleable'] = $default['isToggleable'];
$item['isHidden'] = $default['isHidden'];
if (! $default['isToggleable']) {
$item['isToggled'] = true;
}
if ($item['type'] === self::TABLE_COLUMN_MANAGER_COLUMN_TYPE) {
if (
$default['isToggleable'] &&
is_null($item['isToggledHiddenByDefault'] ?? null) &&
is_bool($default['isToggledHiddenByDefault'])
) {
$item['isToggled'] = ! $default['isToggledHiddenByDefault'];
}
$item['isToggledHiddenByDefault'] = $default['isToggleable'] ? $default['isToggledHiddenByDefault'] : null;
}
return $item;
}
/**
* @param array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool, columns?: array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool}>}> $defaultState
* @return array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool, columns?: array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool}>}>
*/
protected function getNewDefaultColumnStateItems(array $defaultState): array
{
$existingKeys = collect($this->tableColumns)
->map(fn (array $item) => $item['type'] . ':' . $item['name'])
->all();
return collect($defaultState)
->reject(fn (array $item) => in_array($item['type'] . ':' . $item['name'], $existingKeys))
->values()
->all();
}
/**
* @param array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool, columns?: array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool}>} $item
* @param array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool, columns?: array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool}>}> $items
* @return array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool, columns?: array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool}>}|null
*/
protected function findMatchingTableColumnStateItem(array $item, array $items): ?array
{
return collect($items)
->first(
fn (array $candidate) => $candidate['type'] === $item['type'] &&
$candidate['name'] === $item['name']
);
}
protected function hasReorderableTableColumns(): bool
{
return $this->hasReorderableTableColumns ??= $this->getTable()->hasReorderableColumns();
}
protected function hasReorderedTableColumns(): bool
{
$flattenedDefaultColumnState = $this->flattenTableColumnStateItems($this->getDefaultTableColumnState());
$flattenedColumnState = $this->flattenTableColumnStateItems($this->tableColumns);
$matchingDefaultColumns = collect($flattenedDefaultColumnState)
->filter(fn (string $key) => in_array($key, $flattenedColumnState))
->values()
->all();
return $flattenedColumnState !== $matchingDefaultColumns;
}
/**
* @param array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool, columns?: array<int, array{type: string, name: string, label: string, isHidden: bool, isToggled: bool, isToggleable: bool, isToggledHiddenByDefault: ?bool}>}> $items
* @return array<int, string>
*/
protected function flattenTableColumnStateItems(array $items): array
{
$flattenedItems = [];
foreach ($items as $item) {
$prefix = $item['type'] . ':' . $item['name'];
$flattenedItems[] = $prefix;
if ($item['type'] === self::TABLE_COLUMN_MANAGER_GROUP_TYPE && isset($item['columns'])) {
foreach ($item['columns'] as $column) {
$flattenedItems[] = $prefix . ':' . $column['name'];
}
}
}
return $flattenedItems;
}
}
@@ -0,0 +1,119 @@
<?php
namespace Filament\Tables\Concerns;
use Closure;
use Filament\Support\Components\Attributes\ExposedLivewireMethod;
use Filament\Tables\Columns\Column;
use Filament\Tables\Columns\Contracts\Editable;
use Filament\Tables\Columns\Layout\Component as ColumnLayoutComponent;
use Illuminate\Validation\ValidationException;
use Livewire\Attributes\Renderless;
use ReflectionMethod;
trait HasColumns
{
public function callTableColumnAction(string $name, string $recordKey): mixed
{
$record = $this->getTableRecord($recordKey);
if (! $record) {
return null;
}
$column = $this->getTable()->getColumn($name);
if (! $column) {
return null;
}
if ($column->isHidden()) {
return null;
}
$action = $column->getAction();
if (! ($action instanceof Closure)) {
return null;
}
return $column->record($record)->evaluate($action);
}
public function updateTableColumnState(string $column, string $record, mixed $input): mixed
{
$column = $this->getTable()->getColumn($column);
if (! ($column instanceof Editable)) {
return null;
}
$record = $this->getTableRecord($record);
if (! $record) {
return null;
}
$column->record($record);
if ($column->isDisabled()) {
return null;
}
try {
$column->validate($input);
} catch (ValidationException $exception) {
return [
'error' => $exception->getMessage(),
];
}
return $column->updateState($input);
}
/**
* @param array<string, mixed> $arguments
*/
public function callTableColumnMethod(string $name, string $recordKey, string $method, array $arguments = []): mixed
{
$column = $this->getTable()->getColumn($name);
if (! $column) {
return null;
}
if (! method_exists($column, $method)) {
return null;
}
$methodReflection = new ReflectionMethod($column, $method);
if (! $methodReflection->getAttributes(ExposedLivewireMethod::class)) {
return null;
}
if ($methodReflection->getAttributes(Renderless::class)) {
$this->skipRender();
}
$record = $this->getTableRecord($recordKey);
if (! $record) {
return null;
}
$column->record($record);
return $column->{$method}(...$arguments);
}
/**
* @deprecated Override the `table()` method to configure the table.
*
* @return array<Column | ColumnLayoutComponent>
*/
protected function getTableColumns(): array
{
return [];
}
}
@@ -0,0 +1,37 @@
<?php
namespace Filament\Tables\Concerns;
use Illuminate\Contracts\View\View;
/**
* @deprecated Override the `table()` method to configure the table.
*/
trait HasContent
{
/**
* @deprecated Override the `table()` method to configure the table.
*/
protected function getTableContent(): ?View
{
return null;
}
/**
* @deprecated Override the `table()` method to configure the table.
*
* @return array<string, int | null> | null
*/
protected function getTableContentGrid(): ?array
{
return null;
}
/**
* @deprecated Override the `table()` method to configure the table.
*/
protected function getTableContentFooter(): ?View
{
return null;
}
}
@@ -0,0 +1,55 @@
<?php
namespace Filament\Tables\Concerns;
use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Illuminate\Contracts\View\View;
/**
* @deprecated Override the `table()` method to configure the table.
*/
trait HasEmptyState
{
/**
* @deprecated Override the `table()` method to configure the table.
*/
protected function getTableEmptyState(): ?View
{
return null;
}
/**
* @deprecated Override the `table()` method to configure the table.
*
* @return array<Action | ActionGroup>
*/
protected function getTableEmptyStateActions(): array
{
return [];
}
/**
* @deprecated Override the `table()` method to configure the table.
*/
protected function getTableEmptyStateDescription(): ?string
{
return null;
}
/**
* @deprecated Override the `table()` method to configure the table.
*/
protected function getTableEmptyStateHeading(): ?string
{
return null;
}
/**
* @deprecated Override the `table()` method to configure the table.
*/
protected function getTableEmptyStateIcon(): ?string
{
return null;
}
}
@@ -0,0 +1,259 @@
<?php
namespace Filament\Tables\Concerns;
use Filament\QueryBuilder\Forms\Components\RuleBuilder;
use Filament\Schemas\Components\Component;
use Filament\Schemas\Schema;
use Filament\Tables\Filters\BaseFilter;
use Filament\Tables\Filters\QueryBuilder;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
/**
* @property-read Schema $tableFiltersForm
*/
trait HasFilters
{
/**
* @var array<string, mixed> | null
*/
public ?array $tableFilters = null;
/**
* @var array<string, mixed> | null
*/
public ?array $tableDeferredFilters = null;
public function getTableFiltersForm(): Schema
{
if ((! $this->isCachingSchemas) && $this->hasCachedSchema('tableFiltersForm')) {
return $this->getSchema('tableFiltersForm');
}
$table = $this->getTable();
return $this->makeSchema()
->columns($table->getFiltersFormColumns())
->model($table->getModel())
->schema($table->getFiltersFormSchema())
->when(
$table->hasDeferredFilters(),
fn (Schema $schema) => $schema
->statePath('tableDeferredFilters')
->partiallyRender(),
fn (Schema $schema) => $schema
->statePath('tableFilters')
->live(),
);
}
public function updatedTableFilters(): void
{
if ($this->getTable()->hasDeferredFilters()) {
$this->tableDeferredFilters = $this->tableFilters;
}
$this->handleTableFilterUpdates();
}
protected function handleTableFilterUpdates(): void
{
if ($this->getTable()->persistsFiltersInSession()) {
session()->put(
$this->getTableFiltersSessionKey(),
$this->tableFilters,
);
}
if ($this->getTable()->shouldDeselectAllRecordsWhenFiltered()) {
$this->deselectAllTableRecords();
}
$this->resetPage();
}
public function removeTableFilter(string $filterName, ?string $field = null, bool $isRemovingAllFilters = false): void
{
$filter = $this->getTable()->getFilter($filterName);
$filterResetState = $filter->getResetState();
$filterFormGroup = $this->getTableFiltersForm()->getComponentByStatePath($filterName);
if (($filter instanceof QueryBuilder) && blank($field)) {
$filterFormGroup->getChildSchema()->fill();
} elseif ($filter instanceof QueryBuilder) {
$ruleBuilder = $filterFormGroup?->getChildSchema()->getComponent(fn (Component $component): bool => $component instanceof RuleBuilder);
$ruleBuilderRawState = $ruleBuilder?->getRawState() ?? [];
unset($ruleBuilderRawState[$field]);
$ruleBuilder?->rawState($ruleBuilderRawState);
} else {
$filterFields = $filterFormGroup?->getChildSchema()->getFlatFields() ?? [];
if (filled($field) && array_key_exists($field, $filterFields)) {
$filterFields = [$field => $filterFields[$field]];
}
foreach ($filterFields as $fieldName => $field) {
$state = $field->getState();
$field->state($filterResetState[$fieldName] ?? match (true) {
is_array($state) => [],
is_bool($state) => $field->hasNullableBooleanState() ? null : false,
default => null,
});
}
}
if ($isRemovingAllFilters) {
return;
}
if ($this->getTable()->hasDeferredFilters()) {
$this->applyTableFilters();
return;
}
$this->handleTableFilterUpdates();
}
public function removeTableFilters(): void
{
$filters = $this->getTable()->getFilters();
foreach ($filters as $filterName => $filter) {
$this->removeTableFilter(
$filterName,
isRemovingAllFilters: true,
);
}
$this->resetTableSearch();
$this->resetTableColumnSearches();
if ($this->getTable()->hasDeferredFilters()) {
$this->applyTableFilters();
return;
}
$this->handleTableFilterUpdates();
}
public function resetTableFiltersForm(): void
{
$this->getTableFiltersForm()->fill();
if ($this->getTable()->hasDeferredFilters()) {
$this->applyTableFilters();
return;
}
$this->handleTableFilterUpdates();
}
public function applyTableFilters(): void
{
$this->tableFilters = $this->tableDeferredFilters;
$this->handleTableFilterUpdates();
}
protected function applyFiltersToTableQuery(Builder $query): Builder
{
$table = $this->getTable();
if ($table->hasDeferredFilters()) {
$this->getTableFiltersForm()->statePath('tableFilters')->flushCachedAbsoluteStatePaths();
}
try {
foreach ($table->getFilters() as $filter) {
$filter->applyToBaseQuery(
$query,
$this->getTableFilterState($filter->getName()) ?? [],
);
}
return $query->where(function (Builder $query) use ($table): void {
foreach ($table->getFilters() as $filter) {
$filter->apply(
$query,
$this->getTableFilterState($filter->getName()) ?? [],
);
}
});
} finally {
if ($table->hasDeferredFilters()) {
$this->getTableFiltersForm()->statePath('tableDeferredFilters')->flushCachedAbsoluteStatePaths();
}
}
}
public function getTableFilterState(string $name): ?array
{
return Arr::get($this->tableFilters, $this->parseTableFilterName($name));
}
public function getTableFilterFormState(string $name): ?array
{
return Arr::get($this->getTable()->hasDeferredFilters() ? $this->tableDeferredFilters : $this->tableFilters, $this->parseTableFilterName($name));
}
public function parseTableFilterName(string $name): string
{
if (! class_exists($name)) {
return $name;
}
if (! is_subclass_of($name, BaseFilter::class)) {
return $name;
}
return $name::getDefaultName();
}
public function getTableFiltersSessionKey(): string
{
$table = md5($this::class);
return "tables.{$table}_filters";
}
/**
* @deprecated Override the `table()` method to configure the table.
*
* @return array<BaseFilter>
*/
protected function getTableFilters(): array
{
return [];
}
/**
* @deprecated Override the `table()` method to configure the table.
*/
protected function getTableFiltersFormWidth(): ?string
{
return null;
}
/**
* @deprecated Override the `table()` method to configure the table.
*/
protected function getTableFiltersFormMaxHeight(): ?string
{
return null;
}
/**
* @deprecated Override the `table()` method to configure the table.
*/
protected function shouldPersistTableFiltersInSession(): bool
{
return false;
}
}
@@ -0,0 +1,48 @@
<?php
namespace Filament\Tables\Concerns;
use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Contracts\View\View;
/**
* @deprecated Override the `table()` method to configure the table.
*/
trait HasHeader
{
/**
* @deprecated Override the `table()` method to configure the table.
*/
protected function getTableDescription(): string | Htmlable | null
{
return null;
}
/**
* @deprecated Override the `table()` method to configure the table.
*/
protected function getTableHeader(): View | Htmlable | null
{
return null;
}
/**
* @deprecated Override the `table()` method to configure the table.
*
* @return array<Action | ActionGroup>
*/
protected function getTableHeaderActions(): array
{
return [];
}
/**
* @deprecated Override the `table()` method to configure the table.
*/
protected function getTableHeading(): string | Htmlable | null
{
return null;
}
}
@@ -0,0 +1,17 @@
<?php
namespace Filament\Tables\Concerns;
/**
* @deprecated Override the `table()` method to configure the table.
*/
trait HasRecordAction
{
/**
* @deprecated Override the `table()` method to configure the table.
*/
protected function getTableRecordAction(): ?string
{
return null;
}
}
@@ -0,0 +1,300 @@
<?php
namespace Filament\Tables\Concerns;
use Filament\Support\ArrayRecord;
use Illuminate\Contracts\Pagination\CursorPaginator;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use LogicException;
use function Livewire\invade;
trait HasRecords
{
/**
* @deprecated Override the `table()` method to configure the table.
*/
protected bool $allowsDuplicates = false;
protected Collection | Paginator | CursorPaginator | null $cachedTableRecords = null;
public function getFilteredTableQuery(): ?Builder
{
$query = $this->getTable()->getQuery();
if (! $query) {
return null;
}
return $this->filterTableQuery($query);
}
public function filterTableQuery(Builder $query): Builder
{
$this->applyFiltersToTableQuery($query);
$this->applySearchToTableQuery($query);
foreach ($this->getTable()->getVisibleColumns() as $column) {
$column->applyRelationshipAggregates($query);
if ($this->getTable()->isGroupsOnly()) {
continue;
}
$column->applyEagerLoading($query);
}
return $query;
}
public function getFilteredSortedTableQuery(): ?Builder
{
$query = $this->getFilteredTableQuery();
if (! $query) {
return null;
}
$this->applyGroupingToTableQuery($query);
$this->applySortingToTableQuery($query);
return $query;
}
public function getTableQueryForExport(): Builder
{
$query = $this->getTable()->getQuery();
$this->applyFiltersToTableQuery($query);
$this->applySearchToTableQuery($query);
$this->applySortingToTableQuery($query);
return $query;
}
protected function hydratePivotRelationForTableRecords(EloquentCollection | Paginator | CursorPaginator $records): EloquentCollection | Paginator | CursorPaginator
{
$table = $this->getTable();
$relationship = $table->getRelationship();
if ($table->getRelationship() instanceof BelongsToMany && ! $table->allowsDuplicates()) {
invade($relationship)->hydratePivotRelation($records->all());
}
return $records;
}
public function getTableRecords(): Collection | Paginator | CursorPaginator
{
if (! $this->getTable()->hasQuery()) {
if ($this->cachedTableRecords) {
return $this->cachedTableRecords;
}
$records = $this->getTable()->evaluate($this->getTable()->getDataSource(), [
'columnSearches' => fn (): array => $this->getTableColumnSearches(),
'filters' => fn (): ?array => $this->tableFilters,
'page' => fn (): int | string => $this->getTablePage(),
'recordsPerPage' => fn (): int | string => $this->getTableRecordsPerPage(),
'search' => fn (): ?string => $this->getTableSearch(),
'sort' => fn (): array => [$this->getTableSortColumn(), $this->getTableSortDirection()],
'sortColumn' => fn (): ?string => $this->getTableSortColumn(),
'sortDirection' => fn (): ?string => $this->getTableSortDirection(),
]);
if (is_array($records)) {
$collection = collect($records);
} elseif (
($records instanceof Paginator || $records instanceof CursorPaginator) &&
method_exists($records, 'getCollection')
) {
$collection = $records->getCollection();
} else {
$collection = $records;
}
$collection = $collection->mapWithKeys(function (array | Model $record, string | int $key): array {
if ($record instanceof Model) {
return [$record->getKey() => $record];
}
$keyName = ArrayRecord::getKeyName();
$record[$keyName] ??= $key;
$record[$keyName] = (string) $record[$keyName];
return [$record[$keyName] => $record];
});
if (
($records instanceof Paginator || $records instanceof CursorPaginator) &&
method_exists($records, 'setCollection')
) {
$records->setCollection($collection);
} else {
$records = $collection;
}
return $this->cachedTableRecords = $records;
}
if ($translatableContentDriver = $this->makeFilamentTranslatableContentDriver()) {
$setRecordLocales = function (EloquentCollection | Paginator | CursorPaginator $records) use ($translatableContentDriver): EloquentCollection | Paginator | CursorPaginator {
$records->transform(fn (Model $record) => $translatableContentDriver->setRecordLocale($record));
return $records;
};
} else {
$setRecordLocales = fn (EloquentCollection | Paginator | CursorPaginator $records): EloquentCollection | Paginator | CursorPaginator => $records;
}
if ($this->cachedTableRecords) {
return $setRecordLocales($this->cachedTableRecords);
}
$query = $this->getFilteredSortedTableQuery();
if (! $query) {
$livewireClass = $this::class;
throw new LogicException("Table [{$livewireClass}] must have a [query()], [relationship()], or [records()].");
}
if (
(! $this->getTable()->isPaginated()) ||
($this->isTableReordering() && (! $this->getTable()->isPaginatedWhileReordering()))
) {
return $setRecordLocales($this->cachedTableRecords = $this->hydratePivotRelationForTableRecords($query->get()));
}
return $setRecordLocales($this->cachedTableRecords = $this->hydratePivotRelationForTableRecords($this->paginateTableQuery($query)));
}
/**
* @return Model | array<string, mixed> | null
*/
protected function resolveTableRecord(?string $key): Model | array | null
{
if ($key === null) {
return null;
}
if (! $this->getTable()->hasQuery()) {
return $this->getTable()->getRecords()[$key] ?? null;
}
if (! ($this->getTable()->getRelationship() instanceof BelongsToMany)) {
return $this->getFilteredTableQuery()->find($key);
}
/** @var BelongsToMany $relationship */
$relationship = $this->getTable()->getRelationship();
$pivotClass = $relationship->getPivotClass();
$pivotKeyName = app($pivotClass)->getKeyName();
$table = $this->getTable();
$this->applyFiltersToTableQuery($relationship->getQuery());
$query = $table->allowsDuplicates() ?
$relationship->wherePivot($pivotKeyName, $key) :
$relationship->where($relationship->getQualifiedRelatedKeyName(), $key);
$record = $table->selectPivotDataInQuery($query)->first();
return $record?->setRawAttributes($record->getRawOriginal());
}
/**
* @return Model | array<string, mixed> | null
*/
public function getTableRecord(?string $key): Model | array | null
{
$record = $this->resolveTableRecord($key);
if ($record && filled($this->getActiveTableLocale())) {
$this->makeFilamentTranslatableContentDriver()->setRecordLocale($record);
}
return $record;
}
/**
* @param Model | array<string, mixed> $record
*/
public function getTableRecordKey(Model | array $record): string
{
if (is_array($record)) {
return $record[ArrayRecord::getKeyName()] ?? throw new LogicException('Record arrays must have a unique [key] entry for identification.');
}
$table = $this->getTable();
if (! ($table->getRelationship() instanceof BelongsToMany && $table->allowsDuplicates())) {
return $record->getKey();
}
/** @var BelongsToMany $relationship */
$relationship = $table->getRelationship();
$pivotClass = $relationship->getPivotClass();
$pivotKeyName = app($pivotClass)->getKeyName();
return $record->getAttributeValue($pivotKeyName);
}
public function getAllTableRecordsCount(): int
{
if ($this->cachedTableRecords instanceof LengthAwarePaginator) {
return $this->cachedTableRecords->total();
}
return $this->getFilteredTableQuery()->count();
}
public function flushCachedTableRecords(): void
{
$this->cachedTableRecords = null;
}
/**
* @deprecated Override the `table()` method to configure the table.
*/
public function allowsDuplicates(): bool
{
return $this->allowsDuplicates;
}
/**
* @deprecated Override the `table()` method to configure the table.
*/
public function getTableRecordTitle(Model $record): ?string
{
return null;
}
/**
* @deprecated Override the `table()` method to configure the table.
*/
public function getTableModelLabel(): ?string
{
return null;
}
/**
* @deprecated Override the `table()` method to configure the table.
*/
public function getTablePluralModelLabel(): ?string
{
return null;
}
}
@@ -0,0 +1,294 @@
<?php
namespace Filament\Tables\Concerns;
use Filament\Tables\Table;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Relation;
use Livewire\WithPagination;
trait InteractsWithTable
{
use CanBeStriped;
use CanDeferLoading;
use CanGroupRecords;
use CanPaginateRecords;
use CanPollRecords;
use CanReorderRecords;
use CanSearchRecords;
use CanSortRecords;
use CanSummarizeRecords;
use HasActions;
use HasBulkActions;
use HasColumnManager;
use HasColumns;
use HasContent;
use HasEmptyState;
use HasFilters;
use HasHeader;
use HasRecordAction;
use HasRecords;
use WithPagination {
WithPagination::resetPage as resetLivewirePage;
WithPagination::setPage as setLivewirePage;
}
protected Table $table;
protected bool $hasTableModalRendered = false;
protected bool $shouldMountInteractsWithTable = false;
public function bootedInteractsWithTable(): void
{
$this->table = $this->table($this->makeTable());
$this->cacheSchema('tableFiltersForm', $this->getTableFiltersForm());
$this->cacheMountedActions($this->mountedActions);
$this->initTableColumnManager();
if (! $this->shouldMountInteractsWithTable) {
return;
}
$shouldPersistFiltersInSession = $this->getTable()->persistsFiltersInSession();
$filtersSessionKey = $this->getTableFiltersSessionKey();
if (! count($this->tableFilters ?? [])) {
$this->tableFilters = null;
}
if (
($this->tableFilters === null) &&
$shouldPersistFiltersInSession &&
session()->has($filtersSessionKey)
) {
$this->tableFilters = session()->get($filtersSessionKey) ?? [];
}
// https://github.com/filamentphp/filament/pull/7999
if ($this->tableFilters) {
$this->normalizeTableFilterValuesFromQueryString($this->tableFilters);
}
$this->getTableFiltersForm()->fill($this->tableFilters);
if ($this->getTable()->hasDeferredFilters()) {
$this->tableFilters = $this->tableDeferredFilters;
}
if ($shouldPersistFiltersInSession) {
session()->put(
$filtersSessionKey,
$this->tableFilters,
);
}
if ($this->getTable()->isDefaultGroupSelectable()) {
$this->tableGrouping = "{$this->getTable()->getDefaultGroup()->getId()}:asc";
}
$shouldPersistSearchInSession = $this->getTable()->persistsSearchInSession();
$searchSessionKey = $this->getTableSearchSessionKey();
if (
blank($this->tableSearch) &&
$shouldPersistSearchInSession &&
session()->has($searchSessionKey)
) {
$this->tableSearch = session()->get($searchSessionKey);
}
$this->tableSearch = strval($this->tableSearch);
if ($shouldPersistSearchInSession) {
session()->put(
$searchSessionKey,
$this->tableSearch,
);
}
$shouldPersistColumnSearchesInSession = $this->getTable()->persistsColumnSearchesInSession();
$columnSearchesSessionKey = $this->getTableColumnSearchesSessionKey();
if (
(blank($this->tableColumnSearches) || ($this->tableColumnSearches === [])) &&
$shouldPersistColumnSearchesInSession &&
session()->has($columnSearchesSessionKey)
) {
$this->tableColumnSearches = session()->get($columnSearchesSessionKey) ?? [];
}
$this->tableColumnSearches = $this->castTableColumnSearches(
$this->tableColumnSearches ?? [],
);
if ($shouldPersistColumnSearchesInSession) {
session()->put(
$columnSearchesSessionKey,
$this->tableColumnSearches,
);
}
$shouldPersistSortInSession = $this->getTable()->persistsSortInSession();
$sortSessionKey = $this->getTableSortSessionKey();
if (
blank($this->tableSort) &&
$shouldPersistSortInSession &&
session()->has($sortSessionKey)
) {
$this->tableSort = session()->get($sortSessionKey);
}
if ($shouldPersistSortInSession) {
session()->put(
$sortSessionKey,
$this->tableSort,
);
}
if ($this->getTable()->isPaginated()) {
$this->tableRecordsPerPage = $this->getDefaultTableRecordsPerPageSelectOption();
}
}
public function mountInteractsWithTable(): void
{
$this->shouldMountInteractsWithTable = true;
}
public function table(Table $table): Table
{
return $table;
}
public function getTable(): Table
{
return $this->table;
}
protected function makeTable(): Table
{
return Table::make($this)
->query(fn (): Builder | Relation | null => $this->getTableQuery())
->when($this->getTableActions(), fn (Table $table, array $actions): Table => $table->actions($actions))
->when($this->getTableActionsColumnLabel(), fn (Table $table, string $actionsColumnLabel): Table => $table->actionsColumnLabel($actionsColumnLabel))
->when($this->getTableColumns(), fn (Table $table, array $columns): Table => $table->columns($columns))
->when(($columnManagerColumns = $this->getTableColumnToggleFormColumns()) !== 1, fn (Table $table): Table => $table->columnManagerColumns($columnManagerColumns))
->when($this->getTableColumnToggleFormMaxHeight(), fn (Table $table, string $columnManagerMaxHeight): Table => $table->columnManagerMaxHeight($columnManagerMaxHeight))
->when($this->getTableColumnToggleFormWidth(), fn (Table $table, string $columnManagerWidth): Table => $table->columnManagerWidth($columnManagerWidth))
->when($this->getTableContent(), fn (Table $table, View $content): Table => $table->content($content))
->when($this->getTableContentFooter(), fn (Table $table, View $contentFooter): Table => $table->contentFooter($contentFooter))
->when($this->getTableContentGrid(), fn (Table $table, array $contentGrid): Table => $table->contentGrid($contentGrid))
->when($this->getDefaultTableSortColumn(), fn (Table $table, string $defaultSortColumn): Table => $table->defaultSort($defaultSortColumn, $this->getDefaultTableSortDirection()))
->when($this->isTableLoadingDeferred(), fn (Table $table): Table => $table->deferLoading())
->when($this->getTableDescription(), fn (Table $table, string | Htmlable $description): Table => $table->description($description))
->when(! $this->shouldDeselectAllRecordsWhenTableFiltered(), fn (Table $table): Table => $table->deselectAllRecordsWhenFiltered(false))
->when($this->getTableEmptyState(), fn (Table $table, View $emptyState): Table => $table->emptyState($emptyState))
->when($this->getTableEmptyStateActions(), fn (Table $table, array $emptyStateActions): Table => $table->emptyStateActions($emptyStateActions))
->when($this->getTableEmptyStateDescription(), fn (Table $table, string $emptyStateDescription): Table => $table->emptyStateDescription($emptyStateDescription))
->when($this->getTableEmptyStateHeading(), fn (Table $table, string $emptyStateHeading): Table => $table->emptyStateHeading($emptyStateHeading))
->when($this->getTableEmptyStateIcon(), fn (Table $table, string $emptyStateIcon): Table => $table->emptyStateIcon($emptyStateIcon))
->when($this->getTableFilters(), fn (Table $table, array $filters): Table => $table->filters($filters))
->when($this->getTableFiltersFormMaxHeight(), fn (Table $table, string $filtersFormMaxHeight): Table => $table->filtersFormMaxHeight($filtersFormMaxHeight))
->when($this->getTableFiltersFormWidth(), fn (Table $table, string $filtersFormWidth): Table => $table->filtersFormWidth($filtersFormWidth))
->when($this->getTableBulkActions(), fn (Table $table, array $groupedBulkActions): Table => $table->groupedBulkActions($groupedBulkActions))
->when($this->getTableHeader(), fn (Table $table, View | Htmlable $header): Table => $table->header($header))
->when($this->getTableHeaderActions(), fn (Table $table, array $headerActions): Table => $table->headerActions($headerActions))
->when($this->getTableModelLabel(), fn (Table $table, string $modelLabel): Table => $table->modelLabel($modelLabel))
->when(! $this->isTablePaginationEnabled(), fn (Table $table): Table => $table->paginated(false))
->when($this->isTablePaginationEnabledWhileReordering(), fn (Table $table): Table => $table->paginatedWhileReordering())
->when($this->getTableRecordsPerPageSelectOptions(), fn (Table $table, array $paginationPageOptions): Table => $table->paginationPageOptions($paginationPageOptions))
->when($this->shouldPersistTableFiltersInSession(), fn (Table $table): Table => $table->persistFiltersInSession())
->when($this->shouldPersistTableSearchInSession(), fn (Table $table): Table => $table->persistSearchInSession())
->when($this->shouldPersistTableColumnSearchInSession(), fn (Table $table): Table => $table->persistColumnSearchesInSession())
->when($this->shouldPersistTableSortInSession(), fn (Table $table): Table => $table->persistSortInSession())
->when($this->getTablePluralModelLabel(), fn (Table $table, string $pluralModelLabel): Table => $table->pluralModelLabel($pluralModelLabel))
->when($this->getTablePollingInterval(), fn (Table $table, string $pollingInterval): Table => $table->poll($pollingInterval))
->when($this->getTableRecordAction(), fn (Table $table, string $recordAction): Table => $table->recordAction($recordAction))
->recordTitle(fn (Model $record): ?string => $this->getTableRecordTitle($record))
->when($this->getTableReorderColumn(), fn (Table $table, string $reorderColumn): Table => $table->reorderable($reorderColumn))
->when($this->shouldSelectCurrentPageOnly(), fn (Table $table): Table => $table->selectCurrentPageOnly())
->when($this->isTableStriped(), fn (Table $table): Table => $table->striped());
}
protected function getTableQueryStringIdentifier(): ?string
{
return null;
}
public function getIdentifiedTableQueryStringPropertyNameFor(string $property): string
{
if (filled($identifier = $this->getTable()->getQueryStringIdentifier())) {
return $identifier . ucfirst($property);
}
return $property;
}
public function getActiveTableLocale(): ?string
{
return null;
}
public function resetPage(?string $pageName = null): void
{
$this->resetLivewirePage($pageName ?? $this->getTablePaginationPageName());
}
public function setPage(int | string $page, ?string $pageName = null): void
{
$defaultPageName = $this->getTablePaginationPageName();
$pageName ??= $defaultPageName;
$this->setLivewirePage($page, $pageName);
if (($pageName === $defaultPageName) && $this->getTable()->shouldScrollToTopOnPageChange()) {
$this->dispatch('scrollToTopOfTable')->self();
}
}
/**
* @deprecated Override the `table()` method to configure the table.
*/
protected function getTableQuery(): Builder | Relation | null
{
return null;
}
/**
* @param array<string, mixed> $data
*/
protected function normalizeTableFilterValuesFromQueryString(array &$data): void
{
foreach ($data as &$value) {
if (is_array($value)) {
$this->normalizeTableFilterValuesFromQueryString($value);
} elseif ($value === 'null') {
$value = null;
} elseif ($value === 'false') {
$value = false;
} elseif ($value === 'true') {
$value = true;
}
}
}
public function resetTable(): void
{
$this->bootedInteractsWithTable();
$this->resetTableFiltersForm();
$this->resetPage();
$this->flushCachedTableRecords();
}
}