F
Filament6mo ago
snipe

Tabbed container with infolist in one tab, other related tables in the other tabs

Hi folks! Huge fan of Filament (tho I'm very new). Sorry for the n00btastic question, but googling didn't get me where I needed to go. I'm trying to build a tabbed interface where the first tab is the model's general iinfo, and the tabs are populated with already established $table schemas from other models. I'm sure I'm missing something super obvious, but would appreciate a nudge in the right direction.
public static function infolist(Infolist $infolist): Infolist
{
return $infolist
->schema([
Tabs::make('Tabs')
->tabs([
Tabs\Tab::make('Details')
->schema([
TextEntry::make('name'),
])->columns(3),
Tabs\Tab::make('Assets')
->schema([
// related table info here here
]),


])
->persistTab()
->persistTabInQueryString()

])
->columns(1);

}
public static function infolist(Infolist $infolist): Infolist
{
return $infolist
->schema([
Tabs::make('Tabs')
->tabs([
Tabs\Tab::make('Details')
->schema([
TextEntry::make('name'),
])->columns(3),
Tabs\Tab::make('Assets')
->schema([
// related table info here here
]),


])
->persistTab()
->persistTabInQueryString()

])
->columns(1);

}
44 Replies
snipe
snipeOP6mo ago
What I really want is to be able to re-use the model's table definition without haviung to recreate it every time I want to display the related table (I'm probably always going to want that table to look the same and have the same functionality, and I don't really want to have to duplicate that each time I display it) Something like MyModelResource::table() or somesuch I'm 100% sure I'm overlooking something super obvious. Have only been working with filament a few days, so a little lost I'm looking for the infolist style of tabs, where the container spans the full width and the tabs are on top, versus the filtering-style
Chrispian
Chrispian6mo ago
This could be useful. I haven't tried this yet but just thinking of ways to do this. You could create a livewire table for each model that could be reused anywhere. They can be used in infolists so you could possibly combine with your tab layout. Add Livewire component to infolist: https://filamentphp.com/docs/3.x/infolists/adding-an-infolist-to-a-livewire-component Adding a table to a Livewire Component: https://filamentphp.com/docs/3.x/tables/adding-a-table-to-a-livewire-component
Kitty
Kitty6mo ago
Follow the instructions above. Then, in your tab do something like this
ViewEntry::make('asset')->view('livewire.asset')
ViewEntry::make('asset')->view('livewire.asset')
, and then in that view
{{ $this->table}}
{{ $this->table}}
snipe
snipeOP6mo ago
Thanks to both of you - that helped! Now just need to figure out how to pass the current user into the component query so only their items are shown when looking at their details page
Chrispian
Chrispian6mo ago
Glad that helped. I'll have a use for this too so it was very useful research. Query scopes would probably help there and you could also use modifyQueryUsing for the table query. https://filamentphp.com/docs/3.x/tables/filters/query-builder#scoping-relationships https://filamentphp.com/docs/3.x/tables/grouping#customizing-the-eloquent-query-scoping-behavior I think the latter is probably what you want here but you could also do it on the model with a regualr query cope if this is application wide and not just specific to these tables.
snipe
snipeOP6mo ago
Yeah, it's a pretty complex application where I don't want to duplicate table schemas, so I need them to be re-usable. (Users can have assets, accessories, licenses, etc - all of which are first class models/resources, and we'll want to see which users have those assets, licenses, etc from those tables as well) Right now I'm playing around with pulling the main table definition from AssetResource through to the other resource I had to make. But if trial and error right now, but always making sure the tables display the same (even if we add a column, etc) across all places they could be is critical This is all a pretty big experiment - the existing codebase is 11 years old, so trying to see if Filament is the right tool for the job or if we should stick with what we have. There's nothing exactly wrong with what we have, but Filament would hopefully allow us to move a little more quickly once it's all set up Like, this almost works:
public static function userAssetTable(Table $table): Table
{
return AssetResource::table($table)
->relationship(fn (): HasMany => $record->assets);
}
public static function userAssetTable(Table $table): Table
{
return AssetResource::table($table)
->relationship(fn (): HasMany => $record->assets);
}
People who can see all of the assets, (accessories, etc) within the system should see the largely unscoped view, while looking at a specific user, you should only see what's assigned to that user I was hoping that something like this would work:
Tabs\Tab::make('Assets')
->schema([
ViewEntry::make('assets')->view('livewire.view-asset')
])
->icon('fas-barcode')
->badge(fn ($record) => $record->assets->count()),
Tabs\Tab::make('Assets')
->schema([
ViewEntry::make('assets')->view('livewire.view-asset')
])
->icon('fas-barcode')
->badge(fn ($record) => $record->assets->count()),
but that relationship doesn't seem to be scoping it properly
Chrispian
Chrispian6mo ago
So I think query scopes on the models in question would handle showing the right data to solve that part at least. https://laravel.com/docs/10.x/eloquent#query-scopes You could have conditional logic based on the authenticated user and if you use spatie-permissions you can use roles. I use Filament Shield for an easy GUI to manage roles/permissions in our apps.
Laravel - The PHP Framework For Web Artisans
Laravel is a PHP web application framework with expressive, elegant syntax. We’ve already laid the foundation — freeing you to create without sweating the small things.
snipe
snipeOP6mo ago
The relations already exist on the model (user has an assets() relationship, etc) I'll definitely check that out, thanks - I haven't seen Filament shield. We have a very old bespoke permissions system, from before Laravel had a lot of nuanced support. (This was originally built on Laravel 4, and we've been upgrading, but not everything got the full overhaul) In the code above for that ViewEntry bit, since it references the assets() model method, would do it, but it seems not
Chrispian
Chrispian6mo ago
Assuming I understand you correctly, the raltionships should be fine as is. The query scope modifies the base query for everything on that model and will return only the results from that relationship based on permissions. So you add a global scope method to your model and in there you can check your custom permissions and return the query modifier you want.
snipe
snipeOP6mo ago
I'm not even using permissions right now lol Jusr trying to see if I can get the widgets and panels and etc to behave the way I want I can pull over permissions later But that ViewEntry::make('assets')->view('livewire.view-asset') is on the UserResource, which uses User as the model, so I kinda don't know why it's not loading just the assets scoped to that user in the resulting table (Again, I'm super new to Filament. I appreciate all of the suggestions) I don't even know if this is the right solution yet. Have heard some murmurs about performance with a lot of data, and we definitely have a lot of data
Chrispian
Chrispian6mo ago
Ah, gotcha. Could you post your livewire.view-asset related code and the User model? I suspect it's somewhere in that area. As a test, can you add a text field and just dump the assets from within the infolist/page you are using? WIll help narrow it down. Lots of data can be a performance thing, but I had almost a million records in a table with like 30 fields + relationships with no real issues (unless you try to view them all at once lol). Not sure if that's a lot either haha.
snipe
snipeOP6mo ago
The user model method is:
/**
* Establishes the user -> assets relationship
*
* @author A. Gianotto <[email protected]>
* @since [v1.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function assets()
{
return $this->morphMany(Asset::class, 'assigned', 'assigned_type', 'assigned_to')->orderBy('id');
}
/**
* Establishes the user -> assets relationship
*
* @author A. Gianotto <[email protected]>
* @since [v1.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function assets()
{
return $this->morphMany(Asset::class, 'assigned', 'assigned_type', 'assigned_to')->orderBy('id');
}
Livewire view-asset is just:
<div>
@livewire('list-assets')
</div>
<div>
@livewire('list-assets')
</div>
list-assets is just:
<div>
{{ $this->table }}
</div>
<div>
{{ $this->table }}
</div>
It's been a while since I've felt like this much of a n00b lol
Chrispian
Chrispian6mo ago
lol. The front end parts make me feel like a n00b all day every day. There should be one more file for the livewire component, I think. Check in app/Http/Livewire for the list-assets related php file and share that.
snipe
snipeOP6mo ago
There is: ListAssets:
class ListAssets extends Component implements HasForms, HasTable
{
use InteractsWithTable;
use InteractsWithForms;

protected static ?string $model = Asset::class;

public function table(Table $table): Table
{
return AssetResource::table($table)
->query(Asset::query())
->inverseRelationship('user');
// ->relationship(fn (): HasMany => $this->assets)
// ->inverseRelationship('categories');
}


public function render(): View
{
return view('livewire.list-assets');
}
}
class ListAssets extends Component implements HasForms, HasTable
{
use InteractsWithTable;
use InteractsWithForms;

protected static ?string $model = Asset::class;

public function table(Table $table): Table
{
return AssetResource::table($table)
->query(Asset::query())
->inverseRelationship('user');
// ->relationship(fn (): HasMany => $this->assets)
// ->inverseRelationship('categories');
}


public function render(): View
{
return view('livewire.list-assets');
}
}
Again, I'm trying to do some clever table re-using The structure of Filament alone is enough to make anyone feel like a noob. So many resource files Our DB structure is also pretty hairy, as we use a lot of polymorphism
Chrispian
Chrispian6mo ago
Legacy code is always fun lol. I've got some that I really need to clean up too. I like the table reusing, that's a good idea. I was going to ask next about the inverse and relationship, that's where I thought the problem might be. Have you tried including the table directly, just to rule that aspect out?
snipe
snipeOP6mo ago
I'd sort of expect the scope to go in the ListAssets bit, but I don't have access to the $record->id there I haven't yet - so far been doing a lot of poking and hoping lol
Chrispian
Chrispian6mo ago
lol. Try a simple table there just to rule that out. I think you are close, I feel like this should work.
Chrispian
Chrispian6mo ago
You can get acess to $record->id in the mount method in the php file for the livewire compontent like this: https://filamentphp.com/docs/3.x/infolists/advanced#accessing-the-current-record-in-the-livewire-component
Chrispian
Chrispian6mo ago
And speaking of n00b, learning filament has been a humbling experience lol. I don't know where Livewire ends and Laravel begins, not to mention Alpine. I'm loving it now that I know it a lot better but I'm still trying to learn it much deeper.
snipe
snipeOP6mo ago
Yeah, me too. This is like day 5 for me, and seeing where one ends and the other begins is a lot Been working with Laravel since v4, but v11 is even different than v8 (which our legacy codebase is on) "Target [Illuminate\Database\Eloquent\Model] is not instantiable." 😢 I'm not even lying, I have like 400 tabs open for different parts of filament right now
Chrispian
Chrispian6mo ago
haha. I always do!
snipe
snipeOP6mo ago
Scoping is working on that ListAssets livewire php page.
public function table(Table $table): Table
{
return AssetResource::table($table)
->query(Asset::query()->where('assigned_to', auth()->user()->id));
}
public function table(Table $table): Table
{
return AssetResource::table($table)
->query(Asset::query()->where('assigned_to', auth()->user()->id));
}
shows me the assets for the logged in user - which at least means I'm not completely crazy But I get that mounting error if I try to use the model + mount() method there
Chrispian
Chrispian6mo ago
So you added the mount method to the class ListAssets file? Try removing mount and just doing public Model $record; . But thinking about that, it just makes it available to the livewire component. I assume you want to pass it into the query?
snipe
snipeOP6mo ago
Yeah
snipe
snipeOP6mo ago
I bet I don't Hm. "Using $this when not in object context" error I have my infolist on the UserResource tho That's where I have the tabbed UI and pull in the liveewire table component So I'm extending the Resource in that file, not the Component
Chrispian
Chrispian6mo ago
Ah, right. So in UserResource you should have access directly to $record. I'm using it like that in some of mine, eg: Forms\Components\Textarea::make('errors') ->formatStateUsing(function ( $record ) { return str_replace( ",", PHP_EOL, $record->errors ); }) Since this was a closure, I had to pass it in.
snipe
snipeOP6mo ago
I do that in a few other places there (making the urls in the profile, etc) but I couldn't see how to attach that query to the ViewEntry
Chrispian
Chrispian6mo ago
Can you post the UserResource code?
Kitty
Kitty6mo ago
In your Livewire view-asset try something like: <livewire:list-assets :user="$user"/> or whatever model you have set for that table in mount
snipe
snipeOP6mo ago
Ugh, I've changed so many things now that I'm not sure where its broken
snipe
snipeOP6mo ago
GitHub
filament-snipe-it/app/Filament/Admin/Resources/UserResource.php at ...
Experimental - do not use. Contribute to snipe/filament-snipe-it development by creating an account on GitHub.
snipe
snipeOP6mo ago
(It's very long, sorry)
snipe
snipeOP6mo ago
GitHub
filament-snipe-it/app/Filament/Admin/Resources/UserResource.php at ...
Experimental - do not use. Contribute to snipe/filament-snipe-it development by creating an account on GitHub.
snipe
snipeOP6mo ago
Getting closer 🙂 Now the table loads with nothing in it lol
Kitty
Kitty6mo ago
IDK if its a problem but you don't need this file: https://github.com/snipe/filament-snipe-it/blob/main/app/Livewire/ViewAsset.php You can have the view entry without a component
GitHub
filament-snipe-it/app/Livewire/ViewAsset.php at main · snipe/filame...
Experimental - do not use. Contribute to snipe/filament-snipe-it development by creating an account on GitHub.
Kitty
Kitty6mo ago
try $getRecord() <livewire:list-assets :record="$getRecord()"/> where :record is the name of the model from your resource.
snipe
snipeOP6mo ago
If I dump out $getRecord() in the LW component, it does seem to show the correct record - just having a helluva time including it in the query I think I farted up some of the stuff in the resources The current version of this software consumes our own API via bootstrap tables, so it's a bit more confusing to think about this stuff from a LW perspective I really appreciate everyone's time helping me - you folks are the best. If you're ever in Portugal, I'll buy you a ginginha or an imperial 🙂 Where I think I'm messing this up is how I'm approaching the filter query.
public function table(Table $table)
{
return AssetResource::table($table)
->query(Asset::query()->with('user'));
}
public function table(Table $table)
{
return AssetResource::table($table)
->query(Asset::query()->with('user'));
}
if I do that, I need the record ID, so I'm back to being boned. If I do it from the user search:
public function table(Table $table)
{
return AssetResource::table($table)
->query(User::query()->with('assets'));
}
public function table(Table $table)
{
return AssetResource::table($table)
->query(User::query()->with('assets'));
}
the sorting and filtering get screwy where it's trying to sort on asset fields against the user table We alreadsy account for those sorting scopes in our API, but we're not using our API for that, so all is goofed up (Since those sorts are being done on the query reults that are being passed, and that is a user object, which wouldn't have purchase_date, etc on it.)
Chrispian
Chrispian6mo ago
I'm gonna stew on this some more. I'm invested now lol.
snipe
snipeOP6mo ago
I think I got it working But oddly, some of my sortable() columns arde not sortable anymore I don't think the Expose tunnel will stay open for long, but https://s5hiupeera.sharedwithexpose.com - email: [email protected], password: password If you click on the Users tab section and then the assets tab in the users listing (It's all dummy seed data, of course) I'm not sure whether I solved it the right way, mind you 😄
Chrispian
Chrispian6mo ago
Nice! Looks good. I see the sorting issue. I'm betting since it's a modified query + a LW component something needs to be done around that to make it update the table data. "Right way" lol. Never heard of it 😉
snipe
snipeOP6mo ago
Weird that none of the other sorting bonked tho
Chrispian
Chrispian6mo ago
Probably something really stupid lol
snipe
snipeOP6mo ago
Oh of that I'm 100% certain lol
Want results from more Discord servers?
Add your server