Skip to content

Add multiple and prepend features #147

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Mar 27, 2025
Merged
96 changes: 72 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
[![Latest Version on Packagist](https://img.shields.io/packagist/v/codewithdennis/filament-select-tree.svg?style=flat-square)](https://packagist.org/packages/codewithdennis/filament-select-tree)
[![Total Downloads](https://img.shields.io/packagist/dt/codewithdennis/filament-select-tree.svg?style=flat-square)](https://packagist.org/packages/codewithdennis/filament-select-tree)

This package adds a dynamic select tree field to your Laravel / Filament application, allowing you to create interactive hierarchical selection dropdowns based on relationships. It's handy for building selection dropdowns with various customization options.
This package adds a dynamic select tree field to your Laravel / Filament application, allowing you to create interactive hierarchical selection dropdowns based on relationships. It's handy for
building selection dropdowns with various customization options.

![thumbnail](https://raw.githubusercontent.com/CodeWithDennis/filament-select-tree/3.x/resources/images/thumbnail.jpg)

Expand All @@ -23,14 +24,14 @@ php artisan filament:assets

Use the tree for a `BelongsToMany` relationship

```PHP
```php
SelectTree::make('categories')
->relationship('categories', 'name', 'parent_id')
```

Use the tree for a `BelongsTo` relationship

```PHP
```php
SelectTree::make('category_id')
->relationship('category', 'name', 'parent_id')
```
Expand All @@ -39,14 +40,14 @@ SelectTree::make('category_id')

Customize the parent query

```PHP
```php
SelectTree::make('categories')
->relationship(relationship: 'categories', titleAttribute: 'name', parentAttribute: 'parent_id', modifyQueryUsing: fn($query) => $query));
```

Customize the child query

```PHP
```php
SelectTree::make('categories')
->relationship(relationship: 'categories', titleAttribute: 'name', parentAttribute: 'parent_id', modifyChildQueryUsing: fn($query) => $query));
```
Expand All @@ -55,121 +56,167 @@ SelectTree::make('categories')

Set a custom placeholder when no items are selected

```PHP
```php
->placeholder(__('Please select a category'))
```

Enable the selection of groups

```PHP
```php
->enableBranchNode()
```

Customize the label when there are zero search results

```PHP
```php
->emptyLabel(__('Oops, no results have been found!'))
```

Display the count of children alongside the group's name

```PHP
```php
->withCount()
```

Keep the dropdown open at all times

```PHP
```php
->alwaysOpen()
```

Set nodes as dependent

```PHP
```php
->independent(false)
```

Expand the tree with selected values (only works if field is dependent)

```PHP
```php
->expandSelected(false)
```

Set the parent's null value to -1, allowing you to use -1 as a sentinel value (default = null)

```PHP
```php
->parentNullValue(-1)
```

All groups will be opened to this level

```PHP
```php
->defaultOpenLevel(2)
```

Specify the list's force direction. Options include: auto (default), top, and bottom.

```PHP
```php
->direction('top')
```

Display individual leaf nodes instead of the main group when all leaf nodes are selected

```PHP
```php
->grouped(false)
```

Hide the clearable icon

```PHP
```php
->clearable(false)
```

Activate the search functionality

```PHP
```php
->searchable();
```

Disable specific options in the tree

```PHP
```php
->disabledOptions([2, 3, 4])
```

Hide specific options in the tree

```PHP
```php
->hiddenOptions([2, 3, 4])
```

Allow soft deleted items to be displayed

```PHP
```php
->withTrashed()
```

Specify a different key for your model.
For example: you have id, code and parent_code. Your model uses id as key, but the parent-child relation is established between code and parent_code

```PHP
```php
->withKey('code')
```

Store fetched models for additional functionality

```PHP
```php
->storeResults()
```

Now you can access the results in `disabledOptions` or `hiddenOptions`

```PHP
```php
->disabledOptions(function ($state, SelectTree $component) {
$results = $component->getResults();
})
```

By default, the type of selection in the tree (single or multiple) is determined by the relationship type: `BelongsTo` for single selection and `BelongsToMany` for multiple selection. If you want to
explicitly set the selection type, use:

```php
->multiple(false)
```

If you need to prepend an item to the tree menu, use the `prepend` method. This method accepts an array or a closure. It is useful when the tree-select is used as a filter (see example below).

```php
use Filament\Tables\Filters\Filter;
use Illuminate\Database\Eloquent\Builder;
use CodeWithDennis\FilamentSelectTree\SelectTree;
```

```php
->filters([
Filter::make('tree')
->form([
SelectTree::make('category')
->relationship('categories', 'name', 'parent_id')
->enableBranchNode()
->multiple(false)
->prepend([
'name' => 'Uncategorized Products',
'value' => -1,
'parent' => null // optional
'disabled' => false // optional
'hidden' => false // optional
'children' => [] // optional
])
])
->query(function (Builder $query, array $data) {
$categories= [(int) $data['category']];

return $query->when($data['category'], function (Builder $query, $categories) {
if($data['category'] === -1){
return $query->whereDoesntHave('categories');
}

return $query->whereHas('categories', fn(Builder $query) => $query->whereIn('id', $categories));
});
})
])
```

## Filters

Use the tree in your table filters. Here's an example to show you how.
Expand Down Expand Up @@ -205,6 +252,7 @@ use CodeWithDennis\FilamentSelectTree\SelectTree;
```

## Screenshots

![example-1](https://raw.githubusercontent.com/CodeWithDennis/filament-select-tree/3.x/resources/images/example-1.jpg)
![example-2](https://raw.githubusercontent.com/CodeWithDennis/filament-select-tree/3.x/resources/images/example-2.jpg)
![example-3](https://raw.githubusercontent.com/CodeWithDennis/filament-select-tree/3.x/resources/images/example-3.jpg)
Expand Down
27 changes: 24 additions & 3 deletions src/SelectTree.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ class SelectTree extends Field implements HasAffixActions

protected Collection|array|null $results = null;

protected Closure|bool|null $multiple = null;

protected Closure|array|null $prepend = null;

protected function setUp(): void
{
// Load the state from relationships using a callback function.
Expand Down Expand Up @@ -143,7 +147,7 @@ protected function setUp(): void
]);
}

private function buildTree(): Collection
protected function buildTree(): Collection
{
// Start with two separate query builders
$nullParentQuery = $this->getRelationship()->getRelated()->query()->where($this->getParentAttribute(), $this->getParentNullValue());
Expand Down Expand Up @@ -288,6 +292,20 @@ public function parentNullValue(int|string|null $parentNullValue = null): static
return $this;
}

public function multiple(Closure|bool $multiple = true): static
{
$this->multiple = $multiple;

return $this;
}

public function prepend(Closure|array|null $prepend = null): static
{
$this->prepend = $prepend;

return $this;
}

public function getRelationship(): BelongsToMany|BelongsTo
{
return $this->getModelInstance()->{$this->evaluate($this->relationship)}();
Expand Down Expand Up @@ -394,7 +412,8 @@ public function storeResults(bool $storeResults = true): static

public function getTree(): Collection|array
{
return $this->evaluate($this->buildTree());
return $this->evaluate($this->buildTree()->when($this->prepend,
fn (Collection $tree) => $tree->prepend($this->evaluate($this->prepend))));
}

public function getResults(): Collection|array|null
Expand Down Expand Up @@ -434,7 +453,9 @@ public function getWithCount(): bool

public function getMultiple(): bool
{
return $this->evaluate($this->getRelationship() instanceof BelongsToMany);
return $this->evaluate(
is_null($this->multiple) ? $this->getRelationship() instanceof BelongsToMany : $this->evaluate($this->multiple)
);
}

public function getClearable(): bool
Expand Down