F
Filament5d ago
Nico

Set multiple properties on the same field

What I am trying to do: I have a company model which contains the following fields: address, city, zipcode, country and location (postgres geography column: latitude, longitude). I'm attempting to use a Google Maps autocomplete to fill out all of the models on the property from what is returned from the autocomplete field. I would just like to know if there is a simple way to send multiple properties to the view and receive multiple properties back from the view when saving. What I did: I've tried some of the plugins that already exist but I haven't had success configuring them to work exactly how I would like it. My issue/the error: The current solution works fine when editing an existing company but when it's used to create a new the dehydrateStateUsing doesn't seem to apply the properties correctly. Code: The TextInput used:
TextInput::make('address')
->label('Address')
->view('forms.components.address-autocomplete')
->afterStateHydrated(static function (TextInput $component) {
$company = $component->getModelInstance();

$component->state([
'address' => $company->address,
'city' => $company->city,
'zipcode' => $company->zipcode,
'country' => $company->country,
'location' => $company->location?->toJson(),
]);
})
->dehydrateStateUsing(static function (TextInput $component, string|array $state, Set $set) {
if (is_string($state)) {
return $state;
}

$company = $component->getModelInstance();
$company->address = $state['address'] ?? null;
$company->city = $state['city'] ?? null;
$company->zipcode = $state['zipcode'] ?? null;
$company->country = $state['country'] ?? null;
$company->location = Point::fromArray($state['location']);

return $state['address'];
}),
TextInput::make('address')
->label('Address')
->view('forms.components.address-autocomplete')
->afterStateHydrated(static function (TextInput $component) {
$company = $component->getModelInstance();

$component->state([
'address' => $company->address,
'city' => $company->city,
'zipcode' => $company->zipcode,
'country' => $company->country,
'location' => $company->location?->toJson(),
]);
})
->dehydrateStateUsing(static function (TextInput $component, string|array $state, Set $set) {
if (is_string($state)) {
return $state;
}

$company = $component->getModelInstance();
$company->address = $state['address'] ?? null;
$company->city = $state['city'] ?? null;
$company->zipcode = $state['zipcode'] ?? null;
$company->country = $state['country'] ?? null;
$company->location = Point::fromArray($state['location']);

return $state['address'];
}),
The address autocomplete blade view is attached
4 Replies
toeknee
toeknee5d ago
View's are abit tricker because they are designed to render data not send it back, that's handled on the TextInput class it'self. Ideally you would therefore extend the TextInput field and have the properties on the field to store them?
Nico
NicoOP5d ago
Thanks for the response 👌 I've attempted that but it didn't really seem to make any difference. I'm also able to sent the data back fine (dd() in dehydrateStateUsing returns all the data) but it's just not being set on the model when saved during a create. How would I set multiple properties using a extended TextInput class?
toeknee
toeknee4d ago
I haven't done it myself, but fields are just livewire components so you tend to add them to the component class. I suspect when it's new the properties don't seem to exist so are not being passed back after mount if that makes sense?
Nico
NicoOP4d ago
But don't the TextInput only have access to the single field? Which method on the extended TextInput class would I use to actually set multiple fields on the model during save? The properties are passed back fine I believe as a dd($state) in the dehydrateStateUsing returns all the properties currently. But the code to actually set it on the model just isn't being applied. It seems like the model returned from getModelInstance is not being used during create, only update. $company = $component->getModelInstance(); $company->address = $state['address'] ?? null; $company->city = $state['city'] ?? null; Sorry for the many questions. I've looked everywhere for an answer to this but can't really find anything specific. We're currently using Nova (migrating to Filament) where it's as simple as doing this in a Field class:
public function resolve($resource, $attribute = null): void
{
$this->withMeta([
'address' => $resource->address,
'zipcode' => $resource->zipcode,
'city' => $resource->city,
'country' => $resource->country,
'googleMapsApiKey' => config('services.google_maps.key'),
]);

parent::resolve($resource, $attribute);
}

protected function fillAttributeFromRequest(NovaRequest $request, $requestAttribute, $model, $attribute)
{
if ($request->exists($requestAttribute)) {
$value = json_decode($request->input($requestAttribute));
if (!$value) {
return;
}

$model->{$attribute} = new Point($value->latitude, $value->longitude);
$model->address = $request->input('address');
$model->zipcode = $request->input('zipcode');
$model->city = $request->input('city');
$model->country = $request->input('country');
}
}
public function resolve($resource, $attribute = null): void
{
$this->withMeta([
'address' => $resource->address,
'zipcode' => $resource->zipcode,
'city' => $resource->city,
'country' => $resource->country,
'googleMapsApiKey' => config('services.google_maps.key'),
]);

parent::resolve($resource, $attribute);
}

protected function fillAttributeFromRequest(NovaRequest $request, $requestAttribute, $model, $attribute)
{
if ($request->exists($requestAttribute)) {
$value = json_decode($request->input($requestAttribute));
if (!$value) {
return;
}

$model->{$attribute} = new Point($value->latitude, $value->longitude);
$model->address = $request->input('address');
$model->zipcode = $request->input('zipcode');
$model->city = $request->input('city');
$model->country = $request->input('country');
}
}

Did you find this page helpful?