diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e917bd8a..5c6ff504c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to `laravel-livewire-tables` will be documented in this file +## [v3.4.10] - 2024-08-23 +### Bug Fixes +- Default UseComputedProperties to True to default to new views by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1873 +- Allow Single Date DateRangeFilter by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1872 +- Allow clearing of DateRangeFilter by Text Box by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1875 + +### Docs +- Docs Update by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1876 + ## [v3.4.9] - 2024-08-21 ### Bug Fixes - Default UseComputedProperties to False to allow previously published views to work by default by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1869 diff --git a/docs/bulk-actions/customisations.md b/docs/bulk-actions/styling.md similarity index 94% rename from docs/bulk-actions/customisations.md rename to docs/bulk-actions/styling.md index dfe64e2e8..af4d26b38 100644 --- a/docs/bulk-actions/customisations.md +++ b/docs/bulk-actions/styling.md @@ -1,5 +1,5 @@ --- -title: Customising Style +title: Styling weight: 5 --- diff --git a/docs/datatable/available-methods.md b/docs/datatable/available-methods.md index b897d6708..13d4091b3 100644 --- a/docs/datatable/available-methods.md +++ b/docs/datatable/available-methods.md @@ -18,9 +18,21 @@ public function configure(): void } ``` +### useComputedPropertiesDisabled + +If you have published the Views **prior to v3.4.5**, and do not wish to remove the published views, then you should add the following call, which will disable the new Computed Properties behaviour. Note that publishing the views is not recommended! + +```php +public function configure(): void +{ + $this->useComputedPropertiesDisabled(); +} +``` + + ## Attributes -Documentation for Data Table Styling/Attributes is now: Here +Documentation for Data Table Styling/Attributes is now: [Here](../datatable/styling) ## Offline diff --git a/docs/filter-types/filters-daterange.md b/docs/filter-types/filters-daterange.md index 1d12e30b1..71241daba 100644 --- a/docs/filter-types/filters-daterange.md +++ b/docs/filter-types/filters-daterange.md @@ -46,6 +46,7 @@ public function filters(): array A full list of options is below, please see the Flatpickr documentation for reference as to purpose: | Config Option | Type | Default | Description | | ------------- | ------------- | ------------- | ------------- | +| allowInvalidPreload | Boolean | true | Allows the preloading of an invalid date. When disabled, the field will be cleared if the provided date is invalid | | allowInput | Boolean | false | Allows the user to enter a date directly into the input field. By default, direct entry is disabled. | | altFormat | String | "F j, Y" | Exactly the same as date format, but for the altInput field | | altInput | Boolean | false | Show the user a readable date (as per altFormat), but return something totally different to the server. | @@ -153,7 +154,7 @@ If using the CDN approach, ensure the following config matches: Then include the following in your layout: ```html // Flatpickr Core Script - + // Flatpickr Core CSS @@ -193,5 +194,5 @@ You can also add locales using the Flatpickr CDN, ensuring that these are loaded For example to add German (de), ensure that the following is in the "head" section of your layout, ideally before your app.js ```html - + ``` \ No newline at end of file diff --git a/docs/footer/available-methods.md b/docs/footer/available-methods.md index 455b4d9d3..5dca23f70 100644 --- a/docs/footer/available-methods.md +++ b/docs/footer/available-methods.md @@ -82,66 +82,9 @@ public function configure(): void } ``` ---- - -## setFooterTrAttributes - -Set any attributes on the footer row element. - -```php -public function configure(): void -{ - $this->setFooterTrAttributes(function($rows) { - return ['class' => 'bg-gray-100']; - }); -} -``` - -By default, this replaces the default classes on the tr element, if you would like to keep them, set the default flag to true. - -```php -public function configure(): void -{ - $this->setFooterTrAttributes(function($rows) { - return [ - 'default' => true, - 'class' => 'bg-gray-100' - ]; - }); -} -``` - -## setFooterTdAttributes - -Set any attributes on the footer row cells. - -```php -public function configure(): void -{ - $this->setFooterTdAttributes(function(Column $column, $rows) { - if ($column->isField('id')) { - return ['class' => 'text-red-500']; - } - }); -} -``` - -By default, this replaces the default classes on the td element, if you would like to keep them, set the default flag to true. - -```php -public function configure(): void -{ - $this->setFooterTdAttributes(function(Column $column, $rows) { - if ($column->isField('id')) { - return [ - 'default' => true, - 'class' => 'text-red-500' - ]; - } - }); -} -``` --- -See also [footer column configuration](../columns/footer). \ No newline at end of file +See also +[footer styling](./styling). +[footer column configuration](../columns/footer). \ No newline at end of file diff --git a/docs/footer/styling.md b/docs/footer/styling.md new file mode 100644 index 000000000..e1685896a --- /dev/null +++ b/docs/footer/styling.md @@ -0,0 +1,62 @@ +--- +title: Styling +weight: 2 +--- + +## setFooterTrAttributes + +Set any attributes on the footer row element. + +```php +public function configure(): void +{ + $this->setFooterTrAttributes(function($rows) { + return ['class' => 'bg-gray-100']; + }); +} +``` + +By default, this replaces the default classes on the tr element, if you would like to keep them, set the default flag to true. + +```php +public function configure(): void +{ + $this->setFooterTrAttributes(function($rows) { + return [ + 'default' => true, + 'class' => 'bg-gray-100' + ]; + }); +} +``` + +## setFooterTdAttributes + +Set any attributes on the footer row cells. + +```php +public function configure(): void +{ + $this->setFooterTdAttributes(function(Column $column, $rows) { + if ($column->isField('id')) { + return ['class' => 'text-red-500']; + } + }); +} +``` + +By default, this replaces the default classes on the td element, if you would like to keep them, set the default flag to true. + +```php +public function configure(): void +{ + $this->setFooterTdAttributes(function(Column $column, $rows) { + if ($column->isField('id')) { + return [ + 'default' => true, + 'class' => 'text-red-500' + ]; + } + }); +} +``` diff --git a/docs/misc/actions.md b/docs/misc/actions.md new file mode 100644 index 000000000..34850a090 --- /dev/null +++ b/docs/misc/actions.md @@ -0,0 +1,199 @@ +--- +title: Actions (beta) +weight: 4 +--- + +Actions is a beta feature, that allows for the creation of Action Buttons that appear above the toolbar. These are ideal for common Actions that do not impact existing records, such as a "Create", "Assign", "Back" buttons. + +This is NOT recommended for production use at this point in time. + +## Component Available Methods +### setActionWrapperAttributes + +This is used to set attributes for the "div" that wraps all defined Action Buttons: + +```php + public function configure(): void + { + $this->setActionWrapperAttributes([ + 'class' => 'space-x-4' + ]); + } +``` + +### actions() + +Define your actions using the actions() method: + +```php +public function actions(): array +{ + return [ + Action::make('View Dashboard') + ->setRoute('dashboard'), + ]; +} +``` + +## Button Available Methods + +### setActionAttributes + +setActionAttributes is used to pass any attributes that you wish to implement on the "button" element for the action button. By default it will merge with the default classes. + +You can set "default-styling" and "default-colors" to false to replace, rather than over-ride either default-styling or default-colors. +```php +public function actions(): array +{ + return [ + Action::make('View Dashboard') + ->setActionAttributes([ + 'class' => 'dark:bg-blue-500 dark:text-white dark:border-blue-600 dark:hover:border-blue-900 dark:hover:bg-blue-800', + 'default-colors' => false, // Will over-ride the default colors + 'default-styling' => true // Will use the default styling + ]), + ]; +} +``` + +### setIcon + +setIcon is used to set an icon for the action button + +```php +public function actions(): array +{ + return [ + Action::make('Edit Item') + ->setIcon("fas fa-edit"), + ]; +} +``` + +### setIconAttributes + +setIconAttributes is used to set any additional attributes for the Icon for the action button +```php +public function actions(): array +{ + return [ + Action::make('Edit Item') + ->setIcon("fas fa-edit") + ->setIconAttributes(['class' => 'font-4xl text-4xl']), + ]; +} +``` + +### setRoute + +Used for non-wireable butons, to set the route that the action button should take the user to upon clicking. +```php +public function actions(): array +{ + return [ + Action::make('Dashboard') + ->setRoute('dashboard') + ]; +} +``` + +### wireNavigate + +Used in conjunction with setRoute - makes the link "wire:navigate" rather than default behaviour +```php +public function actions(): array +{ + return [ + Action::make('Dashboard') + ->setRoute('dashboard') + ->wireNavigate() + ]; +} +``` + +### setWireAction +```php +public function actions(): array +{ + return [ + Action::make('Create 2') + ->setWireAction("wire:click") + ]; +} +``` + +### setWireActionParams +Specify the action & parameters to pass to the wire:click method + +```php +public function actions(): array +{ + return [ + Action::make('Create 2') + ->setWireAction("wire:click") + ->setWireActionParams(['id' => 'test']), + ]; +} +``` + +### setWireActionDispatchParams + +Use $dispatch rather than a typical wire:click action + +```php +public function actions(): array +{ + return [ + Action::make('Create 2') + ->setWireAction("wire:click") + ->setWireActionDispatchParams("'openModal', { component: 'test-modal' }"), + ]; +} +``` + +### setView + +This is used to set a Custom View for the Button + +```php +public function actions(): array +{ + return [ + Action::make('Edit Item') + ->setView("buttons.edit"), + ]; +} +``` + +## Extending + +You can extend the Base Action class which can be a useful timesaver, when you wish to re-use the same look/feel of an Action, but wish to set a different route (for example). +You can set any defaults in the __construct method, ensuring that the parent constructor is called first! + +```php +use Rappasoft\LaravelLivewireTables\Views\Action; + +class EditAction extends Action +{ + public function __construct(?string $label = null) + { + parent::__construct($label); + $this + ->setActionAttributes([ + 'class' => 'dark:bg-blue-500 dark:text-white dark:border-blue-600 dark:hover:border-blue-900 dark:hover:bg-blue-800', + 'default-colors' => false, + 'default-styling' => true + ]) + ->setIcon("fas fa-edit") + ->setIconAttributes([ + 'class' => 'font-4xl text-4xl' + ]); + } +} +``` + +You may define a Custom View to be used via either the setView method, or by defining the view directly in your class. +```php + protected string $view = 'buttons.edit-action'; +``` + diff --git a/docs/secondary-header/available-methods.md b/docs/secondary-header/available-methods.md index 740c02db5..6bd85d5c3 100644 --- a/docs/secondary-header/available-methods.md +++ b/docs/secondary-header/available-methods.md @@ -43,66 +43,9 @@ public function configure(): void } ``` ---- - -## setSecondaryHeaderTrAttributes - -Set any attributes on the secondary header row element. - -```php -public function configure(): void -{ - $this->setSecondaryHeaderTrAttributes(function($rows) { - return ['class' => 'bg-gray-100']; - }); -} -``` - -By default, this replaces the default classes on the tr element, if you would like to keep them, set the default flag to true. - -```php -public function configure(): void -{ - $this->setSecondaryHeaderTrAttributes(function($rows) { - return [ - 'default' => true, - 'class' => 'bg-gray-100' - ]; - }); -} -``` - -## setSecondaryHeaderTdAttributes - -Set any attributes on the secondary header row cells. - -```php -public function configure(): void -{ - $this->setSecondaryHeaderTdAttributes(function(Column $column, $rows) { - if ($column->isField('id')) { - return ['class' => 'text-red-500']; - } - }); -} -``` - -By default, this replaces the default classes on the td element, if you would like to keep them, set the default flag to true. - -```php -public function configure(): void -{ - $this->setSecondaryHeaderTdAttributes(function(Column $column, $rows) { - if ($column->isField('id')) { - return [ - 'default' => true, - 'class' => 'text-red-500' - ]; - } - }); -} -``` --- -See also [secondary header column configuration](../columns/secondary-header). \ No newline at end of file +See also: +[secondary header styling](./styling). +[secondary header column configuration](../columns/secondary-header). \ No newline at end of file diff --git a/docs/secondary-header/styling.md b/docs/secondary-header/styling.md new file mode 100644 index 000000000..8eb97e517 --- /dev/null +++ b/docs/secondary-header/styling.md @@ -0,0 +1,62 @@ +--- +title: Styling +weight: 2 +--- + +## setSecondaryHeaderTrAttributes + +Set any attributes on the secondary header row element. + +```php +public function configure(): void +{ + $this->setSecondaryHeaderTrAttributes(function($rows) { + return ['class' => 'bg-gray-100']; + }); +} +``` + +By default, this replaces the default classes on the tr element, if you would like to keep them, set the default flag to true. + +```php +public function configure(): void +{ + $this->setSecondaryHeaderTrAttributes(function($rows) { + return [ + 'default' => true, + 'class' => 'bg-gray-100' + ]; + }); +} +``` + +## setSecondaryHeaderTdAttributes + +Set any attributes on the secondary header row cells. + +```php +public function configure(): void +{ + $this->setSecondaryHeaderTdAttributes(function(Column $column, $rows) { + if ($column->isField('id')) { + return ['class' => 'text-red-500']; + } + }); +} +``` + +By default, this replaces the default classes on the td element, if you would like to keep them, set the default flag to true. + +```php +public function configure(): void +{ + $this->setSecondaryHeaderTdAttributes(function(Column $column, $rows) { + if ($column->isField('id')) { + return [ + 'default' => true, + 'class' => 'text-red-500' + ]; + } + }); +} +``` diff --git a/docs/usage/common-issues.md b/docs/usage/common-issues.md new file mode 100644 index 000000000..34b3b5eb1 --- /dev/null +++ b/docs/usage/common-issues.md @@ -0,0 +1,23 @@ +--- +title: Common Issues +weight: 4 +--- + +### Property Errors + +The strong recommendation is to not publish the views for this package. The vast majority of elements can be customised using methods within the package. + +See "Available Methods" in most sections for details, some examples: +[DataTable Styling](../datatable/styling) +[Bulk Actions Styling](../bulk-actions/styling) + +### Previously Published Views + +The strong recommendation is to not publish the views for this package. If you have published the views prior to v3.4.5, and do not wish to remove these, then you should add the following method, to disable the newer (more efficient) behaviour: + +```php +public function configure(): void +{ + $this->useComputedPropertiesDisabled(); +} +``` diff --git a/resources/js/laravel-livewire-tables.js b/resources/js/laravel-livewire-tables.js index 7d48524d2..306c3857d 100644 --- a/resources/js/laravel-livewire-tables.js +++ b/resources/js/laravel-livewire-tables.js @@ -311,7 +311,7 @@ document.addEventListener('alpine:init', () => { altFormat: filterConfig['altFormat'] ?? "F j, Y", altInput: filterConfig['altInput'] ?? false, allowInput: filterConfig['allowInput'] ?? false, - allowInvalidPreload: true, + allowInvalidPreload: filterConfig['allowInvalidPreload'] ?? true, ariaDateFormat: filterConfig['ariaDateFormat'] ?? "F j, Y", clickOpens: true, dateFormat: filterConfig['dateFormat'] ?? "Y-m-d", @@ -333,16 +333,22 @@ document.addEventListener('alpine:init', () => { }, onChange: function (selectedDates, dateStr, instance) { if (selectedDates.length > 1) { - var startDate = dateStr.split(' ')[0]; - var endDate = dateStr.split(' ')[2]; + var dates = dateStr.split(' '); var wireDateArray = {}; window.childElementOpen = false; window.filterPopoverOpen = false; - wireDateArray = { 'minDate': startDate, 'maxDate': endDate }; + wireDateArray = { 'minDate': dates[0], 'maxDate': (typeof dates[2] === "undefined") ? dates[0] : dates[2] }; wire.set('filterComponents.' + filterKey, wireDateArray); } - } + }, }), + changedValue: function(value) { + if (value.length < 5) + { + this.flatpickrInstance.setDate([]); + wire.set('filterComponents.' + filterKey, {}); + } + }, setupWire() { if (this.wireValues !== undefined) { if (this.wireValues.minDate !== undefined && this.wireValues.maxDate !== undefined) { diff --git a/resources/js/laravel-livewire-tables.min.js b/resources/js/laravel-livewire-tables.min.js index f9b60100f..35cecdd4f 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('laravellivewiretable',(wire,showBulkActionsAlpine,tableID,primaryKeyName)=>({listeners:[],childElementOpen:!1,filtersOpen:wire.entangle('filterSlideDownDefaultVisible'),paginationCurrentCount:wire.entangle('paginationCurrentCount'),paginationTotalItemCount:wire.entangle('paginationTotalItemCount'),paginationCurrentItems:wire.entangle('paginationCurrentItems'),selectedItems:wire.entangle('selected'),selectAllStatus:wire.entangle('selectAll'),delaySelectAll:wire.entangle('delaySelectAll'),hideBulkActionsWhenEmpty:wire.entangle('hideBulkActionsWhenEmpty'),dragging:!1,reorderEnabled:!1,sourceID:'',targetID:'',evenRowClasses:'',oddRowClasses:'',currentlyHighlightedElement:'',evenRowClassArray:{},oddRowClassArray:{},evenNotInOdd:{},oddNotInEven:{},orderedRows:[],defaultReorderColumn:wire.get('defaultReorderColumn'),reorderStatus:wire.entangle('reorderStatus'),currentlyReorderingStatus:wire.entangle('currentlyReorderingStatus'),hideReorderColumnUnlessReorderingStatus:wire.entangle('hideReorderColumnUnlessReorderingStatus'),reorderDisplayColumn:wire.entangle('reorderDisplayColumn'),dragStart(a){this.$nextTick(()=>this.setupEvenOddClasses());this.sourceID=a.target.id;a.dataTransfer.effectAllowed='move';a.dataTransfer.setData('text/plain',a.target.id);a.target.classList.add('laravel-livewire-tables-dragging')},dragOverEvent(A){typeof this.currentlyHighlightedElement=='object'&&this.currentlyHighlightedElement.classList.remove('laravel-livewire-tables-highlight-bottom','laravel-livewire-tables-highlight-top');let b=A.target.closest('tr');this.currentlyHighlightedElement=b;A.offsetY<(b.getBoundingClientRect().height/2)?(b.classList.add('laravel-livewire-tables-highlight-top'),b.classList.remove('laravel-livewire-tables-highlight-bottom')):(b.classList.remove('laravel-livewire-tables-highlight-top'),b.classList.add('laravel-livewire-tables-highlight-bottom'))},dragLeaveEvent(_){_.target.closest('tr').classList.remove('laravel-livewire-tables-highlight-bottom','laravel-livewire-tables-highlight-top')},dropEvent(B){typeof this.currentlyHighlightedElement=='object'&&this.currentlyHighlightedElement.classList.remove('laravel-livewire-tables-highlight-bottom','laravel-livewire-tables-highlight-top');let c=B.target.closest('tr');let C=B.target.closest('tr').parentNode;let d=document.getElementById(this.sourceID).closest('tr');d.classList.remove('laravel-livewire-table-dragging');let e=d.rowIndex;let f=c.rowIndex;let g=document.getElementById(tableID);B.offsetY>(c.getBoundingClientRect().height/2)?C.insertBefore(d,c.nextSibling):C.insertBefore(d,c);fthis.setupEvenOddClasses())},cancelReorder(){this.hideReorderColumnUnlessReorderingStatus&&(this.reorderDisplayColumn=!1);wire.disableReordering()},updateOrderedItems(){let _a=document.getElementById(tableID);let D=[];for(let i=1,_B;_B=_a.rows[i];i++)D.push({[primaryKeyName]:_B.getAttribute('rowpk'),[this.defaultReorderColumn]:i});wire.storeReorder(D)},setupEvenOddClasses(){if(this.evenNotInOdd.length===void 0||this.evenNotInOdd.length==0||this.oddNotInEven.length===void 0||this.oddNotInEven.length==0){let _A=document.getElementById(tableID).getElementsByTagName('tbody')[0];let E=[];let _c=[];(_A.rows[0]!==void 0&&_A.rows[1]!==void 0)&&(E=[..._A.rows[0].classList],_c=[..._A.rows[1].classList],this.evenNotInOdd=E.filter(aA=>!_c.includes(aA)),this.oddNotInEven=_c.filter(aB=>!E.includes(aB)),E=[],_c=[])}},toggleSelectAll(){if(!showBulkActionsAlpine)return;if(this.paginationTotalItemCount===this.selectedItems.length){this.clearSelected();this.selectAllStatus=!1}else this.delaySelectAll?this.setAllItemsSelected():this.setAllSelected()},setAllItemsSelected(){if(!showBulkActionsAlpine)return;this.selectAllStatus=!0;this.selectAllOnPage()},setAllSelected(){if(!showBulkActionsAlpine)return;this.delaySelectAll?(this.selectAllStatus=!0,this.selectAllOnPage()):(wire.setAllSelected())},clearSelected(){if(!showBulkActionsAlpine)return;this.selectAllStatus=!1;wire.clearSelected()},selectAllOnPage(){if(!showBulkActionsAlpine)return;let aC=this.selectedItems;var aD=this.paginationCurrentItems.values();for(const aE of aD)aC.push(`${aE}`);this.selectedItems=[...new Set(aC)]},destroy(){for(const aF of this.listeners)aF()}}));Alpine.data('booleanFilter',(aG,aH,_C,_d)=>({switchOn:!1,value:aG.entangle(`filterComponents.${aH}`).live,init(){this.switchOn=!1;this.value!==void 0&&(this.switchOn=!!+this.value);this.listeners.push(Livewire.on('filter-was-set',aI=>{(aI.tableName==_C&&aI.filterKey==aH)&&(this.switchOn=aI.value??_d)}))}}));Alpine.data('numberRangeFilter',(aJ,aK,aL,_D,_e)=>({allFilters:aJ.entangle('filterComponents',!1),originalMin:0,originalMax:100,filterMin:0,filterMax:100,currentMin:0,currentMax:100,hasUpdate:!1,wireValues:aJ.entangle(`filterComponents.${aK}`,!1),defaultMin:_D['minRange'],defaultMax:_D['maxRange'],restrictUpdates:!1,initialiseStyles(){let aM=document.getElementById(aL);aM.style.setProperty('--value-a',this.wireValues['min']??this.filterMin);aM.style.setProperty('--text-value-a',JSON.stringify(this.wireValues['min']??this.filterMin));aM.style.setProperty('--value-b',this.wireValues['max']??this.filterMax);aM.style.setProperty('--text-value-b',JSON.stringify(this.wireValues['max']??this.filterMax))},updateStyles(aN,aO){let aP=document.getElementById(aL);aP.style.setProperty('--value-a',aN);aP.style.setProperty('--text-value-a',JSON.stringify(aN));aP.style.setProperty('--value-b',aO);aP.style.setProperty('--text-value-b',JSON.stringify(aO))},setupWire(){this.wireValues!==void 0?(this.filterMin=this.originalMin=(this.wireValues['min']!==void 0)?this.wireValues['min']:this.defaultMin,this.filterMax=this.originalMax=(this.wireValues['max']!==void 0)?this.wireValues['max']:this.defaultMax):(this.filterMin=this.originalMin=this.defaultMin,this.filterMax=this.originalMax=this.defaultMax);this.updateStyles(this.filterMin,this.filterMax)},allowUpdates(){this.updateWire()},updateWire(){let aQ=parseInt(this.filterMin);let aR=parseInt(this.filterMax);if(aQ!=this.originalMin||aR!=this.originalMax){aRthis.setupWire())}}));Alpine.data('flatpickrFilter',(aS,aT,aU,aV,_E)=>({wireValues:aS.entangle(`filterComponents.${aT}`),flatpickrInstance:flatpickr(aV,{mode:'range',altFormat:aU['altFormat']??'F j, Y',altInput:aU['altInput']??!1,allowInput:aU['allowInput']??!1,allowInvalidPreload:!0,ariaDateFormat:aU['ariaDateFormat']??'F j, Y',clickOpens:!0,dateFormat:aU['dateFormat']??'Y-m-d',defaultDate:aU['defaultDate']??null,defaultHour:aU['defaultHour']??12,defaultMinute:aU['defaultMinute']??0,enableTime:aU['enableTime']??!1,enableSeconds:aU['enableSeconds']??!1,hourIncrement:aU['hourIncrement']??1,locale:aU['locale']??'en',minDate:aU['earliestDate']??null,maxDate:aU['latestDate']??null,minuteIncrement:aU['minuteIncrement']??5,shorthandCurrentMonth:aU['shorthandCurrentMonth']??!1,time_24hr:aU['time_24hr']??!1,weekNumbers:aU['weekNumbers']??!1,onOpen:function(){window.childElementOpen=!0},onChange:function(aW,aX,aY){if(aW.length>1){var aZ=aX.split(' ')[0],bA=aX.split(' ')[2],F={};window.childElementOpen=window.filterPopoverOpen=!1;F={'minDate':aZ,'maxDate':bA};aS.set(`filterComponents.${aT}`,F)}}}),setupWire(){if(this.wireValues!==void 0)if(this.wireValues.minDate!==void 0&&this.wireValues.maxDate!==void 0){this.flatpickrInstance.setDate([this.wireValues.minDate,this.wireValues.maxDate])}else this.flatpickrInstance.setDate([]);else this.flatpickrInstance.setDate([])},init(){this.setupWire();this.$watch('wireValues',value=>this.setupWire())}}));Alpine.data('tableWrapper',(wire,showBulkActionsAlpine)=>({listeners:[],childElementOpen:!1,filtersOpen:wire.entangle('filterSlideDownDefaultVisible'),paginationCurrentCount:wire.entangle('paginationCurrentCount'),paginationTotalItemCount:wire.entangle('paginationTotalItemCount'),paginationCurrentItems:wire.entangle('paginationCurrentItems'),selectedItems:wire.entangle('selected'),selectAllStatus:wire.entangle('selectAll'),delaySelectAll:wire.entangle('delaySelectAll'),hideBulkActionsWhenEmpty:wire.entangle('hideBulkActionsWhenEmpty'),toggleSelectAll(){if(!showBulkActionsAlpine)return;if(this.paginationTotalItemCount===this.selectedItems.length){this.clearSelected();this.selectAllStatus=!1}else this.delaySelectAll?this.setAllItemsSelected():this.setAllSelected()},setAllItemsSelected(){if(!showBulkActionsAlpine)return;this.selectAllStatus=!0;this.selectAllOnPage()},setAllSelected(){if(!showBulkActionsAlpine)return;this.delaySelectAll?(this.selectAllStatus=!0,this.selectAllOnPage()):(wire.setAllSelected())},clearSelected(){if(!showBulkActionsAlpine)return;this.selectAllStatus=!1;wire.clearSelected()},selectAllOnPage(){if(!showBulkActionsAlpine)return;let bB=this.selectedItems;var bC=this.paginationCurrentItems.values();for(const bD of bC)bB.push(`${bD}`);this.selectedItems=[...new Set(bB)]},destroy(){for(const bE of this.listeners)bE()}}));Alpine.data('reorderFunction',(wire,tableID,primaryKeyName)=>({dragging:!1,reorderEnabled:!1,sourceID:'',targetID:'',evenRowClasses:'',oddRowClasses:'',currentlyHighlightedElement:'',evenRowClassArray:{},oddRowClassArray:{},evenNotInOdd:{},oddNotInEven:{},orderedRows:[],defaultReorderColumn:wire.get('defaultReorderColumn'),reorderStatus:wire.get('reorderStatus'),currentlyReorderingStatus:wire.entangle('currentlyReorderingStatus'),hideReorderColumnUnlessReorderingStatus:wire.entangle('hideReorderColumnUnlessReorderingStatus'),reorderDisplayColumn:wire.entangle('reorderDisplayColumn'),dragStart(bF){this.$nextTick(()=>this.setupEvenOddClasses());this.sourceID=bF.target.id;bF.dataTransfer.effectAllowed='move';bF.dataTransfer.setData('text/plain',bF.target.id);bF.target.classList.add('laravel-livewire-tables-dragging')},dragOverEvent(bG){typeof this.currentlyHighlightedElement=='object'&&this.currentlyHighlightedElement.classList.remove('laravel-livewire-tables-highlight-bottom','laravel-livewire-tables-highlight-top');let bH=bG.target.closest('tr');this.currentlyHighlightedElement=bH;bG.offsetY<(bH.getBoundingClientRect().height/2)?(bH.classList.add('laravel-livewire-tables-highlight-top'),bH.classList.remove('laravel-livewire-tables-highlight-bottom')):(bH.classList.remove('laravel-livewire-tables-highlight-top'),bH.classList.add('laravel-livewire-tables-highlight-bottom'))},dragLeaveEvent(bI){bI.target.closest('tr').classList.remove('laravel-livewire-tables-highlight-bottom','laravel-livewire-tables-highlight-top')},dropEvent(bJ){typeof this.currentlyHighlightedElement=='object'&&this.currentlyHighlightedElement.classList.remove('laravel-livewire-tables-highlight-bottom','laravel-livewire-tables-highlight-top');let bK=bJ.target.closest('tr');let bL=bJ.target.closest('tr').parentNode;let bM=document.getElementById(this.sourceID).closest('tr');bM.classList.remove('laravel-livewire-table-dragging');let bN=bM.rowIndex;let _f=bK.rowIndex;let G=document.getElementById(tableID);bJ.offsetY>(bK.getBoundingClientRect().height/2)?bL.insertBefore(bM,bK.nextSibling):bL.insertBefore(bM,bK);_fthis.setupEvenOddClasses());if(this.currentlyReorderingStatus)wire.disableReordering();else{this.setupEvenOddClasses();this.hideReorderColumnUnlessReorderingStatus&&(this.reorderDisplayColumn=!0);wire.enableReordering()}},cancelReorder(){this.hideReorderColumnUnlessReorderingStatus&&(this.reorderDisplayColumn=!1);wire.disableReordering()},updateOrderedItems(){let bP=document.getElementById(tableID);let bQ=[];for(let i=1,bR;bR=bP.rows[i];i++)bQ.push({[primaryKeyName]:bR.getAttribute('rowpk'),[this.defaultReorderColumn]:i});wire.storeReorder(bQ)},setupEvenOddClasses(){if(this.evenNotInOdd.length===void 0||this.evenNotInOdd.length==0||this.oddNotInEven.length===void 0||this.oddNotInEven.length==0){let bS=document.getElementById(tableID).getElementsByTagName('tbody')[0];let bT=[];let bU=[];(bS.rows[0]!==void 0&&bS.rows[1]!==void 0)&&(bT=[...bS.rows[0].classList],bU=[...bS.rows[1].classList],this.evenNotInOdd=bT.filter(bV=>!bU.includes(bV)),this.oddNotInEven=bU.filter(bW=>!bT.includes(bW)),bT=[],bU=[])}},init(){}}))}); +document.addEventListener("alpine:init",()=>{Alpine.data("laravellivewiretable",(e,t,l,i)=>({listeners:[],childElementOpen:!1,filtersOpen:e.entangle("filterSlideDownDefaultVisible"),paginationCurrentCount:e.entangle("paginationCurrentCount"),paginationTotalItemCount:e.entangle("paginationTotalItemCount"),paginationCurrentItems:e.entangle("paginationCurrentItems"),selectedItems:e.entangle("selected"),selectAllStatus:e.entangle("selectAll"),delaySelectAll:e.entangle("delaySelectAll"),hideBulkActionsWhenEmpty:e.entangle("hideBulkActionsWhenEmpty"),dragging:!1,reorderEnabled:!1,sourceID:"",targetID:"",evenRowClasses:"",oddRowClasses:"",currentlyHighlightedElement:"",evenRowClassArray:{},oddRowClassArray:{},evenNotInOdd:{},oddNotInEven:{},orderedRows:[],defaultReorderColumn:e.get("defaultReorderColumn"),reorderStatus:e.entangle("reorderStatus"),currentlyReorderingStatus:e.entangle("currentlyReorderingStatus"),hideReorderColumnUnlessReorderingStatus:e.entangle("hideReorderColumnUnlessReorderingStatus"),reorderDisplayColumn:e.entangle("reorderDisplayColumn"),dragStart(e){this.$nextTick(()=>{this.setupEvenOddClasses()}),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.offsetYt.getBoundingClientRect().height/2?i.insertBefore(s,t.nextSibling):i.insertBefore(s,t),r{this.setupEvenOddClasses()})},cancelReorder(){this.hideReorderColumnUnlessReorderingStatus&&(this.reorderDisplayColumn=!1),e.disableReordering()},updateOrderedItems(){let t=document.getElementById(l),s=[];for(let a=1,r;r=t.rows[a];a++)s.push({[i]:r.getAttribute("rowpk"),[this.defaultReorderColumn]:a});e.storeReorder(s)},setupEvenOddClasses(){if(void 0===this.evenNotInOdd.length||0==this.evenNotInOdd.length||void 0===this.oddNotInEven.length||0==this.oddNotInEven.length){let e=document.getElementById(l).getElementsByTagName("tbody")[0],t=[],i=[];void 0!==e.rows[0]&&void 0!==e.rows[1]&&(t=Array.from(e.rows[0].classList),i=Array.from(e.rows[1].classList),this.evenNotInOdd=t.filter(e=>!i.includes(e)),this.oddNotInEven=i.filter(e=>!t.includes(e)),t=[],i=[])}},toggleSelectAll(){t&&(this.paginationTotalItemCount===this.selectedItems.length?(this.clearSelected(),this.selectAllStatus=!1):this.delaySelectAll?this.setAllItemsSelected():this.setAllSelected())},setAllItemsSelected(){t&&(this.selectAllStatus=!0,this.selectAllOnPage())},setAllSelected(){t&&(this.delaySelectAll?(this.selectAllStatus=!0,this.selectAllOnPage()):e.setAllSelected())},clearSelected(){t&&(this.selectAllStatus=!1,e.clearSelected())},selectAllOnPage(){if(!t)return;let e=this.selectedItems,l=this.paginationCurrentItems.values();for(let i of l)e.push(i.toString());this.selectedItems=[...new Set(e)]},destroy(){this.listeners.forEach(e=>{e()})}})),Alpine.data("booleanFilter",(e,t,l,i)=>({switchOn:!1,value:e.entangle("filterComponents."+t).live,init(){this.switchOn=!1,void 0!==this.value&&(this.switchOn=Boolean(Number(this.value))),this.listeners.push(Livewire.on("filter-was-set",e=>{e.tableName==l&&e.filterKey==t&&(this.switchOn=e.value??i)}))}})),Alpine.data("numberRangeFilter",(e,t,l,i,s)=>({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:i.minRange,defaultMax:i.maxRange,restrictUpdates:!1,initialiseStyles(){let e=document.getElementById(l);e.style.setProperty("--value-a",this.wireValues.min??this.filterMin),e.style.setProperty("--text-value-a",JSON.stringify(this.wireValues.min??this.filterMin)),e.style.setProperty("--value-b",this.wireValues.max??this.filterMax),e.style.setProperty("--text-value-b",JSON.stringify(this.wireValues.max??this.filterMax))},updateStyles(e,t){let i=document.getElementById(l);i.style.setProperty("--value-a",e),i.style.setProperty("--text-value-a",JSON.stringify(e)),i.style.setProperty("--value-b",t),i.style.setProperty("--text-value-b",JSON.stringify(t))},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(this.filterMin,this.filterMax)},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,l,i,s)=>({wireValues:e.entangle("filterComponents."+t),flatpickrInstance:flatpickr(i,{mode:"range",altFormat:l.altFormat??"F j, Y",altInput:l.altInput??!1,allowInput:l.allowInput??!1,allowInvalidPreload:l.allowInvalidPreload??!0,ariaDateFormat:l.ariaDateFormat??"F j, Y",clickOpens:!0,dateFormat:l.dateFormat??"Y-m-d",defaultDate:l.defaultDate??null,defaultHour:l.defaultHour??12,defaultMinute:l.defaultMinute??0,enableTime:l.enableTime??!1,enableSeconds:l.enableSeconds??!1,hourIncrement:l.hourIncrement??1,locale:l.locale??"en",minDate:l.earliestDate??null,maxDate:l.latestDate??null,minuteIncrement:l.minuteIncrement??5,shorthandCurrentMonth:l.shorthandCurrentMonth??!1,time_24hr:l.time_24hr??!1,weekNumbers:l.weekNumbers??!1,onOpen:function(){window.childElementOpen=!0},onChange:function(l,i,s){if(l.length>1){var a=i.split(" "),r={};window.childElementOpen=!1,window.filterPopoverOpen=!1,r={minDate:a[0],maxDate:void 0===a[2]?a[0]:a[2]},e.set("filterComponents."+t,r)}}}),changedValue:function(l){l.length<5&&(this.flatpickrInstance.setDate([]),e.set("filterComponents."+t,{}))},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("tableWrapper",(e,t)=>({listeners:[],childElementOpen:!1,filtersOpen:e.entangle("filterSlideDownDefaultVisible"),paginationCurrentCount:e.entangle("paginationCurrentCount"),paginationTotalItemCount:e.entangle("paginationTotalItemCount"),paginationCurrentItems:e.entangle("paginationCurrentItems"),selectedItems:e.entangle("selected"),selectAllStatus:e.entangle("selectAll"),delaySelectAll:e.entangle("delaySelectAll"),hideBulkActionsWhenEmpty:e.entangle("hideBulkActionsWhenEmpty"),toggleSelectAll(){t&&(this.paginationTotalItemCount===this.selectedItems.length?(this.clearSelected(),this.selectAllStatus=!1):this.delaySelectAll?this.setAllItemsSelected():this.setAllSelected())},setAllItemsSelected(){t&&(this.selectAllStatus=!0,this.selectAllOnPage())},setAllSelected(){t&&(this.delaySelectAll?(this.selectAllStatus=!0,this.selectAllOnPage()):e.setAllSelected())},clearSelected(){t&&(this.selectAllStatus=!1,e.clearSelected())},selectAllOnPage(){if(!t)return;let e=this.selectedItems,l=this.paginationCurrentItems.values();for(let i of l)e.push(i.toString());this.selectedItems=[...new Set(e)]},destroy(){this.listeners.forEach(e=>{e()})}})),Alpine.data("reorderFunction",(e,t,l)=>({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.$nextTick(()=>{this.setupEvenOddClasses()}),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.offsetYl.getBoundingClientRect().height/2?i.insertBefore(s,l.nextSibling):i.insertBefore(s,l),r{this.setupEvenOddClasses()}),this.currentlyReorderingStatus?e.disableReordering():(this.setupEvenOddClasses(),this.hideReorderColumnUnlessReorderingStatus&&(this.reorderDisplayColumn=!0),e.enableReordering())},cancelReorder(){this.hideReorderColumnUnlessReorderingStatus&&(this.reorderDisplayColumn=!1),e.disableReordering()},updateOrderedItems(){let i=document.getElementById(t),s=[];for(let a=1,r;r=i.rows[a];a++)s.push({[l]:r.getAttribute("rowpk"),[this.defaultReorderColumn]:a});e.storeReorder(s)},setupEvenOddClasses(){if(void 0===this.evenNotInOdd.length||0==this.evenNotInOdd.length||void 0===this.oddNotInEven.length||0==this.oddNotInEven.length){let e=document.getElementById(t).getElementsByTagName("tbody")[0],l=[],i=[];void 0!==e.rows[0]&&void 0!==e.rows[1]&&(l=Array.from(e.rows[0].classList),i=Array.from(e.rows[1].classList),this.evenNotInOdd=l.filter(e=>!i.includes(e)),this.oddNotInEven=i.filter(e=>!l.includes(e)),l=[],i=[])}},init(){}}))}); \ No newline at end of file diff --git a/resources/js/partials/filter-date-range.js b/resources/js/partials/filter-date-range.js index f28f42d93..aa630347e 100644 --- a/resources/js/partials/filter-date-range.js +++ b/resources/js/partials/filter-date-range.js @@ -8,7 +8,7 @@ function fpf() { altFormat: filterConfig['altFormat'] ?? "F j, Y", altInput: filterConfig['altInput'] ?? false, allowInput: filterConfig['allowInput'] ?? false, - allowInvalidPreload: true, + allowInvalidPreload: filterConfig['allowInvalidPreload'] ?? true, ariaDateFormat: filterConfig['ariaDateFormat'] ?? "F j, Y", clickOpens: true, dateFormat: filterConfig['dateFormat'] ?? "Y-m-d", @@ -30,15 +30,16 @@ function fpf() { }, onChange: function (selectedDates, dateStr, instance) { if (selectedDates.length > 1) { - var startDate = dateStr.split(' ')[0]; - var endDate = dateStr.split(' ')[2]; + var dates = dateStr.split(' '); + var wireDateArray = {}; window.childElementOpen = false; window.filterPopoverOpen = false; - wireDateArray = { 'minDate': startDate, 'maxDate': endDate }; + wireDateArray = { 'minDate': dates[0], 'maxDate': (typeof dates[2] === "undefined") ? dates[0] : dates[2] }; wire.set('filterComponents.' + filterKey, wireDateArray); } - } + + }, }), setupWire() { if (this.wireValues !== undefined) { diff --git a/resources/js/partials/filter-date-range.min.js b/resources/js/partials/filter-date-range.min.js index 7a7e5b158..e789c0b05 100644 --- a/resources/js/partials/filter-date-range.min.js +++ b/resources/js/partials/filter-date-range.min.js @@ -1 +1 @@ -function a(){Alpine.data('flatpickrFilter',(A,b,c,d,e)=>({wireValues:A.entangle(`filterComponents.${b}`),flatpickrInstance:flatpickr(d,{mode:'range',altFormat:c['altFormat']??'F j, Y',altInput:c['altInput']??!1,allowInput:c['allowInput']??!1,allowInvalidPreload:!0,ariaDateFormat:c['ariaDateFormat']??'F j, Y',clickOpens:!0,dateFormat:c['dateFormat']??'Y-m-d',defaultDate:c['defaultDate']??null,defaultHour:c['defaultHour']??12,defaultMinute:c['defaultMinute']??0,enableTime:c['enableTime']??!1,enableSeconds:c['enableSeconds']??!1,hourIncrement:c['hourIncrement']??1,locale:c['locale']??'en',minDate:c['earliestDate']??null,maxDate:c['latestDate']??null,minuteIncrement:c['minuteIncrement']??5,shorthandCurrentMonth:c['shorthandCurrentMonth']??!1,time_24hr:c['time_24hr']??!1,weekNumbers:c['weekNumbers']??!1,onOpen:function(){window.childElementOpen=!0},onChange:function(_,B,C){if(_.length>1){var D=B.split(' ')[0],E=B.split(' ')[2],f={};window.childElementOpen=window.filterPopoverOpen=!1;f={'minDate':D,'maxDate':E};A.set(`filterComponents.${b}`,f)}}}),setupWire(){if(this.wireValues!==void 0)if(this.wireValues.minDate!==void 0&&this.wireValues.maxDate!==void 0){this.flatpickrInstance.setDate([this.wireValues.minDate,this.wireValues.maxDate])}else this.flatpickrInstance.setDate([]);else this.flatpickrInstance.setDate([])},init(){this.setupWire();this.$watch('wireValues',value=>this.setupWire())}}))}export default a; +function fpf(){Alpine.data("flatpickrFilter",(e,t,a,n,l)=>({wireValues:e.entangle("filterComponents."+t),flatpickrInstance:flatpickr(n,{mode:"range",altFormat:a.altFormat??"F j, Y",altInput:a.altInput??!1,allowInput:a.allowInput??!1,allowInvalidPreload:a.allowInvalidPreload??!0,ariaDateFormat:a.ariaDateFormat??"F j, Y",clickOpens:!0,dateFormat:a.dateFormat??"Y-m-d",defaultDate:a.defaultDate??null,defaultHour:a.defaultHour??12,defaultMinute:a.defaultMinute??0,enableTime:a.enableTime??!1,enableSeconds:a.enableSeconds??!1,hourIncrement:a.hourIncrement??1,locale:a.locale??"en",minDate:a.earliestDate??null,maxDate:a.latestDate??null,minuteIncrement:a.minuteIncrement??5,shorthandCurrentMonth:a.shorthandCurrentMonth??!1,time_24hr:a.time_24hr??!1,weekNumbers:a.weekNumbers??!1,onOpen:function(){window.childElementOpen=!0},onChange:function(a,n,l){if(a.length>1){var i=n.split(" "),r={};window.childElementOpen=!1,window.filterPopoverOpen=!1,r={minDate:i[0],maxDate:void 0===i[2]?i[0]:i[2]},e.set("filterComponents."+t,r)}}}),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())}}))}export default fpf; \ No newline at end of file diff --git a/resources/views/components/includes/actions.blade.php b/resources/views/components/includes/actions.blade.php new file mode 100644 index 000000000..422381ed1 --- /dev/null +++ b/resources/views/components/includes/actions.blade.php @@ -0,0 +1,12 @@ +
merge($this->getActionWrapperAttributes()) + ->class(['flex flex-cols justify-center' => $this->isTailwind && $this->getActionWrapperAttributes()['default-styling'] ?? true]) + ->class(['' => $this->isTailwind && $this->getActionWrapperAttributes()['default-colors'] ?? true]) + ->class(['d-flex flex-cols justify-center' => $this->isBootstrap && $this->getActionWrapperAttributes()['default-styling'] ?? true]) + ->class(['' => $this->isBootstrap && $this->getActionWrapperAttributes()['default-colors'] ?? true]) + ->except(['default-styling','default-colors']) + }} > + @foreach($this->getActions as $action) + {{ $action->render() }} + @endforeach +
\ No newline at end of file diff --git a/resources/views/components/tools/filters/date-range.blade.php b/resources/views/components/tools/filters/date-range.blade.php index bdca66903..4d233b1e4 100644 --- a/resources/views/components/tools/filters/date-range.blade.php +++ b/resources/views/components/tools/filters/date-range.blade.php @@ -14,6 +14,7 @@ type="text" x-ref="dateRangeInput" x-on:click="init" + x-on:change="changedValue($refs.dateRangeInput.value)" value="{{ $filter->getDateString(isset($this->appliedFilters[$filterKey]) ? $this->appliedFilters[$filterKey] : '') }}" wire:key="{{ $filter->generateWireKey($tableName, 'dateRange') }}" id="{{ $tableName }}-filter-dateRange-{{ $filterKey }}" diff --git a/resources/views/datatable.blade.php b/resources/views/datatable.blade.php index 88eadc23a..02cb79d0f 100644 --- a/resources/views/datatable.blade.php +++ b/resources/views/datatable.blade.php @@ -8,6 +8,11 @@
+ @if(method_exists($this,'hasActions') && $this->hasActions()) + + @endif + + @if ($this->hasConfigurableAreaFor('before-tools')) @include($this->getConfigurableAreaFor('before-tools'), $this->getParametersForConfigurableArea('before-tools')) @endif diff --git a/resources/views/includes/actions/button.blade.php b/resources/views/includes/actions/button.blade.php new file mode 100644 index 000000000..0d22c2c91 --- /dev/null +++ b/resources/views/includes/actions/button.blade.php @@ -0,0 +1,24 @@ +merge() + ->class(['justify-center text-center items-center inline-flex rounded-md border shadow-sm px-4 py-2 text-sm font-medium focus:ring focus:ring-opacity-50' => $isTailwind && $attributes['default-styling'] ?? true]) + ->class(['focus:border-indigo-300 focus:ring-indigo-200' => $isTailwind && $attributes['default-colors'] ?? true]) + ->class(['btn btn-sm btn-success' => $isBootstrap && $attributes['default-styling'] ?? true]) + ->class(['' => $isBootstrap && $attributes['default-colors'] ?? true]) + ->except(['default-styling', 'default-colors']) + }} + @if($action->hasWireAction()) + {{ $action->getWireAction() }}="{{ $action->getWireActionParams() }}" + @endif + @if($action->getWireNavigateEnabled()) + wire:navigate + @endif + > + {{ $action->getLabel() }} + + @if($action->hasIcon()) + getIconAttributes() + ->class(["ms-1 ". $action->getIcon() => $isBootstrap]) + ->class(["ml-1 ". $action->getIcon() => $isTailwind]) + ->except('default-styling') + }}> + @endif + \ No newline at end of file diff --git a/src/Traits/HasAllTraits.php b/src/Traits/HasAllTraits.php index d4287dc04..7f048a4a8 100644 --- a/src/Traits/HasAllTraits.php +++ b/src/Traits/HasAllTraits.php @@ -8,6 +8,7 @@ trait HasAllTraits use WithTableHooks; use WithLoadingPlaceholder; use ComponentUtilities, + WithActions, WithData, WithColumns, WithSorting, diff --git a/src/Traits/WithActions.php b/src/Traits/WithActions.php new file mode 100644 index 000000000..31f563a98 --- /dev/null +++ b/src/Traits/WithActions.php @@ -0,0 +1,48 @@ + true, 'default-colors' => true]; + + protected function actions(): array + { + return []; + } + + public function setActionWrapperAttributes(array $actionWrapperAttributes): self + { + $this->actionWrapperAttributes = [...['default-styling' => true, 'default-colors' => true], ...$actionWrapperAttributes]; + + return $this; + } + + #[Computed] + public function getActionWrapperAttributes(): array + { + return [...['default-styling' => true, 'default-colors' => true], ...$this->actionWrapperAttributes]; + } + + #[Computed] + public function hasActions(): bool + { + return (new Collection($this->actions())) + ->filter(fn ($action) => $action instanceof Action)->count() > 0; + } + + #[Computed] + public function getActions(): Collection + { + return (new Collection($this->actions())) + ->filter(fn ($action) => $action instanceof Action) + ->each(function (Action $action, int $key) { + $action->setTheme($this->getTheme()); + }); + + } +} diff --git a/src/Traits/WithSorting.php b/src/Traits/WithSorting.php index 3c0127a31..78c8e9a73 100644 --- a/src/Traits/WithSorting.php +++ b/src/Traits/WithSorting.php @@ -31,7 +31,7 @@ trait WithSorting public string $defaultSortingLabelDesc = 'Z-A'; - protected function queryStringWithSorting(): array + public function queryStringWithSorting(): array { if ($this->queryStringIsEnabled() && $this->sortingIsEnabled()) { return [ diff --git a/src/Views/Action.php b/src/Views/Action.php new file mode 100644 index 000000000..3c6570328 --- /dev/null +++ b/src/Views/Action.php @@ -0,0 +1,44 @@ +label = trim(__($label)); + } + + public static function make(?string $label = null): self + { + return new static($label); + } + + public function render(): null|string|\Illuminate\Support\HtmlString|\Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View + { + $view = view($this->getView()) + ->withAction($this) + ->withIsBootstrap($this->isBootstrap()) + ->withIsTailwind($this->isTailwind()) + ->withAttributes($this->getActionAttributes()); + + return $view; + } +} diff --git a/src/Views/Actions/Action.php b/src/Views/Actions/Action.php new file mode 100644 index 000000000..e30ce0114 --- /dev/null +++ b/src/Views/Actions/Action.php @@ -0,0 +1,13 @@ + true, 'default-colors' => true]; + + public function setActionAttributes(array $actionAttributes): self + { + $this->actionAttributes = [...['default-styling' => true, 'default-colors' => true], ...$actionAttributes]; + + return $this; + } + + public function getActionAttributes(): ComponentAttributeBag + { + $actionAttributes = [...['default-styling' => true, 'default-colors' => true], ...$this->actionAttributes]; + + if (! $this->hasWireAction() && method_exists($this, 'getRoute')) { + $actionAttributes['href'] = $this->getRoute(); + } else { + $actionAttributes['href'] = '#'; + } + + return new ComponentAttributeBag($actionAttributes); + } +} diff --git a/src/Views/Traits/Actions/HasRoute.php b/src/Views/Traits/Actions/HasRoute.php new file mode 100644 index 000000000..5b2696469 --- /dev/null +++ b/src/Views/Traits/Actions/HasRoute.php @@ -0,0 +1,29 @@ +route = $route; + $this->attributes['href'] = $route; + + return $this; + } + + public function setRoute(string $route): self + { + $this->route = $route; + $this->attributes['href'] = $route; + + return $this; + } + + public function getRoute(): string + { + return $this->route; + } +} diff --git a/src/Views/Traits/Core/HasAttributes.php b/src/Views/Traits/Core/HasAttributes.php index 2978d5822..76220d4bc 100644 --- a/src/Views/Traits/Core/HasAttributes.php +++ b/src/Views/Traits/Core/HasAttributes.php @@ -5,7 +5,7 @@ use Closure; use Illuminate\Database\Eloquent\Model; use Illuminate\View\ComponentAttributeBag; -use Rappasoft\LaravelLivewireTables\Views\{Column,Filter}; +use Rappasoft\LaravelLivewireTables\Views\{Action, Column,Filter}; trait HasAttributes { diff --git a/src/Views/Traits/Core/HasIcon.php b/src/Views/Traits/Core/HasIcon.php new file mode 100644 index 000000000..bd80ca841 --- /dev/null +++ b/src/Views/Traits/Core/HasIcon.php @@ -0,0 +1,42 @@ + true]; + + public function setIcon(string $icon): self + { + $this->icon = $icon; + + return $this; + } + + public function hasIcon(): bool + { + return isset($this->icon); + } + + public function getIcon(): string + { + return $this->icon; + } + + public function setIconAttributes(array $iconAttributes): self + { + $this->iconAttributes = [...['default-styling' => true], ...$iconAttributes]; + + return $this; + } + + public function getIconAttributes(): ComponentAttributeBag + { + return new ComponentAttributeBag([...['default-styling' => true], ...$this->iconAttributes]); + } +} diff --git a/src/Views/Traits/Core/HasLabel.php b/src/Views/Traits/Core/HasLabel.php new file mode 100644 index 000000000..cfb2f13c5 --- /dev/null +++ b/src/Views/Traits/Core/HasLabel.php @@ -0,0 +1,13 @@ +label; + } +} diff --git a/src/Views/Traits/Core/HasTheme.php b/src/Views/Traits/Core/HasTheme.php new file mode 100644 index 000000000..65ccfb885 --- /dev/null +++ b/src/Views/Traits/Core/HasTheme.php @@ -0,0 +1,35 @@ +theme = $theme; + + return $this; + } + + public function isTailwind(): bool + { + return $this->theme != 'bootstrap-4' && $this->theme != 'bootstrap-5'; + } + + public function isBootstrap(): bool + { + return $this->theme == 'bootstrap-4' || $this->theme == 'bootstrap-5'; + } + + public function isBootstrap4(): bool + { + return $this->theme == 'bootstrap-4'; + } + + public function isBootstrap5(): bool + { + return $this->theme == 'bootstrap-5'; + } +} diff --git a/src/Views/Traits/Core/HasWireActions.php b/src/Views/Traits/Core/HasWireActions.php new file mode 100644 index 000000000..04d96a050 --- /dev/null +++ b/src/Views/Traits/Core/HasWireActions.php @@ -0,0 +1,67 @@ +shouldWireNavigate = true; + + return $this; + } + + public function getWireNavigateEnabled(): bool + { + return $this->shouldWireNavigate; + } + + public function hasWireAction(): bool + { + return isset($this->wireAction); + } + + public function getWireAction(): string + { + return $this->wireAction; + } + + public function setWireAction(string $wireAction): self + { + $this->wireAction = $wireAction; + + return $this; + } + + public function hasWireActionParams(): bool + { + return isset($this->wireActionParams); + } + + public function getWireActionParams(): string + { + return $this->wireActionParams; + } + + public function setWireActionParams(string $wireActionParams): self + { + $this->wireActionParams = $wireActionParams; + + return $this; + } + + public function setWireActionDispatchParams(string $wireActionParams): self + { + $this->setWireActionParams('$dispatch('.$wireActionParams.')'); + + return $this; + } +} diff --git a/tests/Views/Actions/ActionTest.php b/tests/Views/Actions/ActionTest.php new file mode 100644 index 000000000..9e262803b --- /dev/null +++ b/tests/Views/Actions/ActionTest.php @@ -0,0 +1,310 @@ +setActionAttributes(['class' => 'dark:bg-green-500 dark:text-white dark:border-green-600 dark:hover:border-green-900 dark:hover:bg-green-800', 'default-styling' => true, 'default-colors' => true]) + ->setIcon('fas fa-minus') + ->setIconAttributes(['class' => 'font-sm text-sm']) + ->wireNavigate() + ->route('dashboard2'); + + $this->assertSame('Update Summaries', $action->getLabel()); + } + + public function test_can_get_action_button_icon(): void + { + $action = Action::make('Update Summaries') + ->setActionAttributes([ + 'class' => 'dark:bg-green-500 dark:text-white dark:border-green-600 dark:hover:border-green-900 dark:hover:bg-green-800', + 'default-styling' => true, + 'default-colors' => true, + ]) + ->wireNavigate() + ->route('dashboard2'); + $this->assertFalse($action->hasIcon()); + $action->setIcon('fas fa-minus'); + $this->assertTrue($action->hasIcon()); + $this->assertSame('fas fa-minus', $action->getIcon()); + $this->assertSame('fas fa-minus', $action->icon); + + } + + public function test_can_get_action_button_icon_attributes(): void + { + $action = Action::make('Update Summaries') + ->setActionAttributes([ + 'class' => 'dark:bg-green-500 dark:text-white dark:border-green-600 dark:hover:border-green-900 dark:hover:bg-green-800', + 'default-styling' => true, + 'default-colors' => true, + ]) + ->wireNavigate() + ->route('dashboard2'); + $this->assertFalse($action->hasIcon()); + + $action->setIconAttributes(['class' => 'font-sm text-sm']); + $bag = new \Illuminate\View\ComponentAttributeBag(['default-styling' => true, 'class' => 'font-sm text-sm']); + + $this->assertSame($bag->getAttributes(), $action->getIconAttributes()->getAttributes()); + $this->assertSame(['default-styling' => true, 'class' => 'font-sm text-sm'], $action->iconAttributes); + + } + + public function test_can_get_action_button_route(): void + { + $action = Action::make('Update Summaries') + ->setActionAttributes(['class' => 'dark:bg-green-500 dark:text-white dark:border-green-600 dark:hover:border-green-900 dark:hover:bg-green-800', 'default-styling' => true, 'default-colors' => true]) + ->setIcon('fas fa-minus') + ->setIconAttributes(['class' => 'font-sm text-sm']) + ->wireNavigate(); + $this->assertSame('#', $action->getRoute()); + $this->assertSame('#', $action->route); + $action->route('dashboard2'); + $this->assertSame('dashboard2', $action->route); + + $this->assertSame('dashboard2', $action->getRoute()); + $action->setRoute('dashboard4'); + $this->assertSame('dashboard4', $action->getRoute()); + } + + public function test_can_set_action_button_to_wire_navigate(): void + { + $action = Action::make('Update Summaries') + ->setActionAttributes(['class' => 'dark:bg-green-500 dark:text-white dark:border-green-600 dark:hover:border-green-900 dark:hover:bg-green-800', 'default-styling' => true, 'default-colors' => true]) + ->route('dashboard2'); + $this->assertFalse($action->getWireNavigateEnabled()); + $action->wireNavigate(); + $this->assertTrue($action->getWireNavigateEnabled()); + } + + public function test_can_get_action_button_action_attributes(): void + { + $action = Action::make('Update Summaries') + ->wireNavigate() + ->route('dashboard2'); + $this->assertSame((new ComponentAttributeBag([ + 'default-styling' => true, + 'default-colors' => true, + 'href' => 'dashboard2', + ]))->getAttributes(), $action->getActionAttributes()->getAttributes()); + + $action->setActionAttributes(['class' => 'dark:bg-green-500 dark:text-white dark:border-green-600 dark:hover:border-green-900 dark:hover:bg-green-800', 'default-styling' => true, 'default-colors' => true]); + $this->assertSame((new ComponentAttributeBag([ + 'default-styling' => true, + 'default-colors' => true, + 'class' => 'dark:bg-green-500 dark:text-white dark:border-green-600 dark:hover:border-green-900 dark:hover:bg-green-800', + 'href' => 'dashboard2', + ]))->getAttributes(), $action->getActionAttributes()->getAttributes()); + + $action->setActionAttributes(['class' => 'dark:bg-green-500 dark:text-white dark:border-green-600 dark:hover:border-green-900 dark:hover:bg-green-800', 'default-styling' => true, 'default-colors' => true]); + $this->assertSame((new ComponentAttributeBag([ + 'default-styling' => true, + 'default-colors' => true, + 'class' => 'dark:bg-green-500 dark:text-white dark:border-green-600 dark:hover:border-green-900 dark:hover:bg-green-800', + 'href' => 'dashboard2', + ]))->getAttributes(), $action->getActionAttributes()->getAttributes()); + + $action->setActionAttributes(['class' => 'dark:bg-green-500 dark:text-white dark:border-green-600 dark:hover:border-green-900 dark:hover:bg-green-800', 'default-styling' => true, 'default-colors' => false]); + $this->assertSame((new ComponentAttributeBag([ + 'default-styling' => true, + 'default-colors' => false, + 'class' => 'dark:bg-green-500 dark:text-white dark:border-green-600 dark:hover:border-green-900 dark:hover:bg-green-800', + 'href' => 'dashboard2', + ]))->getAttributes(), $action->getActionAttributes()->getAttributes()); + + $action->setActionAttributes(['class' => 'dark:bg-green-500 dark:text-white dark:border-green-600 dark:hover:border-green-900 dark:hover:bg-green-800', 'default-colors' => false]); + $this->assertSame((new ComponentAttributeBag([ + 'default-styling' => true, + 'default-colors' => false, + 'class' => 'dark:bg-green-500 dark:text-white dark:border-green-600 dark:hover:border-green-900 dark:hover:bg-green-800', + 'href' => 'dashboard2', + ]))->getAttributes(), $action->getActionAttributes()->getAttributes()); + + } + + public function test_can_check_has_wire_action(): void + { + $action = Action::make('Update Summaries') + ->setActionAttributes(['class' => 'dark:bg-green-500 dark:text-white dark:border-green-600 dark:hover:border-green-900 dark:hover:bg-green-800', 'default-styling' => true, 'default-colors' => true]) + ->setIcon('fas fa-minus') + ->setIconAttributes(['class' => 'font-sm text-sm']); + + $this->assertFalse($action->hasWireAction()); + + $action->setWireAction('wire:click') + ->setWireActionParams("\$dispatch('openModal', { component: 'test-modal', arguments: JSON.parse('{\u0022modelID\u0022:\u0022\u0022}') })"); + + $this->assertTrue($action->hasWireAction()); + } + + public function test_can_get_wire_action(): void + { + $action = Action::make('Update Summaries') + ->setActionAttributes(['class' => 'dark:bg-green-500 dark:text-white dark:border-green-600 dark:hover:border-green-900 dark:hover:bg-green-800', 'default-styling' => true, 'default-colors' => true]) + ->setIcon('fas fa-minus') + ->setIconAttributes(['class' => 'font-sm text-sm']) + ->setWireAction('wire:click') + ->setWireActionParams("\$dispatch('openModal', { component: 'test-modal', arguments: JSON.parse('{\u0022modelID\u0022:\u0022\u0022}') })"); + + $this->assertTrue($action->hasWireAction()); + + $this->assertSame('wire:click', $action->getWireAction()); + + } + + public function test_can_get_wire_action_dispatch(): void + { + $action = Action::make('Update Summaries') + ->setActionAttributes(['class' => 'dark:bg-green-500 dark:text-white dark:border-green-600 dark:hover:border-green-900 dark:hover:bg-green-800', 'default-styling' => true, 'default-colors' => true]) + ->setIcon('fas fa-minus') + ->setIconAttributes(['class' => 'font-sm text-sm']) + ->setWireAction('wire:click') + ->setWireActionDispatchParams("'openModal', { component: 'test-modal', arguments: JSON.parse('{\u0022modelID\u0022:\u0022\u0022}') }"); + $this->assertTrue($action->hasWireAction()); + + $this->assertSame('wire:click', $action->getWireAction()); + $this->assertSame("\$dispatch('openModal', { component: 'test-modal', arguments: JSON.parse('{\u0022modelID\u0022:\u0022\u0022}') })", $action->getWireActionParams()); + + } + + public function test_can_get_wire_action_params(): void + { + $action = Action::make('Update Summaries') + ->setActionAttributes(['class' => 'dark:bg-green-500 dark:text-white dark:border-green-600 dark:hover:border-green-900 dark:hover:bg-green-800', 'default-styling' => true, 'default-colors' => true]) + ->setIcon('fas fa-minus') + ->setIconAttributes(['class' => 'font-sm text-sm']) + ->setWireAction('wire:click') + ->setWireActionParams('testactionparams'); + + $this->assertTrue($action->hasWireActionParams()); + $this->assertTrue($action->hasWireAction()); + + $this->assertSame('testactionparams', $action->getWireActionParams()); + $this->assertSame('wire:click', $action->getWireAction()); + + } + + public function test_can_set_action_wrapper_attributes(): void + { + $petsTable = (new class extends PetsTable + { + use \Rappasoft\LaravelLivewireTables\Traits\WithActions; + + public function configure(): void + { + $this->setPrimaryKey('id'); + } + + public function bulkActions(): array + { + return ['exportBulk' => 'exportBulk']; + } + + public function exportBulk($items) + { + return $items; + } + }); + $this->assertSame(['default-styling' => true, 'default-colors' => true], $petsTable->getActionWrapperAttributes()); + $petsTable->setActionWrapperAttributes(['default-styling' => false, 'class' => 'bg-blue-500']); + $this->assertSame([ + 'default-styling' => false, + 'default-colors' => true, + 'class' => 'bg-blue-500', + ], $petsTable->getActionWrapperAttributes()); + + $petsTable->setActionWrapperAttributes(['default-colors' => false, 'class' => 'bg-red-500']); + $this->assertSame([ + 'default-styling' => true, + 'default-colors' => false, + 'class' => 'bg-red-500', + ], $petsTable->getActionWrapperAttributes()); + + } + + public function test_can_check_that_route_is_appended_to_attributes(): void + { + $action = Action::make('Update Summaries') + ->setActionAttributes(['class' => 'dark:bg-green-500 dark:text-white dark:border-green-600 dark:hover:border-green-900 dark:hover:bg-green-800', 'default-styling' => true, 'default-colors' => true]) + ->route('dashboard22'); + $this->assertSame((new ComponentAttributeBag([ + 'default-styling' => true, + 'default-colors' => true, + 'class' => 'dark:bg-green-500 dark:text-white dark:border-green-600 dark:hover:border-green-900 dark:hover:bg-green-800', + 'href' => 'dashboard22', + ]))->getAttributes(), $action->getActionAttributes()->getAttributes()); + } + + public function test_can_check_that_route_is_not_appended_to_attributes_with_wireaction(): void + { + $action = Action::make('Update Summaries') + ->setActionAttributes(['class' => 'dark:bg-green-500 dark:text-white dark:border-green-600 dark:hover:border-green-900 dark:hover:bg-green-800', 'default-styling' => true, 'default-colors' => true]) + ->route('dashboard22') + ->setWireAction('wire:click') + ->setWireActionParams('testactionparams'); + $this->assertSame((new ComponentAttributeBag([ + 'default-styling' => true, + 'default-colors' => true, + 'class' => 'dark:bg-green-500 dark:text-white dark:border-green-600 dark:hover:border-green-900 dark:hover:bg-green-800', + 'href' => '#', + ]))->getAttributes(), $action->getActionAttributes()->getAttributes()); + } + + public function test_can_check_has_actions(): void + { + $petsTable = (new class extends PetsTable + { + use \Rappasoft\LaravelLivewireTables\Traits\WithActions; + + public function actions(): array + { + return [ + \Rappasoft\LaravelLivewireTables\Views\Actions\Action::make('Test Edit 1') + ->setRoute('dashboard24'), + ]; + } + + public function configure(): void + { + $this->setPrimaryKey('id'); + } + + public function bulkActions(): array + { + return ['exportBulk' => 'exportBulk']; + } + + public function exportBulk($items) + { + return $items; + } + }); + $this->assertTrue($petsTable->hasActions()); + + $this->assertSame(1, $petsTable->getActions()->count()); + + } + + public function test_action_renders_correctly(): void + { + $action = Action::make('Update Summaries') + ->setActionAttributes(['class' => 'dark:bg-green-500 dark:text-white dark:border-green-600 dark:hover:border-green-900 dark:hover:bg-green-800', + 'default-styling' => true, + 'default-colors' => true] + ) + ->route('dashboard22'); + + $this->assertStringContainsString('render()); + } +}