Filament plugin for working with tree data: treeselect input and treeview page. Based on Trilist package.
Do you like Filament Trilist? Please support me via Boosty.
| Filament version | Package version |
|---|---|
| ^5.x | ^1.1.x |
| ^4.x | 1.x.x |
| ^3.x | 0.5.x |
You can install the package via composer:
composer require beholdr/filament-trilist
Optionally, you can publish the views using
php artisan vendor:publish --tag="filament-trilist-views"
You can use hierarchical data from any source when it follows format:
[
['id' => 'ID', 'label' => 'Item label', 'children' => [
['id' => 'ID', 'label' => 'Item label', 'children' => [...]],
...
]
]
For example, you can use special library like staudenmeir/laravel-adjacency-list to get tree data:
Category::tree()->get()->toTree()
Or use custom relationship schema and methods, even with ManyToMany (multiple parents) relationship.
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('professions', function (Blueprint $table) {
$table->id();
$table->string('label');
});
Schema::create('profession_profession', function (Blueprint $table) {
$table->primary(['parent_id', 'child_id']);
$table->foreignId('parent_id')->constrained('professions')->cascadeOnDelete();
$table->foreignId('child_id')->constrained('professions')->cascadeOnDelete();
});
}
public function down(): void
{
Schema::dropIfExists('professions');
Schema::dropIfExists('profession_profession');
}
};
namespace App\Models;
use Illuminate\Contracts\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
class Profession extends Model
{
protected $with = ['children'];
public function parents()
{
return $this->belongsToMany(Profession::class, 'profession_profession', 'child_id', 'parent_id');
}
public function children()
{
return $this->belongsToMany(Profession::class, 'profession_profession', 'parent_id', 'child_id');
}
public function scopeRoot(Builder $builder)
{
$builder->doesntHave('parents');
}
}
With given model you can generate tree data like this:
Profession::root()->get();
Import TrilistSelect class and use it on your Filament form:
use Beholdr\FilamentTrilist\Components\TrilistSelect
// with custom tree data
TrilistSelect::make('category_id')
->options($treeData),
// or with relationship
TrilistSelect::make('categories')
->relationship('categories')
->options($treeData)
->multiple(),
Full options list:
TrilistSelect::make(string $fieldName)
->label(string $fieldLabel)
->placeholder(string | Closure $placeholder)
->disabled(bool | Closure $condition)
// array of tree items
->options(array | Closure $options),
// first argument defines name of the relationship, second can be used to modify relationship query
->relationship(string | Closure $relationshipName, ?Closure $modifyQueryUsing = null)
// array of ids (or single id) of disabled items
->disabledOptions(string | int | array | Closure $value)
// multiple selection mode, default: false
->multiple(bool | Closure $condition)
// animate expand/collapse, default: true
->animated(bool | Closure $condition)
// expand initial selected options, default: true
->expandSelected(bool | Closure $condition)
// in independent mode children auto selected when parent is selected, default: false
->independent(bool | Closure $condition)
// in leafs mode, the selected value is not grouped as the parent when all child elements are selected, default: false
->leafs(bool | Closure $condition)
// tree item id field name, default: 'id'
->fieldId(string | Closure $value)
// tree item label field name, default: 'label'
->fieldLabel(string | Closure $value)
// tree item children field name, default: 'children'
->fieldChildren(string | Closure $value)
// hook for generating custom labels, default: '(item) => item.label'
->labelHook(string | Closure $value)
// enable filtering of items, default: false
->searchable(bool | Closure $condition)
// enable autofocus on filter field, default: false
->autofocus(bool | Closure $condition)
// search input placeholder
->searchPrompt(string | Htmlable | Closure $message)
// select button label
->selectButton(string | Htmlable | Closure $message)
// cancel button label
->cancelButton(string | Htmlable | Closure $message)
If you want to customize labels you can use labelHook method. It should return a string that will be processed as JS (pay attention to escaping quotes and special characters):
TrilistSelect::make('parent_id')
->labelHook(fn () => <<<JS
(item) => `\${item.label} \${item.data?.description ? '<div style=\'font-size: 0.85em; opacity: 0.5\'>' + item.data.description + '</div>' : ''}`
JS)
You can use treeselect in custom filter:
use App\Models\Category;
use Filament\Tables\Filters\Filter;
use Illuminate\Database\Eloquent\Builder;
Filter::make('category')
->form([
TrilistSelect::make('category_id')
->multiple()
->independent()
->options(Category::tree()->get()->toTree())
])
->query(function (Builder $query, array $data) {
$query->when(
$data['category_id'],
function (Builder $query, $values) {
$ids = Category::query()
->whereIn('id', $values)
->get()
->map
->descendantsAndSelf
->flatten()
->pluck('id')
->toArray();
$query->whereIn('category_id', $ids);
}
);
})
->indicateUsing(function (array $data) {
if (! $data['category_id']) return null;
return Category::whereIn('id', $data['category_id'])->pluck('name')->toArray();
}),
Create custom page class inside Pages directory of your Filament directory. Note that page class extends Beholdr\FilamentTrilist\Components\TrilistPage:
If you create custom page with
php artisan make:filament-pagecommand, then selectNooption for creating this page in a resource. After page creation you can delete created page view file and$viewproperty of the page class.
namespace App\Filament\Pages;
use App\Filament\Resources\PostResource;
use App\Models\Post;
use Beholdr\FilamentTrilist\Components\TrilistPage;
class TreePosts extends TrilistPage
{
// optional resource class if you want to link tree items to a resource edit page
protected static ?string $resource = PostResource::class;
// optional, if you want to override default title
protected static ?string $title = 'Posts Tree';
// optional navigation parent page title
protected static ?string $navigationParentItem = 'Categories';
// return array of tree items (see below about tree data)
public function getTreeOptions(): array
{
return Post::root()->get()->toArray();
}
}
You can set some tree options by overriding static methods in the custom page class:
class TreeCategories extends TrilistPage
{
public static function getFieldLabel(): string
{
return 'name';
}
}
getFieldId(): tree item id field namegetFieldLabel(): tree item label field namegetFieldChildren(): tree item children field nameisAnimated(): animate expand/collapse, default: trueisSearchable(): enable filtering of items, default: falsegetSearchPrompt(): search input placeholderIf you want to customize labels of the tree items, you can override getLabelHook() method of the TrilistPage.
Say, model for your tree items has a description field that you want to output below the item name. All additional properties of your model are under item.data property, so description will be at item.data.description:
public function getLabelHook(): string
{
if (! $editRoute = $this->getEditRoute()) {
return 'undefined';
}
$template = route($editRoute, ['record' => '#ID#'], false);
return <<<JS
(item) => `<a href='\${'{$template}'.replace('#ID#', item.id)}'>\${item.label}</a> \${item.data?.description ? '<div style=\'font-size: 0.85em; opacity: 0.5\'>' + item.data.description + '</div>' : ''}`
JS;
}
The MIT License (MIT). Please see License File for more information.
How can I help you explore Laravel packages today?