Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Filament Trilist Laravel Package

beholdr/filament-trilist

View on GitHub
Deep Wiki
Context7

Filament Trilist ☘️

Latest Version on Packagist Total Downloads

Filament plugin for working with tree data: treeselect input and treeview page. Based on Trilist package.

Support

Do you like Filament Trilist? Please support me via Boosty.

Features

  • Treeselect input and treeview page
  • Tree items can have multiple parents
  • Works with relationship or custom hierarchical data

Installation

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"

Tree data

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.

Migrations

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');
    }
};

Model

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();

Treeselect input

Treeselect input

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)

Custom labels

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)

Usage in filters

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();
    }),

Treeview page

Treeview page

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-page command, then select No option for creating this page in a resource. After page creation you can delete created page view file and $view property 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();
    }
}

Treeview options

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 name
  • getFieldLabel(): tree item label field name
  • getFieldChildren(): tree item children field name
  • isAnimated(): animate expand/collapse, default: true
  • isSearchable(): enable filtering of items, default: false
  • getSearchPrompt(): search input placeholder

Custom labels

If 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;
}

License

The MIT License (MIT). Please see License File for more information.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jayeshmepani/jpl-moshier-ephemeris-php
elnasnato/laraliveui
labrodev/rest-sdk
sampaui/sampaui
babelqueue/php-sdk
facebook/capi-param-builder-php
babelqueue/symfony
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle