Skip to content

Commit

Permalink
feat: add modifyQueryUsing argument for TrilistSelect
Browse files Browse the repository at this point in the history
  • Loading branch information
beholdr committed Feb 20, 2024
1 parent 799ab63 commit 8593c89
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 123 deletions.
202 changes: 101 additions & 101 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,97 @@ 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:

```php
[
['id' => 'ID', 'label' => 'Item label', 'children' => [
['id' => 'ID', 'label' => 'Item label', 'children' => [...]],
...
]
]
```

For example, you can use special library like [staudenmeir/laravel-adjacency-list](https://github.com/staudenmeir/laravel-adjacency-list) to get tree data:

```php
Category::tree()->get()->toTree()
```

Or use custom relationship schema and methods, even with `ManyToMany` (multiple parents) relationship.

<details>
<summary>Example for self-referencing entity</summary>

#### Migrations

```php
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

```php
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:

```php
Profession::root()->get();
```
</details>

## Treeselect input

![Treeselect input](https://github.com/beholdr/filament-trilist/assets/741973/fcb8803a-dc92-4c6b-a140-cf3bb12deb0b)
Expand All @@ -38,13 +129,15 @@ Import `TrilistSelect` class and use it on your Filament form:
```php
use Beholdr\FilamentTrilist\Components\TrilistSelect

// with custom tree data (see below about tree data)
TrilistSelect::make('fieldname')
->options($optionsArray),
// with custom tree data
TrilistSelect::make('category_id')
->options($treeData),

// or with relationship
TrilistSelect::make('category_id')
->relationship('category', fn () => Category::tree()->get()->toTree()),
TrilistSelect::make('categories')
->relationship('categories')
->options($treeData)
->multiple(),
```

Full options list:
Expand All @@ -55,11 +148,11 @@ TrilistSelect::make(string $fieldName)
->placeholder(string | Closure $placeholder)
->disabled(bool | Closure $condition)

// array of tree items (see below about tree data), not necessary if using relationship() option
// array of tree items
->options(array | Closure $options),

// first argument defines name of the relationship, second should provide array of tree items (see below about tree data)
->relationship(string | Closure $relationshipName, Closure $getTreeOptions)
// 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)
Expand Down Expand Up @@ -256,99 +349,6 @@ public function getLabelHook(): string
```
</details>

## Tree data

You can use hierarchical data from any source when it follows format:

```php
[
['id' => 'ID', 'label' => 'Item label', 'children' => [
['id' => 'ID', 'label' => 'Item label', 'children' => [...]],
...
]
]
```

### Relationships

You can use special library like [staudenmeir/laravel-adjacency-list](https://github.com/staudenmeir/laravel-adjacency-list) to generate tree items data, for example:

```php
Category::tree()->get()->toTree()
```

Or use your own relationship methods, even with `ManyToMany` (multiple parents) relationship. Example for self-referencing entity:

<details>
<summary>Migrations</summary>

```php
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');
}
};
```
</details>

<details>
<summary>Model</summary>

```php
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');
}
}
```
</details>

With given model you can generate tree data like this:

```php
Profession::root()->get();
```

## License

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
55 changes: 33 additions & 22 deletions src/Components/TrilistSelect.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
use Filament\Support\Concerns\HasExtraAlpineAttributes;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

Expand Down Expand Up @@ -47,7 +46,7 @@ class TrilistSelect extends Field

protected string | Closure $fieldChildren = 'children';

protected string | Closure $labelHook = <<<JS
protected string | Closure $labelHook = <<<'JS'
(item) => item.label
JS;

Expand Down Expand Up @@ -184,15 +183,11 @@ public function getFieldChildren(): string

public function relationship(
string | Closure $name = null,
Closure $getTreeOptions = null,
Closure $modifyQueryUsing = null
): static {
$this->relationship = $name ?? $this->getName();

$this->options(static function (TrilistSelect $component) use ($getTreeOptions) {
return $component->evaluate($getTreeOptions);
});

$this->loadStateFromRelationshipsUsing(static function (TrilistSelect $component, $state): void {
$this->loadStateFromRelationshipsUsing(static function (TrilistSelect $component, $state) use ($modifyQueryUsing): void {
if (filled($state)) {
return;
}
Expand All @@ -201,6 +196,10 @@ public function relationship(
return;
}

if ($modifyQueryUsing) {
$component->modifyRelationshipQuery($relationship, $modifyQueryUsing);
}

if (! $relatedModel = $relationship->getResults()) {
return;
}
Expand All @@ -212,34 +211,46 @@ public function relationship(
->pluck($relationship->getRelatedKeyName())
->toArray()
);

return;
} else {
$component->state(
$relatedModel->getAttribute(
$relationship->getOwnerKeyName(),
),
);
}

$component->state(
$relatedModel->getAttribute(
$relationship->getOwnerKeyName(),
),
);
});

$this->saveRelationshipsUsing(static function (TrilistSelect $component, Model $record, $state) {
$this->saveRelationshipsUsing(static function (TrilistSelect $component, $state) use ($modifyQueryUsing) {
$relationship = $component->getRelationship();

if (! $relationship instanceof BelongsToMany) {
$relationship->associate($state);

return;
if ($modifyQueryUsing) {
$component->modifyRelationshipQuery($relationship, $modifyQueryUsing);
}

$relationship->sync($state ?? []);
if ($relationship instanceof BelongsToMany) {
$relationship->detach($relationship->pluck('id')->toArray());
$relationship->attach($state);
} else {
$relationship->associate($state);
}
});

$this->dehydrated(fn (TrilistSelect $component): bool => ! $component->isMultiple());

return $this;
}

public function modifyRelationshipQuery(BelongsTo | BelongsToMany $relationship, Closure $modifyQueryUsing)
{
$relationshipQuery = $relationship->getQuery();

$relationshipQuery = $this->evaluate($modifyQueryUsing, [
'query' => $relationshipQuery,
]) ?? $relationshipQuery;

$relationship->setQuery($relationshipQuery->getQuery());
}

public function getRelationshipName(): ?string
{
return (string) $this->evaluate($this->relationship);
Expand Down

0 comments on commit 8593c89

Please sign in to comment.