F
Filamentā€¢2y ago
Tony

Problem with two select fields using same relationship

I have a polymorphic relationship of filters, Filters have a column describing its type, lets say A and B I want 2 select fields on related model forms, using the third parameter of the relationship() method, i scope the results to one kind (A) or another (B) Example:
Select::make('a')
->label('A')
->multiple()
->searchable()
->relationship('filters', 'name', fn (Builder $query) => $query->where('type', 'a')),
Select::make('b')
->label('B')
->multiple()
->searchable()
->relationship('filters', 'name', fn (Builder $query) => $query->where('type', 'b'))
Select::make('a')
->label('A')
->multiple()
->searchable()
->relationship('filters', 'name', fn (Builder $query) => $query->where('type', 'a')),
Select::make('b')
->label('B')
->multiple()
->searchable()
->relationship('filters', 'name', fn (Builder $query) => $query->where('type', 'b'))
The problem is that the last field on the form for the same relationship, in this case "b" is overwriting the value from the "a" select, i would need it to just add its values to the relationship, but not overwriting it, because of this problem, the only usable select field is the last one in the form, which has scoped values to only show one of the kinds of filters I want to separate the two kinds of filters in different select fields, i know that if i only used one field and didn't scope the options, it would work, but i want to know if there is any way to do it šŸ‘€
17 Replies
Dan Harrin
Dan Harrinā€¢2y ago
the only way to do it is with overriding saveRelationshipsUsing() on each so that it only syncs the relationships for that type use your ide to look at the saveRelationshipsUsing() implementation that is set by the relationship() method and tweak it as required
Tony
TonyOPā€¢2y ago
Thank you @Dan Harrin ! im going to check it out Hmm for some reason, when leaving empty both of the select inputs im getting the $state variable of the callbacks filled with id's that arent present visually in the select field
Dan Harrin
Dan Harrinā€¢2y ago
probably because the ids there arent scoped or something
Tony
TonyOPā€¢2y ago
Tony
TonyOPā€¢2y ago
sorry i was a bit slow to share the photo lol oh... i dont understand, ill take a look tho
Dan Harrin
Dan Harrinā€¢2y ago
oh that is weird yes but it looks like the ids are repeated
Tony
TonyOPā€¢2y ago
yeah pretty weird
Dan Harrin
Dan Harrinā€¢2y ago
yeah i dont know where those would come from maybe you need a custom loadStateFromRelationshipUsing() too
josef
josefā€¢2y ago
what exactly does pathology() on the query do?
Tony
TonyOPā€¢2y ago
its a local scope @josef it adds ->where('type', 'pathology') to the query yeah @Dan Harrin probably ill try that too thank you!
Daniel Plomp
Daniel Plompā€¢2y ago
Hi @bosphoramus. Did you managed to get this working? I'm looking at a similar thing here.
Tony
TonyOPā€¢2y ago
Yes @danielplomp Ill share the code sortly
Daniel Plomp
Daniel Plompā€¢2y ago
Thanks. Do you think that will also works with many-to-many, non-polymorphic?
Tony
TonyOPā€¢2y ago
Yup i think so @danielplomp
<?php

namespace App\Filament\Fields;

use App\Models\Filter;
use Filament\Forms\Components\Select;
use Illuminate\Database\Eloquent\Builder;

class TagSelect extends Select
{
public function setUp(): void
{
parent::setUp();

$this->multiple()
->searchable()
->relationship('filters', 'name', fn (Builder $query) => $query->tag())
->loadStateFromRelationshipsUsing(function (Select $component, $state): void {
if (filled($state)) {
return;
}

$relationship = $component->getRelationship();

$relatedModels = $relationship->where('type', 'tag')->getResults();
$component->state(
$relatedModels
->pluck($relationship->getRelatedKeyName())
->map(static fn ($key): string => strval($key))
->toArray(),
);
})
->saveRelationshipsUsing(function (Select $component, $state) {
$relationship = $component->getRelationship();

$allTagFilters = Filter::where('type', 'tag')->pluck('id');

$relationship->detach($allTagFilters);
$relationship->attach($state);
});
}
}
<?php

namespace App\Filament\Fields;

use App\Models\Filter;
use Filament\Forms\Components\Select;
use Illuminate\Database\Eloquent\Builder;

