JsContent
Package
filament/filament
Package Version
v4
Laravel Version
v12
Livewire Version
v3
PHP Version
8.4
Problem description
JsContent in a label causes error with validation message
setting the validationAttribute fixes it
Expected behavior
Validation message works
Steps to reproduce
have a label with JsContent with some form of validation. Try saving form.
In repo, open users/1/edit and try and save
Reproduction repository (issue will be closed if this is not valid)
https://github.com/andyov17/filamentjscontentbug
Relevant log output
mb_substr(): Argument #1 ($string) must be of type string, Filament\Schemas\JsContent given
This issue is caused here,
public function getValidationAttribute(): string
{
return $this->evaluate($this->validationAttribute) ?? Str::lcfirst($this->getLabel()); // getLabel() returns Htmlable, but it's being treated as a string.
}
In my opinion, there could be two possible fixes (please note this is just my suggestion — I’m not experienced enough to determine whether these fully make sense, but it’s my attempt to explore a solution):
- Make validation message render html and check here if it is instance of Htmlable then convert to Html.
- If Htmlable then retrieve validation attribute from component name instead if user want to customize validation attribute he can set validationattribute to match according to label.
//packages\forms\src\Components\Field.php
public function getLabel(): string | Htmlable | null
{
if (filled($label = $this->getBaseLabel())) {
return $label;
}
- $label = (string) str($this->getName())
- ->afterLast('.')
- ->kebab()
- ->replace(['-', '_'], ' ')
- ->ucfirst();
+ $label = $this->getDefaultLabel();
return $this->shouldTranslateLabel ? __($label) : $label;
}
+
+ public function getDefaultLabel(): string
+ {
+ return str($this->getName())
+ ->afterLast('.')
+ ->kebab()
+ ->replace(['-', '_'], ' ')
+ ->ucfirst();
+ }
//packages\forms\src\Components\Concerns\CanBeValidated.php
public function getValidationAttribute(): string
{
- return $this->evaluate($this->validationAttribute) ?? Str::lcfirst($this->getLabel());
+ return $this->evaluate($this->validationAttribute) ?? $this->getDefaultValidationAttribute();
}
+
+ public function getDefaultValidationAttribute(): string
+ {
+ $label = $this->getLabel();
+ return $label instanceof Htmlable
+ ? $this->getDefaultLabel()
+ : $label;
+ }
If this approach makes sense and doesn’t introduce any breaking changes, I’d be happy to submit a pull request with this fix.
@rohanAdhikari1 thanks for finding the root of the issue - I've also had a look and want to add some thoughts.
The purpose of the getValidationAttribute() method appears to be to get the :attribute value for the error message (e.g. the :attribute field is required.
Your suggestion of adding a getDefaultLabel() sounds sensible to me, an alternative might be for $this->evaludate() to be able to handle JsContent but... I'm not sure what it would do with it.
But thinking about it a little more - if your label is dynamic, your validation attribute probably wants to be dynamic anyway. So maybe it should be a requirement to set the validationAttribute when using JsContent for the label?
@danharrin what do you think?
Workaround
For anyone who needs a workaround before this is fixed, you can specify the Validation attribute on your component and the getValidationAttribute() won't try to evaluate the label:
use Filament\Forms\Components\TextInput;
use Filament\Schemas\JsContent;
TextInput::make('greetingResponse')
->validationAttribute('Greeting Response')
->label(JsContent::make(<<<'JS'
($get('name') === 'John Doe') ? 'Hello, John!' : 'Hello, stranger!'
JS
))
The fix is to use the default label (generated from attribute name) for the validation attribute when an Htmlable object is used as the label, still allowing validationAttribute() to be used to override the value.
Thanks Dan!
@rohanAdhikari1 are you up for making that PR based on Dan's suggestion?