diff --git a/CHANGELOG.md b/CHANGELOG.md
index 56875d62e..ddd8715db 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,12 @@
All notable changes to `laravel-livewire-tables` will be documented in this file
+## [3.0.0-beta.4] - 2023-10-17
+- Introduction of Loading Placeholder
+- Docs livewire namespace fix [Here](https://github.com/rappasoft/laravel-livewire-tables/pull/1420)
+- Add CollapseAlways capability for Columns
+- Fix localisation bug
+
## [3.0.0-beta.3] - 2023-10-13
- Fix for Livewire ^3.0.6 where the table loading causes an additional lifecycle
- Add unminified files to .gitattributes export-ignore
@@ -1000,4 +1006,4 @@ Ground Up Rebuild
[0.1.4]: https://github.com/rappasoft/laravel-livewire-tables/compare/v0.1.3...v0.1.4
[0.1.3]: https://github.com/rappasoft/laravel-livewire-tables/compare/v0.1.2...v0.1.3
[0.1.2]: https://github.com/rappasoft/laravel-livewire-tables/compare/v0.1.1...v0.1.2
-[0.1.1]: https://github.com/rappasoft/laravel-livewire-tables/compare/v0.1.0...v0.1.1
\ No newline at end of file
+[0.1.1]: https://github.com/rappasoft/laravel-livewire-tables/compare/v0.1.0...v0.1.1
diff --git a/docs/datatable/loaders.md b/docs/datatable/loaders.md
new file mode 100644
index 000000000..a18c7b77e
--- /dev/null
+++ b/docs/datatable/loaders.md
@@ -0,0 +1,80 @@
+---
+title: Loaders
+weight: 4
+---
+
+With the introduction of Livewire 3, there are several new methods available for use:
+
+## Loading Placeholder
+```php
+public function configure(): void
+{
+ $this->setLoadingPlaceholderBlade('');
+}
+```
+
+### setLoadingPlaceholderStatus
+```php
+public function configure(): void
+{
+ $this->setLoadingPlaceholderStatus(true);
+}
+```
+
+### setLoadingPlaceholderEnabled
+```php
+public function configure(): void
+{
+ $this->setLoadingPlaceholderEnabled();
+}
+```
+
+
+### setLoadingPlaceholderDisabled
+```php
+public function configure(): void
+{
+ $this->setLoadingPlaceholderDisabled();
+}
+```
+
+### setLoadingPlaceholderContent
+```php
+public function configure(): void
+{
+ $this->setLoadingPlaceholderContent('');
+}
+```
+
+### setLoadingPlaceHolderAttributes
+```php
+public function configure(): void
+{
+ $this->setLoadingPlaceHolderAttributes([]);
+}
+```
+
+### setLoadingPlaceHolderIconAttributes
+```php
+public function configure(): void
+{
+ $this->setLoadingPlacehosetLoadingPlaceHolderIconAttributeslderBlade([]);
+}
+```
+
+### setLoadingPlaceHolderWrapperAttributes
+```php
+public function configure(): void
+{
+ $this->setLoadingPlaceHolderWrapperAttributes([]);
+}
+```
+
+
+### setLoadingPlaceholderBlade
+```php
+public function configure(): void
+{
+ $this->setLoadingPlaceholderBlade('');
+}
+```
diff --git a/docs/examples/advanced-example.md b/docs/examples/advanced-example.md
index b9c498801..33166dc46 100644
--- a/docs/examples/advanced-example.md
+++ b/docs/examples/advanced-example.md
@@ -6,7 +6,7 @@ weight: 2
```php
@endif
-```
\ No newline at end of file
+```
diff --git a/docs/examples/basic-example.md b/docs/examples/basic-example.md
index 60c2e07ca..d100933bd 100644
--- a/docs/examples/basic-example.md
+++ b/docs/examples/basic-example.md
@@ -6,7 +6,7 @@ weight: 1
```php
setLoadingPlaceholderStatus(true);
+ }
+```
+
+### setLoadingPlaceholderEnabled
+
+Use this method to enable the loading placeholder:
+
+```php
+ public function configure(): void
+ {
+ $this->setLoadingPlaceholderEnabled();
+ }
+```
+
+### setLoadingPlaceholderDisabled
+
+Use this method to disable the loading placeholder:
+
+```php
+ public function configure(): void
+ {
+ $this->setLoadingPlaceholderDisabled();
+ }
+```
+
+### setLoadingPlaceholderContent
+
+You may use this method to set custom text for the placeholder:
+
+```php
+ public function configure(): void
+ {
+ $this->setLoadingPlaceholderContent('Text To Display');
+ }
+```
+### setLoadingPlaceHolderWrapperAttributes
+
+This method allows you to customise the attributes for the <tr> element used as a Placeholder when the table is loading. Similar to other setAttribute methods, this accepts a range of attributes, and a boolean "default", which will enable/disable the default attributes.
+
+```php
+ public function configure(): void
+ {
+ $this->setLoadingPlaceHolderWrapperAttributes([
+ 'class' => 'text-bold',
+ 'default' => false,
+ ]);
+ }
+
+```
+
+### setLoadingPlaceHolderIconAttributes
+
+This method allows you to customise the attributes for the <div> element that is used solely for the PlaceholderIcon. Similar to other setAttribute methods, this accepts a range of attributes, and a boolean "default", which will enable/disable the default attributes.
+
+```php
+ public function configure(): void
+ {
+ $this->setLoadingPlaceHolderIconAttributes([
+ 'class' => 'lds-hourglass',
+ 'default' => false,
+ ]);
+ }
+
+```
diff --git a/docs/misc/multiple-tables.md b/docs/misc/multiple-tables.md
index 026d8ca1b..2a6ac0dd4 100644
--- a/docs/misc/multiple-tables.md
+++ b/docs/misc/multiple-tables.md
@@ -1,6 +1,6 @@
---
title: Multiple Tables Same Page
-weight: 2
+weight: 3
---
This feature works for mutiple tables on the same page that are **different** components.
diff --git a/docs/misc/saving-state.md b/docs/misc/saving-state.md
index 3ce5148ee..8d85acd26 100644
--- a/docs/misc/saving-state.md
+++ b/docs/misc/saving-state.md
@@ -1,6 +1,6 @@
---
title: Saving Table State
-weight: 5
+weight: 6
---
There may be occasions that you'd like to save the table state, for example if you have a complex set of filters, search parameters, or simply to remember which page you were on!
diff --git a/docs/start/commands.md b/docs/start/commands.md
index 26e62788d..da02db15b 100644
--- a/docs/start/commands.md
+++ b/docs/start/commands.md
@@ -7,7 +7,7 @@ weight: 6
To generate a new datatable component you can use the `make:datatable` command:
-Create a new datatable component called `UserTable` in `App\Http\Livewire` that uses the `App\Models\User` model.
+Create a new datatable component called `UserTable` in `App\Livewire` that uses the `App\Models\User` model.
```bash
php artisan make:datatable UserTable User
@@ -17,7 +17,7 @@ php artisan make:datatable UserTable User
You may pass a Custom Path to your model, should it not be contained within the "App" or "App\Models" namespaces:
-Create a new datatable component called `TestTable` in `App\Http\Livewire` that uses the `App\Domains\Test\Models\Example` model.
+Create a new datatable component called `TestTable` in `App\Livewire` that uses the `App\Domains\Test\Models\Example` model.
```bash
php artisan make:datatable TestTable example app/Domains/Test/Models/
diff --git a/docs/start/rendering.md b/docs/start/rendering.md
index 5694923fd..e21320aca 100644
--- a/docs/start/rendering.md
+++ b/docs/start/rendering.md
@@ -5,9 +5,9 @@ weight: 5
## Rendering Components
-You render components the same way you [render](https://laravel-livewire.com/docs/2.x/rendering-components) any Livewire component.
+You render components the same way you [render](https://livewire.laravel.com/docs/components#rendering-components) any Livewire component.
-Your component at `App\Http\Livewire\UsersTable.php`
+Your component at `App\Livewire\UsersTable.php`
```html
@@ -24,7 +24,7 @@ By default, all components will use the theme in the config file. But if for som
## Using sub-folders
-If your component does not live in `App\Http\Livewire`, you can specify a different sub-folder. For example if your component lives in `App\Http\Livewire\Backend\Users` you would use the following:
+If your component does not live in `App\Livewire`, you can specify a different sub-folder. For example if your component lives in `App\Livewire\Backend\Users` you would use the following:
```html
diff --git a/docs/start/requirements.md b/docs/start/requirements.md
index 04a236079..1f1a76054 100644
--- a/docs/start/requirements.md
+++ b/docs/start/requirements.md
@@ -7,4 +7,4 @@ The following are required to use this package regardless of what theme you choo
- PHP 8.1+
- [Laravel 10.x](https://laravel.com)
-- [Laravel Livewire 3.x](https://laravel-livewire.com)
+- [Laravel Livewire 3.x](https://livewire.laravel.com)
diff --git a/docs/usage/creating-components.md b/docs/usage/creating-components.md
index d58ed79e3..b5173e2ca 100644
--- a/docs/usage/creating-components.md
+++ b/docs/usage/creating-components.md
@@ -10,7 +10,7 @@ This is what a bare bones component looks like before your customization:
```php
input+output::after,.dark .range-slider>input:first-of-type+output::after{color:#fff}.range-slider::before{--before:1;--at-edge:var(--thumb-close-to-min);counter-reset:x var(--min);left:var(--offset)}.range-slider::after{--at-edge:var(--thumb-close-to-max);counter-reset:x var(--max);right:var(--offset)}.range-slider__progress::after,.range-slider__progress::before{content:"";top:0;right:0;bottom:0;border-radius:inherit;left:0}.range-slider__values{position:relative;top:50%;line-height:0;text-align:justify;width:100%;pointer-events:none;margin:0 auto;z-index:5}.range-slider__values::after{content:"";width:100%;display:inline-block;height:0;background:red}.range-slider__progress{--start-end:calc(var(--thumb-size) / 2);--clip-end:calc(100% - (var(--cb)) * 1%);--clip-start:calc(var(--ca) * 1%);--clip:inset(-20px var(--clip-end) -20px var(--clip-start));position:absolute;left:var(--start-end);right:var(--start-end);top:calc(var(--ticks-gap) * var(--flip-y,0) + var(--thumb-size)/ 2 - var(--track-height)/ 2);height:calc(var(--track-height));background:var(--progress-background,#eee);pointer-events:none;z-index:-1;border-radius:var(--progress-radius)}.range-slider__progress::before{position:absolute;-webkit-clip-path:var(--clip);clip-path:var(--clip);background:var(--fill-color,#0366d6);box-shadow:var(--progress-flll-shadow);z-index:1}.range-slider__progress::after{position:absolute;box-shadow:var(--progress-shadow);pointer-events:none}.range-slider>input{-webkit-appearance:none;width:100%;height:var(--thumb-size);margin:0;position:absolute;left:0;top:calc(50% - Max(var(--track-height),var(--thumb-size))/ 2 + calc(var(--ticks-gap)/ 2 * var(--flip-y,-1)));cursor:-webkit-grab;cursor:grab;outline:0;background:0 0}.range-slider>input:not(:only-of-type){pointer-events:none}.range-slider>input::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;height:var(--thumb-size);width:var(--thumb-size);transform:var(--thumb-transform);border-radius:var(--thumb-radius,50%);background:var(--thumb-color,#fff);box-shadow:var(--thumb-shadow);border:none;pointer-events:auto;-webkit-transition:.1s;transition:.1s}.range-slider>input::-moz-range-thumb{-moz-appearance:none;appearance:none;height:var(--thumb-size);width:var(--thumb-size);transform:var(--thumb-transform);border-radius:var(--thumb-radius,50%);background:var(--thumb-color,#fff);box-shadow:var(--thumb-shadow);border:none;pointer-events:auto;-moz-transition:.1s;transition:.1s}.range-slider>input::-ms-thumb{appearance:none;height:var(--thumb-size);width:var(--thumb-size);transform:var(--thumb-transform);border-radius:var(--thumb-radius,50%);background:var(--thumb-color,#fff);box-shadow:var(--thumb-shadow);border:none;pointer-events:auto;-ms-transition:.1s;transition:.1s}.range-slider>input:hover{--thumb-shadow:var(--thumb-shadow-hover)}.range-slider>input:hover+output{--value-background:var(--value-background-hover, #0366d6);--y-offset:-5px;color:var(--value-active-color,#fff);box-shadow:0 0 0 3px var(--value-background)}.range-slider>input:active{--thumb-shadow:var(--thumb-shadow-active);cursor:-webkit-grabbing;cursor:grabbing;z-index:2}.range-slider>input:active+output{transition:none}.range-slider>input:first-of-type{--is-left-most:Clamp(0, (var(--value-a) - var(--value-b)) * 99999, 1)}.range-slider>input:first-of-type+output{--value:var(--value-a);--x-offset:calc(var(--completed-a) * -1%)}.range-slider>input:first-of-type+output:not(:only-of-type){--flip:calc(var(--thumbs-too-close) * -1)}.range-slider>input:first-of-type+output::after{content:var(--prefix, "") var(--text-value-a) var(--suffix, "")}.range-slider>input:nth-of-type(2){--is-left-most:Clamp(0, (var(--value-b) - var(--value-a)) * 99999, 1)}.range-slider>input:nth-of-type(2)+output{--value:var(--value-b)}.range-slider>input:only-of-type~.range-slider__progress{--clip-start:0}.range-slider>input+output{--flip:-1;--x-offset:calc(var(--completed-b) * -1%);--pos:calc(((var(--value) - var(--min)) / (var(--max) - var(--min))) * 100%);pointer-events:none;position:absolute;z-index:5;background:var(--value-background);border-radius:10px;padding:2px 4px;left:var(--pos);transform:translate(var(--x-offset),calc(150% * var(--flip) - (var(--y-offset,0px) + var(--value-offset-y)) * var(--flip)));transition:.12s ease-out,left}.range-slider>input+output::after{content:var(--prefix, "") var(--text-value-b) var(--suffix, "");font:var(--value-font)}body>.range-slider,label[dir=rtl] .range-slider{width:clamp(300px,50vw,800px);min-width:200px}.superhide{display:none}
\ No newline at end of file
+ 0));--thumb-close-to-max:Min(1, Max(98 - var(--cb), 0));display:inline-block;height:max(var(--track-height),var(--thumb-size));background:linear-gradient(to right,var(--ticks-color,silver) var(--ticks-thickness),transparent 1px) repeat-x;background-size:var(--tickIntervalPerc) var(--ticks-height);background-position-x:calc(var(--thumb-size)/ 2 - var(--ticks-thickness)/ 2);background-position-y:var(--flip-y,bottom);padding-bottom:var(--flip-y,var(--ticks-gap));padding-top:calc(var(--flip-y) * var(--ticks-gap));position:relative;z-index:1}.range-slider::after,.range-slider::before{--offset:calc(var(--thumb-size) / 2);content:counter(x);display:var(--show-min-max,block);font:var(--min-max-font, 12px Arial);position:absolute;bottom:var(--flip-y,-2.5ch);top:calc(-2.5ch * var(--flip-y));opacity:clamp(0, var(--at-edge), var(--min-max-opacity));transform:translateX(calc(var(--min-max-x-offset) * var(--before,-1) * -1)) scale(var(--at-edge));pointer-events:none}.dark .range-slider::after,.dark .range-slider::before,.dark .range-slider>input+output::after,.dark .range-slider>input:first-of-type+output::after{color:#fff}.range-slider::before{--before:1;--at-edge:var(--thumb-close-to-min);counter-reset:x var(--min);left:var(--offset)}.range-slider::after{--at-edge:var(--thumb-close-to-max);counter-reset:x var(--max);right:var(--offset)}.range-slider__progress::after,.range-slider__progress::before{content:"";top:0;right:0;bottom:0;border-radius:inherit;left:0}.range-slider__values{position:relative;top:50%;line-height:0;text-align:justify;width:100%;pointer-events:none;margin:0 auto;z-index:5}.range-slider__values::after{content:"";width:100%;display:inline-block;height:0;background:red}.range-slider__progress{--start-end:calc(var(--thumb-size) / 2);--clip-end:calc(100% - (var(--cb)) * 1%);--clip-start:calc(var(--ca) * 1%);--clip:inset(-20px var(--clip-end) -20px var(--clip-start));position:absolute;left:var(--start-end);right:var(--start-end);top:calc(var(--ticks-gap) * var(--flip-y,0) + var(--thumb-size)/ 2 - var(--track-height)/ 2);height:calc(var(--track-height));background:var(--progress-background,#eee);pointer-events:none;z-index:-1;border-radius:var(--progress-radius)}.range-slider__progress::before{position:absolute;-webkit-clip-path:var(--clip);clip-path:var(--clip);background:var(--fill-color,#0366d6);box-shadow:var(--progress-flll-shadow);z-index:1}.range-slider__progress::after{position:absolute;box-shadow:var(--progress-shadow);pointer-events:none}.range-slider>input{-webkit-appearance:none;width:100%;height:var(--thumb-size);margin:0;position:absolute;left:0;top:calc(50% - Max(var(--track-height),var(--thumb-size))/ 2 + calc(var(--ticks-gap)/ 2 * var(--flip-y,-1)));cursor:-webkit-grab;cursor:grab;outline:0;background:0 0}.range-slider>input:not(:only-of-type){pointer-events:none}.range-slider>input::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;height:var(--thumb-size);width:var(--thumb-size);transform:var(--thumb-transform);border-radius:var(--thumb-radius,50%);background:var(--thumb-color,#fff);box-shadow:var(--thumb-shadow);border:none;pointer-events:auto;-webkit-transition:.1s;transition:.1s}.range-slider>input::-moz-range-thumb{-moz-appearance:none;appearance:none;height:var(--thumb-size);width:var(--thumb-size);transform:var(--thumb-transform);border-radius:var(--thumb-radius,50%);background:var(--thumb-color,#fff);box-shadow:var(--thumb-shadow);border:none;pointer-events:auto;-moz-transition:.1s;transition:.1s}.range-slider>input::-ms-thumb{appearance:none;height:var(--thumb-size);width:var(--thumb-size);transform:var(--thumb-transform);border-radius:var(--thumb-radius,50%);background:var(--thumb-color,#fff);box-shadow:var(--thumb-shadow);border:none;pointer-events:auto;-ms-transition:.1s;transition:.1s}.range-slider>input:hover{--thumb-shadow:var(--thumb-shadow-hover)}.range-slider>input:hover+output{--value-background:var(--value-background-hover, #0366d6);--y-offset:-5px;color:var(--value-active-color,#fff);box-shadow:0 0 0 3px var(--value-background)}.range-slider>input:active{--thumb-shadow:var(--thumb-shadow-active);cursor:-webkit-grabbing;cursor:grabbing;z-index:2}.range-slider>input:active+output{transition:none}.range-slider>input:first-of-type{--is-left-most:Clamp(0, (var(--value-a) - var(--value-b)) * 99999, 1)}.range-slider>input:first-of-type+output{--value:var(--value-a);--x-offset:calc(var(--completed-a) * -1%)}.range-slider>input:first-of-type+output:not(:only-of-type){--flip:calc(var(--thumbs-too-close) * -1)}.range-slider>input:first-of-type+output::after{content:var(--prefix, "") var(--text-value-a) var(--suffix, "")}.range-slider>input:nth-of-type(2){--is-left-most:Clamp(0, (var(--value-b) - var(--value-a)) * 99999, 1)}.range-slider>input:nth-of-type(2)+output{--value:var(--value-b)}.range-slider>input:only-of-type~.range-slider__progress{--clip-start:0}.range-slider>input+output{--flip:-1;--x-offset:calc(var(--completed-b) * -1%);--pos:calc(((var(--value) - var(--min)) / (var(--max) - var(--min))) * 100%);pointer-events:none;position:absolute;z-index:5;background:var(--value-background);border-radius:10px;padding:2px 4px;left:var(--pos);transform:translate(var(--x-offset),calc(150% * var(--flip) - (var(--y-offset,0px) + var(--value-offset-y)) * var(--flip)));transition:.12s ease-out,left}.range-slider>input+output::after{content:var(--prefix, "") var(--text-value-b) var(--suffix, "");font:var(--value-font)}body>.range-slider,label[dir=rtl] .range-slider{width:clamp(300px,50vw,800px);min-width:200px}.superhide{display:none}.lds-hourglass{display:inline-block;position:relative;width:80px;height:80px}.lds-hourglass:after{content:" ";display:block;border-radius:50%;width:0;height:0;margin:8px;box-sizing:border-box;border:32px solid #000;border-color:#000 transparent #fff;animation:1.2s infinite lds-hourglass}.dark .lds-hourglass:after{border:32px solid #fff;border-color:#fff transparent #000}@keyframes lds-hourglass{0%{transform:rotate(0);animation-timing-function:cubic-bezier(0.55,0.055,0.675,0.19)}50%{transform:rotate(900deg);animation-timing-function:cubic-bezier(0.215,0.61,0.355,1)}100%{transform:rotate(1800deg)}}
\ No newline at end of file
diff --git a/resources/views/components/includes/loading.blade.php b/resources/views/components/includes/loading.blade.php
new file mode 100644
index 000000000..7c71772ed
--- /dev/null
+++ b/resources/views/components/includes/loading.blade.php
@@ -0,0 +1,35 @@
+@aware(['isTailwind', 'isBootstrap', 'tableName', 'component'])
+@props(['colCount' => 1])
+
+@php
+$customAttributes['loader-wrapper'] = $component->getLoadingPlaceHolderWrapperAttributes();
+$customAttributes['loader-icon'] = $component->getLoadingPlaceHolderIconAttributes();
+@endphp
+@if($this->hasLoadingPlaceholderBlade())
+ @include($this->getLoadingPlaceHolderBlade(), ['colCount' => $colCount])
+@else
+
+
merge($customAttributes['loader-wrapper'])
+ ->class(['w-full text-center h-screen place-items-center align-middle' => $isTailwind && ($customAttributes['loader-wrapper']['default'] ?? true)])
+ ->class(['w-100 text-center h-100 align-items-center' => $isBootstrap && ($customAttributes['loader-wrapper']['default'] ?? true)]);
+ }}
+ wire:loading.class.remove="hidden d-none"
+ >
+
+
+ merge($customAttributes['loader-icon'])
+ ->class(['lds-hourglass' => $isTailwind && ($customAttributes['loader-icon']['default'] ?? true)])
+ ->class(['lds-hourglass' => $isBootstrap && ($customAttributes['loader-icon']['default'] ?? true)])
+ ->except('default');
+ }}
+ >
+ {{ $component->getLoadingPlaceholderContent() }}
+
+ |
+
+
+@endif
diff --git a/resources/views/components/table/tr.blade.php b/resources/views/components/table/tr.blade.php
index 92d1326ea..fba5f1601 100644
--- a/resources/views/components/table/tr.blade.php
+++ b/resources/views/components/table/tr.blade.php
@@ -2,19 +2,23 @@
@props(['row', 'rowIndex'])
@php
- $customAttributes = $this->getTrAttributes($row, $rowIndex);
+ $customAttributes = $component->getTrAttributes($row, $rowIndex);
@endphp
hasDisplayLoadingPlaceholder())
+ wire:loading.remove
+ @else
wire:loading.class.delay="opacity-50 dark:bg-gray-900 dark:opacity-60"
- id="{{ $tableName }}-row-{{ $row->{$this->getPrimaryKey()} }}"
+ @endif
+ id="{{ $tableName }}-row-{{ $row->{$component->getPrimaryKey()} }}"
:draggable="currentlyReorderingStatus"
- wire:key="{{ $tableName }}-tablerow-tr-{{ $row->{$this->getPrimaryKey()} }}"
+ wire:key="{{ $tableName }}-tablerow-tr-{{ $row->{$component->getPrimaryKey()} }}"
loopType="{{ ($rowIndex % 2 === 0) ? 'even' : 'odd' }}"
{{
$attributes->merge($customAttributes)
diff --git a/resources/views/datatable.blade.php b/resources/views/datatable.blade.php
index 1d6307f94..36f2de741 100644
--- a/resources/views/datatable.blade.php
+++ b/resources/views/datatable.blade.php
@@ -30,6 +30,10 @@
@if($this->secondaryHeaderIsEnabled() && $this->hasColumnsWithSecondaryHeader())
@endif
+ @if($this->hasDisplayLoadingPlaceholder())
+
+ @endif
+
diff --git a/src/DataTableComponent.php b/src/DataTableComponent.php
index 7733b00c0..ebe33c7a2 100644
--- a/src/DataTableComponent.php
+++ b/src/DataTableComponent.php
@@ -14,6 +14,7 @@
use Rappasoft\LaravelLivewireTables\Traits\WithEvents;
use Rappasoft\LaravelLivewireTables\Traits\WithFilters;
use Rappasoft\LaravelLivewireTables\Traits\WithFooter;
+use Rappasoft\LaravelLivewireTables\Traits\WithLoadingPlaceholder;
use Rappasoft\LaravelLivewireTables\Traits\WithPagination;
use Rappasoft\LaravelLivewireTables\Traits\WithRefresh;
use Rappasoft\LaravelLivewireTables\Traits\WithReordering;
@@ -32,6 +33,7 @@ abstract class DataTableComponent extends Component
WithEvents,
WithFilters,
WithFooter,
+ WithLoadingPlaceholder,
WithPagination,
WithRefresh,
WithReordering,
diff --git a/src/LaravelLivewireTablesServiceProvider.php b/src/LaravelLivewireTablesServiceProvider.php
index 5c8ee96d5..cd8e3eb5f 100644
--- a/src/LaravelLivewireTablesServiceProvider.php
+++ b/src/LaravelLivewireTablesServiceProvider.php
@@ -20,7 +20,15 @@ public function boot(): void
__DIR__.'/../config/livewire-tables.php', 'livewire-tables'
);
- $this->loadTranslationsFrom(__DIR__.'/../resources/lang', 'livewire-tables');
+ // Load Default Translations
+ $this->loadJsonTranslationsFrom(
+ __DIR__.'/../resources/lang'
+ );
+
+ // Override if Published
+ $this->loadJsonTranslationsFrom(
+ $this->app->langPath('vendor/rappasoft/livewire-tables')
+ );
$this->loadViewsFrom(__DIR__.'/../resources/views', 'livewire-tables');
@@ -35,8 +43,9 @@ public function boot(): void
public function consoleCommands()
{
if ($this->app->runningInConsole()) {
+
$this->publishes([
- __DIR__.'/../resources/lang' => $this->app->langPath('livewire-tables'),
+ __DIR__.'/../resources/lang' => $this->app->langPath('vendor/rappasoft/livewire-tables'),
], 'livewire-tables-translations');
$this->publishes([
@@ -44,7 +53,7 @@ public function consoleCommands()
], 'livewire-tables-config');
$this->publishes([
- __DIR__.'/../resources/views' => resource_path('views/vendor/livewire-tables'),
+ __DIR__.'/../resources/views' => resource_path('views/vendor/rappasoft/livewire-tables'),
], 'livewire-tables-views');
$this->publishes([
diff --git a/src/Traits/Configuration/LoadingPlaceholderConfiguration.php b/src/Traits/Configuration/LoadingPlaceholderConfiguration.php
new file mode 100644
index 000000000..a88bcccda
--- /dev/null
+++ b/src/Traits/Configuration/LoadingPlaceholderConfiguration.php
@@ -0,0 +1,62 @@
+displayLoadingPlaceholder = $status;
+
+ return $this;
+ }
+
+ public function setLoadingPlaceholderEnabled(): self
+ {
+ $this->setLoadingPlaceholderStatus(true);
+
+ return $this;
+ }
+
+ public function setLoadingPlaceholderDisabled(): self
+ {
+ $this->setLoadingPlaceholderStatus(false);
+
+ return $this;
+ }
+
+ public function setLoadingPlaceholderContent(string $content): self
+ {
+ $this->loadingPlaceholderContent = $content;
+
+ return $this;
+ }
+
+ public function setLoadingPlaceHolderAttributes(array $attributes): self
+ {
+ $this->loadingPlaceHolderAttributes = $attributes;
+
+ return $this;
+ }
+
+ public function setLoadingPlaceHolderIconAttributes(array $attributes): self
+ {
+ $this->loadingPlaceHolderIconAttributes = $attributes;
+
+ return $this;
+ }
+
+ public function setLoadingPlaceHolderWrapperAttributes(array $attributes): self
+ {
+ $this->loadingPlaceHolderWrapperAttributes = $attributes;
+
+ return $this;
+ }
+
+ public function setLoadingPlaceholderBlade(string $customBlade): self
+ {
+ $this->loadingPlaceholderBlade = $customBlade;
+
+ return $this;
+ }
+}
diff --git a/src/Traits/Helpers/LoadingPlaceholderHelpers.php b/src/Traits/Helpers/LoadingPlaceholderHelpers.php
new file mode 100644
index 000000000..4a5a9e7e7
--- /dev/null
+++ b/src/Traits/Helpers/LoadingPlaceholderHelpers.php
@@ -0,0 +1,47 @@
+getDisplayLoadingPlaceholder();
+ }
+
+ public function getDisplayLoadingPlaceholder(): bool
+ {
+ return $this->displayLoadingPlaceholder;
+ }
+
+ public function getLoadingPlaceholderContent(): string
+ {
+ return $this->loadingPlaceholderContent ?? __('livewire-tables:loading');
+ }
+
+ public function getLoadingPlaceholderAttributes(): array
+ {
+ return count($this->loadingPlaceHolderAttributes) ? $this->loadingPlaceHolderAttributes : ['default' => true];
+
+ }
+
+ public function getLoadingPlaceHolderIconAttributes(): array
+ {
+ return count($this->loadingPlaceHolderIconAttributes) ? $this->loadingPlaceHolderIconAttributes : ['default' => true];
+ }
+
+ public function getLoadingPlaceHolderWrapperAttributes(): array
+ {
+ return count($this->loadingPlaceHolderWrapperAttributes) ? $this->loadingPlaceHolderWrapperAttributes : ['default' => true];
+ }
+
+ public function hasLoadingPlaceholderBlade(): bool
+ {
+ return ! is_null($this->getLoadingPlaceHolderBlade());
+ }
+
+ public function getLoadingPlaceHolderBlade(): ?string
+ {
+ return $this->loadingPlaceholderBlade;
+ }
+}
diff --git a/src/Traits/WithLoadingPlaceholder.php b/src/Traits/WithLoadingPlaceholder.php
new file mode 100644
index 000000000..47c3a3072
--- /dev/null
+++ b/src/Traits/WithLoadingPlaceholder.php
@@ -0,0 +1,24 @@
+setPrimaryKey('id')
+ ->setLoadingPlaceholderEnabled()
+ ->setLoadingPlaceholderContent('TestLoadingPlaceholderContentTestTest');
+ }
+
+ public function columns(): array
+ {
+ return [
+ Column::make('ID', 'id')
+ ->sortable()
+ ->setSortingPillTitle('Key')
+ ->setSortingPillDirections('0-9', '9-0'),
+ Column::make('Sort')
+ ->sortable()
+ ->excludeFromColumnSelect(),
+ Column::make('Name')
+ ->sortable()
+ ->secondaryHeader($this->getFilterByKey('pet_name_filter'))
+ ->footerFilter('pet_name_filter')
+ ->searchable(),
+
+ Column::make('Age'),
+
+ Column::make('Breed', 'breed.name')
+ ->secondaryHeaderFilter('breed')
+ ->footer($this->getFilterByKey('breed'))
+ ->sortable(
+ fn (Builder $query, string $direction) => $query->orderBy('pets.id', $direction)
+ )
+ ->searchable(
+ fn (Builder $query, $searchTerm) => $query->orWhere('breed.name', $searchTerm)
+ ),
+
+ Column::make('Other')
+ ->label(function ($row, Column $column) {
+ return 'Other';
+ })
+ ->footer(function ($rows) {
+ return 'Count: '.$rows->count();
+ }),
+
+ LinkColumn::make('Link')
+ ->title(fn ($row) => 'Edit')
+ ->location(fn ($row) => 'http://www.google.com')
+ ->attributes(fn ($row) => [
+ 'class' => 'rounded-full',
+ 'alt' => $row->name.' Avatar',
+ ]),
+ ImageColumn::make('RowImg')
+ ->location(fn ($row) => 'test'.$row->id)
+ ->attributes(fn ($row) => [
+ 'class' => 'rounded-full',
+ 'alt' => $row->name.' Avatar',
+ ]),
+ Column::make('Last Visit', 'last_visit')
+ ->sortable()
+ ->deselected(),
+ ];
+ }
+
+ public function filters(): array
+ {
+ return [
+ MultiSelectFilter::make('Breed')
+ ->options(
+ Breed::query()
+ ->orderBy('name')
+ ->get()
+ ->keyBy('id')
+ ->map(fn ($breed) => $breed->name)
+ ->toArray()
+ )
+ ->filter(function (Builder $builder, array $values) {
+ return $builder->whereIn('breed_id', $values);
+ }),
+ MultiSelectDropdownFilter::make('Species')
+ ->options(
+ Species::query()
+ ->orderBy('name')
+ ->get()
+ ->keyBy('id')
+ ->map(fn ($species) => $species->name)
+ ->toArray()
+ )
+ ->filter(function (Builder $builder, array $values) {
+ return $builder->whereIn('species_id', $values);
+ }),
+ NumberFilter::make('Breed ID', 'breed_id_filter')
+ ->filter(function (Builder $builder, string $value) {
+ return $builder->where('breed_id', '=', $value);
+ }),
+
+ TextFilter::make('Pet Name', 'pet_name_filter')
+ ->filter(function (Builder $builder, string $value) {
+ return $builder->where('pets.name', '=', $value);
+ }),
+
+ DateFilter::make('Last Visit After Date', 'last_visit_date_filter')
+ ->filter(function (Builder $builder, string $value) {
+ return $builder->whereDate('pets.last_visit', '=>', $value);
+ }),
+
+ DateTimeFilter::make('Last Visit Before DateTime', 'last_visit_datetime_filter')
+ ->filter(function (Builder $builder, string $value) {
+ return $builder->whereDate('pets.last_visit', '<=', $value);
+ }),
+
+ SelectFilter::make('Breed SelectFilter', 'breed_select_filter')
+ ->options(
+ Breed::query()
+ ->orderBy('name')
+ ->get()
+ ->keyBy('id')
+ ->map(fn ($breed) => $breed->name)
+ ->toArray()
+ )
+ ->filter(function (Builder $builder, string $value) {
+ return $builder->where('breed_id', $value);
+ })
+ ->setCustomFilterLabel('livewire-tables::tests.testFilterLabel')
+ ->setFilterPillBlade('livewire-tables::tests.testFilterPills'),
+ ];
+ }
+}
diff --git a/tests/Traits/Configuration/LoadingPlaceholderConfigurationTest.php b/tests/Traits/Configuration/LoadingPlaceholderConfigurationTest.php
new file mode 100644
index 000000000..e5b033f99
--- /dev/null
+++ b/tests/Traits/Configuration/LoadingPlaceholderConfigurationTest.php
@@ -0,0 +1,99 @@
+assertFalse($this->basicTable->hasDisplayLoadingPlaceholder());
+
+ $this->basicTable->setLoadingPlaceholderEnabled();
+
+ $this->assertTrue($this->basicTable->hasDisplayLoadingPlaceholder());
+
+ }
+
+ /** @test */
+ public function can_set_loading_placeholder_status_disabled(): void
+ {
+ $this->assertFalse($this->basicTable->hasDisplayLoadingPlaceholder());
+
+ $this->basicTable->setLoadingPlaceholderEnabled();
+
+ $this->assertTrue($this->basicTable->hasDisplayLoadingPlaceholder());
+
+ $this->basicTable->setLoadingPlaceholderDisabled();
+
+ $this->assertFalse($this->basicTable->hasDisplayLoadingPlaceholder());
+
+ }
+
+ /** @test */
+ public function can_set_loading_placeholder_content(): void
+ {
+ $this->basicTable->setLoadingPlaceholderEnabled();
+
+ $this->assertTrue($this->basicTable->hasDisplayLoadingPlaceholder());
+
+ $this->assertSame('Loading', $this->basicTable->getLoadingPlaceholderContent());
+
+ $this->basicTable->setLoadingPlaceholderContent('LoadingConfigurationTest - LoadingLoadingLoading');
+
+ $this->assertSame('LoadingConfigurationTest - LoadingLoadingLoading', $this->basicTable->getLoadingPlaceholderContent());
+
+ }
+
+ /** @test */
+ public function can_set_loading_placeholder_attributes(): void
+ {
+ $this->basicTable->setLoadingPlaceholderEnabled();
+
+ $this->assertSame(['default' => true], $this->basicTable->getLoadingPlaceHolderAttributes());
+
+ $this->basicTable->setLoadingPlaceHolderAttributes(['class' => 'test12345']);
+
+ $this->assertSame(['class' => 'test12345'], $this->basicTable->getLoadingPlaceHolderAttributes());
+
+ }
+
+ /** @test */
+ public function can_set_loading_placeholder_icon_attributes(): void
+ {
+ $this->basicTable->setLoadingPlaceholderEnabled();
+
+ $this->assertSame(['default' => true], $this->basicTable->getLoadingPlaceHolderIconAttributes());
+
+ $this->basicTable->setLoadingPlaceHolderIconAttributes(['class' => 'test123']);
+
+ $this->assertSame(['class' => 'test123'], $this->basicTable->getLoadingPlaceHolderIconAttributes());
+
+ }
+
+ /** @test */
+ public function can_set_loading_placeholder_wrapper_attributes(): void
+ {
+ $this->basicTable->setLoadingPlaceholderEnabled();
+
+ $this->assertSame(['default' => true], $this->basicTable->getLoadingPlaceHolderWrapperAttributes());
+
+ $this->basicTable->setLoadingPlaceHolderWrapperAttributes(['class' => 'test1234567-wrapper']);
+
+ $this->assertSame(['class' => 'test1234567-wrapper'], $this->basicTable->getLoadingPlaceHolderWrapperAttributes());
+ }
+
+ /** @test */
+ public function can_set_loading_placeholder_custom_blade(): void
+ {
+ $this->basicTable->setLoadingPlaceholderEnabled();
+
+ $this->assertNull($this->basicTable->getLoadingPlaceHolderBlade());
+
+ $this->basicTable->setLoadingPlaceholderBlade('test-blade');
+
+ $this->assertSame('test-blade', $this->basicTable->getLoadingPlaceHolderBlade());
+ }
+}
diff --git a/tests/Traits/Helpers/LoadingPlaceholderHelpersTest.php b/tests/Traits/Helpers/LoadingPlaceholderHelpersTest.php
new file mode 100644
index 000000000..5d3b92c28
--- /dev/null
+++ b/tests/Traits/Helpers/LoadingPlaceholderHelpersTest.php
@@ -0,0 +1,21 @@
+assertFalse($this->basicTable->hasDisplayLoadingPlaceholder());
+
+ $this->basicTable->setLoadingPlaceholderEnabled();
+
+ $this->assertTrue($this->basicTable->getDisplayLoadingPlaceholder());
+
+ $this->assertTrue($this->basicTable->hasDisplayLoadingPlaceholder());
+
+ }
+}
diff --git a/tests/Traits/Visuals/LoadingPlaceholderVisualsTest.php b/tests/Traits/Visuals/LoadingPlaceholderVisualsTest.php
new file mode 100644
index 000000000..04cf3e788
--- /dev/null
+++ b/tests/Traits/Visuals/LoadingPlaceholderVisualsTest.php
@@ -0,0 +1,52 @@
+call('setPerPageAccepted', [1, 5, 10])
+ ->assertSeeHtml('tr wire:key="table-loader" class="hidden d-none"')
+ ->call('setPerPage', 5);
+ }
+
+ /** @test */
+ public function can_see_placeholder_custom_text(): void
+ {
+ Livewire::test(PetsTableLoadingPlaceholder::class)
+ ->call('setPerPageAccepted', [1, 5, 10])
+ ->assertSeeHtmlInOrder([
+ '
TestLoadingPlaceholderContentTestTest',
+ ])
+ ->call('setPerPage', 5);
+ }
+
+ /** @test */
+ public function can_see_correct_placeholder_text_visually(): void
+ {
+ Livewire::test(PetsTableLoadingPlaceholder::class)
+ ->call('setPerPageAccepted', [1, 5, 10])
+ ->assertSee('TestLoadingPlaceholderContentTestTest')
+ ->call('setPerPage', 5);
+ }
+
+ /** @test */
+ public function cannot_see_incorrect_placeholder_text_visually(): void
+ {
+ Livewire::test(PetsTableLoadingPlaceholder::class)
+ ->call('setPerPageAccepted', [1, 5, 10])
+ ->assertDontSee('TestLoadingPlaceholderContentTestTest22')
+ ->call('setPerPage', 5);
+ }
+}