class TagSelect extends Select
{
public function setUp(): void
{
parent::setUp();

$this->multiple()
->searchable()
->relationship('filters', 'name', fn (Builder $query) => $query->tag())
->loadStateFromRelationshipsUsing(function (Select $component, $state): void {
if (filled($state)) {
return;
}

$relationship = $component->getRelationship();

$relatedModels = $relationship->where('type', 'tag')->getResults();
$component->state(
$relatedModels
->pluck($relationship->getRelatedKeyName())
->map(static fn ($key): string => strval($key))
->toArray(),
);
})
->saveRelationshipsUsing(function (Select $component, $state) {
$relationship = $component->getRelationship();

$allTagFilters = Filter::where('type', 'tag')->pluck('id');

$relationship->detach($allTagFilters);
$relationship->attach($state);
});
}
}
@danielplomp I extracted the custom component to a class so in the resource i just had to do: TagSelect::make('tag')->label(Town) i have another component like this for the other type of filters, TownSelect its the same, just changing Tag for Town you can put both in the same resource because they are tweaked so they only sync the filters of its own type so they wont collide, or overwrite themselves its a bit hard to explain
Daniel Plomp
Daniel Plompā€¢2y ago
Thanks man. Iā€™m going to take a look šŸ‘šŸ¼ I've got it working. Thanks! I created two helpers so I won't repeat myself for every other type of Article:
class ArticleSelectHelper
{
public static function makeSelect(string $name, int $articleTypeId, string $label): Select
{
$select = Select::make($name)->label($label);
self::configureSelect($select, $articleTypeId);
return $select;
}

public static function configureSelect(Select $component, int $articleTypeId): void
{
$component->multiple()
->searchable()
->preload()
->relationship('articles', 'title', fn (Builder $query) => $query->where('article_type_id', '=', $articleTypeId))
->loadStateFromRelationshipsUsing(function (Select $component, $state) use ($articleTypeId): void {
if (filled($state)) {
return;

}

$relationship = $component->getRelationship();
$relatedModels = $relationship->where('article_type_id', '=', $articleTypeId)->getResults();
$component->state(
$relatedModels
->pluck($relationship->getRelatedKeyName())
->map(static fn ($key): string => strval($key))
->toArray()
);
})
->saveRelationshipsUsing(function (Select $component, $state) use ($articleTypeId) {
$relationship = $component->getRelationship();
$allArticlesFilter = Article::where('article_type_id', '=', $articleTypeId)->pluck('id');
$relationship->detach($allArticlesFilter);
$relationship->attach($state);
});
}
}
class ArticleSelectHelper
{
public static function makeSelect(string $name, int $articleTypeId, string $label): Select
{
$select = Select::make($name)->label($label);
self::configureSelect($select, $articleTypeId);
return $select;
}

public static function configureSelect(Select $component, int $articleTypeId): void
{
$component->multiple()
->searchable()
->preload()
->relationship('articles', 'title', fn (Builder $query) => $query->where('article_type_id', '=', $articleTypeId))
->loadStateFromRelationshipsUsing(function (Select $component, $state) use ($articleTypeId): void {
if (filled($state)) {
return;

}

$relationship = $component->getRelationship();
$relatedModels = $relationship->where('article_type_id', '=', $articleTypeId)->getResults();
$component->state(
$relatedModels
->pluck($relationship->getRelatedKeyName())
->map(static fn ($key): string => strval($key))
->toArray()
);
})
->saveRelationshipsUsing(function (Select $component, $state) use ($articleTypeId) {
$relationship = $component->getRelationship();
$allArticlesFilter = Article::where('article_type_id', '=', $articleTypeId)->pluck('id');
$relationship->detach($allArticlesFilter);
$relationship->attach($state);
});
}
}
Now I can just call this method from the Resource:
ArticleSelectHelper::makeSelect('interviews', 4, 'Select releated interviews'),
ArticleSelectHelper::makeSelect('youtubes', 6, 'Select related Youtube video\'s'),
ArticleSelectHelper::makeSelect('interviews', 4, 'Select releated interviews'),
ArticleSelectHelper::makeSelect('youtubes', 6, 'Select related Youtube video\'s'),
The only think I would like to know is how I could filter out the already selected items, when you open the Select dropdown. Maybe @Dan Harrin has an idea?
Dan Harrin
Dan Harrinā€¢2y ago
its a bug we know about
Daniel Plomp
Daniel Plompā€¢2y ago
This is something the Relationship already does automatically. Ah ok!

Did you find this page helpful?