FileUpload (avatar) when record is in other model

I have made a Product model and a ProductAsset model. Using mutateFormDataBeforeCreate() and afterCreate(), I create a new ProductAsset with "is_primary" flag to tell the app that the uploaded image is the product's "avatar". Then I can create more assets using a RelationManager. So far so good. This is my form:
return $form
->columns(12)
->schema([
Group::make()->schema([
FileUpload::make('image')
->label('Imagen')
->avatar()
->required(),
])
->columnSpan(2),
Group::make()->schema([
TextInput::make('name')
->label('Nombre')
->required(),
Textarea::make('description')
->label('Descripción')
->required(),
Select::make('category_id')
->label('Categoría')
->relationship(name: 'category', titleAttribute: 'name')
->required(),
Toggle::make('is_active')
->default(true)
->label('Activo'),
])
->columnSpan(10),
]);
return $form
->columns(12)
->schema([
Group::make()->schema([
FileUpload::make('image')
->label('Imagen')
->avatar()
->required(),
])
->columnSpan(2),
Group::make()->schema([
TextInput::make('name')
->label('Nombre')
->required(),
Textarea::make('description')
->label('Descripción')
->required(),
Select::make('category_id')
->label('Categoría')
->relationship(name: 'category', titleAttribute: 'name')
->required(),
Toggle::make('is_active')
->default(true)
->label('Activo'),
])
->columnSpan(10),
]);
When viewing/editing that product, how I load that asset on the avatar field? It's currently showing empty field. However, the image shows in the assets.
No description
No description
10 Replies
Mohamed Ayaou
Mohamed Ayaou2mo ago
What is the issue exactly?
awcodes
awcodes2mo ago
Since it’s working on your table it seems that the name in the make() on the form is wrong. Maybe FileUpload::make(‘avatar’) But if you have a custom saving strategy then you’re also going to need a custom hydration strategy.
victorcamposramirez
There's no 'avatar' field on the database table really, is a related model ProductAsset I've done a workaround
FileUpload::make('image')
->label('Imagen')
->avatar()
->placeholder(function ($record) {
if (! $record || ! $record->assets->first()) {
return false;
}

$mainImage = $record->assets->first();
$publicPath = asset('storage/' . $mainImage->path);

return <<<HTML
<img src="{$publicPath}" alt="{$record->name}" class="cropper-crop-box">
HTML;
})
->required(),
FileUpload::make('image')
->label('Imagen')
->avatar()
->placeholder(function ($record) {
if (! $record || ! $record->assets->first()) {
return false;
}

$mainImage = $record->assets->first();
$publicPath = asset('storage/' . $mainImage->path);

return <<<HTML
<img src="{$publicPath}" alt="{$record->name}" class="cropper-crop-box">
HTML;
})
->required(),
It's not the most elegant way tho
awcodes
awcodes2mo ago
And it shouldn’t be necessary. Is ‘image’ the relationship?
victorcamposramirez
The relationship in the model is defined as:
public function assets()
{
return $this->hasMany(ProductAsset::class);
}
public function assets()
{
return $this->hasMany(ProductAsset::class);
}
But I need only the one with the field "is_primary" = true as a placeholder
awcodes
awcodes2mo ago
Ok, that’s why it’s empty on the edit page. You are using ‘image’ as the field on the model, which doesn’t exist. So, use ->afterStateHydrated() to get the assets relationship and return the path for the first item on the relationship. Basically, you have a custom write but not providing the inverse of a custom read. Hope that makes sense.
victorcamposramirez
Ok I'll try and let you know the results 😉 Thanks! I'll admit I don't know how to set the input to the already uploaded image. So far I did this:
FileUpload::make('image')
->label('Imagen')
->avatar()
->afterStateHydrated(function (FileUpload $fileUpload, $record) {
if ( $record && $record->assets->where('is_primary', true)->count() > 0) {
$main_image = $record->assets->where('is_primary', true)->first();
$imagePath = asset("/storage/" . $main_image->path);
$fileUpload->placeholder(<<<HTML
<img src="$imagePath" alt="Imagen principal" class="cropper-crop-box">
HTML);
}
})
->required(),
FileUpload::make('image')
->label('Imagen')
->avatar()
->afterStateHydrated(function (FileUpload $fileUpload, $record) {
if ( $record && $record->assets->where('is_primary', true)->count() > 0) {
$main_image = $record->assets->where('is_primary', true)->first();
$imagePath = asset("/storage/" . $main_image->path);
$fileUpload->placeholder(<<<HTML
<img src="$imagePath" alt="Imagen principal" class="cropper-crop-box">
HTML);
}
})
->required(),
That's a better approach but I think I'm missing some method or concept to link the field state to the file without cheating the placeholder.
victorcamposramirez
It's working tho
No description
toeknee
toeknee2mo ago
That's a very messy approach imho
victorcamposramirez
I agree with you. Do you have any better approach? Here is my (not so better) code right now btw
FileUpload::make('image')
->label('Imagen')
->avatar()
->placeholder(function ($record) {
if ($record && $record->mainImage()) {
$main_image = $record->mainImage();
$imagePath = asset("/storage/" . $main_image->path);
return <<<HTML
<img src="$imagePath" alt="Imagen principal" class="cropper-crop-box">
HTML;
}
})
->afterStateHydrated(function (Forms\Set $set, $record) {
if ($record?->mainImage()?->path) {
$imagePath = $record->mainImage()->path;
$set('image', ["/storage/".$imagePath]);
}
})
->required(),
FileUpload::make('image')
->label('Imagen')
->avatar()
->placeholder(function ($record) {
if ($record && $record->mainImage()) {
$main_image = $record->mainImage();
$imagePath = asset("/storage/" . $main_image->path);
return <<<HTML
<img src="$imagePath" alt="Imagen principal" class="cropper-crop-box">
HTML;
}
})
->afterStateHydrated(function (Forms\Set $set, $record) {
if ($record?->mainImage()?->path) {
$imagePath = $record->mainImage()->path;
$set('image', ["/storage/".$imagePath]);
}
})
->required(),
This way it shows the image, let the user change it, almost working perfect. But, It won't show the new image until I save changes on the form.

Did you find this page helpful?