F
Filamentβ€’15mo ago
Esi

Hierarchy tables

Does anyone knows how to create this type of table in Filament?
No description
27 Replies
Patrick Boivin
Patrick Boivinβ€’15mo ago
Do you mean the nested records, with the - - prefixes?
Patrick Boivin
Patrick Boivinβ€’15mo ago
Have a look at the #fabricator plugin: https://github.com/Z3d0X/filament-fabricator
GitHub
GitHub - Z3d0X/filament-fabricator: Block-Based Page Builder Skelet...
Block-Based Page Builder Skeleton for your Filament Apps - GitHub - Z3d0X/filament-fabricator: Block-Based Page Builder Skeleton for your Filament Apps
Patrick Boivin
Patrick Boivinβ€’15mo ago
It handles nested pages with a parend_id column in the table With a similar technique, you can probably add the prefixes on the Title column and make sure the table's default sort aligns all the children under the right parent.
awcodes
awcodesβ€’15mo ago
I’ve been trying to find a good way to do this, but it really goes beyond filament and gets into the eloquent query level from a laravel pov. Tying the two together has not proven to be easy. The biggest problem comes when trying to render out the routes. Laravel really isn’t set up for catch all routes this way. And the way Wordpress manages to accomplish it becomes a detriment to performance.
Esi
EsiOPβ€’15mo ago
I just want to list the table records in that format, regarding the routes I am keeping the standard one
Patrick Boivin
Patrick Boivinβ€’15mo ago
I'll play around with this on the weekend. Maybe we can compare notes.
awcodes
awcodesβ€’15mo ago
it's easy enough to do if you know the model has a relationship to a 'parent_id' within the same model. But this falls apart if the table gets filtered in any way or pages aren't sequential, so it has to always be compensated for at the query level. That's where it starts getting tricky.
No description
Patrick Boivin
Patrick Boivinβ€’15mo ago
Yeah, I'm thinking along those lines: in addition to a parent_id field, have a sequence_number field that's automatically managed to keep track of the parent/child order, for example:
010000 Home
020000 About
020100 - Team
020200 - History
030000 Contact
040000 Projects
040100 - Lorem
040101 - Case study
040102 - Assets
040103 - Credits
040200 - Ipsum
040201 - Case study
040202 - Assets
040203 - Credits
040300 - Dolor
040301 - Case study
040302 - Assets
040303 - Credits
010000 Home
020000 About
020100 - Team
020200 - History
030000 Contact
040000 Projects
040100 - Lorem
040101 - Case study
040102 - Assets
040103 - Credits
040200 - Ipsum
040201 - Case study
040202 - Assets
040203 - Credits
040300 - Dolor
040301 - Case study
040302 - Assets
040303 - Credits
The table would need to always be hard sorted by this criteria
awcodes
awcodesβ€’15mo ago
yea, it can get pretty complicated. πŸ™‚
Patrick Boivin
Patrick Boivinβ€’15mo ago
No doubt, well it's top of mind at this point but maybe over the weekend I'll have lost interest πŸ₯²
awcodes
awcodesβ€’15mo ago
I think the hard part is just keeping them next to each other when querying the db so they get put in the right places in the table when displayed.
Patrick Boivin
Patrick Boivinβ€’15mo ago
Right. Does this "sequence" idea make sense, or is there some other dimension I'm not thinking about?
awcodes
awcodesβ€’15mo ago
otherwise it will look like they are a child of the wrong page i think the sequence is less relevant since it could be sorted by something like 'published_date'. the important part is keeping them 'grouped' appropriately.
Patrick Boivin
Patrick Boivinβ€’15mo ago
Yes, that's the idea behind the sequence... It keeps track of all the ancestors for the purposes of ordering
awcodes
awcodesβ€’15mo ago
i'm probably just misunderstanding it then. πŸ™‚
Patrick Boivin
Patrick Boivinβ€’15mo ago
Me too πŸ˜‚
Esi
EsiOPβ€’15mo ago
I am doing the same as the proposed but it's complicated to keep the sequence number on edit and delete. It requires to update the hole table sequence numbers always to keep the order
awcodes
awcodesβ€’15mo ago
Yep. It’s a interesting problem. πŸ˜… Realistically it should be easy enough to replicate what Wordpress is doing in filament with eloquent. It’s still just php, but there has to be a clean way to do it, because WP’s way is far from clean.
Esi
EsiOPβ€’15mo ago
I also tried using this package staudenmeir/laravel-adjacency-list which does the heavy job but I can't render the records as they should on Table builder since I don't know how to make a recursive table to loop through children items
Patrick Boivin
Patrick Boivinβ€’15mo ago
I think the simple solution is obviously to disable ordering/searching/filtering on the table. It's not awesome, but it could work in the context of pages and posts... A slightly better option would be to show the nested records on the default table view, and revert to a flat view (no hierarchy) when the table is ordered/searched/filtered. I've been playing with this yesterday (in a spreadsheet, lol) and I'm realizing that the complexity is all in the ordering algorithm, a hinted by awcodes. I'm not even sure it's fully possible to do in SQL with a table of self-nested records. I can come up with a SQL query that works for 2 levels but it breaks down after that. I don't know how to recursively access the chain of parents in the context of a single query.
awcodes
awcodesβ€’14mo ago
So, Have a kind of fake solution to this, but don't have to worry about the query 🀣
->prefix(function (Page $record, Table $table): ?Htmlable {
if (! static::shouldShowParent($table) && $record->parent) {
return new HtmlString('<span style="margin-inline-end: 0.75rem;">&boxur;</span>');
}

return null;
})
->description(function (Page $record, Table $table): ?Htmlable {
if (static::shouldShowParent($table) && $record->parent) {
return new HtmlString('<span>Parent: ' . Str::of($record->parent)->title() . '</span>');
}

return null;
})

