diff --git a/resources/js/components/index.js b/resources/js/components/index.js index 28301645..f3a7bdb6 100644 --- a/resources/js/components/index.js +++ b/resources/js/components/index.js @@ -10,6 +10,7 @@ import './select/slimSelect' import './pg-dropdown' import './pg-export' import './pg-toggle-detail' +import './pg-select-all' import './pg-filter-panel' import './directives' import '../plugins' diff --git a/resources/js/components/pg-select-all.js b/resources/js/components/pg-select-all.js new file mode 100644 index 00000000..a8e182c1 --- /dev/null +++ b/resources/js/components/pg-select-all.js @@ -0,0 +1,62 @@ +window.pgAlpine.data('pgSelectAll', () => ({ + init() { + this.pgTable = this.$el.closest('table') + + this.onRowChange = (event) => { + const target = event.target + + if (target === this.$el || !(target instanceof HTMLInputElement) || target.type !== 'checkbox') { + return + } + + this.refresh() + } + + this.pgTable?.addEventListener('change', this.onRowChange) + + this.onFinished = () => this.refresh() + window.addEventListener('pg-select-all:refresh', this.onFinished) + + this.$nextTick(() => this.refresh()) + }, + + destroy() { + this.pgTable?.removeEventListener('change', this.onRowChange) + window.removeEventListener('pg-select-all:refresh', this.onFinished) + }, + + refresh() { + const table = this.pgTable + + if (!table) { + return + } + + const boxes = Array.from(table.querySelectorAll('tbody input[type="checkbox"]')) + .filter((checkbox) => !checkbox.disabled) + + const total = boxes.length + const checked = boxes.filter((checkbox) => checkbox.checked).length + + this.$el.checked = total > 0 && checked === total + this.$el.indeterminate = checked > 0 && checked < total + }, +})) + +if (!window.pgSelectAllCommitHook) { + window.pgSelectAllCommitHook = true + + document.addEventListener('DOMContentLoaded', () => { + if (!window.Livewire) { + return + } + + Livewire.hook('commit', ({ succeed }) => { + succeed(() => { + requestAnimationFrame(() => { + window.dispatchEvent(new CustomEvent('pg-select-all:refresh')) + }) + }) + }) + }) +} diff --git a/resources/views/components/structure/table/checkbox-all.blade.php b/resources/views/components/structure/table/checkbox-all.blade.php index dd5b071d..811248d9 100644 --- a/resources/views/components/structure/table/checkbox-all.blade.php +++ b/resources/views/components/structure/table/checkbox-all.blade.php @@ -25,10 +25,10 @@
diff --git a/resources/views/components/themes/tailwind/table/checkbox-all.blade.php b/resources/views/components/themes/tailwind/table/checkbox-all.blade.php index 6f99e3c4..f9d38969 100644 --- a/resources/views/components/themes/tailwind/table/checkbox-all.blade.php +++ b/resources/views/components/themes/tailwind/table/checkbox-all.blade.php @@ -25,10 +25,10 @@
diff --git a/src/Concerns/Checkbox.php b/src/Concerns/Checkbox.php index 659a95a8..d32ae9d6 100644 --- a/src/Concerns/Checkbox.php +++ b/src/Concerns/Checkbox.php @@ -26,8 +26,12 @@ trait Checkbox /** * @throws Exception|Throwable */ - public function selectCheckboxAll(): void + public function selectCheckboxAll(?bool $checked = null): void { + if ($checked !== null) { + $this->checkboxAll = $checked; + } + if (! $this->checkboxAll) { $this->checkboxValues = []; diff --git a/src/PowerGridComponent.php b/src/PowerGridComponent.php index e22f65e9..a3f90962 100644 --- a/src/PowerGridComponent.php +++ b/src/PowerGridComponent.php @@ -112,6 +112,10 @@ public function setSummaryValues(array $values): void public function resetToFirstPage(string $pageName = 'page'): void { + if ((int) $this->getPage($pageName) === 1) { + return; + } + $this->gotoPage(1, $pageName); } diff --git a/tests/Feature/CheckboxTest.php b/tests/Feature/CheckboxTest.php index 29cb22ff..6d7b4c7e 100644 --- a/tests/Feature/CheckboxTest.php +++ b/tests/Feature/CheckboxTest.php @@ -65,6 +65,147 @@ public function columns(): array ->toBe([]); }); +it('resetToFirstPage does not navigate when already on the first page', function () { + $component = new class() extends PowerGridComponent + { + public string $tableName = 'test-reset-first-page'; + + /** @var array */ + public array $gotoPageCalls = []; + + public function datasource() + { + return collect([ + ['id' => 1, 'name' => 'Dish 1'], + ['id' => 2, 'name' => 'Dish 2'], + ]); + } + + public function gotoPage($page, $pageName = 'page'): void + { + $this->gotoPageCalls[] = $page; + + parent::gotoPage($page, $pageName); + } + + public function setUp(): array + { + return [ + PowerGrid::footer()->showPerPage(10), + ]; + } + + public function fields(): PowerGridFields + { + return PowerGrid::fields()->add('id')->add('name'); + } + + public function columns(): array + { + return [Column::make('Id', 'id'), Column::make('Name', 'name')]; + } + }; + + $lw = Livewire::test($component::class); + + $lw->call('resetToFirstPage'); + expect($lw->get('gotoPageCalls'))->toBe([]); + + $lw->call('gotoPage', 3) + ->call('resetToFirstPage'); + expect($lw->get('gotoPageCalls'))->toContain(1); +}); + +it('preserves the row selection when the per-page size changes', function () { + $component = new class() extends PowerGridComponent + { + public string $tableName = 'test-checkbox-perpage-change'; + + public function datasource() + { + $data = []; + for ($i = 1; $i <= 30; $i++) { + $data[] = ['id' => $i, 'name' => 'Dish '.$i]; + } + + return collect($data); + } + + public function setUp(): array + { + $this->showCheckBox(); + + return [ + PowerGrid::footer()->showPerPage(10, [10, 30]), + ]; + } + + public function fields(): PowerGridFields + { + return PowerGrid::fields()->add('id')->add('name'); + } + + public function columns(): array + { + return [Column::make('Id', 'id'), Column::make('Name', 'name')]; + } + }; + + $lw = Livewire::test($component::class) + ->set('checkboxAll', true) + ->call('selectCheckboxAll'); + + expect($lw->checkboxValues)->toMatchArray(range(1, 10)); + + $lw->set('setUp.footer.perPage', 30); + + expect($lw->checkboxValues)->toMatchArray(range(1, 10)); +}); + +it('selectCheckboxAll honours the explicit checked intent argument', function () { + $component = new class() extends PowerGridComponent + { + public string $tableName = 'test-checkbox-intent'; + + public function datasource() + { + return collect([ + ['id' => 1, 'name' => 'Dish 1'], + ['id' => 2, 'name' => 'Dish 2'], + ['id' => 3, 'name' => 'Dish 3'], + ]); + } + + public function setUp(): array + { + $this->showCheckBox(); + + return [PowerGrid::footer()->showPerPage(10)]; + } + + public function fields(): PowerGridFields + { + return PowerGrid::fields()->add('id')->add('name'); + } + + public function columns(): array + { + return [Column::make('Id', 'id'), Column::make('Name', 'name')]; + } + }; + + $lw = Livewire::test($component::class) + ->call('selectCheckboxAll', true); + + expect($lw->get('checkboxAll'))->toBeTrue() + ->and($lw->checkboxValues)->toMatchArray(['1', '2', '3']); + + $lw->call('selectCheckboxAll', false); + + expect($lw->get('checkboxAll'))->toBeFalse() + ->and($lw->checkboxValues)->toBe([]); +}); + it('selectCheckboxAll works properly with actionRules disable', function () { $component = new class() extends PowerGridComponent {