John
John
FFilament
Created by John on 4/10/2024 in #❓┊help
Moving route registration removes theme css styling
I want to dynamically determine resource pages. Old setup works:
// app/Filament/Resources/RequestResource.php
public static function getPages(): array
{
return array_filter([
'index' => ListRequests::route('/'),
'create' => CreateRequest::route('/create'),
'edit' => RequestWorkflow::route('/{record:uuid}/edit'),
'stats' => RequestStats::route('/stats'),
]);
}
// app/Filament/Resources/RequestResource.php
public static function getPages(): array
{
return array_filter([
'index' => ListRequests::route('/'),
'create' => CreateRequest::route('/create'),
'edit' => RequestWorkflow::route('/{record:uuid}/edit'),
'stats' => RequestStats::route('/stats'),
]);
}
But when I remove the 'stats' line, and add this:
// routes/web.php
Route::get('requests/stats', RequestStats::class)->name('filament.resources.requests.stats');
// routes/web.php
Route::get('requests/stats', RequestStats::class)->name('filament.resources.requests.stats');
the page still loads, but the Filament theme styling is gone. - No console errors - No laravel errors - Livewire still works fine - route::list gives same result for the stats route, before and after the change - Custom theme styling gone - Also, Vite stops hot reloading Any help is appreciated.
9 replies
FFilament
Created by John on 2/29/2024 in #❓┊help
Why are disabled() fields validated? (e.g. required())
I'm building a workflow app with multiple forms. Sometimes a user can send back to the previous step. In that case, I only want the user to fill in a reason text field, and use a custom form action to send it back. Not bothering with validation of the rest of the form. That all works fine, except for the validation part. I reckoned that if I disabled() the fields, they would skip validation. I believe that's how standard HTML forms work. But that doesn't seem the case in Filament. Can I skip validation in some other way?
4 replies
FFilament
Created by John on 1/29/2024 in #❓┊help
Middleware for "livewire.message"
In my config/filament.php I have set some additional middleware:
'middleware' => [
'auth' => [
Authenticate::class,
CheckProfile::class,
Require2FA::class,
],
'base' => [
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartSession::class,
AuthenticateSession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
SubstituteBindings::class,
DispatchServingFilamentEvent::class,
MirrorConfigToSubpackages::class,
NeedsTenant::class,
EnsureValidTenantSession::class,
],
],
'middleware' => [
'auth' => [
Authenticate::class,
CheckProfile::class,
Require2FA::class,
],
'base' => [
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartSession::class,
AuthenticateSession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
SubstituteBindings::class,
DispatchServingFilamentEvent::class,
MirrorConfigToSubpackages::class,
NeedsTenant::class,
EnsureValidTenantSession::class,
],
],
The middleware is applied to "normal" Filament requests, but NOT to XHR liveware.messages requests. E.g. changing a filter in a overview page to reload the records. Is this an auth vulnerability? Should I also add them in a generic Livewire place? Or on specific routes/components? (I imagine this will break the login page since it's also Livewire but there is no authenticated user yet)
7 replies
FFilament
Created by John on 1/16/2024 in #❓┊help
Using form schema for PDF export
I'm building an application with a LOT of forms. I also need a full PDF export containing multiple forms. The form definition is in a Filament form schema, obviously. The PDF export is built separately. Can I extend the schema or form components to include PDF render functionality? That way I could just throw the same schema at it, and it can return the html needed for PDF print. I'd only need one source of the form schema, which is faster to build and schema changes wouldn't have to be applied twice. Maybe some plugin does a similar extension?
11 replies
FFilament
Created by John on 11/13/2023 in #❓┊help
Alpine wire entangle causes "Uncaught (in promise) TypeError: Cannot read properties of null"
I'm using a custom form component, based on Textarea. As soon as I put this in:
x-data="{ state: $wire.entangle('{{ $getStatePath() }}').defer }"
x-data="{ state: $wire.entangle('{{ $getStatePath() }}').defer }"
and I click a button on the page, I get:
Uncaught (in promise) TypeError: Cannot read properties of null (reading 'child_description')
at livewire.js?id=90730a3b0e7144480175:14:130978
Uncaught (in promise) TypeError: Cannot read properties of null (reading 'child_description')
at livewire.js?id=90730a3b0e7144480175:14:130978
That "child_description" is inside a JSON field:
Textarea::make('viewpoint.child_description')
Textarea::make('viewpoint.child_description')
When the field has a value, the error is gone. But initially, the JSON is null. (empty string doesn't solve the problem) Any idea how to solve this?
4 replies
FFilament
Created by John on 11/6/2023 in #❓┊help
Modify text area content with Alpine
I'd like my text area to automatically add this line in the content: [Edited on 11-6-2023] on blur. (Except when the last line is exactly that already.) I already have some autosave magic going on in afterStateUpdated, so I'd like to have this functionality client side with Alpine. Do I need a custom field? Or can I add Alpine to a standard Textarea component? I see ->extraAlpineAttributes() but I can't find that in the docs. I tried playing around with a custom field, but I have no idea how to change the value while playing nice with Alpine/Filamen state.
2 replies
FFilament
Created by John on 10/3/2023 in #❓┊help
Custom field with multiple checkboxes
No description
22 replies
FFilament
Created by John on 9/25/2023 in #❓┊help
Tenant depending colours from database
I want to set the primary colour based on the current tenant. The colours are set in the database. How can I include them? I guess not here:
module.exports = {
theme: {
extend: {
colors: {
primary: ...
module.exports = {
theme: {
extend: {
colors: {
primary: ...
since this is built once using npm. But I wouldn't know how then.
12 replies
FFilament
Created by John on 9/8/2023 in #❓┊help
Passing multiple variables to a ViewField
What I'm using now:
ViewField::make('myViewField')
->formatStateUsing(function ($record) {
return [
'href' => route('requests.pdf', ['request' => $record]),
'caption' => __('request.pdf.link')
];
})
->view('forms.components.download')
ViewField::make('myViewField')
->formatStateUsing(function ($record) {
return [
'href' => route('requests.pdf', ['request' => $record]),
'caption' => __('request.pdf.link')
];
})
->view('forms.components.download')
<x-dynamic-component
:component="$getFieldWrapperView()"
:id="$getId()"
:label="$getLabel()"
:label-sr-only="$isLabelHidden()"
:helper-text="$getHelperText()"
:hint="$getHint()"
:hint-action="$getHintAction()"
:hint-color="$getHintColor()"
:hint-icon="$getHintIcon()"
:required="$isRequired()"
:state-path="$getStatePath()"
>
<div x-data="{ state: $wire.entangle('{{ $getStatePath() }}').defer }">
<a href="{{ $getState()['href'] }}" class="flex gap-1">
<x-icon-gmdi-file-download-o class="w-6 h-6"/>{{ $getState()['caption'] }}
</a>
</div>
</x-dynamic-component>
<x-dynamic-component
:component="$getFieldWrapperView()"
:id="$getId()"
:label="$getLabel()"
:label-sr-only="$isLabelHidden()"
:helper-text="$getHelperText()"
:hint="$getHint()"
:hint-action="$getHintAction()"
:hint-color="$getHintColor()"
:hint-icon="$getHintIcon()"
:required="$isRequired()"
:state-path="$getStatePath()"
>
<div x-data="{ state: $wire.entangle('{{ $getStatePath() }}').defer }">
<a href="{{ $getState()['href'] }}" class="flex gap-1">
<x-icon-gmdi-file-download-o class="w-6 h-6"/>{{ $getState()['caption'] }}
</a>
</div>
</x-dynamic-component>
This works just fine. Just wondering if others are doing / would do this differently...
11 replies
FFilament
Created by John on 8/31/2023 in #❓┊help
Spatie multitenancy, resolve resource page to tenant specific class
In my resource:
use App\Filament\Resources\RequestResource\Pages\CreateRequest;
use App\Filament\Resources\RequestResource\Pages\ListRequests;

class RequestResource extends App\Filament\BaseResource
{
// [...]

public static function getPages(): array
{
return [
'index' => ListRequests::route('/'),
'create' => CreateRequest::route('/create'),
// TODO Bind current tenant dynamically
'edit' => \App\Classes\Tenant\BredaVo\RequestWorkflow::route('/{record}/edit'),
];
}
}
use App\Filament\Resources\RequestResource\Pages\CreateRequest;
use App\Filament\Resources\RequestResource\Pages\ListRequests;

class RequestResource extends App\Filament\BaseResource
{
// [...]

public static function getPages(): array
{
return [
'index' => ListRequests::route('/'),
'create' => CreateRequest::route('/create'),
// TODO Bind current tenant dynamically
'edit' => \App\Classes\Tenant\BredaVo\RequestWorkflow::route('/{record}/edit'),
];
}
}
I'd like to switch that BredaVO part for the current tenant. The current tenant is determined with this: https://spatie.be/docs/laravel-multitenancy/v3/basic-usage/automatically-determining-the-current-tenant But... Filament getPages() runs before Multitenancy DomainTenantFinder, so at this point I don't have access to the current tenant yet. If this proves too difficult to figure out or fix, I might use a generic RequestWorkflow class, and redirect certain method calls to a tenant specific class. But there are methods specifically created for a tenant, so I'd need some __call() magic to redirect those. I'm afraid it will get quite messy. I've tried this approach, but it got very messy indeed. So I'm still looking for a solution or workaround.
27 replies
FFilament
Created by John on 8/17/2023 in #❓┊help
Filament styling missing after upgrade
I'm trying to upgrade to v3. I followed the steps. I created a new v3 project for comparison. I'm seeing http://localhost/css/filament/filament/app.css?v=3.0.16.0 being fetched by the browser in the new project, but not in the upgraded one. I can't find the place where this is included / where I should add something. Any idea?
31 replies
FFilament
Created by John on 7/17/2023 in #❓┊help
Handling multiple forms, but one at a time
I changed my app's main workflow from using a customised Wizard to building my own component.
class RequestWorkflow extends Page implements HasForms
{
use InteractsWithForms;
use InteractsWithRecord;

protected static string $resource = RequestResource::class;

protected static string $view = 'filament.resources.request-resource.pages.request-workflow';

public array $data;

private array $steps = [];

public string $currentStep = 'student-data';

public function mount($record): void
{
$this->record = $this->resolveRecord($record);
$data = $this->getRecord()->attributesToArray();
$this->form->fill($data);
}

protected function getFormSchema(): array
{
return $this->getSteps()[$this->currentStep]->getSchema();
}

protected function getFormModel(): Model|string|null
{
return $this->record;
}

protected function getFormStatePath(): ?string
{
return 'data';
}

public function nextStep()
{
$this->submit();
$this->currentStep = 'arrangement'; // TODO: determine actual next step
}

public function getSteps()
{
return [
'student-data' => new StudentDataForm,
'arrangement' => new ArrangementForm,
];
}

public function setStep($step)
{
$this->currentStep = $step;
}

public function submit()
{
$data = $this->form->getState();
$this->getRecord()->update($data);
}
}
class RequestWorkflow extends Page implements HasForms
{
use InteractsWithForms;
use InteractsWithRecord;

protected static string $resource = RequestResource::class;

protected static string $view = 'filament.resources.request-resource.pages.request-workflow';

public array $data;

private array $steps = [];

public string $currentStep = 'student-data';

public function mount($record): void
{
$this->record = $this->resolveRecord($record);
$data = $this->getRecord()->attributesToArray();
$this->form->fill($data);
}

protected function getFormSchema(): array
{
return $this->getSteps()[$this->currentStep]->getSchema();
}

protected function getFormModel(): Model|string|null
{
return $this->record;
}

protected function getFormStatePath(): ?string
{
return 'data';
}

public function nextStep()
{
$this->submit();
$this->currentStep = 'arrangement'; // TODO: determine actual next step
}

public function getSteps()
{
return [
'student-data' => new StudentDataForm,
'arrangement' => new ArrangementForm,
];
}

public function setStep($step)
{
$this->currentStep = $step;
}

public function submit()
{
$data = $this->form->getState();
$this->getRecord()->update($data);
}
}
The form schema is determined dynamically, based on the current step. The nextStep action should save the current step and move on to the next. The problem however is, it uses the cached form, resulting in the wrong (old) form being rendered. (This does not occur when I switch tabs using setStep. I don't understand why.) Any advise on how to handle this?
11 replies
FFilament
Created by John on 7/13/2023 in #❓┊help
Flash of unstyled text (FOUT) with self-hosted font
I'm facing a case of "Flash of unstyled text (FOUT)". I'm using a custom font, self-hosted:
// filament.css
@font-face {
font-family: 'Concert One';
font-style: normal;
font-weight: 400;
font-display: block; // previously 'swap'
src: url(../fonts/Concert_One/ConcertOne-Regular.ttf) format('truetype');
}
// filament.css
@font-face {
font-family: 'Concert One';
font-style: normal;
font-weight: 400;
font-display: block; // previously 'swap'
src: url(../fonts/Concert_One/ConcertOne-Regular.ttf) format('truetype');
}
The vite build will parse that url and make it available like so: http://localhost/build/assets/ConcertOne-Regular-56d2074a.ttf. The issue I'm facing is that the text flickers from browser default to my custom font. I've changed font-display from swap to block. It no longer flickers, but it takes about half a second to render. If I add this:
Filament::registerRenderHook(
'head.start',
fn() => Blade::render('<link rel="preload" href="http://localhost/build/assets/ConcertOne-Regular-56d2074a.ttf" as="font" type="font/ttf" crossorigin="anonymous">'),
);
Filament::registerRenderHook(
'head.start',
fn() => Blade::render('<link rel="preload" href="http://localhost/build/assets/ConcertOne-Regular-56d2074a.ttf" as="font" type="font/ttf" crossorigin="anonymous">'),
);
the problem is solved. But... that href is dynamically built by vite using a random hash. Is there another way to prevent this?
14 replies
FFilament
Created by John on 7/12/2023 in #❓┊help
Wizard steps / pages - hybrid
My app includes a workflow with a lot of steps. I started out using a Wizard. But I'm running into some problems now; - apparently there are steps that include some sub-wizard inside, where you can add related items, including custom buttons to send notifications, etc. - steps need custom actions, not only on the last step - steps need to be saved separately All in all, I figured that a Wizard isn't suited for my case. So I switched to a bunch of custom Resource Pages. Every page contains what used to be a Wizard Step. And on the left side, tab links to all the other pages. Every "step" is a simple <a href> link to another Page. Which of course means full page reloads and content jumping around. I'm afraid I'm not very experienced in Livewire and even less in Alpine. Would it be hard to refactor my current approach to mimic the Wizard behaviour?
16 replies
FFilament
Created by John on 7/10/2023 in #❓┊help
Using filtered result in aggregate function
I'm using ListRecords function getTableContentFooter to show the average time between two datetime fields. Like so:
{{ $this
->getTableQuery()
->selectRaw('AVG(TIMESTAMPDIFF(DAY, date_request, date_closed)) as average_time')
->whereNotNull('date_request')
->whereNotNull('date_closed')
->first()
->average_time }}
{{ $this
->getTableQuery()
->selectRaw('AVG(TIMESTAMPDIFF(DAY, date_request, date_closed)) as average_time')
->whereNotNull('date_request')
->whereNotNull('date_closed')
->first()
->average_time }}
If the user applies some filters to the overview, they are not reflected in my average. How can I apply the user applied filters to my average query? Note: I don't want to apply pagination (average should be of complete filtered result, not only current page).
3 replies
FFilament
Created by John on 5/25/2023 in #❓┊help
Icons best practices
I'd like more icon options to choose from. So I investigated some. And what I'm doing now is: - Search icon here: https://blade-ui-kit.com/blade-icons?search=list&set=1#search - If it's a icon package I don't have yet, install it with this: https://github.com/blade-ui-kit/blade-icons - Profit Is this the way to go? Also, I think I can't use Heroicon v2 icons, because Filament 2 is depending on v1 icons? Or is there a way around that? Any other tips in general for me and others that wish to use more icons than the default heroicons?
16 replies
FFilament
Created by John on 5/9/2023 in #❓┊help
Need advice on recursive multi checkbox edit page
15 replies
FFilament
Created by John on 4/25/2023 in #❓┊help
Duplicate queries despite closures and static variables
I changed to closures, which reduced duplicate queries. Then I added some local caching for the current request by using static variables, further reducing duplicate queries. Still, the query inside options() is called 3 times by hidden(). Is there any way to get rid of these duplicate queries?
Select::make('arrangement_id')
->options(function (Closure $get) {
static $result = null;
$result ??= Arrangement
::forArrangementTypeId($get('arrangement_type_id'))
->pluck('arrangement', 'arrangement_id');
return $result;
})
->required()
->hidden(function (Select $component) {
static $result = null;
$result ??= count($component->getOptions());
return $result == 0;
})
Select::make('arrangement_id')
->options(function (Closure $get) {
static $result = null;
$result ??= Arrangement
::forArrangementTypeId($get('arrangement_type_id'))
->pluck('arrangement', 'arrangement_id');
return $result;
})
->required()
->hidden(function (Select $component) {
static $result = null;
$result ??= count($component->getOptions());
return $result == 0;
})
26 replies
FFilament
Created by John on 4/24/2023 in #❓┊help
Duplicate queries using custom filter
This:
Filter::make('arrangement_id')
->form([
Select::make('arrangement_id')->options(
fn() => Arrangement::pluck('arrangement', 'arrangement_id')
)->label(__('model.Arrangement.1'))
])
Filter::make('arrangement_id')
->form([
Select::make('arrangement_id')->options(
fn() => Arrangement::pluck('arrangement', 'arrangement_id')
)->label(__('model.Arrangement.1'))
])
produces 1 query:
select `arrangement`, `arrangement_id` from `ctlv__arrangement`
select `arrangement`, `arrangement_id` from `ctlv__arrangement`
But if I change the closure to a simple pluck:
/*fn() => */Arrangement::pluck('arrangement', 'arrangement_id')
/*fn() => */Arrangement::pluck('arrangement', 'arrangement_id')
I get duplicate queries. Before the query above, the exact same query is executed 18 times. I cannot relate the number 18 to anything. It's not the number of select options, or records in the result, or anything else I could think of. If I do the same /* fn() => */ on another filter, the same thing happens and I end up with 36 duplicate queries. Backtrace of the single query:
16. /app/Filament/Resources/RequestResource.php:69
17. /vendor/filament/filament/src/Resources/Pages/ListRecords.php:88
18. /vendor/filament/filament/src/Resources/Pages/ListRecords.php:59
19. /vendor/filament/filament/src/Resources/Pages/ListRecords.php:400
21. /vendor/spatie/invade/src/Invader.php:50
16. /app/Filament/Resources/RequestResource.php:69
17. /vendor/filament/filament/src/Resources/Pages/ListRecords.php:88
18. /vendor/filament/filament/src/Resources/Pages/ListRecords.php:59
19. /vendor/filament/filament/src/Resources/Pages/ListRecords.php:400
21. /vendor/spatie/invade/src/Invader.php:50
Backtrace of the duplicated query:
16. /app/Filament/Resources/RequestResource.php:69
17. /vendor/filament/filament/src/Resources/Pages/ListRecords.php:88
18. /vendor/filament/filament/src/Resources/Pages/ListRecords.php:59
19. /vendor/filament/filament/src/Resources/Pages/ListRecords.php:390
20. /vendor/filament/filament/src/Resources/Pages/ListRecords.php:395
16. /app/Filament/Resources/RequestResource.php:69
17. /vendor/filament/filament/src/Resources/Pages/ListRecords.php:88
18. /vendor/filament/filament/src/Resources/Pages/ListRecords.php:59
19. /vendor/filament/filament/src/Resources/Pages/ListRecords.php:390
20. /vendor/filament/filament/src/Resources/Pages/ListRecords.php:395
protected function getTableReorderColumn(): ?string
{
return $this->getResourceTable()->getReorderColumn(); // <-- this is line 390
}

protected function isTableReorderable(): bool
{
return filled($this->getTableReorderColumn()) && static::getResource()::canReorder(); // <-- this is line 395
}
protected function getTableReorderColumn(): ?string
{
return $this->getResourceTable()->getReorderColumn(); // <-- this is line 390
}

protected function isTableReorderable(): bool
{
return filled($this->getTableReorderColumn()) && static::getResource()::canReorder(); // <-- this is line 395
}
It's not a problem to always use a closure, I'm just trying to understand what's going on and prevent similar situations.
6 replies
FFilament
Created by John on 4/18/2023 in #❓┊help
How to add a custom action to my custom Wizard?
I've implemented a CustomWizard extending the Filament Wizard. Including my own blade file. Next to the {{ $getSubmitAction() }} I'd like some additional actions. If I try to include this action there, the button is added, but the ->action() inside never gets called. Giving it a ->url() works, but I'd prefer to solve this the Livewire way. Another option would be to add additional submit buttons, but I'd have to know how to distinguish them in the backend. Any help is appreciated.
4 replies