public static function shouldShowParent(Table $table): bool {
$isSearching = $table->hasSearch();
$isSorting = $table->getSortColumn();
$hasActiveFilters = collect($table->getFilters())
->filter(fn (\Filament\Tables\Filters\BaseFilter $filter) => $filter->getIndicators())
->isNotEmpty();

return $isSearching || $isSorting || $hasActiveFilters;
}
->prefix(function (Page $record, Table $table): ?Htmlable {
if (! static::shouldShowParent($table) && $record->parent) {
return new HtmlString('<span style="margin-inline-end: 0.75rem;">&boxur;</span>');
}

return null;
})
->description(function (Page $record, Table $table): ?Htmlable {
if (static::shouldShowParent($table) && $record->parent) {
return new HtmlString('<span>Parent: ' . Str::of($record->parent)->title() . '</span>');
}

return null;
})

public static function shouldShowParent(Table $table): bool {
$isSearching = $table->hasSearch();
$isSorting = $table->getSortColumn();
$hasActiveFilters = collect($table->getFilters())
->filter(fn (\Filament\Tables\Filters\BaseFilter $filter) => $filter->getIndicators())
->isNotEmpty();

return $isSearching || $isSorting || $hasActiveFilters;
}
No description
No description
Patrick Boivin
Patrick Boivinβ€’14mo ago
Nice! I think that would do for many use-cases. This is what I landed on in my exploration:
Tables\Columns\TextColumn::make('title')
->sortable()
->searchable()
->formatStateUsing(function ($livewire, $record, $state) {
if ($livewire->tableSortColumn || $livewire->tableSearch) {
return $state;
}

$prefix = collect(array_fill(0, $record->level, ''))
->map(fn () => '&nbsp;&nbsp;&nbsp;β€”')
->join('') . ' ';

return new HtmlString("{$prefix}{$state}");
}),

// ...

->defaultSort('sequence')
Tables\Columns\TextColumn::make('title')
->sortable()
->searchable()
->formatStateUsing(function ($livewire, $record, $state) {
if ($livewire->tableSortColumn || $livewire->tableSearch) {
return $state;
}

$prefix = collect(array_fill(0, $record->level, ''))
->map(fn () => '&nbsp;&nbsp;&nbsp;β€”')
->join('') . ' ';

return new HtmlString("{$prefix}{$state}");
}),

// ...

->defaultSort('sequence')
Patrick Boivin
Patrick Boivinβ€’14mo ago
Patrick Boivin
Patrick Boivinβ€’14mo ago
But as you said, keeping the logical grouping across all operations (filtering, etc.) is a real challenge. I gave up as I have no actual need for this in a client project πŸ˜… And I guess I just learned about the ->prefix() method on the columns πŸ‘Œ
awcodes
awcodesβ€’14mo ago
yea, badgeable column plugin is using prefix() and suffix() to appended the badges. πŸ™‚
qcol
qcolβ€’14mo ago
Recursive Nested Data in Laravel, The Right Way | Codecourse Blog
Nested data is a pain point for a lot of developers. Relationships, eager loading and recursively iterating and displaying hierarchical data is enough to cause a headache.
qcol
qcolβ€’14mo ago
GitHub
GitHub - staudenmeir/laravel-adjacency-list: Recursive Laravel Eloq...
Recursive Laravel Eloquent relationships with CTEs - GitHub - staudenmeir/laravel-adjacency-list: Recursive Laravel Eloquent relationships with CTEs
Want results from more Discord servers?
Add your server