diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 47306d1..f65b084 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,8 +59,6 @@ jobs: - name: Code analysis if: matrix.php == '8.1' && matrix.laravel == '9.*' run: | - # Remove this line once Larastan and Livewire are working well together - sed -i -e 's#.*protected \$enablesPackageDiscoveries.*#&\nprotected function overrideApplicationBindings($app){return["livewire"=>"Livewire\\\\LivewireManager"];}#' vendor/nunomaduro/larastan/src/ApplicationResolver.php vendor/bin/pint --test -vvv vendor/bin/phpmd config,src,tests text phpmd.xml vendor/bin/phpstan analyse diff --git a/CHANGELOG.md b/CHANGELOG.md index e27e605..46093be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [5.1.0](https://github.com/Okipa/laravel-table/compare/5.0.2...5.1.0) + +2022-10-25 + +* Added ability to chain a `->when(bool $condition)` method to an instantiated head action, in order to enable it conditionally +* Added a new built-in `RedirectHeadAction`, that will be used by the pre-configured `CreateHeadAction` +* Added an optional `bool $openInNewWindow = false` to the `CreateHeadAction` +* Added a new [JavaScript snippet](/README.md#set-up-a-few-lines-of-javascript) to handle head action link opening in tab: you'll have to add it if you want to benefit from this new ability + ## [5.0.2](https://github.com/Okipa/laravel-table/compare/5.0.1...5.0.2) 2022-10-07 diff --git a/README.md b/README.md index 585a35b..6d8da76 100644 --- a/README.md +++ b/README.md @@ -473,11 +473,18 @@ Configure a table action that will be displayed as a button positioned at the ri If no head action is declared, the dedicated slot for it in the table head will remain empty. This package provides the following built-in head actions: -* `CreateHeadAction`: - * Requires a `string $createUrl` argument on instantiation +* `RedirectHeadAction`: + * Requires `string $url`, `string $label`, `string $icon`, `array $class = ['btn', 'btn-success']` and `bool $openInNewWindow = false` arguments on instantiation * Redirects to the model create page from a click on a `Create` button +* `CreateHeadAction`: + * Requires `string $createUrl` and `bool $openInNewWindow = false` arguments on instantiation + * Instantiate a pre-configured `RedirectHeadAction` with the given `$createUrl` as URL, `__('Create')` as label and `config('laravel-table.icon.create')` as icon -To use it, you'll have to pass an instance of it to the `headAction` method. +To use one of them, you'll have to pass an instance of it to the `headAction` method. + +You'll be able to chain the following method to your head action: +* `when(bool $condition): Okipa\LaravelTable\Abstracts\AbstractHeadAction` + * Determines whether the head action should be enabled ```php namespace App\Tables; @@ -493,7 +500,8 @@ class UsersTable extends AbstractTableConfiguration { return Table::make() ->model(User::class) - ->headAction(new CreateHeadAction(route('user.create'))); + // Create head action will not be available when authenticated user is not allowed to create users + ->headAction((new CreateHeadAction(route('user.create')))->when(Auth::user()->cannot('create_users'))); } } ``` @@ -549,9 +557,9 @@ This package provides the built-in following bulk actions: To use them, you'll have to pass a closure parameter to the `bulkActions` method. This closure will allow you to manipulate a `Illuminate\Database\Eloquent $model` argument and has to return an array containing bulk action instances. -You'll ben able to chain the following methods to your bulk actions: +You'll be able to chain the following methods to your bulk actions: * `when(bool $condition): Okipa\LaravelTable\Abstracts\AbstractBulkAction` - * Determines if action should be available on table rows + * Determines whether the bulk action should be enabled on the table rows * `confirmationQuestion(string|false $confirmationQuestion): Okipa\LaravelTable\Abstracts\AbstractBulkAction` * Overrides the default action confirmation message * `feedbackMessage(string|false $feedbackMessage): Okipa\LaravelTable\Abstracts\AbstractBulkAction`: @@ -1185,6 +1193,14 @@ Livewire.on('laraveltable:action:feedback', (feedbackMessage) => { }); ``` +Finally, in order to allow head `RedirectHeadAction` and `CreateHeadAction` to open link in new tab, you'll also have to add the following JS snippet: + +```javascript +Livewire.on('laraveltable:link:open:newtab', (url) => { + window.open(url, '_blank').focus(); +}); +``` + ### Trigger Livewire events on table load You may want to trigger some events on table load, in order to load UI third party JS libraries for example. diff --git a/src/Abstracts/AbstractHeadAction.php b/src/Abstracts/AbstractHeadAction.php index 9fb368b..b767f62 100644 --- a/src/Abstracts/AbstractHeadAction.php +++ b/src/Abstracts/AbstractHeadAction.php @@ -9,12 +9,26 @@ abstract class AbstractHeadAction { public string $rowActionClass; + protected bool $isAllowed = true; + abstract protected function class(): array; abstract protected function icon(): string; abstract protected function title(): string; + public function when(bool $condition): self + { + $this->isAllowed = $condition; + + return $this; + } + + public function isAllowed(): bool + { + return $this->isAllowed; + } + /** @return mixed|void */ abstract public function action(Component $livewire); diff --git a/src/HeadActions/CreateHeadAction.php b/src/HeadActions/CreateHeadAction.php index c6ac0ab..a989e2b 100644 --- a/src/HeadActions/CreateHeadAction.php +++ b/src/HeadActions/CreateHeadAction.php @@ -2,35 +2,40 @@ namespace Okipa\LaravelTable\HeadActions; -use Illuminate\Http\RedirectResponse; use Livewire\Component; -use Livewire\Redirector; use Okipa\LaravelTable\Abstracts\AbstractHeadAction; class CreateHeadAction extends AbstractHeadAction { - public function __construct(public string $createUrl) + protected RedirectHeadAction $redirectHeadAction; + + public function __construct(public string $createUrl, bool $openInNewWindow = false) { - // + $this->redirectHeadAction = new RedirectHeadAction( + url: $createUrl, + label: __('Create'), + icon: config('laravel-table.icon.create'), + openInNewWindow: $openInNewWindow + ); } protected function class(): array { - return ['btn', 'btn-success']; + return $this->redirectHeadAction->class(); } protected function title(): string { - return __('Create'); + return $this->redirectHeadAction->title(); } protected function icon(): string { - return config('laravel-table.icon.create'); + return $this->redirectHeadAction->icon(); } - public function action(Component $livewire): RedirectResponse|Redirector + public function action(Component $livewire): void { - return redirect()->to($this->createUrl); + $this->redirectHeadAction->action($livewire); } } diff --git a/src/HeadActions/RedirectHeadAction.php b/src/HeadActions/RedirectHeadAction.php new file mode 100644 index 0000000..3a98002 --- /dev/null +++ b/src/HeadActions/RedirectHeadAction.php @@ -0,0 +1,41 @@ +class; + } + + protected function title(): string + { + return __($this->label); + } + + protected function icon(): string + { + return $this->icon; + } + + public function action(Component $livewire): void + { + $this->openInNewWindow + ? $livewire->emit('laraveltable:link:open:newtab', $this->url) + : redirect()->to($this->url); + } +} diff --git a/src/Livewire/Table.php b/src/Livewire/Table.php index 2d73151..03179a2 100644 --- a/src/Livewire/Table.php +++ b/src/Livewire/Table.php @@ -52,7 +52,7 @@ class Table extends Component public bool $resetFilters = false; - public array|null $headActionArray; + public array $headActionArray; public bool $selectAll = false; @@ -233,7 +233,15 @@ public function sortBy(string $columnKey): void public function headAction(): mixed { - return AbstractHeadAction::make($this->headActionArray)->action($this); + if (! $this->headActionArray) { + return null; + } + $headActionInstance = AbstractHeadAction::make($this->headActionArray); + if (! $headActionInstance->isAllowed()) { + return null; + } + + return $headActionInstance->action($this); } public function updatedSelectAll(): void diff --git a/src/Table.php b/src/Table.php index fe241bb..5e7584b 100644 --- a/src/Table.php +++ b/src/Table.php @@ -354,16 +354,20 @@ public function getFilterClosures(array $filtersArray, array $selectedFilters): return $filterClosures; } - public function getHeadActionArray(): array|null + public function getHeadActionArray(): array { if (! $this->headAction) { - return null; + return []; } $this->headAction->setup(); + if (! $this->headAction->isAllowed()) { + return []; + } return (array) $this->headAction; } + /** @throws \JsonException */ public function getRowClass(): array { $tableRowClass = []; @@ -374,7 +378,10 @@ public function getRowClass(): array $tableRowClass[$model->laravel_table_unique_identifier] = ($this->rowClassesClosure)($model); } - return $tableRowClass; + return json_decode(json_encode( + $tableRowClass, + JSON_THROW_ON_ERROR + ), true, 512, JSON_THROW_ON_ERROR); } /** @throws \JsonException */ diff --git a/tests/Unit/Bootstrap5/TableHeadActionTest.php b/tests/Unit/Bootstrap5/TableHeadActionTest.php index 807cf4e..2f77838 100644 --- a/tests/Unit/Bootstrap5/TableHeadActionTest.php +++ b/tests/Unit/Bootstrap5/TableHeadActionTest.php @@ -49,4 +49,38 @@ protected function columns(): array ->call('headAction') ->assertRedirect(route('user.create')); } + + /** @test */ + public function it_can_allow_head_action_conditionally(): void + { + app('router')->get('/user/create', ['as' => 'user.create']); + Config::set('laravel-table.icon.create', 'create-icon'); + $config = new class extends AbstractTableConfiguration + { + protected function table(): Table + { + return Table::make()->model(User::class) + ->headAction((new CreateHeadAction(route('user.create'), true))->when(false)); + } + + protected function columns(): array + { + return [ + Column::make('name'), + ]; + } + }; + Livewire::test(\Okipa\LaravelTable\Livewire\Table::class, ['config' => $config::class]) + ->call('init') + ->assertDontSeeHtml([ + '', + 'create-icon Create', + '', + ]) + ->call('headAction') + ->assertNotEmitted('laraveltable:link:open:newtab'); + } }