Tricks

Reuse categories, tags (or any other morph many relationship) form fields, table columns, actions on many resources

Oct 15, 2022
studiowizjo
Admin panel, Table builder, Form builder

If you got any morphs many model, eg. tags or categories, which you want to use on many resources, but don't want to repeat yourself, you can do as follows.

Trick shows how to:

  1. Generaly extract code to share form fields, table columns, actions and so on between resources
  2. Have ready to copy & paste solution to maintain tags or categories
  3. Make bulk action which allows to add (attach), remove (detach) or overwite (sync) related records

Form hints

So here is the meat:

create_tags_table.php:

Schema::create('tags', function (Blueprint $table) {
$table->id();
$table->string('name', 50);
$table->timestamps();
});

create_taggables_table.php.php:

Schema::create('taggables', function (Blueprint $table) {
$table->foreignId('tag_id');
$table->morphs('taggable');
});

YourTaggableModel.php:

public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}

MyResource.php:

use App\Filament\Resources\Traits\HasTags;
 
class MyResource extends Resource
{
use HasTags;
 
public static function form(Form $form): Form
{
return $form->schema([
//...other fields
self::formTagsField()
->columnSpan(['md' => 2, 'lg' => 3]), //you can add of course extra settings which you need in given resource
])->columns(['md' => 2, 'lg' => 3]);
}
 
public static function table(Table $table): Table
{
return $table
->columns([
//...other columns
self::tagsColumn(),
])
->bulkActions([
//...other bulk actions
self::changeTagsAction(),
]);
}

HasTags.php:

namespace App\Filament\Resources\Traits;
 
use App\Models\Tag;
use Illuminate\Support\Collection;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Select;
use Filament\Tables\Actions\BulkAction;
use Filament\Tables\Columns\TagsColumn;
use Filament\Forms\Components\TextInput;
 
trait HasTags
{
public static function formTagsField() : Select
{
return self::tagsField()
->relationship('tags', 'name');
}
 
public static function tagsField() : Select
{
return Select::make('tags')
->options(Tag::pluck('name', 'id'))
->multiple()
->searchable()
->preload()
->createOptionForm([
TextInput::make('name')
->lazy()
->afterStateUpdated(fn ($set, $state) => $set('name', ucfirst($state)))
->required(),
]);
}
 
public static function changeTagsAction() : BulkAction
{
return BulkAction::make('change_tags')
->action(function (Collection $records, array $data): void {
foreach ($records as $record) {
$record->tags()->{$data['action']}($data['tags']);
}
})
->form([
Grid::make()
->schema([
Select::make('action')
->label('For selected records')
->options([
'attach' => 'add',
'detach' => 'remove',
'sync' => 'overwrite',
])
->default('sync')
->required(),
 
self::tagsField(),
 
])->columns(2)
]);
}
 
public static function tagsColumn() : TagsColumn
{
return TagsColumn::make('tags.name')
->limit(3);
}
}

No comments yet…