From 272457d745b38c1aa9eed27a38884352b6220d2e Mon Sep 17 00:00:00 2001 From: Joe <104938042+lrljoe@users.noreply.github.com> Date: Thu, 6 Jun 2024 02:11:27 +0100 Subject: [PATCH 01/21] Adjust for HTML Columns --- resources/views/datatable.blade.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/resources/views/datatable.blade.php b/resources/views/datatable.blade.php index 73d8cf071..5c9625bb0 100644 --- a/resources/views/datatable.blade.php +++ b/resources/views/datatable.blade.php @@ -53,7 +53,11 @@ @continue($column->isReorderColumn() && !$this->getCurrentlyReorderingStatus() && $this->getHideReorderColumnUnlessReorderingStatus()) - {{ $column->renderContents($row) }} + @if($column->isHtml()) + {!! $column->renderContents($row) !!} + @else + {{ $column->renderContents($row) }} + @endif @endforeach From 8b3e3cff74c8f033b2b47037b9ea3c542d779437 Mon Sep 17 00:00:00 2001 From: Joe <104938042+lrljoe@users.noreply.github.com> Date: Thu, 6 Jun 2024 02:13:28 +0100 Subject: [PATCH 02/21] Update ChangeLog and SP --- CHANGELOG.md | 4 ++++ src/LaravelLivewireTablesServiceProvider.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 499b54b76..7060f51c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to `laravel-livewire-tables` will be documented in this file +## [v3.2.7] - 2024-06-05 +### Bug Fixes +- Ensure HTML Columns return HTML correctly by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1737 + ## [v3.2.6] - 2024-06-05 ### New Features - Add configurable wire:model for filters by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1699 diff --git a/src/LaravelLivewireTablesServiceProvider.php b/src/LaravelLivewireTablesServiceProvider.php index 0cdd740db..f0a50ed70 100644 --- a/src/LaravelLivewireTablesServiceProvider.php +++ b/src/LaravelLivewireTablesServiceProvider.php @@ -14,7 +14,7 @@ class LaravelLivewireTablesServiceProvider extends ServiceProvider public function boot(): void { - AboutCommand::add('Rappasoft Laravel Livewire Tables', fn () => ['Version' => '3.2.6']); + AboutCommand::add('Rappasoft Laravel Livewire Tables', fn () => ['Version' => '3.2.7']); $this->mergeConfigFrom( __DIR__.'/../config/livewire-tables.php', 'livewire-tables' From 2cd6684769082a51f031e1db43c6092e00669543 Mon Sep 17 00:00:00 2001 From: Matt Pickering Date: Sat, 29 Jun 2024 20:52:25 +0100 Subject: [PATCH 03/21] =?UTF-8?q?fix:=20Apply=20cursor=20pointer=20only=20?= =?UTF-8?q?on=20clickable=20columns=20when=20using=20Bootst=E2=80=A6=20(#1?= =?UTF-8?q?742)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Ensure HTML Columns Return HTML Correctly (#1737) * Adjust for HTML Columns * fix: Apply cursor pointer only on clickable columns when using Bootstrap --------- Co-authored-by: Joe <104938042+lrljoe@users.noreply.github.com> --- resources/views/components/table/td.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/components/table/td.blade.php b/resources/views/components/table/td.blade.php index 1ae7e41d6..0d6973242 100644 --- a/resources/views/components/table/td.blade.php +++ b/resources/views/components/table/td.blade.php @@ -21,7 +21,7 @@ ->class(['d-none' => $component->isBootstrap() && $column && $column->shouldCollapseAlways()]) ->class(['d-none d-md-table-cell' => $component->isBootstrap() && $column && $column->shouldCollapseOnMobile()]) ->class(['d-none d-lg-table-cell' => $component->isBootstrap() && $column && $column->shouldCollapseOnTablet()]) - ->style(['cursor:pointer' => $component->isBootstrap()]) + ->style(['cursor:pointer' => $component->isBootstrap() && $column && $column->isClickable()]) ->except('default') }} > From f5dba56bcf5c4f98e5141c0660489e7533c77009 Mon Sep 17 00:00:00 2001 From: lrljoe Date: Sat, 29 Jun 2024 19:52:51 +0000 Subject: [PATCH 04/21] Fix styling --- src/DataTableComponent.php | 4 +--- src/Exceptions/DataTableConfigurationException.php | 4 +--- src/Exceptions/NoColumnsException.php | 4 +--- src/Exceptions/NoSearchableColumnsException.php | 4 +--- src/Exceptions/NoSortableColumnsException.php | 4 +--- src/Traits/WithReordering.php | 5 +---- .../Traits/Configuration/ImageColumnConfiguration.php | 4 +--- .../Traits/Configuration/LinkColumnConfiguration.php | 4 +--- src/Views/Traits/Helpers/ImageColumnHelpers.php | 4 +--- src/Views/Traits/Helpers/LinkColumnHelpers.php | 4 +--- tests/DataTableComponentTest.php | 8 ++------ tests/Http/Livewire/FailingTables/NoPrimaryKeyTable.php | 4 +--- 12 files changed, 13 insertions(+), 40 deletions(-) diff --git a/src/DataTableComponent.php b/src/DataTableComponent.php index b89da3dee..906878d7d 100644 --- a/src/DataTableComponent.php +++ b/src/DataTableComponent.php @@ -29,9 +29,7 @@ public function boot(): void /** * Runs on every request, after the component is mounted or hydrated, but before any update methods are called */ - public function booted(): void - { - } + public function booted(): void {} public function render(): \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View { diff --git a/src/Exceptions/DataTableConfigurationException.php b/src/Exceptions/DataTableConfigurationException.php index 1dabdf83a..795d82b06 100644 --- a/src/Exceptions/DataTableConfigurationException.php +++ b/src/Exceptions/DataTableConfigurationException.php @@ -2,6 +2,4 @@ namespace Rappasoft\LaravelLivewireTables\Exceptions; -class DataTableConfigurationException extends \Exception -{ -} +class DataTableConfigurationException extends \Exception {} diff --git a/src/Exceptions/NoColumnsException.php b/src/Exceptions/NoColumnsException.php index 5d11959c3..875a457cb 100644 --- a/src/Exceptions/NoColumnsException.php +++ b/src/Exceptions/NoColumnsException.php @@ -2,6 +2,4 @@ namespace Rappasoft\LaravelLivewireTables\Exceptions; -class NoColumnsException extends \Exception -{ -} +class NoColumnsException extends \Exception {} diff --git a/src/Exceptions/NoSearchableColumnsException.php b/src/Exceptions/NoSearchableColumnsException.php index de6b56713..5597b1235 100644 --- a/src/Exceptions/NoSearchableColumnsException.php +++ b/src/Exceptions/NoSearchableColumnsException.php @@ -2,6 +2,4 @@ namespace Rappasoft\LaravelLivewireTables\Exceptions; -class NoSearchableColumnsException extends \Exception -{ -} +class NoSearchableColumnsException extends \Exception {} diff --git a/src/Exceptions/NoSortableColumnsException.php b/src/Exceptions/NoSortableColumnsException.php index 825c54339..7ba3828f9 100644 --- a/src/Exceptions/NoSortableColumnsException.php +++ b/src/Exceptions/NoSortableColumnsException.php @@ -2,6 +2,4 @@ namespace Rappasoft\LaravelLivewireTables\Exceptions; -class NoSortableColumnsException extends \Exception -{ -} +class NoSortableColumnsException extends \Exception {} diff --git a/src/Traits/WithReordering.php b/src/Traits/WithReordering.php index 33679a1aa..553c7c42e 100644 --- a/src/Traits/WithReordering.php +++ b/src/Traits/WithReordering.php @@ -40,10 +40,7 @@ public function setupReordering(): void $this->restartReorderingIfNecessary(); } - public function enablePaginatedReordering(): void - { - - } + public function enablePaginatedReordering(): void {} public function enableReordering(): void { diff --git a/src/Views/Traits/Configuration/ImageColumnConfiguration.php b/src/Views/Traits/Configuration/ImageColumnConfiguration.php index e1283c2dc..8ab662b87 100644 --- a/src/Views/Traits/Configuration/ImageColumnConfiguration.php +++ b/src/Views/Traits/Configuration/ImageColumnConfiguration.php @@ -2,6 +2,4 @@ namespace Rappasoft\LaravelLivewireTables\Views\Traits\Configuration; -trait ImageColumnConfiguration -{ -} +trait ImageColumnConfiguration {} diff --git a/src/Views/Traits/Configuration/LinkColumnConfiguration.php b/src/Views/Traits/Configuration/LinkColumnConfiguration.php index dedcd6430..9d07ca9ad 100644 --- a/src/Views/Traits/Configuration/LinkColumnConfiguration.php +++ b/src/Views/Traits/Configuration/LinkColumnConfiguration.php @@ -2,6 +2,4 @@ namespace Rappasoft\LaravelLivewireTables\Views\Traits\Configuration; -trait LinkColumnConfiguration -{ -} +trait LinkColumnConfiguration {} diff --git a/src/Views/Traits/Helpers/ImageColumnHelpers.php b/src/Views/Traits/Helpers/ImageColumnHelpers.php index 98de49507..2226586a1 100644 --- a/src/Views/Traits/Helpers/ImageColumnHelpers.php +++ b/src/Views/Traits/Helpers/ImageColumnHelpers.php @@ -2,6 +2,4 @@ namespace Rappasoft\LaravelLivewireTables\Views\Traits\Helpers; -trait ImageColumnHelpers -{ -} +trait ImageColumnHelpers {} diff --git a/src/Views/Traits/Helpers/LinkColumnHelpers.php b/src/Views/Traits/Helpers/LinkColumnHelpers.php index ef8026bc0..4555786d2 100644 --- a/src/Views/Traits/Helpers/LinkColumnHelpers.php +++ b/src/Views/Traits/Helpers/LinkColumnHelpers.php @@ -2,6 +2,4 @@ namespace Rappasoft\LaravelLivewireTables\Views\Traits\Helpers; -trait LinkColumnHelpers -{ -} +trait LinkColumnHelpers {} diff --git a/tests/DataTableComponentTest.php b/tests/DataTableComponentTest.php index c62fa4e09..5cd0115ff 100644 --- a/tests/DataTableComponentTest.php +++ b/tests/DataTableComponentTest.php @@ -55,9 +55,7 @@ public function test_default_fingerprint_will_always_be_the_same_for_same_datata public function test_default_datatable_fingerprints_will_be_different_for_each_table(): void { - $mockTable = new class() extends PetsTable - { - }; + $mockTable = new class() extends PetsTable {}; $this->assertNotSame($this->basicTable->getDataTableFingerprint(), $mockTable->getDataTableFingerprint()); } @@ -66,9 +64,7 @@ public function test_default_fingerprint_will_be_url_friendy(): void { $mocks = []; for ($i = 0; $i < 9; $i++) { - $mocks[$i] = new class() extends PetsTable - { - }; + $mocks[$i] = new class() extends PetsTable {}; $this->assertFalse(filter_var('http://'.$mocks[$i]->getDataTableFingerprint().'.dev', FILTER_VALIDATE_URL) === false); } // control diff --git a/tests/Http/Livewire/FailingTables/NoPrimaryKeyTable.php b/tests/Http/Livewire/FailingTables/NoPrimaryKeyTable.php index d71f7f3ec..be6f0100c 100644 --- a/tests/Http/Livewire/FailingTables/NoPrimaryKeyTable.php +++ b/tests/Http/Livewire/FailingTables/NoPrimaryKeyTable.php @@ -22,9 +22,7 @@ class NoPrimaryKeyTable extends DataTableComponent { public $model = Pet::class; - public function configure(): void - { - } + public function configure(): void {} public function columns(): array { From 52ad60b62b4f7cc2b73e77d5a8217ed32799dea1 Mon Sep 17 00:00:00 2001 From: Joe <104938042+lrljoe@users.noreply.github.com> Date: Sat, 29 Jun 2024 20:59:42 +0100 Subject: [PATCH 05/21] Fix hide bulk actions when empty not reflecting in frontend (#1747) * Fix issue with Hide Bulk Actions When Empty not reflecting in frontend * Fix styling * Add development branch into tests --------- Co-authored-by: lrljoe --- .github/workflows/run-tests-pcov-pull.yml | 1 + .github/workflows/run-tests-pull.yml | 1 + resources/js/laravel-livewire-tables.js | 2 +- resources/js/laravel-livewire-tables.min.js | 2 +- .../views/components/tools/toolbar/items/bulk-actions.blade.php | 2 +- 5 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/run-tests-pcov-pull.yml b/.github/workflows/run-tests-pcov-pull.yml index 08e7eea08..0a5f52d3e 100644 --- a/.github/workflows/run-tests-pcov-pull.yml +++ b/.github/workflows/run-tests-pcov-pull.yml @@ -4,6 +4,7 @@ on: pull_request: branches: - 'develop' + - 'development' - 'master' jobs: diff --git a/.github/workflows/run-tests-pull.yml b/.github/workflows/run-tests-pull.yml index 50c0c38be..1abeaeee8 100644 --- a/.github/workflows/run-tests-pull.yml +++ b/.github/workflows/run-tests-pull.yml @@ -4,6 +4,7 @@ on: pull_request: branches: - 'develop' + - 'development' - 'master' jobs: diff --git a/resources/js/laravel-livewire-tables.js b/resources/js/laravel-livewire-tables.js index 532a615ea..0d068dac1 100644 --- a/resources/js/laravel-livewire-tables.js +++ b/resources/js/laravel-livewire-tables.js @@ -8,7 +8,7 @@ document.addEventListener('alpine:init', () => { paginationTotalItemCount: wire.entangle('paginationTotalItemCount'), paginationCurrentItems: wire.entangle('paginationCurrentItems'), selectedItems: wire.entangle('selected'), - alwaysShowBulkActions: !wire.entangle('hideBulkActionsWhenEmpty'), + hideBulkActionsWhenEmpty: wire.entangle('hideBulkActionsWhenEmpty'), toggleSelectAll() { if (!showBulkActionsAlpine) { return; diff --git a/resources/js/laravel-livewire-tables.min.js b/resources/js/laravel-livewire-tables.min.js index 6d78ed178..3b5991c4a 100644 --- a/resources/js/laravel-livewire-tables.min.js +++ b/resources/js/laravel-livewire-tables.min.js @@ -1 +1 @@ -document.addEventListener("alpine:init",()=>{Alpine.data("tableWrapper",(e,t)=>({childElementOpen:!1,filtersOpen:e.entangle("filterSlideDownDefaultVisible"),paginationCurrentCount:e.entangle("paginationCurrentCount"),paginationTotalItemCount:e.entangle("paginationTotalItemCount"),paginationCurrentItems:e.entangle("paginationCurrentItems"),selectedItems:e.entangle("selected"),alwaysShowBulkActions:!e.entangle("hideBulkActionsWhenEmpty"),toggleSelectAll(){t&&(this.paginationTotalItemCount===this.selectedItems.length?this.clearSelected():this.setAllSelected())},setAllSelected(){t&&e.setAllSelected()},clearSelected(){t&&e.clearSelected()},selectAllOnPage(){if(!t)return;let e=this.selectedItems,i=this.paginationCurrentItems.values();for(let l of i)e.push(l.toString());this.selectedItems=[...new Set(e)]}})),Alpine.data("numberRangeFilter",(e,t,i,l,a)=>({allFilters:e.entangle("filterComponents",!1),originalMin:0,originalMax:100,filterMin:0,filterMax:100,currentMin:0,currentMax:100,hasUpdate:!1,wireValues:e.entangle("filterComponents."+t,!1),defaultMin:l.minRange,defaultMax:l.maxRange,restrictUpdates:!1,updateStyles(){let e=document.getElementById(i),t=document.getElementById(a+"-min"),l=document.getElementById(a+"-max");e.style.setProperty("--value-a",t.value),e.style.setProperty("--text-value-a",JSON.stringify(t.value)),e.style.setProperty("--value-b",l.value),e.style.setProperty("--text-value-b",JSON.stringify(l.value))},setupWire(){void 0!==this.wireValues?(this.filterMin=this.originalMin=void 0!==this.wireValues.min?this.wireValues.min:this.defaultMin,this.filterMax=this.originalMax=void 0!==this.wireValues.max?this.wireValues.max:this.defaultMax):(this.filterMin=this.originalMin=this.defaultMin,this.filterMax=this.originalMax=this.defaultMax),this.updateStyles()},allowUpdates(){this.updateWire()},updateWire(){let e=parseInt(this.filterMin),t=parseInt(this.filterMax);(e!=this.originalMin||t!=this.originalMax)&&(tthis.setupWire())}})),Alpine.data("flatpickrFilter",(e,t,i,l,a)=>({wireValues:e.entangle("filterComponents."+t),flatpickrInstance:flatpickr(l,{mode:"range",clickOpens:!0,allowInvalidPreload:!0,defaultDate:[],ariaDateFormat:i.ariaDateFormat,allowInput:i.allowInput,altFormat:i.altFormat,altInput:i.altInput,dateFormat:i.dateFormat,locale:"en",minDate:i.earliestDate,maxDate:i.latestDate,onOpen:function(){window.childElementOpen=!0},onChange:function(i,l,a){if(i.length>1){var s=l.split(" ")[0],r=l.split(" ")[2],n={};window.childElementOpen=!1,window.filterPopoverOpen=!1,n={minDate:s,maxDate:r},e.set("filterComponents."+t,n)}}}),setupWire(){if(void 0!==this.wireValues){if(void 0!==this.wireValues.minDate&&void 0!==this.wireValues.maxDate){let e=[this.wireValues.minDate,this.wireValues.maxDate];this.flatpickrInstance.setDate(e)}else this.flatpickrInstance.setDate([])}else this.flatpickrInstance.setDate([])},init(){this.setupWire(),this.$watch("wireValues",e=>this.setupWire())}})),Alpine.data("reorderFunction",(e,t,i)=>({dragging:!1,reorderEnabled:!1,sourceID:"",targetID:"",evenRowClasses:"",oddRowClasses:"",currentlyHighlightedElement:"",evenRowClassArray:{},oddRowClassArray:{},evenNotInOdd:{},oddNotInEven:{},orderedRows:[],defaultReorderColumn:e.get("defaultReorderColumn"),reorderStatus:e.get("reorderStatus"),currentlyReorderingStatus:e.entangle("currentlyReorderingStatus"),hideReorderColumnUnlessReorderingStatus:e.entangle("hideReorderColumnUnlessReorderingStatus"),reorderDisplayColumn:e.entangle("reorderDisplayColumn"),dragStart(e){this.sourceID=e.target.id,e.dataTransfer.effectAllowed="move",e.dataTransfer.setData("text/plain",e.target.id),e.target.classList.add("laravel-livewire-tables-dragging")},dragOverEvent(e){"object"==typeof this.currentlyHighlightedElement&&this.currentlyHighlightedElement.classList.remove("laravel-livewire-tables-highlight-bottom","laravel-livewire-tables-highlight-top");let t=e.target.closest("tr");this.currentlyHighlightedElement=t,e.offsetYi.getBoundingClientRect().height/2?l.insertBefore(a,i.nextSibling):l.insertBefore(a,i),r!l.includes(e)),this.oddNotInEven=l.filter(e=>!i.includes(e)),i=[],l=[])}},init(){this.$watch("currentlyReorderingStatus",e=>this.setupEvenOddClasses())}}))}); \ No newline at end of file +document.addEventListener("alpine:init",()=>{Alpine.data("tableWrapper",(e,t)=>({childElementOpen:!1,filtersOpen:e.entangle("filterSlideDownDefaultVisible"),paginationCurrentCount:e.entangle("paginationCurrentCount"),paginationTotalItemCount:e.entangle("paginationTotalItemCount"),paginationCurrentItems:e.entangle("paginationCurrentItems"),selectedItems:e.entangle("selected"),hideBulkActionsWhenEmpty:e.entangle("hideBulkActionsWhenEmpty"),toggleSelectAll(){t&&(this.paginationTotalItemCount===this.selectedItems.length?this.clearSelected():this.setAllSelected())},setAllSelected(){t&&e.setAllSelected()},clearSelected(){t&&e.clearSelected()},selectAllOnPage(){if(!t)return;let e=this.selectedItems,i=this.paginationCurrentItems.values();for(let l of i)e.push(l.toString());this.selectedItems=[...new Set(e)]}})),Alpine.data("numberRangeFilter",(e,t,i,l,a)=>({allFilters:e.entangle("filterComponents",!1),originalMin:0,originalMax:100,filterMin:0,filterMax:100,currentMin:0,currentMax:100,hasUpdate:!1,wireValues:e.entangle("filterComponents."+t,!1),defaultMin:l.minRange,defaultMax:l.maxRange,restrictUpdates:!1,updateStyles(){let e=document.getElementById(i),t=document.getElementById(a+"-min"),l=document.getElementById(a+"-max");e.style.setProperty("--value-a",t.value),e.style.setProperty("--text-value-a",JSON.stringify(t.value)),e.style.setProperty("--value-b",l.value),e.style.setProperty("--text-value-b",JSON.stringify(l.value))},setupWire(){void 0!==this.wireValues?(this.filterMin=this.originalMin=void 0!==this.wireValues.min?this.wireValues.min:this.defaultMin,this.filterMax=this.originalMax=void 0!==this.wireValues.max?this.wireValues.max:this.defaultMax):(this.filterMin=this.originalMin=this.defaultMin,this.filterMax=this.originalMax=this.defaultMax),this.updateStyles()},allowUpdates(){this.updateWire()},updateWire(){let e=parseInt(this.filterMin),t=parseInt(this.filterMax);(e!=this.originalMin||t!=this.originalMax)&&(tthis.setupWire())}})),Alpine.data("flatpickrFilter",(e,t,i,l,a)=>({wireValues:e.entangle("filterComponents."+t),flatpickrInstance:flatpickr(l,{mode:"range",clickOpens:!0,allowInvalidPreload:!0,defaultDate:[],ariaDateFormat:i.ariaDateFormat,allowInput:i.allowInput,altFormat:i.altFormat,altInput:i.altInput,dateFormat:i.dateFormat,locale:"en",minDate:i.earliestDate,maxDate:i.latestDate,onOpen:function(){window.childElementOpen=!0},onChange:function(i,l,a){if(i.length>1){var s=l.split(" ")[0],r=l.split(" ")[2],n={};window.childElementOpen=!1,window.filterPopoverOpen=!1,n={minDate:s,maxDate:r},e.set("filterComponents."+t,n)}}}),setupWire(){if(void 0!==this.wireValues){if(void 0!==this.wireValues.minDate&&void 0!==this.wireValues.maxDate){let e=[this.wireValues.minDate,this.wireValues.maxDate];this.flatpickrInstance.setDate(e)}else this.flatpickrInstance.setDate([])}else this.flatpickrInstance.setDate([])},init(){this.setupWire(),this.$watch("wireValues",e=>this.setupWire())}})),Alpine.data("reorderFunction",(e,t,i)=>({dragging:!1,reorderEnabled:!1,sourceID:"",targetID:"",evenRowClasses:"",oddRowClasses:"",currentlyHighlightedElement:"",evenRowClassArray:{},oddRowClassArray:{},evenNotInOdd:{},oddNotInEven:{},orderedRows:[],defaultReorderColumn:e.get("defaultReorderColumn"),reorderStatus:e.get("reorderStatus"),currentlyReorderingStatus:e.entangle("currentlyReorderingStatus"),hideReorderColumnUnlessReorderingStatus:e.entangle("hideReorderColumnUnlessReorderingStatus"),reorderDisplayColumn:e.entangle("reorderDisplayColumn"),dragStart(e){this.sourceID=e.target.id,e.dataTransfer.effectAllowed="move",e.dataTransfer.setData("text/plain",e.target.id),e.target.classList.add("laravel-livewire-tables-dragging")},dragOverEvent(e){"object"==typeof this.currentlyHighlightedElement&&this.currentlyHighlightedElement.classList.remove("laravel-livewire-tables-highlight-bottom","laravel-livewire-tables-highlight-top");let t=e.target.closest("tr");this.currentlyHighlightedElement=t,e.offsetYi.getBoundingClientRect().height/2?l.insertBefore(a,i.nextSibling):l.insertBefore(a,i),r!l.includes(e)),this.oddNotInEven=l.filter(e=>!i.includes(e)),i=[],l=[])}},init(){this.$watch("currentlyReorderingStatus",e=>this.setupEvenOddClasses())}}))}); \ No newline at end of file diff --git a/resources/views/components/tools/toolbar/items/bulk-actions.blade.php b/resources/views/components/tools/toolbar/items/bulk-actions.blade.php index 82fe34ca1..811a27b99 100644 --- a/resources/views/components/tools/toolbar/items/bulk-actions.blade.php +++ b/resources/views/components/tools/toolbar/items/bulk-actions.blade.php @@ -1,7 +1,7 @@ @aware(['component', 'tableName'])
$component->isBootstrap(), 'w-full md:w-auto mb-4 md:mb-0' => $component->isTailwind(), From ce95a70af99ba2cd36063c04ca44eae6d8c7a2a1 Mon Sep 17 00:00:00 2001 From: Joe <104938042+lrljoe@users.noreply.github.com> Date: Mon, 1 Jul 2024 13:25:08 +0100 Subject: [PATCH 06/21] Change Return Type for attributes() to static (#1749) --- src/Views/Traits/Core/HasAttributes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Views/Traits/Core/HasAttributes.php b/src/Views/Traits/Core/HasAttributes.php index 2707b8671..2978d5822 100644 --- a/src/Views/Traits/Core/HasAttributes.php +++ b/src/Views/Traits/Core/HasAttributes.php @@ -11,7 +11,7 @@ trait HasAttributes { protected ?Closure $attributesCallback = null; - public function attributes(Closure $callback): self + public function attributes(Closure $callback): static { $this->attributesCallback = $callback; From 5c404729e9e28e991bb807fd4b0d104d48d7feca Mon Sep 17 00:00:00 2001 From: Joe <104938042+lrljoe@users.noreply.github.com> Date: Mon, 1 Jul 2024 13:25:22 +0100 Subject: [PATCH 07/21] Switch to using Composer\InstalledVersions for AboutCommand to reduce necessity to update ServiceProvider with each update (#1748) --- src/LaravelLivewireTablesServiceProvider.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/LaravelLivewireTablesServiceProvider.php b/src/LaravelLivewireTablesServiceProvider.php index f0a50ed70..c3c32fe2d 100644 --- a/src/LaravelLivewireTablesServiceProvider.php +++ b/src/LaravelLivewireTablesServiceProvider.php @@ -14,7 +14,11 @@ class LaravelLivewireTablesServiceProvider extends ServiceProvider public function boot(): void { - AboutCommand::add('Rappasoft Laravel Livewire Tables', fn () => ['Version' => '3.2.7']); + if (class_exists(AboutCommand::class) && class_exists(\Composer\InstalledVersions::class)) { + AboutCommand::add('Rappasoft Laravel Livewire Tables', [ + 'Version' => \Composer\InstalledVersions::getPrettyVersion('rappasoft/laravel-livewire-tables'), + ]); + } $this->mergeConfigFrom( __DIR__.'/../config/livewire-tables.php', 'livewire-tables' From 119401b2f609bac3ce6549e8f7a4ed701a03a2da Mon Sep 17 00:00:00 2001 From: Joe <104938042+lrljoe@users.noreply.github.com> Date: Mon, 1 Jul 2024 13:48:18 +0100 Subject: [PATCH 08/21] Two improvements to improve typehinting, migrate to larastan/larastan, cleanup of test (#1750) --- composer.json | 2 +- phpstan.neon | 6 +++--- src/LaravelLivewireTablesServiceProvider.php | 2 +- src/Traits/Helpers/TableAttributeHelpers.php | 4 ++-- tests/Views/Columns/LinkColumnTest.php | 6 ++---- 5 files changed, 9 insertions(+), 11 deletions(-) diff --git a/composer.json b/composer.json index d3e9e6372..bda267c29 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,7 @@ "laravel/pint": "^1.10", "monolog/monolog": "*", "nunomaduro/collision": "^6.0|^7.0|^8.0", - "nunomaduro/larastan": "^2.6", + "larastan/larastan": "^2.6", "orchestra/testbench": "^7.0|^8.0|^9.0", "phpunit/phpunit": "^9.0|^10.0|^11.0" }, diff --git a/phpstan.neon b/phpstan.neon index 7ed8f5027..a0324459d 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,5 @@ includes: - - vendor/nunomaduro/larastan/extension.neon + - vendor/larastan/larastan/extension.neon parameters: paths: @@ -10,11 +10,11 @@ parameters: tmpDir: build/phpstan checkOctaneCompatibility: true checkModelProperties: true - checkMissingIterableValueType: false treatPhpDocTypesAsCertain: false reportUnmatchedIgnoredErrors: false - checkGenericClassInNonGenericObjectType: false ignoreErrors: + - identifier: missingType.generics + - identifier: missingType.iterableValue - '#Access to an undefined property Rappasoft\\LaravelLivewireTables\\Views\\Column\:\:\$view#' - "#Unsafe usage of new static#" - '#on array\, mixed\>\> in empty\(\) does not exist.#' diff --git a/src/LaravelLivewireTablesServiceProvider.php b/src/LaravelLivewireTablesServiceProvider.php index c3c32fe2d..e10027b51 100644 --- a/src/LaravelLivewireTablesServiceProvider.php +++ b/src/LaravelLivewireTablesServiceProvider.php @@ -44,7 +44,7 @@ public function boot(): void } - public function consoleCommands() + public function consoleCommands(): void { if ($this->app->runningInConsole()) { diff --git a/src/Traits/Helpers/TableAttributeHelpers.php b/src/Traits/Helpers/TableAttributeHelpers.php index 3da2da12e..866e72526 100644 --- a/src/Traits/Helpers/TableAttributeHelpers.php +++ b/src/Traits/Helpers/TableAttributeHelpers.php @@ -84,12 +84,12 @@ public function hasTableRowUrl(): bool return $this->trUrlCallback !== null; } - public function getTableRowUrl($row): ?string + public function getTableRowUrl(int|Model $row): ?string { return $this->trUrlCallback ? call_user_func($this->trUrlCallback, $row) : null; } - public function getTableRowUrlTarget($row): ?string + public function getTableRowUrlTarget(int|Model $row): ?string { return $this->trUrlTargetCallback ? call_user_func($this->trUrlTargetCallback, $row) : null; } diff --git a/tests/Views/Columns/LinkColumnTest.php b/tests/Views/Columns/LinkColumnTest.php index 4a204c0ff..d981d7aad 100644 --- a/tests/Views/Columns/LinkColumnTest.php +++ b/tests/Views/Columns/LinkColumnTest.php @@ -44,8 +44,7 @@ public function test_can_render_field_if_title_and_location_callback(): void $this->assertNotEmpty($column); } - /** @test */ - public function can_check_ishtml_from_html_column(): void + public function test_can_check_ishtml_from_html_column(): void { $column = LinkColumn::make('Name', 'name') ->title(fn ($row) => 'Title') @@ -55,8 +54,7 @@ public function can_check_ishtml_from_html_column(): void $this->assertTrue($column->isHtml()); } - /** @test */ - public function can_get_html_from_html_label_column(): void + public function test_can_get_html_from_html_label_column(): void { $column = LinkColumn::make('Name', 'name') ->title(fn ($row) => 'My Label') From ea2c8fc3cfa6e8c2bb808946d349147b9589bffe Mon Sep 17 00:00:00 2001 From: Joe <104938042+lrljoe@users.noreply.github.com> Date: Mon, 1 Jul 2024 13:48:45 +0100 Subject: [PATCH 09/21] Add ArrayColumn (BETA) (#1751) * Add ArrayColumn * Fix styling --------- Co-authored-by: lrljoe --- src/Views/Columns/ArrayColumn.php | 31 +++++++++ .../ArrayColumnConfiguration.php | 29 ++++++++ .../Traits/Helpers/ArrayColumnHelpers.php | 69 +++++++++++++++++++ 3 files changed, 129 insertions(+) create mode 100644 src/Views/Columns/ArrayColumn.php create mode 100644 src/Views/Traits/Configuration/ArrayColumnConfiguration.php create mode 100644 src/Views/Traits/Helpers/ArrayColumnHelpers.php diff --git a/src/Views/Columns/ArrayColumn.php b/src/Views/Columns/ArrayColumn.php new file mode 100644 index 000000000..be9366db0 --- /dev/null +++ b/src/Views/Columns/ArrayColumn.php @@ -0,0 +1,31 @@ +'; + + public string $emptyValue = ''; + + protected mixed $dataCallback = null; + + protected mixed $outputFormat = null; + + public function __construct(string $title, ?string $from = null) + { + parent::__construct($title, $from); + if (! isset($from)) { + $this->label(fn () => null); + } + } +} diff --git a/src/Views/Traits/Configuration/ArrayColumnConfiguration.php b/src/Views/Traits/Configuration/ArrayColumnConfiguration.php new file mode 100644 index 000000000..72188fd79 --- /dev/null +++ b/src/Views/Traits/Configuration/ArrayColumnConfiguration.php @@ -0,0 +1,29 @@ +separator = $value; + + return $this; + } + + public function data(callable $callable): self + { + $this->dataCallback = $callable; + + return $this; + } + + public function outputFormat(callable $callable): self + { + $this->outputFormat = $callable; + + return $this; + } +} diff --git a/src/Views/Traits/Helpers/ArrayColumnHelpers.php b/src/Views/Traits/Helpers/ArrayColumnHelpers.php new file mode 100644 index 000000000..7c32f7c70 --- /dev/null +++ b/src/Views/Traits/Helpers/ArrayColumnHelpers.php @@ -0,0 +1,69 @@ +separator !== null && is_string($this->separator); + } + + public function getSeparator(): string + { + return $this->separator; + } + + public function getEmptyValue(): string + { + return $this->emptyValue; + } + + public function hasDataCallback(): bool + { + return isset($this->dataCallback) && is_callable($this->dataCallback); + } + + public function getDataCallback(): ?callable + { + return $this->dataCallback; + } + + public function hasOutputFormatCallback(): bool + { + return isset($this->outputFormat) && is_callable($this->outputFormat); + } + + public function getOutputFormatCallback(): ?callable + { + return $this->outputFormat; + } + + public function getContents(Model $row): null|string|\BackedEnum|HtmlString|DataTableConfigurationException|\Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View + { + $outputValues = []; + $value = $this->getValue($row); + + if (! $this->hasSeparator()) { + throw new DataTableConfigurationException('You must set a valid separator on an ArrayColumn'); + } + + if (! $this->hasDataCallback()) { + throw new DataTableConfigurationException('You must set a data() method on an ArrayColumn'); + } + + if (! $this->hasOutputFormatCallback()) { + throw new DataTableConfigurationException('You must set an outputFormat() method on an ArrayColumn'); + } + + foreach (call_user_func($this->getDataCallback(), $value, $row) as $i => $v) { + $outputValues[] = call_user_func($this->getOutputFormatCallback(), $i, $v); + } + + return new HtmlString((! empty($outputValues) ? implode($this->getSeparator(), $outputValues) : $this->getEmptyValue())); + } +} From 2acac1b70c041218bf4893670ee973bca219f5c8 Mon Sep 17 00:00:00 2001 From: Joe <104938042+lrljoe@users.noreply.github.com> Date: Mon, 1 Jul 2024 14:09:05 +0100 Subject: [PATCH 10/21] Always hide bulk actions option (#1752) * Add option to "Always Hide Bulk Actions" * Fix styling * Fix test function name clash --------- Co-authored-by: lrljoe --- docs/bulk-actions/available-methods.md | 35 ++++++ .../views/components/tools/toolbar.blade.php | 2 +- .../BulkActionsConfiguration.php | 21 ++++ src/Traits/Helpers/BulkActionsHelpers.php | 8 ++ src/Traits/WithBulkActions.php | 2 + .../Traits/Visuals/BulkActionsVisualsTest.php | 109 ++++++++++++++++++ 6 files changed, 176 insertions(+), 1 deletion(-) diff --git a/docs/bulk-actions/available-methods.md b/docs/bulk-actions/available-methods.md index 22b8210ee..ad98e1999 100644 --- a/docs/bulk-actions/available-methods.md +++ b/docs/bulk-actions/available-methods.md @@ -239,3 +239,38 @@ public function configure(): void ]); } ``` + +## setShouldAlwaysHideBulkActionsDropdownOption + +Allows hiding the Bulk Actions button & menu, regardless of whether there are any items selected, or hideBulkActionsWhenEmptyEnabled behaviour + +```php +public function configure(): void +{ + $this->setShouldAlwaysHideBulkActionsDropdownOption(true); +} +``` + + +## setShouldAlwaysHideBulkActionsDropdownOptionEnabled + +Allows hiding the Bulk Actions button & menu, regardless of whether there are any items selected, or hideBulkActionsWhenEmptyEnabled behaviour + +```php +public function configure(): void +{ + $this->setShouldAlwaysHideBulkActionsDropdownOptionEnabled(); +} +``` + + +## setShouldAlwaysHideBulkActionsDropdownOptionDisabled + +Restores the Bulk Actions to default functionality, so it will respect the hideBulkActionsWhenEmptyEnabled behaviour + +```php +public function configure(): void +{ + $this->setShouldAlwaysHideBulkActionsDropdownOptionDisabled(); +} +``` diff --git a/resources/views/components/tools/toolbar.blade.php b/resources/views/components/tools/toolbar.blade.php index a753403c6..84bb061e8 100644 --- a/resources/views/components/tools/toolbar.blade.php +++ b/resources/views/components/tools/toolbar.blade.php @@ -56,7 +56,7 @@ @include($component->getConfigurableAreaFor('toolbar-right-start'), $component->getParametersForConfigurableArea('toolbar-right-start')) @endif - @if ($component->showBulkActionsDropdownAlpine()) + @if ($component->showBulkActionsDropdownAlpine() && $this->shouldAlwaysHideBulkActionsDropdownOption != true) @endif diff --git a/src/Traits/Configuration/BulkActionsConfiguration.php b/src/Traits/Configuration/BulkActionsConfiguration.php index 813d46f42..d0d5e17f3 100644 --- a/src/Traits/Configuration/BulkActionsConfiguration.php +++ b/src/Traits/Configuration/BulkActionsConfiguration.php @@ -150,4 +150,25 @@ public function setBulkActionsTdCheckboxAttributes(array $bulkActionsTdCheckboxA return $this; } + + public function setShouldAlwaysHideBulkActionsDropdownOption(bool $status = false): self + { + $this->alwaysHideBulkActionsDropdownOption = $status; + + return $this; + } + + public function setShouldAlwaysHideBulkActionsDropdownOptionEnabled(): self + { + $this->setShouldAlwaysHideBulkActionsDropdownOption(true); + + return $this; + } + + public function setShouldAlwaysHideBulkActionsDropdownOptionDisabled(): self + { + $this->setShouldAlwaysHideBulkActionsDropdownOption(false); + + return $this; + } } diff --git a/src/Traits/Helpers/BulkActionsHelpers.php b/src/Traits/Helpers/BulkActionsHelpers.php index 29f162bb6..70ea427a5 100644 --- a/src/Traits/Helpers/BulkActionsHelpers.php +++ b/src/Traits/Helpers/BulkActionsHelpers.php @@ -2,6 +2,8 @@ namespace Rappasoft\LaravelLivewireTables\Traits\Helpers; +use Livewire\Attributes\Computed; + trait BulkActionsHelpers { public function getBulkActionsStatus(): bool @@ -212,4 +214,10 @@ public function getBulkActionsTdCheckboxAttributes(): array { return $this->bulkActionsTdCheckboxAttributes ?? ['default' => true]; } + + #[Computed] + public function shouldAlwaysHideBulkActionsDropdownOption(): bool + { + return $this->alwaysHideBulkActionsDropdownOption ?? false; + } } diff --git a/src/Traits/WithBulkActions.php b/src/Traits/WithBulkActions.php index e05175733..08f69197b 100644 --- a/src/Traits/WithBulkActions.php +++ b/src/Traits/WithBulkActions.php @@ -34,6 +34,8 @@ trait WithBulkActions protected array $bulkActionsTdCheckboxAttributes = ['default' => true]; + protected bool $alwaysHideBulkActionsDropdownOption = false; + public function bulkActions(): array { return property_exists($this, 'bulkActions') ? $this->bulkActions : []; diff --git a/tests/Traits/Visuals/BulkActionsVisualsTest.php b/tests/Traits/Visuals/BulkActionsVisualsTest.php index 5447891da..91f73c177 100644 --- a/tests/Traits/Visuals/BulkActionsVisualsTest.php +++ b/tests/Traits/Visuals/BulkActionsVisualsTest.php @@ -80,4 +80,113 @@ public function test_bulk_actions_row_shows_correct_for_select_some(): void ->assertSee('do you want to select all') ->assertDontSee('You are currently selecting all'); }*/ + + public function test_bulk_dropdown_shows_when_necessary_extended(): void + { + Livewire::test(new class extends PetsTable + { + public function configure(): void + { + $this->setPrimaryKey('id'); + } + + public function bulkActions(): array + { + return ['exportBulk' => 'exportBulk']; + } + + public function exportBulk($items) + { + return $items; + } + })->assertSee('Bulk Actions'); + } + + public function test_bulk_dropdown_shows_when_not_permanently_hidden(): void + { + Livewire::test(new class extends PetsTable + { + public function configure(): void + { + $this->setPrimaryKey('id') + ->setShouldAlwaysHideBulkActionsDropdownOption(false); + } + + public function bulkActions(): array + { + return ['exportBulk' => 'exportBulk']; + } + + public function exportBulk($items) + { + return $items; + } + })->assertSee('Bulk Actions'); + } + + public function test_bulk_dropdown_hides_when_permanently_hidden(): void + { + Livewire::test(new class extends PetsTable + { + public function configure(): void + { + $this->setPrimaryKey('id') + ->setShouldAlwaysHideBulkActionsDropdownOption(true); + } + + public function bulkActions(): array + { + return ['exportBulk' => 'exportBulk']; + } + + public function exportBulk($items) + { + return $items; + } + })->assertDontSee('Bulk Actions'); + } + + public function test_bulk_dropdown_shows_when_not_permanently_hidden_disabled(): void + { + Livewire::test(new class extends PetsTable + { + public function configure(): void + { + $this->setPrimaryKey('id') + ->setShouldAlwaysHideBulkActionsDropdownOptionDisabled(); + } + + public function bulkActions(): array + { + return ['exportBulk' => 'exportBulk']; + } + + public function exportBulk($items) + { + return $items; + } + })->assertSee('Bulk Actions'); + } + + public function test_bulk_dropdown_hides_when_permanently_hidden_enabled(): void + { + Livewire::test(new class extends PetsTable + { + public function configure(): void + { + $this->setPrimaryKey('id') + ->setShouldAlwaysHideBulkActionsDropdownOptionEnabled(); + } + + public function bulkActions(): array + { + return ['exportBulk' => 'exportBulk']; + } + + public function exportBulk($items) + { + return $items; + } + })->assertDontSee('Bulk Actions'); + } } From 363374370e9c5d261a50a94bf2c53606033b004f Mon Sep 17 00:00:00 2001 From: Joe <104938042+lrljoe@users.noreply.github.com> Date: Mon, 1 Jul 2024 15:29:33 +0100 Subject: [PATCH 11/21] Optionally disable count for simple pagination (#1755) * Add option for setShouldRetrieveTotalItemCountStatus * Fix styling --------- Co-authored-by: lrljoe --- docs/pagination/available-methods.md | 33 +++++++++++++++++++ .../Configuration/PaginationConfiguration.php | 22 +++++++++++++ src/Traits/Helpers/PaginationHelpers.php | 6 ++++ src/Traits/WithData.php | 18 ++++++---- src/Traits/WithPagination.php | 2 ++ .../Traits/Helpers/PaginationHelpersTest.php | 30 +++++++++++++++++ 6 files changed, 104 insertions(+), 7 deletions(-) diff --git a/docs/pagination/available-methods.md b/docs/pagination/available-methods.md index d1b60a375..638e58a4b 100644 --- a/docs/pagination/available-methods.md +++ b/docs/pagination/available-methods.md @@ -263,3 +263,36 @@ public function configure(): void ]); } ``` + +## setShouldRetrieveTotalItemCountStatus + +Used when "simple" pagination is being used, allows the enabling/disabling of the "total records" count. This may be desirable to disable in larger data sets. This is enabled by default. + +```php +public function configure(): void +{ + $this->setShouldRetrieveTotalItemCountStatus(false); +} +``` + +## setShouldRetrieveTotalItemCountEnabled + +Used when "simple" pagination is being used, enables the "total records" count. + +```php +public function configure(): void +{ + $this->setShouldRetrieveTotalItemCountEnabled(); +} +``` + +## setShouldRetrieveTotalItemCountDisabled + +Used when "simple" pagination is being used, disables the "total records" count. + +```php +public function configure(): void +{ + $this->setShouldRetrieveTotalItemCountDisabled(); +} +``` diff --git a/src/Traits/Configuration/PaginationConfiguration.php b/src/Traits/Configuration/PaginationConfiguration.php index d7ffa5c34..a8877653a 100644 --- a/src/Traits/Configuration/PaginationConfiguration.php +++ b/src/Traits/Configuration/PaginationConfiguration.php @@ -155,4 +155,26 @@ public function setPerPageFieldAttributes(array $attributes = []): self return $this; } + + public function setShouldRetrieveTotalItemCountStatus(bool $status): self + { + $this->shouldRetrieveTotalItemCount = $status; + + return $this; + + } + + public function setShouldRetrieveTotalItemCountEnabled(): self + { + $this->setShouldRetrieveTotalItemCountStatus(true); + + return $this; + } + + public function setShouldRetrieveTotalItemCountDisabled(): self + { + $this->setShouldRetrieveTotalItemCountStatus(false); + + return $this; + } } diff --git a/src/Traits/Helpers/PaginationHelpers.php b/src/Traits/Helpers/PaginationHelpers.php index 7a744f225..717af7c12 100644 --- a/src/Traits/Helpers/PaginationHelpers.php +++ b/src/Traits/Helpers/PaginationHelpers.php @@ -149,4 +149,10 @@ public function getPerPageFieldAttributes(): array { return $this->perPageFieldAttributes; } + + #[Computed] + public function getShouldRetrieveTotalItemCount(): bool + { + return $this->shouldRetrieveTotalItemCount; + } } diff --git a/src/Traits/WithData.php b/src/Traits/WithData.php index 9dcd44a4c..6d3261c93 100644 --- a/src/Traits/WithData.php +++ b/src/Traits/WithData.php @@ -80,21 +80,25 @@ protected function executeQuery(): Collection|CursorPaginator|Paginator|LengthAw $this->paginationTotalItemCount = $paginatedResults->total() ?? 0; return $paginatedResults; - } - - if ($this->isPaginationMethod('simple')) { + } elseif ($this->isPaginationMethod('simple')) { - $this->paginationTotalItemCount = $this->getBuilder()->count(); + if ($this->getShouldRetrieveTotalItemCount()) { + $this->paginationTotalItemCount = $this->getBuilder()->count(); - return $this->getBuilder()->simplePaginate($this->getPerPage() === -1 ? $this->paginationTotalItemCount : $this->getPerPage(), ['*'], $this->getComputedPageName()); + return $this->getBuilder()->simplePaginate($this->getPerPage() === -1 ? $this->paginationTotalItemCount : $this->getPerPage(), ['*'], $this->getComputedPageName()); + } else { + $this->paginationTotalItemCount = -1; - } + return $this->getBuilder()->simplePaginate($this->getPerPage() === -1 ? 10 : $this->getPerPage(), ['*'], $this->getComputedPageName()); + } - if ($this->isPaginationMethod('cursor')) { + } elseif ($this->isPaginationMethod('cursor')) { $this->paginationTotalItemCount = $this->getBuilder()->count(); return $this->getBuilder()->cursorPaginate($this->getPerPage() === -1 ? $this->paginationTotalItemCount : $this->getPerPage(), ['*'], $this->getComputedPageName()); + } else { + throw new DataTableConfigurationException('Pagination method must be either simple, standard or cursor'); } } diff --git a/src/Traits/WithPagination.php b/src/Traits/WithPagination.php index 676d034b6..6f022a9fb 100644 --- a/src/Traits/WithPagination.php +++ b/src/Traits/WithPagination.php @@ -41,6 +41,8 @@ trait WithPagination protected array $perPageFieldAttributes = ['default-styling' => true, 'default-colors' => true, 'class' => '']; + protected bool $shouldRetrieveTotalItemCount = true; + public function mountWithPagination(): void { $sessionPerPage = session()->get($this->getPerPagePaginationSessionKey(), $this->getPerPageAccepted()[0] ?? 10); diff --git a/tests/Traits/Helpers/PaginationHelpersTest.php b/tests/Traits/Helpers/PaginationHelpersTest.php index 471ee0d7f..0ec6557a4 100644 --- a/tests/Traits/Helpers/PaginationHelpersTest.php +++ b/tests/Traits/Helpers/PaginationHelpersTest.php @@ -141,4 +141,34 @@ public function test_can_get_pagination_field_attributes(): void $this->assertSame(['default-styling' => false, 'default-colors' => true, 'class' => 'bg-blue-500 dark:bg-red-500'], $this->basicTable->getPerPageFieldAttributes()); } + + public function test_can_toggle_total_item_count_retrieval(): void + { + + $this->assertTrue($this->basicTable->getShouldRetrieveTotalItemCount()); + + $this->basicTable->setShouldRetrieveTotalItemCountDisabled(); + + $this->assertFalse($this->basicTable->getShouldRetrieveTotalItemCount()); + + $this->basicTable->setShouldRetrieveTotalItemCountEnabled(); + + $this->assertTrue($this->basicTable->getShouldRetrieveTotalItemCount()); + + } + + public function test_can_toggle_total_item_count_retrieval_via_status(): void + { + + $this->assertTrue($this->basicTable->getShouldRetrieveTotalItemCount()); + + $this->basicTable->setShouldRetrieveTotalItemCountStatus(false); + + $this->assertFalse($this->basicTable->getShouldRetrieveTotalItemCount()); + + $this->basicTable->setShouldRetrieveTotalItemCountStatus(true); + + $this->assertTrue($this->basicTable->getShouldRetrieveTotalItemCount()); + + } } From f0cfe568f32e7b1d98040f5a6986ce45eb3c852d Mon Sep 17 00:00:00 2001 From: Joe <104938042+lrljoe@users.noreply.github.com> Date: Tue, 2 Jul 2024 01:50:30 +0100 Subject: [PATCH 12/21] Update ChangeLog For 3.2.8 Release (#1754) * Update ChangeLog for 3.2.8 --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7060f51c2..1add18837 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ All notable changes to `laravel-livewire-tables` will be documented in this file +## [v3.2.8] - UNRELEASED +### Bug Fixes +- Fix hide bulk actions when empty not reflecting in frontend by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1747 +- Apply cursor pointer only on clickable columns when using Bootstrap by @MP70 in https://github.com/rappasoft/laravel-livewire-tables/pull/1742 + +### New Features +- Always hide bulk actions option by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1752 +- Add ArrayColumn (BETA) by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1751 +- Optionally disable total item count for simple pagination by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1755 + +### Tweaks +- TypeHint fixes by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1750 +- Change Return Type for attributes() to static by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1749 +- Switch to using Composer\InstalledVersions for AboutCommand by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1748 + ## [v3.2.7] - 2024-06-05 ### Bug Fixes - Ensure HTML Columns return HTML correctly by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1737 From 1adebfb04828d65a5d91a004e751fe9adf97752a Mon Sep 17 00:00:00 2001 From: Joe <104938042+lrljoe@users.noreply.github.com> Date: Wed, 3 Jul 2024 22:02:49 +0100 Subject: [PATCH 13/21] Add release date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1add18837..6993a50e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to `laravel-livewire-tables` will be documented in this file -## [v3.2.8] - UNRELEASED +## [v3.2.8] - 2024-07-03 ### Bug Fixes - Fix hide bulk actions when empty not reflecting in frontend by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1747 - Apply cursor pointer only on clickable columns when using Bootstrap by @MP70 in https://github.com/rappasoft/laravel-livewire-tables/pull/1742 From 13fe6a5addba0f7dc6a0f224ddc023b42ca5fb11 Mon Sep 17 00:00:00 2001 From: Joe <104938042+lrljoe@users.noreply.github.com> Date: Wed, 3 Jul 2024 22:04:12 +0100 Subject: [PATCH 14/21] Fix phpstan unescaped | --- phpstan.neon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpstan.neon b/phpstan.neon index a0324459d..25867a7d5 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -19,6 +19,6 @@ parameters: - "#Unsafe usage of new static#" - '#on array\, mixed\>\> in empty\(\) does not exist.#' - '#on array, mixed>> in isset\(\) does not exist#' - - '#on non-empty-array<1|string, array, mixed>> in isset\(\) does not exist.#' + - '#on non-empty-array<1\|string, array, mixed>> in isset\(\) does not exist.#' - '#\$callback of method Illuminate\\Support\\Collection::filter\(\) expects \(callable\(string, int\): bool\)\|null, Closure\(mixed\): int<0, max> given.#' - '#Property Illuminate\\Database\\Query\\Builder\:\:\$joins \(array\) on left side of \?\? is not nullable.#' From 51f4c945948a057e65f550cfbc6109eb41e7a864 Mon Sep 17 00:00:00 2001 From: Joe <104938042+lrljoe@users.noreply.github.com> Date: Wed, 3 Jul 2024 23:14:24 +0100 Subject: [PATCH 15/21] Fix missing typehints (#1757) * Add additional typehints * Fix styling * Add filterCollection typehint * Fix styling * trUrlCallback fixes * Use Collection rather than collect() helper * Fix styling * Add ignore for "Unable to resolve the template type" for Illuminate Collection, add typehint for empty * Add ignore for $model has no defined type (allows for non Eloquent Model to be used longer term) * Adjust concurrency * Adjust Test * Adjust Again * Adjust PHPStan * Add Max Parallel * Use v4 of checkout/cache * Run one at a time * Add Clear Cache Workflow * Fix * Migrate to v4 and adjust workflows * Adjust workflow run rules * Adjust Run-Tests to separate L10 and L11 jobs * Adjust run-tests * Adjust Test * Add Laravel matrix * Adjust Concurrency * Adjust * Adjust Pull Jobs to Match Push jobs --------- Co-authored-by: lrljoe --- .github/workflows/discord-releases.yml | 2 +- .github/workflows/pint.yml | 7 +- .github/workflows/run-phpstan.yml | 18 ++- .github/workflows/run-tests-pcov-pull.yml | 6 +- .github/workflows/run-tests-pull.yml | 111 +++++++++++++++--- .github/workflows/run-tests.yml | 105 +++++++++++++++-- phpstan.neon | 3 + src/Commands/MakeCommand.php | 12 +- src/Mechanisms/RappasoftFrontendAssets.php | 2 +- .../Configuration/FilterConfiguration.php | 2 +- src/Traits/Helpers/FilterHelpers.php | 2 +- src/Traits/Helpers/FooterHelpers.php | 4 +- src/Traits/Helpers/SecondaryHeaderHelpers.php | 4 +- src/Traits/Helpers/TableAttributeHelpers.php | 14 +-- src/Traits/WithData.php | 2 +- src/Traits/WithEvents.php | 4 +- src/Traits/WithFilters.php | 3 +- src/Traits/WithFooter.php | 4 +- src/Traits/WithPagination.php | 2 +- src/Traits/WithSecondaryHeader.php | 4 +- src/Traits/WithTableAttributes.php | 12 +- src/Traits/WithTableHooks.php | 4 +- .../Filters/MultiSelectDropdownFilter.php | 5 +- src/Views/Filters/SelectFilter.php | 8 +- .../Traits/Helpers/ColorColumnHelpers.php | 3 +- src/Views/Traits/Helpers/ColumnHelpers.php | 2 +- 26 files changed, 265 insertions(+), 80 deletions(-) diff --git a/.github/workflows/discord-releases.yml b/.github/workflows/discord-releases.yml index c75bedd41..84ea5b888 100644 --- a/.github/workflows/discord-releases.yml +++ b/.github/workflows/discord-releases.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Package Releases uses: SethCohen/github-releases-to-discord@v1.13.1 with: diff --git a/.github/workflows/pint.yml b/.github/workflows/pint.yml index 097689b83..230a58799 100644 --- a/.github/workflows/pint.yml +++ b/.github/workflows/pint.yml @@ -2,6 +2,11 @@ name: Fix PHP code style issues on: push: + branches: + - '*' # matches every branch that doesn't contain a '/' + - '*/*' # matches every branch containing a single '/' + - '**' # matches every branch + - '!master' paths: - '**.php' @@ -14,7 +19,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.head_ref }} diff --git a/.github/workflows/run-phpstan.yml b/.github/workflows/run-phpstan.yml index a94ed2a24..4faf52718 100644 --- a/.github/workflows/run-phpstan.yml +++ b/.github/workflows/run-phpstan.yml @@ -1,6 +1,16 @@ name: run-phpstan -on: [push, pull_request] +on: + pull_request: + branches: + - 'master' + - 'development' + push: + branches: + - '*' # matches every branch that doesn't contain a '/' + - '*/*' # matches every branch containing a single '/' + - '**' # matches every branch + - '!master' jobs: test: @@ -20,7 +30,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup cache environment id: extcache @@ -31,7 +41,7 @@ jobs: key: ${{ env.extensionKey }} - name: Cache extensions - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.extcache.outputs.dir }} key: ${{ steps.extcache.outputs.key }} @@ -55,7 +65,7 @@ jobs: run: | echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-PHPStan-P${{ matrix.php }}-L${{ matrix.laravel }}-composer-${{ hashFiles('**/composer.json') }} diff --git a/.github/workflows/run-tests-pcov-pull.yml b/.github/workflows/run-tests-pcov-pull.yml index 0a5f52d3e..3a24a91ba 100644 --- a/.github/workflows/run-tests-pcov-pull.yml +++ b/.github/workflows/run-tests-pcov-pull.yml @@ -25,7 +25,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup cache environment id: extcache @@ -47,7 +47,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Cache extensions - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.extcache.outputs.dir }} key: ${{ steps.extcache.outputs.key }} @@ -61,7 +61,7 @@ jobs: run: | echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-PCOV-PULL-PHP${{ matrix.php }}-Laravel${{ matrix.laravel }}-composer-${{ hashFiles('**/composer.json') }} diff --git a/.github/workflows/run-tests-pull.yml b/.github/workflows/run-tests-pull.yml index 1abeaeee8..38f635f9c 100644 --- a/.github/workflows/run-tests-pull.yml +++ b/.github/workflows/run-tests-pull.yml @@ -8,27 +8,25 @@ on: - 'master' jobs: - test: + test-laravel10: runs-on: ${{ matrix.os }} strategy: fail-fast: false + max-parallel: 3 matrix: os: [ubuntu-latest] php: [8.1, 8.2, 8.3] - laravel: [10.*,11.*] + laravel: [10.*] stability: [prefer-dist] - exclude: - - laravel: 11.* - php: 8.1 - name: STD-PULL - ${{ matrix.os }} - P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} + name: PULL PHP-${{ matrix.php }} - Laravel-10 env: - extensionKey: phpextensions-${{ matrix.os }}-P${{ matrix.php }} + extensionKey: phpextensions-${{ matrix.os }}-P${{ matrix.php }}-L{{ matrix.laravel }} extensions: dom, curl, libxml, mbstring, zip, pcntl, pcov, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo, :psr steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup cache environment id: extcache @@ -36,14 +34,14 @@ jobs: with: php-version: ${{ matrix.php }} extensions: ${{ env.extensions }} - key: ${{ runner.os }}-${{ env.extensionKey }} + key: ${{ env.extensionKey }} - name: Cache extensions - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.extcache.outputs.dir }} - key: ${{ steps.extcache.outputs.key }} - restore-keys: ${{ steps.extcache.outputs.key }} + key: ${{ runner.os }}-${{ steps.extcache.outputs.key }} + restore-keys: ${{ runner.os }}-${{ steps.extcache.outputs.key }} - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -64,11 +62,11 @@ jobs: run: | echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-STDPULL-PHP-${{ matrix.php }}-L${{ matrix.laravel }}-composer-${{ hashFiles('**/composer.json') }} - restore-keys: ${{ runner.os }}-STDPULL-PHP-${{ matrix.php }}-L${{ matrix.laravel }}-composer- + key: ${{ runner.os }}-STDPUSH-PHP${{ matrix.php }}-L${{ matrix.laravel }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-STDPUSH-PHP${{ matrix.php }}-L${{ matrix.laravel }}-composer- - name: Add token run: | @@ -79,7 +77,90 @@ jobs: run: composer require "laravel/framework:${{ matrix.laravel }}.*" --no-interaction --no-update - name: Update dependencies + if: steps.composer-cache.outputs.cache-hit != 'true' run: composer update --${{ matrix.stability }} --no-interaction + - name: Setup problem matchers for PHPUnit + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - name: Run Unit Tests + run: php ./vendor/bin/paratest --no-coverage --processes=4 + + + test-laravel11: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + max-parallel: 2 + matrix: + os: [ubuntu-latest] + php: [8.2, 8.3] + laravel: [11.*] + stability: [prefer-dist] + + name: PULL PHP-${{ matrix.php }} - Laravel-11 + env: + extensionKey: phpextensions-${{ matrix.os }}-P${{ matrix.php }}-L{{ matrix.laravel }} + extensions: dom, curl, libxml, mbstring, zip, pcntl, pcov, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo, :psr + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup cache environment + id: extcache + uses: shivammathur/cache-extensions@v1 + with: + php-version: ${{ matrix.php }} + extensions: ${{ env.extensions }} + key: ${{ env.extensionKey }} + + - name: Cache extensions + uses: actions/cache@v4 + with: + path: ${{ steps.extcache.outputs.dir }} + key: ${{ runner.os }}-${{ steps.extcache.outputs.key }} + restore-keys: ${{ runner.os }}-${{ steps.extcache.outputs.key }} + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: ${{ env.extensions }} + tools: phpunit:latest + ini-values: memory_limit=512M + coverage: none + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup problem matchers for PHP + run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" + + - name: Get composer cache directory + id: composer-cache + run: | + echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-STDPUSH-PHP${{ matrix.php }}-L${{ matrix.laravel }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-STDPUSH-PHP${{ matrix.php }}-L${{ matrix.laravel }}-composer- + + - name: Add token + run: | + composer config github-oauth.github.com ${{ secrets.GITHUB_TOKEN }} + + - name: Install dependencies + if: steps.composer-cache.outputs.cache-hit != 'true' + run: composer require "laravel/framework:${{ matrix.laravel }}.*" --no-interaction --no-update + + - name: Update dependencies + if: steps.composer-cache.outputs.cache-hit != 'true' + run: composer update --${{ matrix.stability }} --no-interaction + + - name: Setup problem matchers for PHPUnit + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + - name: Run Unit Tests run: php ./vendor/bin/paratest --no-coverage --processes=4 diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index ce258214d..9f6997a6c 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -1,29 +1,33 @@ name: run-tests -on: [push] +on: + push: + branches: + - '*' # matches every branch that doesn't contain a '/' + - '*/*' # matches every branch containing a single '/' + - '**' # matches every branch + - '!master' jobs: - test: + test-laravel10: runs-on: ${{ matrix.os }} strategy: fail-fast: false + max-parallel: 3 matrix: os: [ubuntu-latest] php: [8.1, 8.2, 8.3] - laravel: [10.*,11.*] + laravel: [10.*] stability: [prefer-dist] - exclude: - - laravel: 11.* - php: 8.1 - name: STD-PUSH - ${{ matrix.os }} - P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} + name: PHP-${{ matrix.php }} - Laravel-10 env: - extensionKey: phpextensions-${{ matrix.os }}-P${{ matrix.php }} + extensionKey: phpextensions-${{ matrix.os }}-P${{ matrix.php }}-L{{ matrix.laravel }} extensions: dom, curl, libxml, mbstring, zip, pcntl, pcov, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo, :psr steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup cache environment id: extcache @@ -34,7 +38,7 @@ jobs: key: ${{ env.extensionKey }} - name: Cache extensions - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.extcache.outputs.dir }} key: ${{ runner.os }}-${{ steps.extcache.outputs.key }} @@ -59,7 +63,86 @@ jobs: run: | echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - - uses: actions/cache@v3 + - uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-STDPUSH-PHP${{ matrix.php }}-L${{ matrix.laravel }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-STDPUSH-PHP${{ matrix.php }}-L${{ matrix.laravel }}-composer- + + - name: Add token + run: | + composer config github-oauth.github.com ${{ secrets.GITHUB_TOKEN }} + + - name: Install dependencies + if: steps.composer-cache.outputs.cache-hit != 'true' + run: composer require "laravel/framework:${{ matrix.laravel }}.*" --no-interaction --no-update + + - name: Update dependencies + if: steps.composer-cache.outputs.cache-hit != 'true' + run: composer update --${{ matrix.stability }} --no-interaction + + - name: Setup problem matchers for PHPUnit + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - name: Run Unit Tests + run: php ./vendor/bin/paratest --no-coverage --processes=4 + + + test-laravel11: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + max-parallel: 2 + matrix: + os: [ubuntu-latest] + php: [8.2, 8.3] + laravel: [11.*] + stability: [prefer-dist] + + name: PHP-${{ matrix.php }} - Laravel-11 + env: + extensionKey: phpextensions-${{ matrix.os }}-P${{ matrix.php }}-L{{ matrix.laravel }} + extensions: dom, curl, libxml, mbstring, zip, pcntl, pcov, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo, :psr + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup cache environment + id: extcache + uses: shivammathur/cache-extensions@v1 + with: + php-version: ${{ matrix.php }} + extensions: ${{ env.extensions }} + key: ${{ env.extensionKey }} + + - name: Cache extensions + uses: actions/cache@v4 + with: + path: ${{ steps.extcache.outputs.dir }} + key: ${{ runner.os }}-${{ steps.extcache.outputs.key }} + restore-keys: ${{ runner.os }}-${{ steps.extcache.outputs.key }} + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: ${{ env.extensions }} + tools: phpunit:latest + ini-values: memory_limit=512M + coverage: none + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup problem matchers for PHP + run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" + + - name: Get composer cache directory + id: composer-cache + run: | + echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-STDPUSH-PHP${{ matrix.php }}-L${{ matrix.laravel }}-composer-${{ hashFiles('**/composer.json') }} diff --git a/phpstan.neon b/phpstan.neon index 25867a7d5..7ff21a707 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -15,6 +15,9 @@ parameters: ignoreErrors: - identifier: missingType.generics - identifier: missingType.iterableValue + - '#Property Rappasoft\\LaravelLivewireTables\\DataTableComponent\:\:\$model has no type specified#' + - '#Unable to resolve the template type TMapWithKeysKey in call to method Illuminate\\Support\\Collection#' + - '#Unable to resolve the template type TMapWithKeysValue in call to method Illuminate\\Support\\Collection#' - '#Access to an undefined property Rappasoft\\LaravelLivewireTables\\Views\\Column\:\:\$view#' - "#Unsafe usage of new static#" - '#on array\, mixed\>\> in empty\(\) does not exist.#' diff --git a/src/Commands/MakeCommand.php b/src/Commands/MakeCommand.php index cdaa67130..8338a1216 100644 --- a/src/Commands/MakeCommand.php +++ b/src/Commands/MakeCommand.php @@ -5,6 +5,7 @@ use Illuminate\Console\Command; use Illuminate\Contracts\Console\PromptsForMissingInput; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\File; use Illuminate\Support\Str; use Livewire\Features\SupportConsoleCommands\Commands\ComponentParser; @@ -97,10 +98,7 @@ protected function createClass(bool $force = false): bool return $classPath; } - /** - * @param mixed $path - */ - protected function ensureDirectoryExists($path): void + protected function ensureDirectoryExists(string $path): void { if (! File::isDirectory(dirname($path))) { File::makeDirectory(dirname($path), 0777, true, true); @@ -147,7 +145,7 @@ public function getModelImport(): string /* * Credits to Harm Smits: https://stackoverflow.com/a/67099502/2263114 */ - private function getClassesList($file): array + private function getClassesList(string $file): array { $classes = []; $namespace = ''; @@ -215,7 +213,7 @@ private function generateColumns(string $modelName): string return $columns; } - protected function possibleModels() + protected function possibleModels(): array { $modelPath = is_dir(app_path('Models')) ? app_path('Models') : app_path(); @@ -226,7 +224,7 @@ protected function possibleModels() ->all(); } - protected function promptForMissingArguments(InputInterface $input, OutputInterface $output) + protected function promptForMissingArguments(InputInterface $input, OutputInterface $output): void { if ($this->didReceiveOptions($input)) { diff --git a/src/Mechanisms/RappasoftFrontendAssets.php b/src/Mechanisms/RappasoftFrontendAssets.php index 62889d0ff..d460f5431 100644 --- a/src/Mechanisms/RappasoftFrontendAssets.php +++ b/src/Mechanisms/RappasoftFrontendAssets.php @@ -56,7 +56,7 @@ public function boot(): void } - protected function registerBladeDirectives() + protected function registerBladeDirectives(): void { Blade::directive('rappasoftTableScripts', [static::class, 'rappasoftTableScripts']); Blade::directive('rappasoftTableStyles', [static::class, 'rappasoftTableStyles']); diff --git a/src/Traits/Configuration/FilterConfiguration.php b/src/Traits/Configuration/FilterConfiguration.php index 9eac5d75f..3367c9168 100644 --- a/src/Traits/Configuration/FilterConfiguration.php +++ b/src/Traits/Configuration/FilterConfiguration.php @@ -121,7 +121,7 @@ public function generateFilterGenericData(): array return (new FilterGenericData($this))->toArray(); } - public function setFilterGenericData(array $filterGenericData = []) + public function setFilterGenericData(array $filterGenericData = []): void { $this->filterGenericData = $filterGenericData; } diff --git a/src/Traits/Helpers/FilterHelpers.php b/src/Traits/Helpers/FilterHelpers.php index 55c824fad..d4c043531 100644 --- a/src/Traits/Helpers/FilterHelpers.php +++ b/src/Traits/Helpers/FilterHelpers.php @@ -312,7 +312,7 @@ public function hasFilterGenericData(): bool return ! empty($this->filterGenericData); } - public function getFilterGenericData() + public function getFilterGenericData(): array { if (! $this->hasFilterGenericData()) { $this->setFilterGenericData($this->generateFilterGenericData()); diff --git a/src/Traits/Helpers/FooterHelpers.php b/src/Traits/Helpers/FooterHelpers.php index 37e8276bd..df220e3c0 100644 --- a/src/Traits/Helpers/FooterHelpers.php +++ b/src/Traits/Helpers/FooterHelpers.php @@ -47,7 +47,7 @@ public function useHeaderAsFooterIsDisabled(): bool */ public function getFooterTrAttributes($rows): array { - return $this->footerTrAttributesCallback ? call_user_func($this->footerTrAttributesCallback, $rows) : ['default' => true]; + return isset($this->footerTrAttributesCallback) ? call_user_func($this->footerTrAttributesCallback, $rows) : ['default' => true]; } /** @@ -56,6 +56,6 @@ public function getFooterTrAttributes($rows): array */ public function getFooterTdAttributes(Column $column, $rows, int $index): array { - return $this->footerTdAttributesCallback ? call_user_func($this->footerTdAttributesCallback, $column, $rows, $index) : ['default' => true]; + return isset($this->footerTdAttributesCallback) ? call_user_func($this->footerTdAttributesCallback, $column, $rows, $index) : ['default' => true]; } } diff --git a/src/Traits/Helpers/SecondaryHeaderHelpers.php b/src/Traits/Helpers/SecondaryHeaderHelpers.php index cc7694002..6af852483 100644 --- a/src/Traits/Helpers/SecondaryHeaderHelpers.php +++ b/src/Traits/Helpers/SecondaryHeaderHelpers.php @@ -32,7 +32,7 @@ public function secondaryHeaderIsDisabled(): bool */ public function getSecondaryHeaderTrAttributes($rows): array { - return $this->secondaryHeaderTrAttributesCallback ? call_user_func($this->secondaryHeaderTrAttributesCallback, $rows) : ['default' => true]; + return isset($this->secondaryHeaderTrAttributesCallback) ? call_user_func($this->secondaryHeaderTrAttributesCallback, $rows) : ['default' => true]; } /** @@ -41,6 +41,6 @@ public function getSecondaryHeaderTrAttributes($rows): array */ public function getSecondaryHeaderTdAttributes(Column $column, $rows, int $index): array { - return $this->secondaryHeaderTdAttributesCallback ? call_user_func($this->secondaryHeaderTdAttributesCallback, $column, $rows, $index) : ['default' => true]; + return isset($this->secondaryHeaderTdAttributesCallback) ? call_user_func($this->secondaryHeaderTdAttributesCallback, $column, $rows, $index) : ['default' => true]; } } diff --git a/src/Traits/Helpers/TableAttributeHelpers.php b/src/Traits/Helpers/TableAttributeHelpers.php index 866e72526..ffebab51c 100644 --- a/src/Traits/Helpers/TableAttributeHelpers.php +++ b/src/Traits/Helpers/TableAttributeHelpers.php @@ -52,7 +52,7 @@ public function getTbodyAttributes(): array */ public function getThAttributes(Column $column): array { - return $this->thAttributesCallback ? call_user_func($this->thAttributesCallback, $column) : ['default' => true]; + return isset($this->thAttributesCallback) ? call_user_func($this->thAttributesCallback, $column) : ['default' => true]; } /** @@ -60,7 +60,7 @@ public function getThAttributes(Column $column): array */ public function getThSortButtonAttributes(Column $column): array { - return $this->thSortButtonAttributesCallback ? call_user_func($this->thSortButtonAttributesCallback, $column) : ['default' => true]; + return isset($this->thSortButtonAttributesCallback) ? call_user_func($this->thSortButtonAttributesCallback, $column) : ['default' => true]; } /** @@ -68,7 +68,7 @@ public function getThSortButtonAttributes(Column $column): array */ public function getTrAttributes(Model $row, int $index): array { - return $this->trAttributesCallback ? call_user_func($this->trAttributesCallback, $row, $index) : ['default' => true]; + return isset($this->trAttributesCallback) ? call_user_func($this->trAttributesCallback, $row, $index) : ['default' => true]; } /** @@ -76,21 +76,21 @@ public function getTrAttributes(Model $row, int $index): array */ public function getTdAttributes(Column $column, Model $row, int $colIndex, int $rowIndex): array { - return $this->tdAttributesCallback ? call_user_func($this->tdAttributesCallback, $column, $row, $colIndex, $rowIndex) : ['default' => true]; + return isset($this->tdAttributesCallback) ? call_user_func($this->tdAttributesCallback, $column, $row, $colIndex, $rowIndex) : ['default' => true]; } public function hasTableRowUrl(): bool { - return $this->trUrlCallback !== null; + return isset($this->trUrlCallback); } public function getTableRowUrl(int|Model $row): ?string { - return $this->trUrlCallback ? call_user_func($this->trUrlCallback, $row) : null; + return isset($this->trUrlCallback) ? call_user_func($this->trUrlCallback, $row) : null; } public function getTableRowUrlTarget(int|Model $row): ?string { - return $this->trUrlTargetCallback ? call_user_func($this->trUrlTargetCallback, $row) : null; + return isset($this->trUrlTargetCallback) ? call_user_func($this->trUrlTargetCallback, $row) : null; } } diff --git a/src/Traits/WithData.php b/src/Traits/WithData.php index 6d3261c93..5e1ca3298 100644 --- a/src/Traits/WithData.php +++ b/src/Traits/WithData.php @@ -175,7 +175,7 @@ protected function joinRelation(Column $column): Builder return $this->getBuilder(); } - protected function performJoin($table, $foreign, $other, $type = 'left'): Builder + protected function performJoin(string $table, string $foreign, string $other, string $type = 'left'): Builder { $joins = []; diff --git a/src/Traits/WithEvents.php b/src/Traits/WithEvents.php index 448dcc69f..dca549f2a 100644 --- a/src/Traits/WithEvents.php +++ b/src/Traits/WithEvents.php @@ -4,7 +4,7 @@ trait WithEvents { - public function setSortEvent($field, $direction): void + public function setSortEvent(string $field, string $direction): void { $this->setSort($field, $direction); } @@ -14,7 +14,7 @@ public function clearSortEvent(): void $this->clearSorts(); } - public function setFilterEvent($filter, $value): void + public function setFilterEvent(string $filter, string $value): void { $this->setFilter($filter, $value); } diff --git a/src/Traits/WithFilters.php b/src/Traits/WithFilters.php index ec6c7aac2..878c660ce 100644 --- a/src/Traits/WithFilters.php +++ b/src/Traits/WithFilters.php @@ -3,6 +3,7 @@ namespace Rappasoft\LaravelLivewireTables\Traits; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Support\Collection; use Rappasoft\LaravelLivewireTables\Traits\Configuration\FilterConfiguration; use Rappasoft\LaravelLivewireTables\Traits\Helpers\FilterHelpers; @@ -23,7 +24,7 @@ trait WithFilters public int $filterCount; - protected $filterCollection; + protected ?Collection $filterCollection; public array $filterComponents = []; diff --git a/src/Traits/WithFooter.php b/src/Traits/WithFooter.php index 376f2340b..b1035cf29 100644 --- a/src/Traits/WithFooter.php +++ b/src/Traits/WithFooter.php @@ -16,9 +16,9 @@ trait WithFooter protected bool $columnsWithFooter = false; - protected $footerTrAttributesCallback; + protected ?object $footerTrAttributesCallback; - protected $footerTdAttributesCallback; + protected ?object $footerTdAttributesCallback; public function setupFooter(): void { diff --git a/src/Traits/WithPagination.php b/src/Traits/WithPagination.php index 6f022a9fb..946bed2cb 100644 --- a/src/Traits/WithPagination.php +++ b/src/Traits/WithPagination.php @@ -53,7 +53,7 @@ public function mountWithPagination(): void } // TODO: Test - public function updatedPerPage($value): void + public function updatedPerPage(int|string $value): void { if (! in_array((int) $value, $this->getPerPageAccepted(), false)) { $value = $this->getPerPageAccepted()[0] ?? 10; diff --git a/src/Traits/WithSecondaryHeader.php b/src/Traits/WithSecondaryHeader.php index 94022b5f1..875d0ae03 100644 --- a/src/Traits/WithSecondaryHeader.php +++ b/src/Traits/WithSecondaryHeader.php @@ -14,9 +14,9 @@ trait WithSecondaryHeader protected bool $columnsWithSecondaryHeader = false; - protected $secondaryHeaderTrAttributesCallback; + protected ?object $secondaryHeaderTrAttributesCallback; - protected $secondaryHeaderTdAttributesCallback; + protected ?object $secondaryHeaderTdAttributesCallback; public function bootedWithSecondaryHeader(): void { diff --git a/src/Traits/WithTableAttributes.php b/src/Traits/WithTableAttributes.php index f75f75f9e..b58c6baf0 100644 --- a/src/Traits/WithTableAttributes.php +++ b/src/Traits/WithTableAttributes.php @@ -21,15 +21,15 @@ trait WithTableAttributes protected array $tbodyAttributes = []; - protected $thAttributesCallback; + protected ?object $thAttributesCallback; - protected $thSortButtonAttributesCallback; + protected ?object $thSortButtonAttributesCallback; - protected $trAttributesCallback; + protected ?object $trAttributesCallback; - protected $tdAttributesCallback; + protected ?object $tdAttributesCallback; - protected $trUrlCallback; + protected ?object $trUrlCallback; - protected $trUrlTargetCallback; + protected ?object $trUrlTargetCallback; } diff --git a/src/Traits/WithTableHooks.php b/src/Traits/WithTableHooks.php index 160713e56..eb0f18fd8 100644 --- a/src/Traits/WithTableHooks.php +++ b/src/Traits/WithTableHooks.php @@ -6,14 +6,14 @@ trait WithTableHooks { - public function callHook($name, $params = []) + public function callHook(string $name, array $params = []): void { if (method_exists($this, $name)) { wrap($this)->__call($name, $params); } } - public function callTraitHook($name, $params = []) + public function callTraitHook(string $name, array $params = []): void { foreach (class_uses_recursive($this) as $trait) { $method = $name.class_basename($trait); diff --git a/src/Views/Filters/MultiSelectDropdownFilter.php b/src/Views/Filters/MultiSelectDropdownFilter.php index ae784b58c..3022ddba5 100644 --- a/src/Views/Filters/MultiSelectDropdownFilter.php +++ b/src/Views/Filters/MultiSelectDropdownFilter.php @@ -2,6 +2,7 @@ namespace Rappasoft\LaravelLivewireTables\Views\Filters; +use Illuminate\Support\Collection; use Rappasoft\LaravelLivewireTables\Views\Filter; use Rappasoft\LaravelLivewireTables\Views\Traits\Core\HasWireables; use Rappasoft\LaravelLivewireTables\Views\Traits\Filters\{HasOptions,IsArrayFilter}; @@ -42,7 +43,7 @@ public function getFilterPillValue($value): ?string foreach ($value as $item) { $found = $this->getCustomFilterPillValue($item) - ?? collect($this->getOptions()) + ?? (new Collection($this->getOptions())) ->mapWithKeys(fn ($options, $optgroupLabel) => is_iterable($options) ? $options : [$optgroupLabel => $options])[$item] ?? null; @@ -54,7 +55,7 @@ public function getFilterPillValue($value): ?string return implode(', ', $values); } - public function isEmpty($value): bool + public function isEmpty(mixed $value): bool { if (! is_array($value)) { return true; diff --git a/src/Views/Filters/SelectFilter.php b/src/Views/Filters/SelectFilter.php index a62260dd9..0f247ada4 100644 --- a/src/Views/Filters/SelectFilter.php +++ b/src/Views/Filters/SelectFilter.php @@ -2,6 +2,7 @@ namespace Rappasoft\LaravelLivewireTables\Views\Filters; +use Illuminate\Support\Collection; use Rappasoft\LaravelLivewireTables\Views\Filter; use Rappasoft\LaravelLivewireTables\Views\Traits\Core\HasWireables; use Rappasoft\LaravelLivewireTables\Views\Traits\Filters\{HasOptions,IsStringFilter}; @@ -22,8 +23,8 @@ class SelectFilter extends Filter public function getKeys(): array { - return collect($this->getOptions()) - ->map(fn ($value, $key) => is_iterable($value) ? collect($value)->keys() : $key) + return (new Collection($this->getOptions())) + ->map(fn ($value, $key) => is_iterable($value) ? (new Collection($value))->keys() : $key) ->flatten() ->map(fn ($value) => (string) $value) ->filter(fn ($value) => strlen($value) > 0) @@ -42,8 +43,9 @@ public function validate(string $value): array|string|bool public function getFilterPillValue($value): ?string { + return $this->getCustomFilterPillValue($value) - ?? collect($this->getOptions()) + ?? (new Collection($this->getOptions())) ->mapWithKeys(fn ($options, $optgroupLabel) => is_iterable($options) ? $options : [$optgroupLabel => $options])[$value] ?? null; } diff --git a/src/Views/Traits/Helpers/ColorColumnHelpers.php b/src/Views/Traits/Helpers/ColorColumnHelpers.php index 00f017e9e..d437243e7 100644 --- a/src/Views/Traits/Helpers/ColorColumnHelpers.php +++ b/src/Views/Traits/Helpers/ColorColumnHelpers.php @@ -2,12 +2,13 @@ namespace Rappasoft\LaravelLivewireTables\Views\Traits\Helpers; +use Illuminate\Database\Eloquent\Model; use Illuminate\View\ComponentAttributeBag; trait ColorColumnHelpers { // TODO: Test - public function getColor($row): string + public function getColor(Model|int $row): string { return $this->hasColorCallback() ? app()->call($this->getColorCallback(), ['row' => $row]) : ($this->getValue($row)); } diff --git a/src/Views/Traits/Helpers/ColumnHelpers.php b/src/Views/Traits/Helpers/ColumnHelpers.php index a43d21681..fb2fbfb38 100644 --- a/src/Views/Traits/Helpers/ColumnHelpers.php +++ b/src/Views/Traits/Helpers/ColumnHelpers.php @@ -131,7 +131,7 @@ public function getContents(Model $row): null|string|\BackedEnum|HtmlString|Data } // TODO: Test - public function getValue(Model $row) + public function getValue(Model $row): mixed { if ($this->isBaseColumn()) { return $row->{$this->getField()}; From 37691f9d3d621a49ae2a9c53619eb0bf60be05ee Mon Sep 17 00:00:00 2001 From: Joe <104938042+lrljoe@users.noreply.github.com> Date: Thu, 11 Jul 2024 00:28:33 +0100 Subject: [PATCH 16/21] Add CountColumn, simpler adding of WithCounts, With (#1761) * Initial Commit * Adjust CountColumn * Add ExtraWiths * Add AggregateColumn * Add SumColumn * Update Docs - Add Column Types Section * Add exceptions for empty data source, add standard tests * Ensure pcov runs on push to master/development/develop * Update to use codecov v4 --------- Co-authored-by: lrljoe --- .github/workflows/run-tests-pcov-pull.yml | 9 +- .gitignore | 4 +- CHANGELOG.md | 4 + coverage.xml | 2894 ----------------- docs/bulk-actions/_index.md | 2 +- docs/column-types/_index.md | 4 + docs/column-types/array_column.md | 22 + docs/column-types/avg_column.md | 14 + docs/column-types/boolean_columns.md | 81 + docs/column-types/button_group_column.md | 34 + docs/column-types/color_columns.md | 41 + docs/column-types/component_column.md | 28 + docs/column-types/count_column.md | 14 + docs/column-types/date_columns.md | 28 + docs/column-types/image_columns.md | 26 + docs/column-types/link_columns.md | 24 + .../column-types/livewire_component_column.md | 5 + docs/column-types/sum_column.md | 14 + docs/columns/other-column-types.md | 241 +- docs/examples/_index.md | 2 +- docs/filter-types/_index.md | 2 +- docs/filters/_index.md | 2 +- docs/footer/_index.md | 2 +- docs/misc/_index.md | 2 +- docs/pagination/_index.md | 2 +- docs/reordering/_index.md | 2 +- docs/rows/_index.md | 2 +- docs/search/_index.md | 2 +- docs/secondary-header/_index.md | 2 +- docs/sorting/_index.md | 2 +- src/Traits/ComponentUtilities.php | 8 + .../Configuration/ComponentConfiguration.php | 56 + src/Traits/Helpers/ColumnHelpers.php | 28 + src/Traits/Helpers/ComponentHelpers.php | 40 + src/Traits/WithData.php | 26 +- src/Views/Columns/AggregateColumn.php | 23 + src/Views/Columns/AvgColumn.php | 18 + src/Views/Columns/CountColumn.php | 18 + src/Views/Columns/SumColumn.php | 18 + .../AggregateColumnConfiguration.php | 59 + .../ArrayColumnConfiguration.php | 10 + .../Traits/Helpers/AggregateColumnHelpers.php | 46 + .../Traits/Helpers/ArrayColumnHelpers.php | 4 - src/Views/Traits/IsAggregateColumn.php | 14 + tests/Attributes/AggregateColumnProvider.php | 13 + tests/Http/Livewire/SpeciesTable.php | 38 + tests/TestCase.php | 22 +- .../ComponentConfigurationTest.php | 102 + tests/Views/Columns/ArrayColumnTest.php | 87 + tests/Views/Columns/AvgColumnTest.php | 111 + tests/Views/Columns/CountColumnTest.php | 50 + tests/Views/Columns/SumColumnTest.php | 111 + 52 files changed, 1260 insertions(+), 3153 deletions(-) delete mode 100644 coverage.xml create mode 100644 docs/column-types/_index.md create mode 100644 docs/column-types/array_column.md create mode 100644 docs/column-types/avg_column.md create mode 100644 docs/column-types/boolean_columns.md create mode 100644 docs/column-types/button_group_column.md create mode 100644 docs/column-types/color_columns.md create mode 100644 docs/column-types/component_column.md create mode 100644 docs/column-types/count_column.md create mode 100644 docs/column-types/date_columns.md create mode 100644 docs/column-types/image_columns.md create mode 100644 docs/column-types/link_columns.md create mode 100644 docs/column-types/livewire_component_column.md create mode 100644 docs/column-types/sum_column.md create mode 100644 src/Views/Columns/AggregateColumn.php create mode 100644 src/Views/Columns/AvgColumn.php create mode 100644 src/Views/Columns/CountColumn.php create mode 100644 src/Views/Columns/SumColumn.php create mode 100644 src/Views/Traits/Configuration/AggregateColumnConfiguration.php create mode 100644 src/Views/Traits/Helpers/AggregateColumnHelpers.php create mode 100644 src/Views/Traits/IsAggregateColumn.php create mode 100644 tests/Attributes/AggregateColumnProvider.php create mode 100644 tests/Http/Livewire/SpeciesTable.php create mode 100644 tests/Views/Columns/ArrayColumnTest.php create mode 100644 tests/Views/Columns/AvgColumnTest.php create mode 100644 tests/Views/Columns/CountColumnTest.php create mode 100644 tests/Views/Columns/SumColumnTest.php diff --git a/.github/workflows/run-tests-pcov-pull.yml b/.github/workflows/run-tests-pcov-pull.yml index 3a24a91ba..ef73e4b42 100644 --- a/.github/workflows/run-tests-pcov-pull.yml +++ b/.github/workflows/run-tests-pcov-pull.yml @@ -1,6 +1,11 @@ name: run-tests-pcov-pull on: + push: + branches: + - 'develop' + - 'development' + - 'master' pull_request: branches: - 'develop' @@ -18,7 +23,7 @@ jobs: laravel: [10] stability: [prefer-dist] - name: PCOV-PULL - ${{ matrix.os }} - P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} + name: PCOV - ${{ matrix.os }} - P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} env: extensionKey: phpextensions-${{ matrix.os }}-P${{ matrix.php }}-withpcov extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pcov,pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo @@ -86,7 +91,7 @@ jobs: run: php ./vendor/bin/paratest --cache-directory=".phpunit.cache/code-coverage" --strict-coverage --coverage-clover ./coverage.xml --processes=4 - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: diff --git a/.gitignore b/.gitignore index a36981422..d3e4b8ca4 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,6 @@ phpunit.xml.dist.dev .history/* .env phpunit.xml.bak -phpstan.txt \ No newline at end of file +phpstan.txt +coverage.xml +./tmp/** \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 6993a50e3..7b13aa903 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to `laravel-livewire-tables` will be documented in this file +## UNRELEASED +### New Features +- Add new columns (ArrayColumn, AvgColumn, CountColumn, SumColumn) by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1761 + ## [v3.2.8] - 2024-07-03 ### Bug Fixes - Fix hide bulk actions when empty not reflecting in frontend by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1747 diff --git a/coverage.xml b/coverage.xml deleted file mode 100644 index 7428459d2..000000000 --- a/coverage.xml +++ /dev/null @@ -1,2894 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/bulk-actions/_index.md b/docs/bulk-actions/_index.md index f76ab3244..48cb71a17 100644 --- a/docs/bulk-actions/_index.md +++ b/docs/bulk-actions/_index.md @@ -1,4 +1,4 @@ --- title: Bulk Actions -weight: 9 +weight: 10 --- diff --git a/docs/column-types/_index.md b/docs/column-types/_index.md new file mode 100644 index 000000000..821c869a2 --- /dev/null +++ b/docs/column-types/_index.md @@ -0,0 +1,4 @@ +--- +title: Column Types +weight: 5 +--- diff --git a/docs/column-types/array_column.md b/docs/column-types/array_column.md new file mode 100644 index 000000000..1571da2b9 --- /dev/null +++ b/docs/column-types/array_column.md @@ -0,0 +1,22 @@ +--- +title: Array Columns (beta) +weight: 1 +--- + +Array columns provide an easy way to work with and display an array of data from a field. + +``` +ArrayColumn::make('notes', 'name') + ->data(fn($value, $row) => ($row->notes)) + ->outputFormat(fn($index, $value) => "".$value->name."") + ->separator('
') + ->sortable(), +``` + +### Empty Value +You may define the default/empty value using the "emptyValue" method + +``` +ArrayColumn::make('notes', 'name') + ->emptyValue('Unknown'), +``` \ No newline at end of file diff --git a/docs/column-types/avg_column.md b/docs/column-types/avg_column.md new file mode 100644 index 000000000..faa653ca2 --- /dev/null +++ b/docs/column-types/avg_column.md @@ -0,0 +1,14 @@ +--- +title: Avg Columns (beta) +weight: 2 +--- + +Avg columns provide an easy way to display the "Average" of a field on a relation. + +``` + AvgColumn::make('Average Related User Age') + ->setDataSource('users','age') + ->sortable(), +``` + +The "sortable()" callback can accept a callback, or you can use the default behaviour, which calculates the correct field to sort on. \ No newline at end of file diff --git a/docs/column-types/boolean_columns.md b/docs/column-types/boolean_columns.md new file mode 100644 index 000000000..32a759918 --- /dev/null +++ b/docs/column-types/boolean_columns.md @@ -0,0 +1,81 @@ +--- +title: Boolean Columns +weight: 3 +--- + +Boolean columns are good if you have a column type that is a true/false, or 0/1 value. + +For example: + +```php +BooleanColumn::make('Active') +``` + +Would yield: + +![Boolean Column](https://imgur.com/LAk6gHY.png) + +### Using your own view + +If you don't want to use the default view and icons you can set your own: + +```php +BooleanColumn::make('Active') + ->setView('my.active.view') +``` + +You will have access to `$component`, `$status`, and `$successValue`. + +To help you better understand, this is the Tailwind implementation of BooleanColumn: + +```html +@if ($status) + + + +@else + + + +@endif +``` + +### Setting the truthy value + +If you want the false value to be the green option, you can set: + +```php +BooleanColumn::make('Active') + ->setSuccessValue(false); // Makes false the 'successful' option +``` + +That would swap the colors of the icons in the image above. + +### Setting the status value + +By default, the `$status` is set to: + +```php +(bool)$value === true +``` + +You can override this functionality: + +```php +BooleanColumn::make('Active') + // Note: Parameter `$row` available as of v2.4 + ->setCallback(function(string $value, $row) { + // Figure out what makes $value true + }), +``` + +### Different types of boolean display + +By default, the BooleanColumn displays icons. + +If you would like the BooleanColumn to display a plain Yes/No, you can set: + +```php +BooleanColumn::make('Active') + ->yesNo() +``` diff --git a/docs/column-types/button_group_column.md b/docs/column-types/button_group_column.md new file mode 100644 index 000000000..5a3390049 --- /dev/null +++ b/docs/column-types/button_group_column.md @@ -0,0 +1,34 @@ +--- +title: Button Group Columns +weight: 4 +--- + +Button group columns let you provide an array of LinkColumns to display in a single cell. + +```php +ButtonGroupColumn::make('Actions') + ->attributes(function($row) { + return [ + 'class' => 'space-x-2', + ]; + }) + ->buttons([ + LinkColumn::make('View') // make() has no effect in this case but needs to be set anyway + ->title(fn($row) => 'View ' . $row->name) + ->location(fn($row) => route('user.show', $row)) + ->attributes(function($row) { + return [ + 'class' => 'underline text-blue-500 hover:no-underline', + ]; + }), + LinkColumn::make('Edit') + ->title(fn($row) => 'Edit ' . $row->name) + ->location(fn($row) => route('user.edit', $row)) + ->attributes(function($row) { + return [ + 'target' => '_blank', + 'class' => 'underline text-blue-500 hover:no-underline', + ]; + }), + ]), +``` diff --git a/docs/column-types/color_columns.md b/docs/column-types/color_columns.md new file mode 100644 index 000000000..e4920144d --- /dev/null +++ b/docs/column-types/color_columns.md @@ -0,0 +1,41 @@ +--- +title: Color Columns +weight: 5 +--- + +Color columns provide an easy way to a Color in a Column + +You may pass either pass a CSS-compliant colour as a field +```php +ColorColumn::make('Favourite Colour', 'favourite_color'), +``` + +Or you may use a Callback +```php +ColorColumn::make('Favourite Colour') + ->color( + function ($row) { + if ($row->success_rate < 40) + { + return '#ff0000'; + } + else if ($row->success_rate > 90) + { + return '#008000'; + } + else return '#ffa500'; + + } + ), +``` + +You may also specify attributes to use on the div displaying the color, to adjust the size or appearance, this receives the full row. By default, this will replace the standard classes, to retain them, set "default" to true. To then over-ride, you should prefix your classes with "!" to signify importance. +```php + ColorColumn::make('Favourite Colour') + ->attributes(function ($row) { + return [ + 'class' => '!rounded-lg self-center', + 'default' => true, + ]; + }), +``` diff --git a/docs/column-types/component_column.md b/docs/column-types/component_column.md new file mode 100644 index 000000000..0db9c9483 --- /dev/null +++ b/docs/column-types/component_column.md @@ -0,0 +1,28 @@ +--- +title: Component Columns +weight: 6 +--- + +Component columns let you specify a component name and attributes and provides the column value to the slot. + +```php +// Before +Column::make("Email", "email") + ->format(function ($value) { + return view('components.alert') + ->with('attributes', new ComponentAttributeBag([ + 'type' => Str::endsWith($value, 'example.org') ? 'success' : 'danger', + 'dismissible' => true, + ])) + ->with('slot', $value); + }), + +// After +ComponentColumn::make('E-mail', 'email') + ->component('email') + ->attributes(fn ($value, $row, Column $column) => [ + 'type' => Str::endsWith($value, 'example.org') ? 'success' : 'danger', + 'dismissible' => true, + ]), +``` + diff --git a/docs/column-types/count_column.md b/docs/column-types/count_column.md new file mode 100644 index 000000000..d33cf5df9 --- /dev/null +++ b/docs/column-types/count_column.md @@ -0,0 +1,14 @@ +--- +title: Count Columns (beta) +weight: 7 +--- + +Count columns provide an easy way to display the "Count" of a relation. + +``` + CountColumn::make('Related Users') + ->setDataSource('users') + ->sortable(), +``` + +The "sortable()" callback can accept a callback, or you can use the default behaviour, which calculates the correct field to sort on. \ No newline at end of file diff --git a/docs/column-types/date_columns.md b/docs/column-types/date_columns.md new file mode 100644 index 000000000..abc8bbb42 --- /dev/null +++ b/docs/column-types/date_columns.md @@ -0,0 +1,28 @@ +--- +title: Date Columns +weight: 8 +--- + +Date columns provide an easy way to display dates in a given format, without having to use repetitive format() methods or partial views. + +You may pass either a DateTime object, in which you can define an "outputFormat" +```php +DateColumn::make('Updated At', 'updated_at') + ->outputFormat('Y-m-d H:i:s), +``` + +Or you may pass a string, in which case you can define an "inputFormat" in addition to the outputFormat: +```php +DateColumn::make('Last Charged', 'last_charged_at') + ->inputFormat('Y-m-d H:i:s') + ->outputFormat('Y-m-d'), +``` + +You may also set an "emptyValue" to use when there is no value from the database: +```php +DateColumn::make('Last Charged', 'last_charged_at') + ->inputFormat('Y-m-d H:i:s') + ->outputFormat('Y-m-d') + ->emptyValue('Not Found'), +``` + diff --git a/docs/column-types/image_columns.md b/docs/column-types/image_columns.md new file mode 100644 index 000000000..f816280af --- /dev/null +++ b/docs/column-types/image_columns.md @@ -0,0 +1,26 @@ +--- +title: Image Columns +weight: 9 +--- + +Image columns provide a way to display images in your table without having to use `format()` or partial views: + +```php +ImageColumn::make('Avatar') + ->location( + fn($row) => storage_path('app/public/avatars/' . $row->id . '.jpg') + ), +``` + +You may also pass an array of attributes to apply to the image tag: + +```php +ImageColumn::make('Avatar') + ->location( + fn($row) => storage_path('app/public/avatars/' . $row->id . '.jpg') + ) + ->attributes(fn($row) => [ + 'class' => 'rounded-full', + 'alt' => $row->name . ' Avatar', + ]), +``` diff --git a/docs/column-types/link_columns.md b/docs/column-types/link_columns.md new file mode 100644 index 000000000..cba4bc887 --- /dev/null +++ b/docs/column-types/link_columns.md @@ -0,0 +1,24 @@ +--- +title: Link Columns +weight: 10 +--- + +Link columns provide a way to display HTML links in your table without having to use `format()` or partial views: + +```php +LinkColumn::make('Action') + ->title(fn($row) => 'Edit') + ->location(fn($row) => route('admin.users.edit', $row)), +``` + +You may also pass an array of attributes to apply to the `a` tag: + +```php +LinkColumn::make('Action') + ->title(fn($row) => 'Edit') + ->location(fn($row) => route('admin.users.edit', $row)) + ->attributes(fn($row) => [ + 'class' => 'rounded-full', + 'alt' => $row->name . ' Avatar', + ]), +``` diff --git a/docs/column-types/livewire_component_column.md b/docs/column-types/livewire_component_column.md new file mode 100644 index 000000000..b75172881 --- /dev/null +++ b/docs/column-types/livewire_component_column.md @@ -0,0 +1,5 @@ +--- +title: Livewire Component (beta) +weight: 11 +--- + diff --git a/docs/column-types/sum_column.md b/docs/column-types/sum_column.md new file mode 100644 index 000000000..7f99f7cab --- /dev/null +++ b/docs/column-types/sum_column.md @@ -0,0 +1,14 @@ +--- +title: Sum Columns (beta) +weight: 12 +--- + +Sum columns provide an easy way to display the "Sum" of a field on a relation. + +``` + SumColumn::make('Total Age of Related Users') + ->setDataSource('users','age') + ->sortable(), +``` + +The "sortable()" callback can accept a callback, or you can use the default behaviour, which calculates the correct field to sort on. \ No newline at end of file diff --git a/docs/columns/other-column-types.md b/docs/columns/other-column-types.md index dc80da449..2e515eedf 100644 --- a/docs/columns/other-column-types.md +++ b/docs/columns/other-column-types.md @@ -3,247 +3,14 @@ title: Other Column Types weight: 4 --- -## Boolean Columns -Boolean columns are good if you have a column type that is a true/false, or 0/1 value. -For example: -```php -BooleanColumn::make('Active') -``` -Would yield: +## Aggregate Columns -![Boolean Column](https://imgur.com/LAk6gHY.png) +### AvgColumn -### Using your own view +### CountColumn -If you don't want to use the default view and icons you can set your own: - -```php -BooleanColumn::make('Active') - ->setView('my.active.view') -``` - -You will have access to `$component`, `$status`, and `$successValue`. - -To help you better understand, this is the Tailwind implementation of BooleanColumn: - -```html -@if ($status) - - - -@else - - - -@endif -``` - -### Setting the truthy value - -If you want the false value to be the green option, you can set: - -```php -BooleanColumn::make('Active') - ->setSuccessValue(false); // Makes false the 'successful' option -``` - -That would swap the colors of the icons in the image above. - -### Setting the status value - -By default, the `$status` is set to: - -```php -(bool)$value === true -``` - -You can override this functionality: - -```php -BooleanColumn::make('Active') - // Note: Parameter `$row` available as of v2.4 - ->setCallback(function(string $value, $row) { - // Figure out what makes $value true - }), -``` - -### Different types of boolean display - -By default, the BooleanColumn displays icons. - -If you would like the BooleanColumn to display a plain Yes/No, you can set: - -```php -BooleanColumn::make('Active') - ->yesNo() -``` -## Color Columns - -Color columns provide an easy way to a Color in a Column - -You may pass either pass a CSS-compliant colour as a field -```php -ColorColumn::make('Favourite Colour', 'favourite_color'), -``` - -Or you may use a Callback -```php -ColorColumn::make('Favourite Colour') - ->color( - function ($row) { - if ($row->success_rate < 40) - { - return '#ff0000'; - } - else if ($row->success_rate > 90) - { - return '#008000'; - } - else return '#ffa500'; - - } - ), -``` - -You may also specify attributes to use on the div displaying the color, to adjust the size or appearance, this receives the full row. By default, this will replace the standard classes, to retain them, set "default" to true. To then over-ride, you should prefix your classes with "!" to signify importance. -```php - ColorColumn::make('Favourite Colour') - ->attributes(function ($row) { - return [ - 'class' => '!rounded-lg self-center', - 'default' => true, - ]; - }), -``` - -## Date Columns - -Date columns provide an easy way to display dates in a given format, without having to use repetitive format() methods or partial views. - -You may pass either a DateTime object, in which you can define an "outputFormat" -```php -DateColumn::make('Updated At', 'updated_at') - ->outputFormat('Y-m-d H:i:s), -``` - -Or you may pass a string, in which case you can define an "inputFormat" in addition to the outputFormat: -```php -DateColumn::make('Last Charged', 'last_charged_at') - ->inputFormat('Y-m-d H:i:s') - ->outputFormat('Y-m-d'), -``` - -You may also set an "emptyValue" to use when there is no value from the database: -```php -DateColumn::make('Last Charged', 'last_charged_at') - ->inputFormat('Y-m-d H:i:s') - ->outputFormat('Y-m-d') - ->emptyValue('Not Found'), -``` - -## Image Columns - -Image columns provide a way to display images in your table without having to use `format()` or partial views: - -```php -ImageColumn::make('Avatar') - ->location( - fn($row) => storage_path('app/public/avatars/' . $row->id . '.jpg') - ), -``` - -You may also pass an array of attributes to apply to the image tag: - -```php -ImageColumn::make('Avatar') - ->location( - fn($row) => storage_path('app/public/avatars/' . $row->id . '.jpg') - ) - ->attributes(fn($row) => [ - 'class' => 'rounded-full', - 'alt' => $row->name . ' Avatar', - ]), -``` - -## Link Columns - -Link columns provide a way to display HTML links in your table without having to use `format()` or partial views: - -```php -LinkColumn::make('Action') - ->title(fn($row) => 'Edit') - ->location(fn($row) => route('admin.users.edit', $row)), -``` - -You may also pass an array of attributes to apply to the `a` tag: - -```php -LinkColumn::make('Action') - ->title(fn($row) => 'Edit') - ->location(fn($row) => route('admin.users.edit', $row)) - ->attributes(fn($row) => [ - 'class' => 'rounded-full', - 'alt' => $row->name . ' Avatar', - ]), -``` - -## Button Group Columns - -Button group columns let you provide an array of LinkColumns to display in a single cell. - -```php -ButtonGroupColumn::make('Actions') - ->attributes(function($row) { - return [ - 'class' => 'space-x-2', - ]; - }) - ->buttons([ - LinkColumn::make('View') // make() has no effect in this case but needs to be set anyway - ->title(fn($row) => 'View ' . $row->name) - ->location(fn($row) => route('user.show', $row)) - ->attributes(function($row) { - return [ - 'class' => 'underline text-blue-500 hover:no-underline', - ]; - }), - LinkColumn::make('Edit') - ->title(fn($row) => 'Edit ' . $row->name) - ->location(fn($row) => route('user.edit', $row)) - ->attributes(function($row) { - return [ - 'target' => '_blank', - 'class' => 'underline text-blue-500 hover:no-underline', - ]; - }), - ]), -``` - -## Component Columns - -Component columns let you specify a component name and attributes and provides the column value to the slot. - -```php -// Before -Column::make("Email", "email") - ->format(function ($value) { - return view('components.alert') - ->with('attributes', new ComponentAttributeBag([ - 'type' => Str::endsWith($value, 'example.org') ? 'success' : 'danger', - 'dismissible' => true, - ])) - ->with('slot', $value); - }), - -// After -ComponentColumn::make('E-mail', 'email') - ->component('email') - ->attributes(fn ($value, $row, Column $column) => [ - 'type' => Str::endsWith($value, 'example.org') ? 'success' : 'danger', - 'dismissible' => true, - ]), -``` +### SumColumn \ No newline at end of file diff --git a/docs/examples/_index.md b/docs/examples/_index.md index a831d1a26..03b1d982b 100644 --- a/docs/examples/_index.md +++ b/docs/examples/_index.md @@ -1,4 +1,4 @@ --- title: Examples -weight: 14 +weight: 16 --- diff --git a/docs/filter-types/_index.md b/docs/filter-types/_index.md index 6c6d8ccf4..3631183ba 100644 --- a/docs/filter-types/_index.md +++ b/docs/filter-types/_index.md @@ -1,4 +1,4 @@ --- title: Filter Types -weight: 11 +weight: 12 --- diff --git a/docs/filters/_index.md b/docs/filters/_index.md index 43af373bd..e0ac299f1 100644 --- a/docs/filters/_index.md +++ b/docs/filters/_index.md @@ -1,4 +1,4 @@ --- title: Filters -weight: 10 +weight: 11 --- diff --git a/docs/footer/_index.md b/docs/footer/_index.md index e16f7efb5..4edcb2460 100644 --- a/docs/footer/_index.md +++ b/docs/footer/_index.md @@ -1,4 +1,4 @@ --- title: Footer -weight: 13 +weight: 15 --- diff --git a/docs/misc/_index.md b/docs/misc/_index.md index 9f806b32e..193fabdf4 100644 --- a/docs/misc/_index.md +++ b/docs/misc/_index.md @@ -1,4 +1,4 @@ --- title: Misc. -weight: 15 +weight: 17 --- diff --git a/docs/pagination/_index.md b/docs/pagination/_index.md index 723cb0c11..50491640d 100644 --- a/docs/pagination/_index.md +++ b/docs/pagination/_index.md @@ -1,4 +1,4 @@ --- title: Pagination -weight: 7 +weight: 8 --- diff --git a/docs/reordering/_index.md b/docs/reordering/_index.md index bc21a450b..e66081c28 100644 --- a/docs/reordering/_index.md +++ b/docs/reordering/_index.md @@ -1,4 +1,4 @@ --- title: Reordering -weight: 11 +weight: 13 --- diff --git a/docs/rows/_index.md b/docs/rows/_index.md index f731c19d2..7d5ebba99 100644 --- a/docs/rows/_index.md +++ b/docs/rows/_index.md @@ -1,4 +1,4 @@ --- title: Rows -weight: 5 +weight: 6 --- diff --git a/docs/search/_index.md b/docs/search/_index.md index edb174fde..4aac1542b 100644 --- a/docs/search/_index.md +++ b/docs/search/_index.md @@ -1,4 +1,4 @@ --- title: Search -weight: 8 +weight: 9 --- diff --git a/docs/secondary-header/_index.md b/docs/secondary-header/_index.md index 5e2b9aba5..f334c9e68 100644 --- a/docs/secondary-header/_index.md +++ b/docs/secondary-header/_index.md @@ -1,4 +1,4 @@ --- title: Secondary Header -weight: 12 +weight: 14 --- diff --git a/docs/sorting/_index.md b/docs/sorting/_index.md index a99eabf1d..422739fb6 100644 --- a/docs/sorting/_index.md +++ b/docs/sorting/_index.md @@ -1,4 +1,4 @@ --- title: Sorting -weight: 6 +weight: 7 --- diff --git a/src/Traits/ComponentUtilities.php b/src/Traits/ComponentUtilities.php index 1e421e0b6..8ec4bd8b6 100644 --- a/src/Traits/ComponentUtilities.php +++ b/src/Traits/ComponentUtilities.php @@ -37,6 +37,14 @@ trait ComponentUtilities protected array $additionalSelects = []; + protected array $extraWiths = []; + + protected array $extraWithCounts = []; + + protected array $extraWithSums = []; + + protected array $extraWithAvgs = []; + /** * Set any configuration options */ diff --git a/src/Traits/Configuration/ComponentConfiguration.php b/src/Traits/Configuration/ComponentConfiguration.php index 90611185a..044f5655e 100644 --- a/src/Traits/Configuration/ComponentConfiguration.php +++ b/src/Traits/Configuration/ComponentConfiguration.php @@ -96,4 +96,60 @@ public function setDataTableFingerprint(string $dataTableFingerprint): self return $this; } + + public function setExtraWiths(array $extraWiths): self + { + $this->extraWiths = $extraWiths; + + return $this; + } + + public function addExtraWith(string $extraWith): self + { + $this->extraWiths[] = $extraWith; + + return $this; + } + + public function addExtraWiths(array $extraWiths): self + { + $this->extraWiths = [...$this->extraWiths, ...$extraWiths]; + + return $this; + } + + public function setExtraWithCounts(array $extraWithCounts): self + { + $this->extraWithCounts = $extraWithCounts; + + return $this; + } + + public function addExtraWithCount(string $extraWithCount): self + { + $this->extraWithCounts[] = $extraWithCount; + + return $this; + } + + public function addExtraWithCounts(array $extraWithCounts): self + { + $this->extraWithCounts = [...$this->extraWithCounts, ...$extraWithCounts]; + + return $this; + } + + public function addExtraWithSum(string $relationship, string $column): self + { + $this->extraWithSums[] = ['table' => $relationship, 'field' => $column]; + + return $this; + } + + public function addExtraWithAvg(string $relationship, string $column): self + { + $this->extraWithAvgs[] = ['table' => $relationship, 'field' => $column]; + + return $this; + } } diff --git a/src/Traits/Helpers/ColumnHelpers.php b/src/Traits/Helpers/ColumnHelpers.php index 009a6f4bd..920d63fe6 100644 --- a/src/Traits/Helpers/ColumnHelpers.php +++ b/src/Traits/Helpers/ColumnHelpers.php @@ -4,6 +4,7 @@ use Illuminate\Support\Collection; use Rappasoft\LaravelLivewireTables\Views\Column; +use Rappasoft\LaravelLivewireTables\Views\Columns\AggregateColumn; trait ColumnHelpers { @@ -18,6 +19,15 @@ public function setColumns(): void ->filter(fn ($column) => $column instanceof Column) ->map(function (Column $column) { $column->setComponent($this); + if ($column instanceof AggregateColumn) { + if ($column->getAggregateMethod() == 'count' && $column->hasDataSource()) { + $this->addExtraWithCount($column->getDataSource()); + } elseif ($column->getAggregateMethod() == 'sum' && $column->hasDataSource() && $column->hasForeignColumn()) { + $this->addExtraWithSum($column->getDataSource(), $column->getForeignColumn()); + } elseif ($column->getAggregateMethod() == 'avg' && $column->hasDataSource() && $column->hasForeignColumn()) { + $this->addExtraWithAvg($column->getDataSource(), $column->getForeignColumn()); + } + } if ($column->hasField()) { if ($column->isBaseColumn()) { @@ -198,6 +208,15 @@ public function getPrependedColumns(): Collection ->filter(fn ($column) => $column instanceof Column) ->map(function (Column $column) { $column->setComponent($this); + if ($column instanceof AggregateColumn) { + if ($column->getAggregateMethod() == 'count' && $column->hasDataSource()) { + $this->addExtraWithCount($column->getDataSource()); + } elseif ($column->getAggregateMethod() == 'sum' && $column->hasDataSource() && $column->hasForeignColumn()) { + $this->addExtraWithSum($column->getDataSource(), $column->getForeignColumn()); + } elseif ($column->getAggregateMethod() == 'avg' && $column->hasDataSource() && $column->hasForeignColumn()) { + $this->addExtraWithAvg($column->getDataSource(), $column->getForeignColumn()); + } + } if ($column->hasField()) { if ($column->isBaseColumn()) { @@ -217,6 +236,15 @@ public function getAppendedColumns(): Collection ->filter(fn ($column) => $column instanceof Column) ->map(function (Column $column) { $column->setComponent($this); + if ($column instanceof AggregateColumn) { + if ($column->getAggregateMethod() == 'count' && $column->hasDataSource()) { + $this->addExtraWithCount($column->getDataSource()); + } elseif ($column->getAggregateMethod() == 'sum' && $column->hasDataSource() && $column->hasForeignColumn()) { + $this->addExtraWithSum($column->getDataSource(), $column->getForeignColumn()); + } elseif ($column->getAggregateMethod() == 'avg' && $column->hasDataSource() && $column->hasForeignColumn()) { + $this->addExtraWithAvg($column->getDataSource(), $column->getForeignColumn()); + } + } if ($column->hasField()) { if ($column->isBaseColumn()) { diff --git a/src/Traits/Helpers/ComponentHelpers.php b/src/Traits/Helpers/ComponentHelpers.php index c600200b5..96eae35f8 100644 --- a/src/Traits/Helpers/ComponentHelpers.php +++ b/src/Traits/Helpers/ComponentHelpers.php @@ -149,4 +149,44 @@ public function getAdditionalSelects(): array { return $this->additionalSelects; } + + public function hasExtraWiths(): bool + { + return ! empty($this->extraWiths); + } + + public function getExtraWiths(): array + { + return $this->extraWiths; + } + + public function hasExtraWithCounts(): bool + { + return ! empty($this->extraWithCounts); + } + + public function getExtraWithCounts(): array + { + return $this->extraWithCounts; + } + + public function hasExtraWithSums(): bool + { + return ! empty($this->extraWithSums); + } + + public function getExtraWithSums(): array + { + return $this->extraWithSums; + } + + public function hasExtraWithAvgs(): bool + { + return ! empty($this->extraWithAvgs); + } + + public function getExtraWithAvgs(): array + { + return $this->extraWithAvgs; + } } diff --git a/src/Traits/WithData.php b/src/Traits/WithData.php index 5e1ca3298..23d1055bb 100644 --- a/src/Traits/WithData.php +++ b/src/Traits/WithData.php @@ -56,6 +56,29 @@ protected function baseQuery(): Builder $this->setBuilder($this->applyFilters()); + $builder = $this->getBuilder(); + + if ($this->hasExtraWiths()) { + $builder->with($this->getExtraWiths()); + } + + if ($this->hasExtraWithSums()) { + foreach ($this->getExtraWithSums() as $extraSum) { + $builder->withSum($extraSum['table'], $extraSum['field']); + } + } + if ($this->hasExtraWithAvgs()) { + foreach ($this->getExtraWithAvgs() as $extraAvg) { + $builder->withAvg($extraAvg['table'], $extraAvg['field']); + } + } + + if ($this->hasExtraWithCounts()) { + $builder->withCount($this->getExtraWithCounts()); + } + + $this->setBuilder($builder); + return $this->getBuilder(); } @@ -249,7 +272,8 @@ protected function getTableAlias(?string $currentTableAlias, string $relationPar public function builder(): Builder { if ($this->hasModel()) { - return $this->getModel()::query()->with($this->getRelationships()); + return $this->getModel()::query() + ->with($this->getRelationships()); } // If model does not exist diff --git a/src/Views/Columns/AggregateColumn.php b/src/Views/Columns/AggregateColumn.php new file mode 100644 index 000000000..a284b08ff --- /dev/null +++ b/src/Views/Columns/AggregateColumn.php @@ -0,0 +1,23 @@ +label(fn () => null); + } +} diff --git a/src/Views/Columns/AvgColumn.php b/src/Views/Columns/AvgColumn.php new file mode 100644 index 000000000..3af197873 --- /dev/null +++ b/src/Views/Columns/AvgColumn.php @@ -0,0 +1,18 @@ +label(fn () => null); + } +} diff --git a/src/Views/Columns/CountColumn.php b/src/Views/Columns/CountColumn.php new file mode 100644 index 000000000..4a1ffd812 --- /dev/null +++ b/src/Views/Columns/CountColumn.php @@ -0,0 +1,18 @@ +label(fn () => null); + } +} diff --git a/src/Views/Columns/SumColumn.php b/src/Views/Columns/SumColumn.php new file mode 100644 index 000000000..a01822d7b --- /dev/null +++ b/src/Views/Columns/SumColumn.php @@ -0,0 +1,18 @@ +label(fn () => null); + } +} diff --git a/src/Views/Traits/Configuration/AggregateColumnConfiguration.php b/src/Views/Traits/Configuration/AggregateColumnConfiguration.php new file mode 100644 index 000000000..2f181ed2f --- /dev/null +++ b/src/Views/Traits/Configuration/AggregateColumnConfiguration.php @@ -0,0 +1,59 @@ +dataSource = $dataSource; + + if (isset($foreignColumn)) { + $this->setForeignColumn($foreignColumn); + } + + $this->setDefaultLabel(); + + return $this; + } + + public function setAggregateMethod(string $aggregateMethod): self + { + $this->aggregateMethod = $aggregateMethod; + $this->setDefaultLabel(); + + return $this; + } + + public function setForeignColumn(string $foreignColumn): self + { + $this->foreignColumn = $foreignColumn; + $this->setDefaultLabel(); + + return $this; + } + + public function setDefaultLabel(): void + { + $this->label(function ($row, Column $column) { + if ($this->hasForeignColumn()) { + return $row->{$this->getDataSource().'_'.$this->getAggregateMethod().'_'.$this->getForeignColumn()}; + } + + return $row->{$this->getDataSource().'_'.$this->getAggregateMethod()}; + }); + + } + + public function sortable(?callable $callback = null): self + { + $this->sortable = true; + + $this->sortCallback = ($callback === null) ? ($this->hasForeignColumn() ? fn (Builder $query, string $direction) => $query->orderBy($this->getDataSource().'_'.$this->getAggregateMethod().'_'.$this->getForeignColumn(), $direction) : fn (Builder $query, string $direction) => $query->orderBy($this->dataSource.'_count', $direction)) : $callback; + + return $this; + } +} diff --git a/src/Views/Traits/Configuration/ArrayColumnConfiguration.php b/src/Views/Traits/Configuration/ArrayColumnConfiguration.php index 72188fd79..c0f7657b7 100644 --- a/src/Views/Traits/Configuration/ArrayColumnConfiguration.php +++ b/src/Views/Traits/Configuration/ArrayColumnConfiguration.php @@ -26,4 +26,14 @@ public function outputFormat(callable $callable): self return $this; } + + /** + * Define the Empty Value to use for the Column + */ + public function emptyValue(string $emptyValue): self + { + $this->emptyValue = $emptyValue; + + return $this; + } } diff --git a/src/Views/Traits/Helpers/AggregateColumnHelpers.php b/src/Views/Traits/Helpers/AggregateColumnHelpers.php new file mode 100644 index 000000000..1748b8091 --- /dev/null +++ b/src/Views/Traits/Helpers/AggregateColumnHelpers.php @@ -0,0 +1,46 @@ +dataSource; + } + + public function hasDataSource(): bool + { + return isset($this->dataSource); + } + + public function getAggregateMethod(): string + { + return $this->aggregateMethod; + } + + public function hasForeignColumn(): bool + { + return isset($this->foreignColumn); + } + + public function getForeignColumn(): string + { + return $this->foreignColumn; + } + + public function getContents(Model $row): null|string|\BackedEnum|HtmlString|DataTableConfigurationException|\Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View + { + if (! isset($this->dataSource)) { + throw new DataTableConfigurationException('You must specify a data source'); + } else { + return parent::getContents($row); + } + } +} diff --git a/src/Views/Traits/Helpers/ArrayColumnHelpers.php b/src/Views/Traits/Helpers/ArrayColumnHelpers.php index 7c32f7c70..8289038e4 100644 --- a/src/Views/Traits/Helpers/ArrayColumnHelpers.php +++ b/src/Views/Traits/Helpers/ArrayColumnHelpers.php @@ -48,10 +48,6 @@ public function getContents(Model $row): null|string|\BackedEnum|HtmlString|Data $outputValues = []; $value = $this->getValue($row); - if (! $this->hasSeparator()) { - throw new DataTableConfigurationException('You must set a valid separator on an ArrayColumn'); - } - if (! $this->hasDataCallback()) { throw new DataTableConfigurationException('You must set a data() method on an ArrayColumn'); } diff --git a/src/Views/Traits/IsAggregateColumn.php b/src/Views/Traits/IsAggregateColumn.php new file mode 100644 index 000000000..1824118b9 --- /dev/null +++ b/src/Views/Traits/IsAggregateColumn.php @@ -0,0 +1,14 @@ +setPrimaryKey('id'); + } + + public function columns(): array + { + return [ + Column::make('ID', 'id') + ->sortable() + ->setSortingPillTitle('Key') + ->setSortingPillDirections('0-9', '9-0'), + Column::make('Name') + ->sortable() + ->searchable(), + AvgColumn::make('Average Age') + ->setDataSource('pets', 'age'), + CountColumn::make('Number of Pets') + ->setDataSource('pets'), + SumColumn::make('Total Age') + ->setDataSource('pets', 'age'), + + ]; + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index fdcbd44a6..ab22227bd 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -9,7 +9,7 @@ use Livewire\LivewireServiceProvider; use Orchestra\Testbench\TestCase as Orchestra; use Rappasoft\LaravelLivewireTables\LaravelLivewireTablesServiceProvider; -use Rappasoft\LaravelLivewireTables\Tests\Http\Livewire\{PetsTable,PetsTableUnpaginated}; +use Rappasoft\LaravelLivewireTables\Tests\Http\Livewire\{PetsTable,PetsTableUnpaginated,SpeciesTable}; use Rappasoft\LaravelLivewireTables\Tests\Models\Breed; use Rappasoft\LaravelLivewireTables\Tests\Models\Pet; use Rappasoft\LaravelLivewireTables\Tests\Models\Species; @@ -19,6 +19,8 @@ class TestCase extends Orchestra { public PetsTable $basicTable; + public SpeciesTable $speciesTable; + public PetsTableUnpaginated $unpaginatedTable; /** @@ -76,7 +78,7 @@ protected function setUp(): void } $this->setupBasicTable(); $this->setupUnpaginatedTable(); - + $this->setupSpeciesTable(); } protected function setupBasicTable() @@ -95,6 +97,22 @@ protected function setupBasicTable() $this->basicTable->render(); } + protected function setupSpeciesTable() + { + $view = view('livewire-tables::datatable'); + $this->speciesTable = new SpeciesTable(); + $this->speciesTable->boot(); + $this->speciesTable->bootedComponentUtilities(); + $this->speciesTable->bootedWithData(); + $this->speciesTable->bootedWithColumns(); + $this->speciesTable->bootedWithColumnSelect(); + $this->speciesTable->bootedWithSecondaryHeader(); + $this->speciesTable->booted(); + $this->speciesTable->renderingWithData($view, []); + $this->speciesTable->renderingWithPagination($view, []); + $this->speciesTable->render(); + } + protected function setupUnpaginatedTable() { diff --git a/tests/Traits/Configuration/ComponentConfigurationTest.php b/tests/Traits/Configuration/ComponentConfigurationTest.php index 001cea2a0..3bed87443 100644 --- a/tests/Traits/Configuration/ComponentConfigurationTest.php +++ b/tests/Traits/Configuration/ComponentConfigurationTest.php @@ -300,4 +300,106 @@ public function test_can_set_hide_configurable_areas_when_reordering_status(): v $this->basicTable->setHideConfigurableAreasWhenReorderingStatus(true); } + + public function test_no_extra_withs_by_default(): void + { + $this->assertFalse($this->basicTable->hasExtraWiths()); + $this->assertEmpty($this->basicTable->getExtraWiths()); + } + + public function test_can_add_extra_with(): void + { + $this->assertFalse($this->basicTable->hasExtraWiths()); + $this->assertEmpty($this->basicTable->getExtraWiths()); + $this->basicTable->addExtraWith('user'); + $this->assertTrue($this->basicTable->hasExtraWiths()); + $this->assertSame(['user'], $this->basicTable->getExtraWiths()); + } + + public function test_can_add_extra_withs(): void + { + $this->assertFalse($this->basicTable->hasExtraWiths()); + $this->assertEmpty($this->basicTable->getExtraWiths()); + $this->basicTable->addExtraWiths(['user', 'pets']); + $this->assertTrue($this->basicTable->hasExtraWiths()); + $this->assertSame(['user', 'pets'], $this->basicTable->getExtraWiths()); + } + + public function test_can_set_extra_withs(): void + { + $this->assertFalse($this->basicTable->hasExtraWiths()); + $this->assertEmpty($this->basicTable->getExtraWiths()); + $this->basicTable->addExtraWith('test'); + $this->assertSame(['test'], $this->basicTable->getExtraWiths()); + $this->assertTrue($this->basicTable->hasExtraWiths()); + $this->basicTable->setExtraWiths(['user', 'pets']); + $this->assertTrue($this->basicTable->hasExtraWiths()); + $this->assertSame(['user', 'pets'], $this->basicTable->getExtraWiths()); + } + + public function test_no_extra_with_counts_by_default(): void + { + $this->assertFalse($this->basicTable->hasExtraWithCounts()); + $this->assertEmpty($this->basicTable->getExtraWithCounts()); + } + + public function test_can_add_extra_with_count(): void + { + $this->assertFalse($this->basicTable->hasExtraWithCounts()); + $this->assertEmpty($this->basicTable->getExtraWithCounts()); + $this->basicTable->addExtraWithCount('users'); + $this->assertTrue($this->basicTable->hasExtraWithCounts()); + $this->assertSame(['users'], $this->basicTable->getExtraWithCounts()); + } + + public function test_can_add_extra_with_counts(): void + { + $this->assertFalse($this->basicTable->hasExtraWithCounts()); + $this->assertEmpty($this->basicTable->getExtraWithCounts()); + $this->basicTable->addExtraWithCounts(['user', 'pets']); + $this->assertTrue($this->basicTable->hasExtraWithCounts()); + $this->assertSame(['user', 'pets'], $this->basicTable->getExtraWithCounts()); + } + + public function test_can_set_extra_with_counts(): void + { + $this->assertFalse($this->basicTable->hasExtraWithCounts()); + $this->assertEmpty($this->basicTable->getExtraWithCounts()); + $this->basicTable->addExtraWithCount('test'); + $this->assertSame(['test'], $this->basicTable->getExtraWithCounts()); + $this->assertTrue($this->basicTable->hasExtraWithCounts()); + $this->basicTable->setExtraWithCounts(['user', 'pets']); + $this->assertTrue($this->basicTable->hasExtraWithCounts()); + $this->assertSame(['user', 'pets'], $this->basicTable->getExtraWithCounts()); + } + + public function test_no_extra_with_sums_by_default(): void + { + $this->assertFalse($this->basicTable->hasExtraWithSums()); + $this->assertEmpty($this->basicTable->getExtraWithSums()); + } + + public function test_can_add_extra_with_sum(): void + { + $this->assertFalse($this->basicTable->hasExtraWithSums()); + $this->assertEmpty($this->basicTable->getExtraWithSums()); + $this->basicTable->addExtraWithSum('users', 'age'); + $this->assertTrue($this->basicTable->hasExtraWithSums()); + $this->assertSame([['table' => 'users', 'field' => 'age']], $this->basicTable->getExtraWithSums()); + } + + public function test_no_extra_with_avgs_by_default(): void + { + $this->assertFalse($this->basicTable->hasExtraWiths()); + $this->assertEmpty($this->basicTable->getExtraWiths()); + } + + public function test_can_add_extra_with_avg(): void + { + $this->assertFalse($this->basicTable->hasExtraWithAvgs()); + $this->assertEmpty($this->basicTable->getExtraWithAvgs()); + $this->basicTable->addExtraWithAvg('user', 'age'); + $this->assertTrue($this->basicTable->hasExtraWithAvgs()); + $this->assertSame([['table' => 'user', 'field' => 'age']], $this->basicTable->getExtraWithAvgs()); + } } diff --git a/tests/Views/Columns/ArrayColumnTest.php b/tests/Views/Columns/ArrayColumnTest.php new file mode 100644 index 000000000..0eff2808b --- /dev/null +++ b/tests/Views/Columns/ArrayColumnTest.php @@ -0,0 +1,87 @@ +assertSame('Array Col', $column->getTitle()); + } + + public function test_can_set_the_separator(): void + { + $column = ArrayColumn::make('Array Col'); + + $this->assertSame('
', $column->getSeparator()); + $column->separator('

'); + $this->assertTrue($column->hasSeparator()); + + $this->assertSame('

', $column->getSeparator()); + } + + public function test_can_set_the_output_format(): void + { + $column = ArrayColumn::make('Array Col'); + + $this->assertNull($column->getOutputFormatCallback()); + $this->assertFalse($column->hasOutputFormatCallback()); + $column->outputFormat(fn ($index, $value) => "".$value->name.''); + $this->assertTrue($column->hasOutputFormatCallback()); + } + + public function test_requires_the_data_callback(): void + { + $this->expectException(DataTableConfigurationException::class); + $column = ArrayColumn::make('Average Age') + ->separator('

') + ->sortable(); + $contents = $column->getContents(Pet::find(1)); + $this->assertNull($contents); + } + + public function test_can_get_the_output_format_callback(): void + { + $this->expectException(DataTableConfigurationException::class); + $column = ArrayColumn::make('Average Age') + ->separator('

') + ->data(fn ($value, $row) => ($row->pets)) + ->sortable(); + $this->assertNotNull($column->getDataCallback()); + + $contents = $column->getContents(Pet::find(1)); + $this->assertNull($contents); + } + + public function test_requires_the_output_format_callback(): void + { + $this->expectException(DataTableConfigurationException::class); + $column = ArrayColumn::make('Average Age') + ->separator('

') + ->data(fn ($value, $row) => ($row->pets)) + ->sortable(); + + $contents = $column->getContents(Pet::find(1)); + $this->assertNull($contents); + } + + public function test_can_get_empty_value(): void + { + $column = ArrayColumn::make('Average Age') + ->separator('

') + ->data(fn ($value, $row) => ($row->pets)) + ->sortable(); + + $this->assertSame('', $column->getEmptyValue()); + $column->emptyValue('Unknown'); + $this->assertSame('Unknown', $column->getEmptyValue()); + + } +} diff --git a/tests/Views/Columns/AvgColumnTest.php b/tests/Views/Columns/AvgColumnTest.php new file mode 100644 index 000000000..f3657c710 --- /dev/null +++ b/tests/Views/Columns/AvgColumnTest.php @@ -0,0 +1,111 @@ +assertSame('Average Age', $column->getTitle()); + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_can_setup_column_correctly(string $relation_name, string $foreign_field): void + { + $column = AvgColumn::make('Average Age') + ->setDataSource($relation_name, $foreign_field) + ->sortable(); + + $this->assertNotEmpty($column); + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_can_not_skip_set_data_source(string $relation_name, string $foreign_field): void + { + $this->expectException(DataTableConfigurationException::class); + + $column = AvgColumn::make('Average Age') + ->sortable(); + $contents = $column->getContents(Pet::find(1)); + $this->assertNull($contents); + + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_can_get_data_source(string $relation_name, string $foreign_field): void + { + $column = AvgColumn::make('Average Age') + ->setDataSource($relation_name, $foreign_field) + ->sortable(); + $this->assertTrue($column->hasDataSource()); + $this->assertSame($relation_name, $column->getDataSource()); + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_can_get_foreign_column(string $relation_name, string $foreign_field): void + { + $column = AvgColumn::make('Average Age') + ->setDataSource($relation_name, $foreign_field) + ->sortable(); + $this->assertTrue($column->hasForeignColumn()); + $this->assertSame($foreign_field, $column->getForeignColumn()); + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_can_set_foreign_column(string $relation_name, string $foreign_field): void + { + $column = AvgColumn::make('Average Age') + ->setDataSource($relation_name, $foreign_field) + ->sortable(); + $this->assertTrue($column->hasForeignColumn()); + $this->assertSame($foreign_field, $column->getForeignColumn()); + $column->setForeignColumn('test'); + $this->assertTrue($column->hasForeignColumn()); + $this->assertSame('test', $column->getForeignColumn()); + + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_can_get_data_source_fields(string $relation_name, string $foreign_field): void + { + $column = AvgColumn::make('Average Age') + ->setDataSource($relation_name, $foreign_field) + ->sortable(); + $this->assertTrue($column->hasDataSource()); + $this->assertSame($relation_name, $column->getDataSource()); + $this->assertTrue($column->hasForeignColumn()); + $this->assertSame($foreign_field, $column->getForeignColumn()); + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_can_get_aggregate_method(string $relation_name, string $foreign_field): void + { + $column = AvgColumn::make('Average Age') + ->setDataSource($relation_name, $foreign_field) + ->sortable(); + $this->assertSame('avg', $column->getAggregateMethod()); + $column->setAggregateMethod('test_avg'); + $this->assertSame('test_avg', $column->getAggregateMethod()); + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_renders_correctly(string $relation_name, string $foreign_field): void + { + $rows = $this->speciesTable->getRows(); + $column = AvgColumn::make('Average Age') + ->setDataSource('pets', 'age'); + $contents = $column->getContents($rows->first()); + $this->assertSame('15', $contents); + $contents = $column->getContents($rows[2]); + $this->assertSame('6', $contents); + } +} diff --git a/tests/Views/Columns/CountColumnTest.php b/tests/Views/Columns/CountColumnTest.php new file mode 100644 index 000000000..6c3d1e772 --- /dev/null +++ b/tests/Views/Columns/CountColumnTest.php @@ -0,0 +1,50 @@ +assertSame('Total Users', $column->getTitle()); + } + + public function test_can_setup_column_correctly(): void + { + $column = CountColumn::make('Total Users') + ->setDataSource('users') + ->sortable(); + + $this->assertNotEmpty($column); + } + + public function test_can_not_skip_set_data_source(): void + { + $this->expectException(DataTableConfigurationException::class); + + $column = CountColumn::make('Average Age') + ->sortable(); + $contents = $column->getContents(Pet::find(1)); + $this->assertNull($contents); + + } + + public function test_renders_correctly(): void + { + $rows = $this->speciesTable->getRows(); + $row1 = $rows->first(); + $column = CountColumn::make('Pets') + ->setDataSource('pets'); + $contents = $column->getContents($rows->first()); + $this->assertSame('2', $contents); + $contents = $column->getContents($rows->last()); + $this->assertSame('0', $contents); + } +} diff --git a/tests/Views/Columns/SumColumnTest.php b/tests/Views/Columns/SumColumnTest.php new file mode 100644 index 000000000..9d5cbff51 --- /dev/null +++ b/tests/Views/Columns/SumColumnTest.php @@ -0,0 +1,111 @@ +assertSame('Sum User Age', $column->getTitle()); + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_can_setup_column_correctly(string $relation_name, string $foreign_field): void + { + $column = SumColumn::make('Sum User Age') + ->setDataSource($relation_name, $foreign_field) + ->sortable(); + + $this->assertNotEmpty($column); + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_can_not_skip_set_data_source(string $relation_name, string $foreign_field): void + { + $this->expectException(DataTableConfigurationException::class); + + $column = SumColumn::make('Sum User Age') + ->sortable(); + $contents = $column->getContents(Pet::find(1)); + $this->assertNull($contents); + + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_can_set_foreign_column(string $relation_name, string $foreign_field): void + { + $column = SumColumn::make('Sum User Age') + ->setDataSource($relation_name, $foreign_field) + ->sortable(); + $this->assertTrue($column->hasForeignColumn()); + $this->assertSame($foreign_field, $column->getForeignColumn()); + $column->setForeignColumn('test'); + $this->assertTrue($column->hasForeignColumn()); + $this->assertSame('test', $column->getForeignColumn()); + + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_can_get_data_source(string $relation_name, string $foreign_field): void + { + $column = SumColumn::make('Sum User Age') + ->setDataSource($relation_name, $foreign_field) + ->sortable(); + $this->assertTrue($column->hasDataSource()); + $this->assertSame($relation_name, $column->getDataSource()); + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_can_get_foreign_column(string $relation_name, string $foreign_field): void + { + $column = SumColumn::make('Sum User Age') + ->setDataSource($relation_name, $foreign_field) + ->sortable(); + $this->assertTrue($column->hasForeignColumn()); + $this->assertSame($foreign_field, $column->getForeignColumn()); + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_can_get_data_source_fields(string $relation_name, string $foreign_field): void + { + $column = SumColumn::make('Sum User Age') + ->setDataSource($relation_name, $foreign_field) + ->sortable(); + $this->assertTrue($column->hasDataSource()); + $this->assertSame($relation_name, $column->getDataSource()); + $this->assertTrue($column->hasForeignColumn()); + $this->assertSame($foreign_field, $column->getForeignColumn()); + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_can_get_aggregate_method(string $relation_name, string $foreign_field): void + { + $column = SumColumn::make('Sum User Age') + ->setDataSource($relation_name, $foreign_field) + ->sortable(); + $this->assertSame('sum', $column->getAggregateMethod()); + $column->setAggregateMethod('test_sum'); + $this->assertSame('test_sum', $column->getAggregateMethod()); + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_renders_correctly(string $relation_name, string $foreign_field): void + { + $rows = $this->speciesTable->getRows(); + $column = SumColumn::make('Total Age') + ->setDataSource('pets', 'age'); + $contents = $column->getContents($rows->first()); + $this->assertSame('30', $contents); + $contents = $column->getContents($rows[2]); + $this->assertSame('12', $contents); + } +} From 799ccfdd19927fe67c608eef163328e7b81a220d Mon Sep 17 00:00:00 2001 From: Joe <104938042+lrljoe@users.noreply.github.com> Date: Thu, 11 Jul 2024 00:50:53 +0100 Subject: [PATCH 17/21] Add Option to Retain Selected when Searching/Filtering (#1762) * Initial Commit for Retaining Selected * Update Test for Search/Filter --------- Co-authored-by: lrljoe --- docs/bulk-actions/available-methods.md | 72 +++++++++++++++++++ .../BulkActionsConfiguration.php | 42 +++++++++++ src/Traits/Helpers/BulkActionsHelpers.php | 10 +++ src/Traits/WithBulkActions.php | 4 ++ src/Traits/WithFilters.php | 8 ++- src/Traits/WithSearch.php | 8 ++- .../Traits/Helpers/BulkActionsHelpersTest.php | 70 ++++++++++++++++++ 7 files changed, 208 insertions(+), 6 deletions(-) diff --git a/docs/bulk-actions/available-methods.md b/docs/bulk-actions/available-methods.md index ad98e1999..512394c23 100644 --- a/docs/bulk-actions/available-methods.md +++ b/docs/bulk-actions/available-methods.md @@ -274,3 +274,75 @@ public function configure(): void $this->setShouldAlwaysHideBulkActionsDropdownOptionDisabled(); } ``` + + +## setClearSelectedOnSearch + +By default, any selected items for Bulk Actions are cleared upon searching. You may configure this behaviour here. + +```php +public function configure(): void +{ + $this->setClearSelectedOnSearch(true); +} +``` + + +## setClearSelectedOnSearchEnabled + +By default, any selected items for Bulk Actions are cleared upon searching. This enables this behaviour. + +```php +public function configure(): void +{ + $this->setClearSelectedOnSearchEnabled(); +} +``` + + +## setClearSelectedOnSearchDisabled + +By default, any selected items for Bulk Actions are cleared upon searching. This disables this behaviour, ensuring that selected items are retained after searching. + +```php +public function configure(): void +{ + $this->setClearSelectedOnSearchDisabled(); +} +``` + + +## setClearSelectedOnFilter + +By default, any selected items for Bulk Actions are cleared upon filtering. You may configure this behaviour here. + +```php +public function configure(): void +{ + $this->setClearSelectedOnFilter(true); +} +``` + + +## setClearSelectedOnFilterEnabled + +By default, any selected items for Bulk Actions are cleared upon filtering. This enables this behaviour. + +```php +public function configure(): void +{ + $this->setClearSelectedOnFilterEnabled(); +} +``` + + +## setClearSelectedOnFilterDisabled + +By default, any selected items for Bulk Actions are cleared upon filtering. This disables this behaviour, ensuring that selected items are retained after filtering. + +```php +public function configure(): void +{ + $this->setClearSelectedOnFilterDisabled(); +} +``` diff --git a/src/Traits/Configuration/BulkActionsConfiguration.php b/src/Traits/Configuration/BulkActionsConfiguration.php index d0d5e17f3..6af318f0e 100644 --- a/src/Traits/Configuration/BulkActionsConfiguration.php +++ b/src/Traits/Configuration/BulkActionsConfiguration.php @@ -171,4 +171,46 @@ public function setShouldAlwaysHideBulkActionsDropdownOptionDisabled(): self return $this; } + + public function setClearSelectedOnSearch(bool $status): self + { + $this->clearSelectedOnSearch = $status; + + return $this; + } + + public function setClearSelectedOnSearchEnabled(): self + { + $this->setClearSelectedOnSearch(true); + + return $this; + } + + public function setClearSelectedOnSearchDisabled(): self + { + $this->setClearSelectedOnSearch(false); + + return $this; + } + + public function setClearSelectedOnFilter(bool $status): self + { + $this->clearSelectedOnFilter = $status; + + return $this; + } + + public function setClearSelectedOnFilterEnabled(): self + { + $this->setClearSelectedOnFilter(true); + + return $this; + } + + public function setClearSelectedOnFilterDisabled(): self + { + $this->setClearSelectedOnFilter(false); + + return $this; + } } diff --git a/src/Traits/Helpers/BulkActionsHelpers.php b/src/Traits/Helpers/BulkActionsHelpers.php index 70ea427a5..77a263635 100644 --- a/src/Traits/Helpers/BulkActionsHelpers.php +++ b/src/Traits/Helpers/BulkActionsHelpers.php @@ -220,4 +220,14 @@ public function shouldAlwaysHideBulkActionsDropdownOption(): bool { return $this->alwaysHideBulkActionsDropdownOption ?? false; } + + public function getClearSelectedOnSearch(): bool + { + return $this->clearSelectedOnSearch ?? true; + } + + public function getClearSelectedOnFilter(): bool + { + return $this->clearSelectedOnFilter ?? true; + } } diff --git a/src/Traits/WithBulkActions.php b/src/Traits/WithBulkActions.php index 08f69197b..37a5cd111 100644 --- a/src/Traits/WithBulkActions.php +++ b/src/Traits/WithBulkActions.php @@ -36,6 +36,10 @@ trait WithBulkActions protected bool $alwaysHideBulkActionsDropdownOption = false; + public bool $clearSelectedOnSearch = true; + + public bool $clearSelectedOnFilter = true; + public function bulkActions(): array { return property_exists($this, 'bulkActions') ? $this->bulkActions : []; diff --git a/src/Traits/WithFilters.php b/src/Traits/WithFilters.php index 878c660ce..981d0e855 100644 --- a/src/Traits/WithFilters.php +++ b/src/Traits/WithFilters.php @@ -74,9 +74,11 @@ public function updatedFilterComponents(string|array|null $value, string $filter { $this->resetComputedPage(); - // Clear bulk actions on filter - $this->clearSelected(); - $this->setSelectAllDisabled(); + // Clear bulk actions on filter - if enabled + if ($this->getClearSelectedOnFilter()) { + $this->clearSelected(); + $this->setSelectAllDisabled(); + } // Clear filters on empty value $filter = $this->getFilterByKey($filterName); diff --git a/src/Traits/WithSearch.php b/src/Traits/WithSearch.php index 0ef402573..5d90a991f 100644 --- a/src/Traits/WithSearch.php +++ b/src/Traits/WithSearch.php @@ -70,9 +70,11 @@ public function updatedSearch(string|array|null $value): void { $this->resetComputedPage(); - // Clear bulk actions on search - $this->clearSelected(); - $this->setSelectAllDisabled(); + // Clear bulk actions on search - if enabled + if ($this->getClearSelectedOnSearch()) { + $this->clearSelected(); + $this->setSelectAllDisabled(); + } if (is_null($value) || $value === '') { $this->clearSearch(); diff --git a/tests/Traits/Helpers/BulkActionsHelpersTest.php b/tests/Traits/Helpers/BulkActionsHelpersTest.php index bf4cc9e6e..5506fcb4b 100644 --- a/tests/Traits/Helpers/BulkActionsHelpersTest.php +++ b/tests/Traits/Helpers/BulkActionsHelpersTest.php @@ -207,4 +207,74 @@ public function test_bulk_actions_th_checkbox_attributes_returns_default_true_if { $this->assertSame(['default' => true], $this->basicTable->getBulkActionsThCheckboxAttributes()); } + + public function test_select_clears_by_default(): void + { + $this->basicTable->setSelected([1, 2, 3, 4, 5]); + $this->assertSame([1, 2, 3, 4, 5], $this->basicTable->getSelected()); + + $this->basicTable->setSearch('Anthony'); + $this->basicTable->updatedSearch('Anthony'); + + $this->assertSame([], $this->basicTable->getSelected()); + } + + public function test_select_does_not_clear_when_disabled(): void + { + $this->basicTable->setClearSelectedOnSearchDisabled(); + + $this->basicTable->setSelected([1, 2, 3, 4, 5]); + $this->assertSame([1, 2, 3, 4, 5], $this->basicTable->getSelected()); + + $this->basicTable->setSearch('Anthony'); + $this->basicTable->updatedSearch('Anthony'); + + $this->assertSame([1, 2, 3, 4, 5], $this->basicTable->getSelected()); + } + + public function test_select_does_clear_when_enabled(): void + { + $this->basicTable->setClearSelectedOnSearchEnabled(); + + $this->basicTable->setSelected([1, 2, 3, 4, 5]); + $this->assertSame([1, 2, 3, 4, 5], $this->basicTable->getSelected()); + + $this->basicTable->setSearch('Anthony'); + $this->basicTable->updatedSearch('Anthony'); + + $this->assertSame([], $this->basicTable->getSelected()); + + } + + public function test_select_clears_by_default_when_filtering(): void + { + $this->basicTable->setSelected([1, 2, 3, 4, 5]); + + $this->assertSame([1, 2, 3, 4, 5], $this->basicTable->getSelected()); + + $this->basicTable->setFilter('breed_id_filter', '2'); + $this->basicTable->updatedFilterComponents('2', 'breed_id_filter'); + + $this->assertSame([], $this->basicTable->getSelected()); + } + + public function test_select_does_clear_when_filtering_when_enabled(): void + { + $this->basicTable->setSelected([1, 2, 3, 4, 5]); + $this->assertSame([1, 2, 3, 4, 5], $this->basicTable->getSelected()); + $this->basicTable->setClearSelectedOnFilterEnabled(); + $this->basicTable->setFilter('breed_id_filter', '2'); + $this->basicTable->updatedFilterComponents('2', 'breed_id_filter'); + $this->assertSame([], $this->basicTable->getSelected()); + } + + public function test_select_does_not_clear_when_filtering_when_disabled(): void + { + $this->basicTable->setSelected([1, 2, 3, 4, 5]); + $this->assertSame([1, 2, 3, 4, 5], $this->basicTable->getSelected()); + $this->basicTable->setClearSelectedOnFilterDisabled(); + $this->basicTable->setFilter('breed_id_filter', '2'); + $this->basicTable->updatedFilterComponents('2', 'breed_id_filter'); + $this->assertSame([1, 2, 3, 4, 5], $this->basicTable->getSelected()); + } } From 220d6da90abd90aef1f8e8aa4512052ba5ebc377 Mon Sep 17 00:00:00 2001 From: Joe <104938042+lrljoe@users.noreply.github.com> Date: Thu, 11 Jul 2024 01:41:24 +0100 Subject: [PATCH 18/21] Add WireLink Column (#1763) * Add WireLinkColumn * Add Tests for WireLinkColumn --------- Co-authored-by: lrljoe --- docs/column-types/wire_link_column.md | 37 +++++++++++ .../includes/columns/wire-link.blade.php | 9 +++ src/Views/Columns/WireLinkColumn.php | 45 +++++++++++++ .../WireLinkColumnConfiguration.php | 5 ++ src/Views/Traits/Core/HasActionCallback.php | 28 ++++++++ src/Views/Traits/Core/HasConfirmation.php | 28 ++++++++ .../Traits/Helpers/WireLinkColumnHelpers.php | 5 ++ tests/Views/Columns/WireLinkColumnTest.php | 66 +++++++++++++++++++ 8 files changed, 223 insertions(+) create mode 100644 docs/column-types/wire_link_column.md create mode 100644 resources/views/includes/columns/wire-link.blade.php create mode 100644 src/Views/Columns/WireLinkColumn.php create mode 100644 src/Views/Traits/Configuration/WireLinkColumnConfiguration.php create mode 100644 src/Views/Traits/Core/HasActionCallback.php create mode 100644 src/Views/Traits/Core/HasConfirmation.php create mode 100644 src/Views/Traits/Helpers/WireLinkColumnHelpers.php create mode 100644 tests/Views/Columns/WireLinkColumnTest.php diff --git a/docs/column-types/wire_link_column.md b/docs/column-types/wire_link_column.md new file mode 100644 index 000000000..3577ad95b --- /dev/null +++ b/docs/column-types/wire_link_column.md @@ -0,0 +1,37 @@ +--- +title: Wire Link Column (beta) +weight: 13 +--- + +WireLink columns provide a way to display Wired Links in your table without having to use `format()` or partial views, with or without a Confirmation Message + +WireLinkColumn requires title, and an "action", which must be a valid LiveWire method in the current class, or a global method + +Without a Confirmation Message +```php + WireLinkColumn::make("Delete Item") + ->title(fn($row) => 'Delete Item') + ->action(fn($row) => 'delete("'.$row->id.'")'), +``` + +You may also pass a string to "confirmMessage", which will utilise LiveWire 3's "wire:confirm" approach to display a confirmation modal. + +```php + WireLinkColumn::make("Delete Item") + ->title(fn($row) => 'Delete Item') + ->confirmMessage('Are you sure you want to delete this item?') + ->action(fn($row) => 'delete("'.$row->id.'")') + ->attributes(fn($row) => [ + 'class' => 'btn btn-danger', + ]), +``` + +And you may also pass an array of attributes, which will be applied to the "button" element used within the Column +```php + WireLinkColumn::make("Delete Item") + ->title(fn($row) => 'Delete Item') + ->action(fn($row) => 'delete("'.$row->id.'")') + ->attributes(fn($row) => [ + 'class' => 'btn btn-danger', + ]), +``` diff --git a/resources/views/includes/columns/wire-link.blade.php b/resources/views/includes/columns/wire-link.blade.php new file mode 100644 index 000000000..191ba5f1d --- /dev/null +++ b/resources/views/includes/columns/wire-link.blade.php @@ -0,0 +1,9 @@ + diff --git a/src/Views/Columns/WireLinkColumn.php b/src/Views/Columns/WireLinkColumn.php new file mode 100644 index 000000000..343f28ef9 --- /dev/null +++ b/src/Views/Columns/WireLinkColumn.php @@ -0,0 +1,45 @@ +label(fn () => null); + } + + public function getContents(Model $row): null|string|\Illuminate\Support\HtmlString|DataTableConfigurationException|\Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View + { + if (! $this->hasTitleCallback()) { + throw new DataTableConfigurationException('You must specify a title callback for a WireLink column.'); + } + + if (! $this->hasActionCallback()) { + throw new DataTableConfigurationException('You must specify an action callback for a WireLink column.'); + } + + return view($this->getView()) + ->withColumn($this) + ->withTitle(app()->call($this->getTitleCallback(), ['row' => $row])) + ->withPath(app()->call($this->getActionCallback(), ['row' => $row])) + ->withAttributes($this->hasAttributesCallback() ? app()->call($this->getAttributesCallback(), ['row' => $row]) : []); + } +} diff --git a/src/Views/Traits/Configuration/WireLinkColumnConfiguration.php b/src/Views/Traits/Configuration/WireLinkColumnConfiguration.php new file mode 100644 index 000000000..238b36fbf --- /dev/null +++ b/src/Views/Traits/Configuration/WireLinkColumnConfiguration.php @@ -0,0 +1,5 @@ +actionCallback = $callback; + + return $this; + } + + public function getActionCallback(): ?callable + { + return $this->actionCallback; + } + + public function hasActionCallback(): bool + { + return $this->actionCallback !== null; + } +} diff --git a/src/Views/Traits/Core/HasConfirmation.php b/src/Views/Traits/Core/HasConfirmation.php new file mode 100644 index 000000000..9a8597947 --- /dev/null +++ b/src/Views/Traits/Core/HasConfirmation.php @@ -0,0 +1,28 @@ +confirmMessage = $confirmMessage; + + return $this; + } + + public function hasConfirmMessage(): bool + { + return isset($this->confirmMessage); + } + + public function getConfirmMessage(): string + { + return $this->confirmMessage; + } +} diff --git a/src/Views/Traits/Helpers/WireLinkColumnHelpers.php b/src/Views/Traits/Helpers/WireLinkColumnHelpers.php new file mode 100644 index 000000000..f24c7e460 --- /dev/null +++ b/src/Views/Traits/Helpers/WireLinkColumnHelpers.php @@ -0,0 +1,5 @@ +assertSame('Name', $column->getTitle()); + } + + public function test_can_not_infer_field_name_from_title_if_no_from(): void + { + $column = WireLinkColumn::make('My Title'); + + $this->assertNull($column->getField()); + } + + public function test_can_not_render_field_if_no_title_callback(): void + { + $this->expectException(DataTableConfigurationException::class); + + WireLinkColumn::make('Name')->getContents(Pet::find(1)); + } + + public function test_can_not_render_field_if_no_action_callback(): void + { + $this->expectException(DataTableConfigurationException::class); + + WireLinkColumn::make('Name')->title(fn ($row) => 'Edit')->getContents(Pet::find(1)); + } + + public function test_can_render_field_if_title_and_action_callback(): void + { + $column = WireLinkColumn::make('Name')->title(fn ($row) => 'Edit')->action(fn ($row) => 'delete("'.$row->id.'")')->getContents(Pet::find(1)); + + $this->assertNotEmpty($column); + } + + public function test_can_render_field_if_confirm_set(): void + { + $column = WireLinkColumn::make('Name')->title(fn ($row) => 'Edit')->action(fn ($row) => 'delete("'.$row->id.'")')->confirmMessage('Test')->getContents(Pet::find(1)); + + $this->assertNotEmpty($column); + } + + public function test_can_add_confirm_message(): void + { + $column = WireLinkColumn::make('Name', 'name'); + + $this->assertFalse($column->hasConfirmMessage()); + + $column->confirmMessage('Test'); + + $this->assertTrue($column->hasConfirmMessage()); + + $this->assertSame('Test', $column->getConfirmMessage()); + } +} From 2593615745cf91e9f21b71dc3fd8c0a4f3730259 Mon Sep 17 00:00:00 2001 From: lrljoe Date: Thu, 11 Jul 2024 00:44:48 +0000 Subject: [PATCH 19/21] Fix styling --- src/Traits/WithBulkActions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Traits/WithBulkActions.php b/src/Traits/WithBulkActions.php index 5411f230a..37a5cd111 100644 --- a/src/Traits/WithBulkActions.php +++ b/src/Traits/WithBulkActions.php @@ -40,7 +40,7 @@ trait WithBulkActions public bool $clearSelectedOnFilter = true; - public function bulkActions(): array + public function bulkActions(): array { return property_exists($this, 'bulkActions') ? $this->bulkActions : []; } From 16314aca53306cd815f4c89fd8440465dd2e25ca Mon Sep 17 00:00:00 2001 From: Joe <104938042+lrljoe@users.noreply.github.com> Date: Thu, 11 Jul 2024 01:50:46 +0100 Subject: [PATCH 20/21] Update ChangeLog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 940323134..f2f5b502f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,11 @@ All notable changes to `laravel-livewire-tables` will be documented in this file -## [v3.3.0] - 2024-07-12 +## [v3.3.0] - 2024-07-11 ### New Features - Add new columns (ArrayColumn, AvgColumn, CountColumn, SumColumn) by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1761 +- Add new column WireLinkColumn by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1763 +- Add Option to Retain Selected when Searching/Filtering by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1762 ## [v3.2.8] - 2024-07-03 ### Bug Fixes From 11fbd707eb0806e8862c6d4ea2a944d709edf03a Mon Sep 17 00:00:00 2001 From: Joe <104938042+lrljoe@users.noreply.github.com> Date: Thu, 11 Jul 2024 03:21:40 +0100 Subject: [PATCH 21/21] Adjust column documentation (#1767) * Update OtherColumnTypes docs * Add Links for Additional Docs --- docs/column-types/array_column.md | 20 ++++++- docs/column-types/avg_column.md | 20 ++++++- docs/column-types/boolean_columns.md | 18 ++++++- docs/column-types/button_group_column.md | 9 +++- docs/column-types/color_columns.md | 18 ++++++- docs/column-types/component_column.md | 17 +++++- docs/column-types/count_column.md | 20 ++++++- docs/column-types/date_columns.md | 17 +++++- docs/column-types/image_columns.md | 18 ++++++- docs/column-types/link_columns.md | 18 ++++++- .../column-types/livewire_component_column.md | 29 +++++++++- docs/column-types/standard_column.md | 31 +++++++++++ docs/column-types/sum_column.md | 20 ++++++- docs/column-types/wire_link_column.md | 18 ++++++- docs/columns/available-methods.md | 2 +- docs/columns/footer.md | 2 +- docs/columns/other-column-types.md | 53 +++++++++++++++---- docs/columns/secondary-header.md | 2 +- 18 files changed, 301 insertions(+), 31 deletions(-) create mode 100644 docs/column-types/standard_column.md diff --git a/docs/column-types/array_column.md b/docs/column-types/array_column.md index 1571da2b9..7802e78aa 100644 --- a/docs/column-types/array_column.md +++ b/docs/column-types/array_column.md @@ -1,6 +1,6 @@ --- title: Array Columns (beta) -weight: 1 +weight: 2 --- Array columns provide an easy way to work with and display an array of data from a field. @@ -19,4 +19,20 @@ You may define the default/empty value using the "emptyValue" method ``` ArrayColumn::make('notes', 'name') ->emptyValue('Unknown'), -``` \ No newline at end of file +``` + +Please also see the following for other available methods: + \ No newline at end of file diff --git a/docs/column-types/avg_column.md b/docs/column-types/avg_column.md index faa653ca2..d5e4b32fb 100644 --- a/docs/column-types/avg_column.md +++ b/docs/column-types/avg_column.md @@ -1,6 +1,6 @@ --- title: Avg Columns (beta) -weight: 2 +weight: 3 --- Avg columns provide an easy way to display the "Average" of a field on a relation. @@ -11,4 +11,20 @@ Avg columns provide an easy way to display the "Average" of a field on a relatio ->sortable(), ``` -The "sortable()" callback can accept a callback, or you can use the default behaviour, which calculates the correct field to sort on. \ No newline at end of file +The "sortable()" callback can accept a callback, or you can use the default behaviour, which calculates the correct field to sort on. + +Please also see the following for other available methods: + \ No newline at end of file diff --git a/docs/column-types/boolean_columns.md b/docs/column-types/boolean_columns.md index 32a759918..8732db839 100644 --- a/docs/column-types/boolean_columns.md +++ b/docs/column-types/boolean_columns.md @@ -1,6 +1,6 @@ --- title: Boolean Columns -weight: 3 +weight: 4 --- Boolean columns are good if you have a column type that is a true/false, or 0/1 value. @@ -79,3 +79,19 @@ If you would like the BooleanColumn to display a plain Yes/No, you can set: BooleanColumn::make('Active') ->yesNo() ``` + +Please also see the following for other available methods: + \ No newline at end of file diff --git a/docs/column-types/button_group_column.md b/docs/column-types/button_group_column.md index 5a3390049..da8ff7399 100644 --- a/docs/column-types/button_group_column.md +++ b/docs/column-types/button_group_column.md @@ -1,6 +1,6 @@ --- title: Button Group Columns -weight: 4 +weight: 5 --- Button group columns let you provide an array of LinkColumns to display in a single cell. @@ -32,3 +32,10 @@ ButtonGroupColumn::make('Actions') }), ]), ``` + + +Please also see the following for other available methods: +- [https://rappasoft.com/docs/laravel-livewire-tables/v3/columns/available-methods](Available Methods) +- [https://rappasoft.com/docs/laravel-livewire-tables/v3/columns/column-selection](Column Selection) +- [https://rappasoft.com/docs/laravel-livewire-tables/v3/columns/secondary-header](Secondary Header) +- [https://rappasoft.com/docs/laravel-livewire-tables/v3/columns/footer](Footer) diff --git a/docs/column-types/color_columns.md b/docs/column-types/color_columns.md index e4920144d..eeae20b2f 100644 --- a/docs/column-types/color_columns.md +++ b/docs/column-types/color_columns.md @@ -1,6 +1,6 @@ --- title: Color Columns -weight: 5 +weight: 6 --- Color columns provide an easy way to a Color in a Column @@ -39,3 +39,19 @@ You may also specify attributes to use on the div displaying the color, to adjus ]; }), ``` + +Please also see the following for other available methods: + \ No newline at end of file diff --git a/docs/column-types/component_column.md b/docs/column-types/component_column.md index 0db9c9483..79611121f 100644 --- a/docs/column-types/component_column.md +++ b/docs/column-types/component_column.md @@ -1,6 +1,6 @@ --- title: Component Columns -weight: 6 +weight: 7 --- Component columns let you specify a component name and attributes and provides the column value to the slot. @@ -26,3 +26,18 @@ ComponentColumn::make('E-mail', 'email') ]), ``` +Please also see the following for other available methods: + \ No newline at end of file diff --git a/docs/column-types/count_column.md b/docs/column-types/count_column.md index d33cf5df9..4b1fe6c3a 100644 --- a/docs/column-types/count_column.md +++ b/docs/column-types/count_column.md @@ -1,6 +1,6 @@ --- title: Count Columns (beta) -weight: 7 +weight: 8 --- Count columns provide an easy way to display the "Count" of a relation. @@ -11,4 +11,20 @@ Count columns provide an easy way to display the "Count" of a relation. ->sortable(), ``` -The "sortable()" callback can accept a callback, or you can use the default behaviour, which calculates the correct field to sort on. \ No newline at end of file +The "sortable()" callback can accept a callback, or you can use the default behaviour, which calculates the correct field to sort on. + +Please also see the following for other available methods: + \ No newline at end of file diff --git a/docs/column-types/date_columns.md b/docs/column-types/date_columns.md index abc8bbb42..3e2e6ca0a 100644 --- a/docs/column-types/date_columns.md +++ b/docs/column-types/date_columns.md @@ -1,6 +1,6 @@ --- title: Date Columns -weight: 8 +weight: 9 --- Date columns provide an easy way to display dates in a given format, without having to use repetitive format() methods or partial views. @@ -26,3 +26,18 @@ DateColumn::make('Last Charged', 'last_charged_at') ->emptyValue('Not Found'), ``` +Please also see the following for other available methods: + \ No newline at end of file diff --git a/docs/column-types/image_columns.md b/docs/column-types/image_columns.md index f816280af..5e6868792 100644 --- a/docs/column-types/image_columns.md +++ b/docs/column-types/image_columns.md @@ -1,6 +1,6 @@ --- title: Image Columns -weight: 9 +weight: 10 --- Image columns provide a way to display images in your table without having to use `format()` or partial views: @@ -24,3 +24,19 @@ ImageColumn::make('Avatar') 'alt' => $row->name . ' Avatar', ]), ``` + +Please also see the following for other available methods: + \ No newline at end of file diff --git a/docs/column-types/link_columns.md b/docs/column-types/link_columns.md index cba4bc887..e3f3120a4 100644 --- a/docs/column-types/link_columns.md +++ b/docs/column-types/link_columns.md @@ -1,6 +1,6 @@ --- title: Link Columns -weight: 10 +weight: 11 --- Link columns provide a way to display HTML links in your table without having to use `format()` or partial views: @@ -22,3 +22,19 @@ LinkColumn::make('Action') 'alt' => $row->name . ' Avatar', ]), ``` + +Please also see the following for other available methods: + \ No newline at end of file diff --git a/docs/column-types/livewire_component_column.md b/docs/column-types/livewire_component_column.md index b75172881..b12ada72e 100644 --- a/docs/column-types/livewire_component_column.md +++ b/docs/column-types/livewire_component_column.md @@ -1,5 +1,32 @@ --- title: Livewire Component (beta) -weight: 11 +weight: 12 --- +Livewire Component Columns allow for the use of a Livewire Component as a Column. + +This is **not recommended** as due to the nature of Livewire, it becomes inefficient at scale. + +## component +``` +LivewireComponentColumn::make('Action') + ->title(fn($row) => 'Edit') + ->component('PathToLivewireComponent'), + +``` + +Please also see the following for other available methods: + \ No newline at end of file diff --git a/docs/column-types/standard_column.md b/docs/column-types/standard_column.md new file mode 100644 index 000000000..6f7844982 --- /dev/null +++ b/docs/column-types/standard_column.md @@ -0,0 +1,31 @@ +--- +title: Standard Column +weight: 1 +--- + +A standard Column has a multitude of different options, making it simple for you to display your data, as documented below: + + diff --git a/docs/column-types/sum_column.md b/docs/column-types/sum_column.md index 7f99f7cab..c643253a7 100644 --- a/docs/column-types/sum_column.md +++ b/docs/column-types/sum_column.md @@ -1,6 +1,6 @@ --- title: Sum Columns (beta) -weight: 12 +weight: 13 --- Sum columns provide an easy way to display the "Sum" of a field on a relation. @@ -11,4 +11,20 @@ Sum columns provide an easy way to display the "Sum" of a field on a relation. ->sortable(), ``` -The "sortable()" callback can accept a callback, or you can use the default behaviour, which calculates the correct field to sort on. \ No newline at end of file +The "sortable()" callback can accept a callback, or you can use the default behaviour, which calculates the correct field to sort on. + +Please also see the following for other available methods: + \ No newline at end of file diff --git a/docs/column-types/wire_link_column.md b/docs/column-types/wire_link_column.md index 3577ad95b..eaee09537 100644 --- a/docs/column-types/wire_link_column.md +++ b/docs/column-types/wire_link_column.md @@ -1,6 +1,6 @@ --- title: Wire Link Column (beta) -weight: 13 +weight: 14 --- WireLink columns provide a way to display Wired Links in your table without having to use `format()` or partial views, with or without a Confirmation Message @@ -35,3 +35,19 @@ And you may also pass an array of attributes, which will be applied to the "butt 'class' => 'btn btn-danger', ]), ``` + +Please also see the following for other available methods: + \ No newline at end of file diff --git a/docs/columns/available-methods.md b/docs/columns/available-methods.md index ab41c7095..26f34b926 100644 --- a/docs/columns/available-methods.md +++ b/docs/columns/available-methods.md @@ -25,7 +25,7 @@ Column::make(__('Address')) ### [Multi-column sorting](../sorting/available-methods#setsinglesortingstatus) -In v2, multi-column sorting is **disabled by default**. To enable it you can set the `setSingleSortingDisabled()` method on the component. +Multi-column sorting is **disabled by default**. To enable it you can set the `setSingleSortingDisabled()` method on the component. ```php public function configure(): void diff --git a/docs/columns/footer.md b/docs/columns/footer.md index caf9a45d4..c4b3877e7 100644 --- a/docs/columns/footer.md +++ b/docs/columns/footer.md @@ -21,7 +21,7 @@ See also [footer component configuration](../footer/available-methods). ## Using a filter as a footer -As of version 2.7, you can use a filter as a footer. +You can use a filter as a footer. ```php // Example filter diff --git a/docs/columns/other-column-types.md b/docs/columns/other-column-types.md index 2e515eedf..c3a8bd154 100644 --- a/docs/columns/other-column-types.md +++ b/docs/columns/other-column-types.md @@ -3,14 +3,45 @@ title: Other Column Types weight: 4 --- - - - - -## Aggregate Columns - -### AvgColumn - -### CountColumn - -### SumColumn \ No newline at end of file + \ No newline at end of file diff --git a/docs/columns/secondary-header.md b/docs/columns/secondary-header.md index 31790b3e8..258db8d30 100644 --- a/docs/columns/secondary-header.md +++ b/docs/columns/secondary-header.md @@ -21,7 +21,7 @@ See also [secondary header component configuration](../secondary-header/availabl ## Using a filter as a secondary header -As of version 2.7, you can use a filter as a header. +You can use a filter as a header. ```php // Example filter