Sometimes a single attribute just isn't enough to identify the correct record in a select fields options. Sure, you can concatenate attributes in an accessor but it can be ugly. What if you need to display an image? Sounds like we need a little HTML!
Since Filament implemented Choices.js we've had the ability to render HTML inside each select option. You just need to switch the feature on using the new allowHtml()
modifier when defining your select field.
Here's a common example of a Select field for users with avatars.
Select::make('user_id') ->label('User') ->allowHtml() // Apply the new modifier to enable HTML in the options - it's disabled by default ->searchable() // Don't forget to make it searchable otherwise there is no choices.js magic! ->getSearchResultsUsing(function (string $search) { $users = User::where('name', 'like', "%{$search}%")->limit(50)->get(); return $users->mapWithKeys(function ($user) { return [$user->getKey() => static::getCleanOptionString($user)]; })->toArray(); }) ->getOptionLabelUsing(function ($value): string { $user = User::find($value); return static::getCleanOptionString($user); })
->getSearchResultsUsing()
- needs to return a key/value pair for each search result. The key should be the model (User) id and the value should be your HTML string.
->getOptionLabelUsing()
- needs to return your HTML string.
To keep it DRY I've added a static method to the resource to return the view.
public static function getCleanOptionString(Model $model): string { return Purify::clean( view('filament.components.select-user-result') ->with('name', $model?->name) ->with('email', $model?->email) ->with('image', $model?->image) ->render() ); }
Note: enabling
allowHtml
() can introduce vulnerability to XSS attacks. Sanitize your HTML strings to stay safe!
Finally, create a blade file for the view
<div class="flex rounded-md relative"> <div class="flex"> <div class="px-2 py-3"> <div class="h-10 w-10"> <img src="{{ url('/storage/'.$image.'') }}" alt="{{ $name }}" role="img" class="h-full w-full rounded-full overflow-hidden shadow object-cover" /> </div> </div> <div class="flex flex-col justify-center pl-3 py-2"> <p class="text-sm font-bold pb-1">{{ $name }}</p> <div class="flex flex-col items-start"> <p class="text-xs leading-5">{{ $email }}</p> </div> </div> </div></div>
How can i use livewire component like "blade-icon" in view? if i use Purify ( https://github.com/stevebauman/purify) the package remove the component :S
Congrats, great example!
I solved it by removing Purify
How can I use
preload
with this custom component?Hi ! Nice one, but it is possible to have this renderer for the defaut option visible before doing a search ?
just use the same mechanism for options() works like a charm
Amazing !
what if user want only name to be selected and in options it will show both name and email>
I can't get any of the markup working. I have the
allowHtml
but the select list is just rendered in plain formatting.you have to add getOptionLabelUsing and getOptionLabelsUsing
Amazing work ! Thank you so much !
you're welcome !
what if user want only name to be selected and in options it will show both name and email?
Perfect solution! But if I use select component with
allowHtml()
then reactivedisabled(fn () ..)
is not working :(THANKS, I used this on Filament3, it works like a charm - though I need to refactor and claen my code.
I am now looking to do similar with FILTERs, any ideas ?
Would this introduce an N+1?
Amazing work ! Thank you so much ! But how I can implement similar with relationship? e.g. ->relationship('product','name') ....