From 831be33fce679c781f34515a621112244508f6c8 Mon Sep 17 00:00:00 2001 From: pxpm Date: Fri, 11 Oct 2024 13:41:04 +0100 Subject: [PATCH 01/52] wip --- src/Backpack.php | 16 +++ src/BackpackManager.php | 52 +++++++ src/BackpackServiceProvider.php | 23 ++- .../Contracts/CrudControllerContract.php | 7 + src/app/Http/Controllers/CrudController.php | 26 ++-- src/app/Library/CrudPanel/CrudPanel.php | 9 +- src/app/Library/Datatable/Datatable.php | 20 +++ .../views/crud/columns/crud_column.blade.php | 5 + .../views/crud/datatable/datatable.blade.php | 134 ++++++++++++++++++ .../datatables_logic.blade.php | 6 +- src/resources/views/crud/list.blade.php | 17 --- 11 files changed, 279 insertions(+), 36 deletions(-) create mode 100644 src/Backpack.php create mode 100644 src/BackpackManager.php create mode 100644 src/app/Http/Controllers/Contracts/CrudControllerContract.php create mode 100644 src/app/Library/Datatable/Datatable.php create mode 100644 src/resources/views/crud/columns/crud_column.blade.php create mode 100644 src/resources/views/crud/datatable/datatable.blade.php rename src/resources/views/crud/{inc => datatable}/datatables_logic.blade.php (99%) diff --git a/src/Backpack.php b/src/Backpack.php new file mode 100644 index 0000000000..8033e33157 --- /dev/null +++ b/src/Backpack.php @@ -0,0 +1,16 @@ +cruds[$controllerClass])) { + return $this->cruds[$controllerClass]; + } + + $instance = new CrudPanel(); + + $this->cruds[$controllerClass] = $instance; + + return $this->cruds[$controllerClass]; + } + + public function crudFromController(string $controller): CrudPanel + { + $controller = new $controller(); + + $crud = $this->crud($controller); + + // TODO: it's hardcoded, but we should figure out a way to make it dynamic if needed. + $crud->setOperation('list'); + + $primaryControllerRequest = $this->cruds[array_key_first($this->cruds)]->getRequest(); + + $controller->initializeCrud($primaryControllerRequest, 'list'); + + return $this->cruds[get_class($controller)]; + } + + public function hasCrudController(string $controller): bool + { + return isset($this->cruds[$controller]); + } + + public function getControllerCrud(string $controller): CrudPanel + { + return $this->cruds[$controller]; + } +} diff --git a/src/BackpackServiceProvider.php b/src/BackpackServiceProvider.php index 816a044e77..339678d26e 100644 --- a/src/BackpackServiceProvider.php +++ b/src/BackpackServiceProvider.php @@ -7,6 +7,7 @@ use Backpack\CRUD\app\Http\Middleware\ThrottlePasswordRecovery; use Backpack\CRUD\app\Library\CrudPanel\CrudPanel; use Backpack\CRUD\app\Library\Database\DatabaseSchema; +use Backpack\CRUD\app\Library\Datatable\Datatable; use Backpack\CRUD\app\Library\Uploaders\Support\UploadersRepository; use Illuminate\Contracts\Debug\ExceptionHandler; use Illuminate\Routing\Router; @@ -63,6 +64,7 @@ public function boot(Router $router) $this->sendUsageStats(); Basset::addViewPath(realpath(__DIR__.'/resources/views')); + Blade::component('datatable', Datatable::class); } /** @@ -84,7 +86,24 @@ public function register() // Bind the CrudPanel object to Laravel's service container $this->app->scoped('crud', function ($app) { - return new CrudPanel(); + // loop the stack trace to find the CrudControllerContract that called this method + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + $controller = null; + foreach ($trace as $step) { + if (isset($step['class']) && is_a($step['class'], app\Http\Controllers\Contracts\CrudControllerContract::class, true)) { + $controller = $step['class']; + break; + } + } + if (! $controller) { + throw new \Exception('Could not identify the crud controller method. You sure you are calling this from a CrudController?'); + } + + return Backpack::getControllerCrud($controller); + }); + + $this->app->scoped('backpack-manager', function ($app) { + return new BackpackManager(); }); $this->app->scoped('DatabaseSchema', function ($app) { @@ -333,7 +352,7 @@ public function loadHelpers() */ public function provides() { - return ['crud', 'widgets', 'BackpackViewNamespaces', 'DatabaseSchema', 'UploadersRepository']; + return ['widgets', 'BackpackViewNamespaces', 'DatabaseSchema', 'UploadersRepository']; } private function registerBackpackErrorViews() diff --git a/src/app/Http/Controllers/Contracts/CrudControllerContract.php b/src/app/Http/Controllers/Contracts/CrudControllerContract.php new file mode 100644 index 0000000000..8789d57277 --- /dev/null +++ b/src/app/Http/Controllers/Contracts/CrudControllerContract.php @@ -0,0 +1,7 @@ +crud) { - return; - } - // --------------------------- // Create the CrudPanel object // --------------------------- @@ -38,9 +36,9 @@ public function __construct() // It's done inside a middleware closure in order to have // the complete request inside the CrudPanel object. $this->middleware(function ($request, $next) { - $this->crud = app('crud'); - - $this->crud->setRequest($request); + if (! Backpack::hasCrudController(get_class($this))) { + $this->initializeCrud($request); + } LifecycleHook::trigger('crud:before_setup_defaults', [$this]); $this->setupDefaults(); @@ -56,6 +54,14 @@ public function __construct() }); } + public function initializeCrud($request, $operation = null) + { + $this->crud = Backpack::crud($this)->setRequest($request); + $this->setupDefaults(); + $this->setup(); + $this->setupConfigurationForCurrentOperation($operation); + } + /** * Allow developers to set their configuration options for a CrudPanel. */ @@ -105,9 +111,9 @@ protected function setupDefaults() * Allow developers to insert default settings by creating a method * that looks like setupOperationNameOperation (aka setupXxxOperation). */ - protected function setupConfigurationForCurrentOperation() + protected function setupConfigurationForCurrentOperation(?string $operation = null) { - $operationName = $this->crud->getCurrentOperation(); + $operationName = $operation ?? $this->crud->getCurrentOperation(); if (! $operationName) { return; } diff --git a/src/app/Library/CrudPanel/CrudPanel.php b/src/app/Library/CrudPanel/CrudPanel.php index 6b8d030c95..632fd536e8 100644 --- a/src/app/Library/CrudPanel/CrudPanel.php +++ b/src/app/Library/CrudPanel/CrudPanel.php @@ -68,11 +68,6 @@ class CrudPanel public function __construct() { - $this->setRequest(); - - if ($this->getCurrentOperation()) { - $this->setOperation($this->getCurrentOperation()); - } } /** @@ -80,9 +75,11 @@ public function __construct() * * @param \Illuminate\Http\Request $request */ - public function setRequest($request = null) + public function setRequest($request = null): self { $this->request = $request ?? \Request::instance(); + + return $this; } /** diff --git a/src/app/Library/Datatable/Datatable.php b/src/app/Library/Datatable/Datatable.php new file mode 100644 index 0000000000..ce5c2d090f --- /dev/null +++ b/src/app/Library/Datatable/Datatable.php @@ -0,0 +1,20 @@ + $this->crud, + ]); + } +} diff --git a/src/resources/views/crud/columns/crud_column.blade.php b/src/resources/views/crud/columns/crud_column.blade.php new file mode 100644 index 0000000000..d97d163b57 --- /dev/null +++ b/src/resources/views/crud/columns/crud_column.blade.php @@ -0,0 +1,5 @@ +@php +$columnCrud = \Backpack\CRUD\Backpack::crudFromController($column['controller']); +@endphp + + \ No newline at end of file diff --git a/src/resources/views/crud/datatable/datatable.blade.php b/src/resources/views/crud/datatable/datatable.blade.php new file mode 100644 index 0000000000..b5307cd9a7 --- /dev/null +++ b/src/resources/views/crud/datatable/datatable.blade.php @@ -0,0 +1,134 @@ +
+
+ @if ( $crud->buttons()->where('stack', 'top')->count() || $crud->exportButtons()) +
+ + @include('crud::inc.button_stack', ['stack' => 'top']) + +
+ @endif +
+ @if($crud->getOperationSetting('searchableTable')) +
+
+
+ + + + +
+
+
+ @endif +
+ + {{-- Backpack List Filters --}} + @if ($crud->filtersEnabled()) + @include('crud::inc.filters_navbar') + @endif + +
+ + + + {{-- Table columns --}} + @foreach ($crud->columns() as $column) + @php + $exportOnlyColumn = $column['exportOnlyColumn'] ?? false; + $visibleInTable = $column['visibleInTable'] ?? ($exportOnlyColumn ? false : true); + $visibleInModal = $column['visibleInModal'] ?? ($exportOnlyColumn ? false : true); + $visibleInExport = $column['visibleInExport'] ?? true; + $forceExport = $column['forceExport'] ?? (isset($column['exportOnlyColumn']) ? true : false); + @endphp + + @endforeach + + @if ( $crud->buttons()->where('stack', 'line')->count() ) + + @endif + + + + + + + {{-- Table columns --}} + @foreach ($crud->columns() as $column) + + @endforeach + + @if ( $crud->buttons()->where('stack', 'line')->count() ) + + @endif + + +
if developer forced column to be in the table with 'visibleInTable => true' + data-visible => regular visibility of the column + data-can-be-visible-in-table => prevents the column to be visible into the table (export-only) + data-visible-in-modal => if column appears on responsive modal + data-visible-in-export => if this column is exportable + data-force-export => force export even if columns are hidden + --}} + + data-visible="{{ $exportOnlyColumn ? 'false' : var_export($visibleInTable) }}" + data-visible-in-table="{{ var_export($visibleInTable) }}" + data-can-be-visible-in-table="{{ $exportOnlyColumn ? 'false' : 'true' }}" + data-visible-in-modal="{{ var_export($visibleInModal) }}" + data-visible-in-export="{{ $exportOnlyColumn ? 'true' : ($visibleInExport ? 'true' : 'false') }}" + data-force-export="{{ var_export($forceExport) }}" + > + {{-- Bulk checkbox --}} + @if($loop->first && $crud->getOperationSetting('bulkActions')) + {!! View::make('crud::columns.inc.bulk_actions_checkbox')->render() !!} + @endif + {!! $column['label'] !!} + {{ trans('backpack::crud.actions') }}
+ {{-- Bulk checkbox --}} + @if($loop->first && $crud->getOperationSetting('bulkActions')) + {!! View::make('crud::columns.inc.bulk_actions_checkbox')->render() !!} + @endif + {!! $column['label'] !!} + {{ trans('backpack::crud.actions') }}
+
+ + @if ( $crud->buttons()->where('stack', 'bottom')->count() ) +
+ @include('crud::inc.button_stack', ['stack' => 'bottom']) + +
+ @endif + + @section('after_styles') + {{-- DATA TABLES --}} + @basset('https://cdn.datatables.net/1.13.1/css/dataTables.bootstrap5.min.css') + @basset('https://cdn.datatables.net/fixedheader/3.3.1/css/fixedHeader.dataTables.min.css') + @basset('https://cdn.datatables.net/responsive/2.4.0/css/responsive.dataTables.min.css') + + {{-- CRUD LIST CONTENT - crud_list_styles stack --}} + @stack('crud_list_styles') +@endsection + +@section('after_scripts') + @include('crud::datatable.datatables_logic') + + {{-- CRUD LIST CONTENT - crud_list_scripts stack --}} + @stack('crud_list_scripts') +@endsection \ No newline at end of file diff --git a/src/resources/views/crud/inc/datatables_logic.blade.php b/src/resources/views/crud/datatable/datatables_logic.blade.php similarity index 99% rename from src/resources/views/crud/inc/datatables_logic.blade.php rename to src/resources/views/crud/datatable/datatables_logic.blade.php index 0dcad1bcce..5beccbab74 100644 --- a/src/resources/views/crud/inc/datatables_logic.blade.php +++ b/src/resources/views/crud/datatable/datatables_logic.blade.php @@ -102,7 +102,8 @@ window.location.href = persistentUrl; } @endif - + var updatesUrl = false; + console.log('updatesUrl', updatesUrl); window.crud = { exportButtons: JSON.parse('{!! json_encode($crud->get('list.export_buttons')) !!}'), functionsToRunOnDataTablesDrawEvent: [], @@ -125,6 +126,9 @@ functionsToRunOnDataTablesDrawEvent: [], fn.apply(window, args); }, updateUrl : function (url) { + if(updatesUrl === false) { + return; + } let urlStart = "{{ url($crud->getOperationSetting("datatablesUrl")) }}"; let urlEnd = url.replace(urlStart, ''); urlEnd = urlEnd.replace('/search', ''); diff --git a/src/resources/views/crud/list.blade.php b/src/resources/views/crud/list.blade.php index c0d3333dec..fba81bf89c 100644 --- a/src/resources/views/crud/list.blade.php +++ b/src/resources/views/crud/list.blade.php @@ -148,20 +148,3 @@ class="{{ backpack_theme_config('classes.table') ?? 'table table-striped table-h @endsection - -@section('after_styles') - {{-- DATA TABLES --}} - @basset('https://cdn.datatables.net/1.13.1/css/dataTables.bootstrap5.min.css') - @basset('https://cdn.datatables.net/fixedheader/3.3.1/css/fixedHeader.dataTables.min.css') - @basset('https://cdn.datatables.net/responsive/2.4.0/css/responsive.dataTables.min.css') - - {{-- CRUD LIST CONTENT - crud_list_styles stack --}} - @stack('crud_list_styles') -@endsection - -@section('after_scripts') - @include('crud::inc.datatables_logic') - - {{-- CRUD LIST CONTENT - crud_list_scripts stack --}} - @stack('crud_list_scripts') -@endsection From 3a61e540ffd50eb32343c6a87161ec6242335efc Mon Sep 17 00:00:00 2001 From: pxpm Date: Fri, 11 Oct 2024 15:24:48 +0100 Subject: [PATCH 02/52] wip --- src/BackpackServiceProvider.php | 2 +- src/app/Http/Controllers/CrudController.php | 14 ++++++++++++-- src/app/Library/CrudPanel/CrudButton.php | 8 +++++--- .../views/crud/inc/button_stack.blade.php | 2 +- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/BackpackServiceProvider.php b/src/BackpackServiceProvider.php index 339678d26e..b900464495 100644 --- a/src/BackpackServiceProvider.php +++ b/src/BackpackServiceProvider.php @@ -85,7 +85,7 @@ public function register() $this->registerBackpackErrorViews(); // Bind the CrudPanel object to Laravel's service container - $this->app->scoped('crud', function ($app) { + $this->app->bind('crud', function ($app) { // loop the stack trace to find the CrudControllerContract that called this method $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); $controller = null; diff --git a/src/app/Http/Controllers/CrudController.php b/src/app/Http/Controllers/CrudController.php index 78d05cdc9e..69d8e99188 100644 --- a/src/app/Http/Controllers/CrudController.php +++ b/src/app/Http/Controllers/CrudController.php @@ -21,7 +21,7 @@ class CrudController extends Controller implements CrudControllerContract { use DispatchesJobs, ValidatesRequests; - public $crud; + //public $crud; public $data = []; @@ -56,7 +56,7 @@ public function __construct() public function initializeCrud($request, $operation = null) { - $this->crud = Backpack::crud($this)->setRequest($request); + Backpack::crud($this)->setRequest($request); $this->setupDefaults(); $this->setup(); $this->setupConfigurationForCurrentOperation($operation); @@ -143,4 +143,14 @@ protected function setupConfigurationForCurrentOperation(?string $operation = nu LifecycleHook::trigger($operationName.':after_setup', [$this]); } + + public function __get($name) + { + if ($name == 'crud') { + return Backpack::getControllerCrud(get_class($this)); + } + + return $this->{$name}; + } + } diff --git a/src/app/Library/CrudPanel/CrudButton.php b/src/app/Library/CrudPanel/CrudButton.php index e34bd0ca09..9b2907828e 100644 --- a/src/app/Library/CrudPanel/CrudButton.php +++ b/src/app/Library/CrudPanel/CrudButton.php @@ -2,6 +2,8 @@ namespace Backpack\CRUD\app\Library\CrudPanel; +use Backpack\CRUD\app\Http\Controllers\CrudController; +use Backpack\CRUD\Backpack; use Backpack\CRUD\ViewNamespaces; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Support\Traits\Conditionable; @@ -297,10 +299,10 @@ public function section($stack) * @param object|null $entry The eloquent Model for the current entry or null if no current entry. * @return \Illuminate\Contracts\View\View */ - public function getHtml($entry = null) + public function getHtml($entry = null, $crud = null) { $button = $this; - $crud = $this->crud(); + $crud = $crud ?? $this->crud(); if ($this->type == 'model_function') { if (is_null($entry)) { @@ -434,7 +436,7 @@ public function collection() /** * Access the global CrudPanel object. * - * @return \Backpack\CRUD\app\Library\CrudPanel\CrudPanel + * @return CrudPanel */ public function crud() { diff --git a/src/resources/views/crud/inc/button_stack.blade.php b/src/resources/views/crud/inc/button_stack.blade.php index c1fb4f4734..8397057ae1 100644 --- a/src/resources/views/crud/inc/button_stack.blade.php +++ b/src/resources/views/crud/inc/button_stack.blade.php @@ -1,5 +1,5 @@ @if ($crud->buttons()->where('stack', $stack)->count()) @foreach ($crud->buttons()->where('stack', $stack) as $button) - {!! $button->getHtml($entry ?? null) !!} + {!! $button->getHtml($entry ?? null, $crud) !!} @endforeach @endif From f8fec3f10b3ada4623005101457e856cbb5c395d Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Fri, 11 Oct 2024 14:25:06 +0000 Subject: [PATCH 03/52] Apply fixes from StyleCI [ci skip] [skip ci] --- src/app/Http/Controllers/CrudController.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/Http/Controllers/CrudController.php b/src/app/Http/Controllers/CrudController.php index 69d8e99188..1848ec34a2 100644 --- a/src/app/Http/Controllers/CrudController.php +++ b/src/app/Http/Controllers/CrudController.php @@ -152,5 +152,4 @@ public function __get($name) return $this->{$name}; } - } From c222441710818b3d1eae926235d03af27277a50b Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Wed, 12 Mar 2025 10:01:44 +0000 Subject: [PATCH 04/52] Apply fixes from StyleCI [ci skip] [skip ci] --- src/app/Http/Controllers/CrudController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/Http/Controllers/CrudController.php b/src/app/Http/Controllers/CrudController.php index 1848ec34a2..947b7d5389 100644 --- a/src/app/Http/Controllers/CrudController.php +++ b/src/app/Http/Controllers/CrudController.php @@ -4,8 +4,8 @@ use Backpack\CRUD\app\Http\Controllers\Contracts\CrudControllerContract; use Backpack\CRUD\app\Library\Attributes\DeprecatedIgnoreOnRuntime; -use Backpack\CRUD\Backpack; use Backpack\CRUD\app\Library\CrudPanel\Hooks\Facades\LifecycleHook; +use Backpack\CRUD\Backpack; use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Routing\Controller; From 5e5969050fb96e01f8e9116b49e0f2a67505bb5e Mon Sep 17 00:00:00 2001 From: pxpm Date: Wed, 19 Mar 2025 10:35:05 +0000 Subject: [PATCH 05/52] wip --- src/BackpackManager.php | 11 +- src/app/Http/Controllers/CrudController.php | 25 +- src/app/Library/CrudPanel/CrudPanel.php | 7 + .../views/crud/datatable/datatable.blade.php | 206 ++-- .../crud/datatable/datatables_logic.blade.php | 942 +++++++++--------- src/resources/views/crud/list.blade.php | 118 +-- 6 files changed, 613 insertions(+), 696 deletions(-) diff --git a/src/BackpackManager.php b/src/BackpackManager.php index 0b76477633..24400def0f 100644 --- a/src/BackpackManager.php +++ b/src/BackpackManager.php @@ -9,10 +9,14 @@ final class BackpackManager { private array $cruds; + private $requestController = null; + public function crud(CrudControllerContract $controller): CrudPanel { $controllerClass = get_class($controller); + $this->requestController = $controllerClass; + if (isset($this->cruds[$controllerClass])) { return $this->cruds[$controllerClass]; } @@ -30,14 +34,13 @@ public function crudFromController(string $controller): CrudPanel $crud = $this->crud($controller); - // TODO: it's hardcoded, but we should figure out a way to make it dynamic if needed. $crud->setOperation('list'); $primaryControllerRequest = $this->cruds[array_key_first($this->cruds)]->getRequest(); $controller->initializeCrud($primaryControllerRequest, 'list'); - return $this->cruds[get_class($controller)]; + return $crud; } public function hasCrudController(string $controller): bool @@ -47,6 +50,10 @@ public function hasCrudController(string $controller): bool public function getControllerCrud(string $controller): CrudPanel { + if (! isset($this->cruds[$controller])) { + return $this->crudFromController($this->requestController ?? $controller); + } + return $this->cruds[$controller]; } } diff --git a/src/app/Http/Controllers/CrudController.php b/src/app/Http/Controllers/CrudController.php index 947b7d5389..cb27d29b9c 100644 --- a/src/app/Http/Controllers/CrudController.php +++ b/src/app/Http/Controllers/CrudController.php @@ -25,6 +25,8 @@ class CrudController extends Controller implements CrudControllerContract public $data = []; + public $initialized = false; + public function __construct() { // --------------------------- @@ -38,8 +40,9 @@ public function __construct() $this->middleware(function ($request, $next) { if (! Backpack::hasCrudController(get_class($this))) { $this->initializeCrud($request); - } + return $next($request); + } LifecycleHook::trigger('crud:before_setup_defaults', [$this]); $this->setupDefaults(); LifecycleHook::trigger('crud:after_setup_defaults', [$this]); @@ -50,16 +53,30 @@ public function __construct() $this->setupConfigurationForCurrentOperation(); + $this->initialized = true; + return $next($request); }); } public function initializeCrud($request, $operation = null) { - Backpack::crud($this)->setRequest($request); + $crudController = Backpack::crud($this)->setRequest($request); + if ($crudController->isInitialized()) { + return; + } + + LifecycleHook::trigger('crud:before_setup_defaults', [$crudController]); $this->setupDefaults(); + LifecycleHook::trigger('crud:after_setup_defaults', [$crudController]); + + LifecycleHook::trigger('crud:before_setup', [$crudController]); $this->setup(); - $this->setupConfigurationForCurrentOperation($operation); + + LifecycleHook::trigger('crud:after_setup', [$crudController]); + $this->setupConfigurationForCurrentOperation(); + + $this->crud->initialized = true; } /** @@ -146,7 +163,7 @@ protected function setupConfigurationForCurrentOperation(?string $operation = nu public function __get($name) { - if ($name == 'crud') { + if ($name === 'crud') { return Backpack::getControllerCrud(get_class($this)); } diff --git a/src/app/Library/CrudPanel/CrudPanel.php b/src/app/Library/CrudPanel/CrudPanel.php index 632fd536e8..334d055da4 100644 --- a/src/app/Library/CrudPanel/CrudPanel.php +++ b/src/app/Library/CrudPanel/CrudPanel.php @@ -64,12 +64,19 @@ class CrudPanel protected $request; + public $initialized = false; + // The following methods are used in CrudController or your EntityCrudController to manipulate the variables above. public function __construct() { } + public function isInitialized() + { + return $this->initialized; + } + /** * Set the request instance for this CRUD. * diff --git a/src/resources/views/crud/datatable/datatable.blade.php b/src/resources/views/crud/datatable/datatable.blade.php index b5307cd9a7..dbc0c79930 100644 --- a/src/resources/views/crud/datatable/datatable.blade.php +++ b/src/resources/views/crud/datatable/datatable.blade.php @@ -1,120 +1,120 @@
-
- @if ( $crud->buttons()->where('stack', 'top')->count() || $crud->exportButtons()) -
+
+ @if ( $crud->buttons()->where('stack', 'top')->count() || $crud->exportButtons()) +
- @include('crud::inc.button_stack', ['stack' => 'top']) + @include('crud::inc.button_stack', ['stack' => 'top']) -
- @endif -
- @if($crud->getOperationSetting('searchableTable')) -
-
-
- - - - -
-
@endif
- - {{-- Backpack List Filters --}} - @if ($crud->filtersEnabled()) - @include('crud::inc.filters_navbar') + @if($crud->getOperationSetting('searchableTable')) +
+
+
+ + + + +
+
+
@endif +
-
- - - - {{-- Table columns --}} - @foreach ($crud->columns() as $column) - @php - $exportOnlyColumn = $column['exportOnlyColumn'] ?? false; - $visibleInTable = $column['visibleInTable'] ?? ($exportOnlyColumn ? false : true); - $visibleInModal = $column['visibleInModal'] ?? ($exportOnlyColumn ? false : true); - $visibleInExport = $column['visibleInExport'] ?? true; - $forceExport = $column['forceExport'] ?? (isset($column['exportOnlyColumn']) ? true : false); - @endphp - + @endif + + +
if developer forced column to be in the table with 'visibleInTable => true' - data-visible => regular visibility of the column - data-can-be-visible-in-table => prevents the column to be visible into the table (export-only) - data-visible-in-modal => if column appears on responsive modal - data-visible-in-export => if this column is exportable - data-force-export => force export even if columns are hidden - --}} +{{-- Backpack List Filters --}} +@if ($crud->filtersEnabled()) + @include('crud::inc.filters_navbar') +@endif - data-visible="{{ $exportOnlyColumn ? 'false' : var_export($visibleInTable) }}" - data-visible-in-table="{{ var_export($visibleInTable) }}" - data-can-be-visible-in-table="{{ $exportOnlyColumn ? 'false' : 'true' }}" - data-visible-in-modal="{{ var_export($visibleInModal) }}" - data-visible-in-export="{{ $exportOnlyColumn ? 'true' : ($visibleInExport ? 'true' : 'false') }}" - data-force-export="{{ var_export($forceExport) }}" - > - {{-- Bulk checkbox --}} - @if($loop->first && $crud->getOperationSetting('bulkActions')) +
+ + + + {{-- Table columns --}} + @foreach ($crud->columns() as $column) + @php + $exportOnlyColumn = $column['exportOnlyColumn'] ?? false; + $visibleInTable = $column['visibleInTable'] ?? ($exportOnlyColumn ? false : true); + $visibleInModal = $column['visibleInModal'] ?? ($exportOnlyColumn ? false : true); + $visibleInExport = $column['visibleInExport'] ?? true; + $forceExport = $column['forceExport'] ?? (isset($column['exportOnlyColumn']) ? true : false); + @endphp + - @endforeach + @endif + {!! $column['label'] !!} + + @endforeach - @if ( $crud->buttons()->where('stack', 'line')->count() ) - - @endif - - - - - - - {{-- Table columns --}} - @foreach ($crud->columns() as $column) - + @endif + + + + + + + {{-- Table columns --}} + @foreach ($crud->columns() as $column) + - @endforeach + @endif + {!! $column['label'] !!} + + @endforeach - @if ( $crud->buttons()->where('stack', 'line')->count() ) - - @endif - - -
if developer forced column to be in the table with 'visibleInTable => true' + data-visible => regular visibility of the column + data-can-be-visible-in-table => prevents the column to be visible into the table (export-only) + data-visible-in-modal => if column appears on responsive modal + data-visible-in-export => if this column is exportable + data-force-export => force export even if columns are hidden + --}} + + data-visible="{{ $exportOnlyColumn ? 'false' : var_export($visibleInTable) }}" + data-visible-in-table="{{ var_export($visibleInTable) }}" + data-can-be-visible-in-table="{{ $exportOnlyColumn ? 'false' : 'true' }}" + data-visible-in-modal="{{ var_export($visibleInModal) }}" + data-visible-in-export="{{ $exportOnlyColumn ? 'true' : ($visibleInExport ? 'true' : 'false') }}" + data-force-export="{{ var_export($forceExport) }}" + > + {{-- Bulk checkbox --}} + @if($loop->first && $crud->getOperationSetting('bulkActions')) {!! View::make('crud::columns.inc.bulk_actions_checkbox')->render() !!} - @endif - {!! $column['label'] !!} - {{ trans('backpack::crud.actions') }}
- {{-- Bulk checkbox --}} - @if($loop->first && $crud->getOperationSetting('bulkActions')) + @if ( $crud->buttons()->where('stack', 'line')->count() ) + {{ trans('backpack::crud.actions') }}
+ {{-- Bulk checkbox --}} + @if($loop->first && $crud->getOperationSetting('bulkActions')) {!! View::make('crud::columns.inc.bulk_actions_checkbox')->render() !!} - @endif - {!! $column['label'] !!} - {{ trans('backpack::crud.actions') }}
-
+ @if ( $crud->buttons()->where('stack', 'line')->count() ) +
{{ trans('backpack::crud.actions') }}
+
- @if ( $crud->buttons()->where('stack', 'bottom')->count() ) -
- @include('crud::inc.button_stack', ['stack' => 'bottom']) - -
- @endif +@if ( $crud->buttons()->where('stack', 'bottom')->count() ) +
+ @include('crud::inc.button_stack', ['stack' => 'bottom']) + +
+@endif @section('after_styles') {{-- DATA TABLES --}} diff --git a/src/resources/views/crud/datatable/datatables_logic.blade.php b/src/resources/views/crud/datatable/datatables_logic.blade.php index 5beccbab74..b061ee9c8d 100644 --- a/src/resources/views/crud/datatable/datatables_logic.blade.php +++ b/src/resources/views/crud/datatable/datatables_logic.blade.php @@ -1,518 +1,520 @@ - @php - // as it is possible that we can be redirected with persistent table we save the alerts in a variable - // and flush them from session, so we will get them later from localStorage. - $backpack_alerts = \Alert::getMessages(); - \Alert::flush(); - @endphp - - {{-- DATA TABLES SCRIPT --}} - @basset('https://cdn.datatables.net/1.13.1/js/jquery.dataTables.min.js') - @basset('https://cdn.datatables.net/1.13.1/js/dataTables.bootstrap5.min.js') - @basset('https://cdn.datatables.net/responsive/2.4.0/js/dataTables.responsive.min.js') - @basset('https://cdn.datatables.net/responsive/2.4.0/css/responsive.dataTables.min.css') - @basset('https://cdn.datatables.net/fixedheader/3.3.1/js/dataTables.fixedHeader.min.js') - @basset('https://cdn.datatables.net/fixedheader/3.3.1/css/fixedHeader.dataTables.min.css') - - @basset(base_path('vendor/backpack/crud/src/resources/assets/img/spinner.svg'), false) - - - @include('crud::inc.export_buttons') - - +@include('crud::inc.export_buttons') + + +} + - @include('crud::inc.details_row_logic') +@include('crud::inc.details_row_logic') diff --git a/src/resources/views/crud/list.blade.php b/src/resources/views/crud/list.blade.php index fba81bf89c..469ea428c3 100644 --- a/src/resources/views/crud/list.blade.php +++ b/src/resources/views/crud/list.blade.php @@ -25,123 +25,7 @@ {{-- THE ACTUAL CONTENT --}}
-
-
- @if ( $crud->buttons()->where('stack', 'top')->count() || $crud->exportButtons()) -
- - @include('crud::inc.button_stack', ['stack' => 'top']) - -
- @endif -
- @if($crud->getOperationSetting('searchableTable')) -
-
-
- - - - -
-
-
- @endif -
- - {{-- Backpack List Filters --}} - @if ($crud->filtersEnabled()) - @include('crud::inc.filters_navbar') - @endif - -
- - - - {{-- Table columns --}} - @foreach ($crud->columns() as $column) - @php - $exportOnlyColumn = $column['exportOnlyColumn'] ?? false; - $visibleInTable = $column['visibleInTable'] ?? ($exportOnlyColumn ? false : true); - $visibleInModal = $column['visibleInModal'] ?? ($exportOnlyColumn ? false : true); - $visibleInExport = $column['visibleInExport'] ?? true; - $forceExport = $column['forceExport'] ?? (isset($column['exportOnlyColumn']) ? true : false); - @endphp - - @endforeach - - @if ( $crud->buttons()->where('stack', 'line')->count() ) - - @endif - - - - - - - {{-- Table columns --}} - @foreach ($crud->columns() as $column) - - @endforeach - - @if ( $crud->buttons()->where('stack', 'line')->count() ) - - @endif - - -
if developer forced column to be in the table with 'visibleInTable => true' - data-visible => regular visibility of the column - data-can-be-visible-in-table => prevents the column to be visible into the table (export-only) - data-visible-in-modal => if column appears on responsive modal - data-visible-in-export => if this column is exportable - data-force-export => force export even if columns are hidden - --}} - - data-visible="{{ $exportOnlyColumn ? 'false' : var_export($visibleInTable) }}" - data-visible-in-table="{{ var_export($visibleInTable) }}" - data-can-be-visible-in-table="{{ $exportOnlyColumn ? 'false' : 'true' }}" - data-visible-in-modal="{{ var_export($visibleInModal) }}" - data-visible-in-export="{{ $exportOnlyColumn ? 'true' : ($visibleInExport ? 'true' : 'false') }}" - data-force-export="{{ var_export($forceExport) }}" - > - {{-- Bulk checkbox --}} - @if($loop->first && $crud->getOperationSetting('bulkActions')) - {!! View::make('crud::columns.inc.bulk_actions_checkbox')->render() !!} - @endif - {!! $column['label'] !!} - {{ trans('backpack::crud.actions') }}
- {{-- Bulk checkbox --}} - @if($loop->first && $crud->getOperationSetting('bulkActions')) - {!! View::make('crud::columns.inc.bulk_actions_checkbox')->render() !!} - @endif - {!! $column['label'] !!} - {{ trans('backpack::crud.actions') }}
-
- - @if ( $crud->buttons()->where('stack', 'bottom')->count() ) -
- @include('crud::inc.button_stack', ['stack' => 'bottom']) - -
- @endif +
From 792da513c74ae06c2b0c7124efb711a38a019ca2 Mon Sep 17 00:00:00 2001 From: pxpm Date: Wed, 19 Mar 2025 15:53:03 +0000 Subject: [PATCH 06/52] wip --- src/app/Http/Controllers/CrudController.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/app/Http/Controllers/CrudController.php b/src/app/Http/Controllers/CrudController.php index cb27d29b9c..703138cd5d 100644 --- a/src/app/Http/Controllers/CrudController.php +++ b/src/app/Http/Controllers/CrudController.php @@ -61,19 +61,21 @@ public function __construct() public function initializeCrud($request, $operation = null) { - $crudController = Backpack::crud($this)->setRequest($request); - if ($crudController->isInitialized()) { + $crudPanel = Backpack::crud($this)->setRequest($request); + if ($crudPanel->isInitialized()) { + dd($crudPanel); + return; } - - LifecycleHook::trigger('crud:before_setup_defaults', [$crudController]); + //dd($crudPanel); + LifecycleHook::trigger('crud:before_setup_defaults', [$this]); $this->setupDefaults(); - LifecycleHook::trigger('crud:after_setup_defaults', [$crudController]); + LifecycleHook::trigger('crud:after_setup_defaults', [$this]); - LifecycleHook::trigger('crud:before_setup', [$crudController]); + LifecycleHook::trigger('crud:before_setup', [$this]); $this->setup(); - LifecycleHook::trigger('crud:after_setup', [$crudController]); + LifecycleHook::trigger('crud:after_setup', [$this]); $this->setupConfigurationForCurrentOperation(); $this->crud->initialized = true; From dd89d3d921f564959a48a7066b3bf6098630fb50 Mon Sep 17 00:00:00 2001 From: pxpm Date: Mon, 24 Mar 2025 13:50:15 +0000 Subject: [PATCH 07/52] wip --- src/BackpackManager.php | 19 +++++- src/BackpackServiceProvider.php | 60 +++++++++++++++---- src/app/Http/Controllers/CrudController.php | 43 ++++++------- src/app/Library/CrudPanel/CrudField.php | 2 +- src/app/Library/CrudPanel/CrudPanel.php | 7 ++- src/app/Library/CrudPanel/CrudPanelFacade.php | 4 +- src/app/Library/CrudPanel/Traits/AutoSet.php | 2 +- .../Traits/FieldsProtectedMethods.php | 2 +- src/app/Library/CrudPanel/Traits/Query.php | 1 - tests/Unit/CrudPanel/CrudPanelFieldsTest.php | 2 +- tests/Unit/CrudPanel/CrudPanelTest.php | 5 +- tests/config/CrudPanel/BaseCrudPanel.php | 10 ++-- tests/config/Http/CrudControllerTest.php | 23 ++++--- tests/config/Models/TestModel.php | 2 +- 14 files changed, 118 insertions(+), 64 deletions(-) diff --git a/src/BackpackManager.php b/src/BackpackManager.php index 24400def0f..f73435853b 100644 --- a/src/BackpackManager.php +++ b/src/BackpackManager.php @@ -7,10 +7,22 @@ final class BackpackManager { - private array $cruds; + private array $cruds = []; + + private CrudPanel $crudPanelInstance; private $requestController = null; + public function __construct() + { + $this->crudPanelInstance = new CrudPanel(); + } + + public function getCrudPanelInstance(): CrudPanel + { + return $this->crudPanelInstance; + } + public function crud(CrudControllerContract $controller): CrudPanel { $controllerClass = get_class($controller); @@ -56,4 +68,9 @@ public function getControllerCrud(string $controller): CrudPanel return $this->cruds[$controller]; } + + public function getCruds(): array + { + return $this->cruds; + } } diff --git a/src/BackpackServiceProvider.php b/src/BackpackServiceProvider.php index b900464495..e2e10696ef 100644 --- a/src/BackpackServiceProvider.php +++ b/src/BackpackServiceProvider.php @@ -86,20 +86,56 @@ public function register() // Bind the CrudPanel object to Laravel's service container $this->app->bind('crud', function ($app) { - // loop the stack trace to find the CrudControllerContract that called this method + // Prioritize explicit controller context $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); $controller = null; + foreach ($trace as $step) { - if (isset($step['class']) && is_a($step['class'], app\Http\Controllers\Contracts\CrudControllerContract::class, true)) { + if (isset($step['class']) && + is_a($step['class'], app\Http\Controllers\Contracts\CrudControllerContract::class, true)) { $controller = $step['class']; break; } } - if (! $controller) { - throw new \Exception('Could not identify the crud controller method. You sure you are calling this from a CrudController?'); + + if ($controller) { + $crudPanel = Backpack::getControllerCrud($controller); + + return $crudPanel; + } + // Fallback to a more robust initialization + $cruds = Backpack::getCruds(); + + if (! empty($cruds)) { + + $crudPanel = reset($cruds); + + // Ensure upload events are registered + return $crudPanel; + } + + return Backpack::getCrudPanelInstance(); + }); + + // Bind a special CrudPanel object for the CrudPanelFacade + $this->app->bind('backpack.crud', function ($app) { + // Similar logic to 'crud' binding + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + $controller = null; + foreach ($trace as $step) { + if (isset($step['class']) && + is_a($step['class'], app\Http\Controllers\Contracts\CrudControllerContract::class, true)) { + $controller = $step['class']; + break; + } } - return Backpack::getControllerCrud($controller); + if ($controller) { + $crudPanel = Backpack::getControllerCrud($controller); + + return $crudPanel; + } + return Backpack::getCrudPanelInstance(); }); $this->app->scoped('backpack-manager', function ($app) { @@ -200,7 +236,7 @@ public function publishFiles() /** * Define the routes for the application. * - * @param \Illuminate\Routing\Router $router + * @param Router $router * @return void */ public function setupRoutes(Router $router) @@ -219,7 +255,7 @@ public function setupRoutes(Router $router) /** * Load custom routes file. * - * @param \Illuminate\Routing\Router $router + * @param Router $router * @return void */ public function setupCustomRoutes(Router $router) @@ -277,7 +313,7 @@ public function loadConfigs() // add the root disk to filesystem configuration app()->config['filesystems.disks.'.config('backpack.base.root_disk_name')] = [ 'driver' => 'local', - 'root' => base_path(), + 'root' => base_path(), ]; /* @@ -296,7 +332,7 @@ public function loadConfigs() [ 'backpack' => [ 'driver' => 'eloquent', - 'model' => config('backpack.base.user_model_fqn'), + 'model' => config('backpack.base.user_model_fqn'), ], ]; @@ -314,8 +350,8 @@ public function loadConfigs() [ 'backpack' => [ 'provider' => 'backpack', - 'table' => $backpackPasswordBrokerTable, - 'expire' => config('backpack.base.password_recovery_token_expiration', 60), + 'table' => $backpackPasswordBrokerTable, + 'expire' => config('backpack.base.password_recovery_token_expiration', 60), 'throttle' => config('backpack.base.password_recovery_throttle_notifications'), ], ]; @@ -324,7 +360,7 @@ public function loadConfigs() app()->config['auth.guards'] = app()->config['auth.guards'] + [ 'backpack' => [ - 'driver' => 'session', + 'driver' => 'session', 'provider' => 'backpack', ], ]; diff --git a/src/app/Http/Controllers/CrudController.php b/src/app/Http/Controllers/CrudController.php index 703138cd5d..3481e563f6 100644 --- a/src/app/Http/Controllers/CrudController.php +++ b/src/app/Http/Controllers/CrudController.php @@ -4,6 +4,7 @@ use Backpack\CRUD\app\Http\Controllers\Contracts\CrudControllerContract; use Backpack\CRUD\app\Library\Attributes\DeprecatedIgnoreOnRuntime; +use Backpack\CRUD\app\Library\CrudPanel\CrudPanel; use Backpack\CRUD\app\Library\CrudPanel\Hooks\Facades\LifecycleHook; use Backpack\CRUD\Backpack; use Illuminate\Foundation\Bus\DispatchesJobs; @@ -14,7 +15,7 @@ /** * Class CrudController. * - * @property-read \Backpack\CRUD\app\Library\CrudPanel\CrudPanel $crud + * @property-read CrudPanel $crud * @property array $data */ class CrudController extends Controller implements CrudControllerContract @@ -43,42 +44,45 @@ public function __construct() return $next($request); } - LifecycleHook::trigger('crud:before_setup_defaults', [$this]); - $this->setupDefaults(); - LifecycleHook::trigger('crud:after_setup_defaults', [$this]); - LifecycleHook::trigger('crud:before_setup', [$this]); - $this->setup(); - LifecycleHook::trigger('crud:after_setup', [$this]); - - $this->setupConfigurationForCurrentOperation(); + $this->triggerControllerHooks(); $this->initialized = true; + + Backpack::crud($this)->setRequest($request); return $next($request); }); } - public function initializeCrud($request, $operation = null) + public function initializeCrud($request, $crudPanel = null, $operation = null): CrudPanel { - $crudPanel = Backpack::crud($this)->setRequest($request); - if ($crudPanel->isInitialized()) { - dd($crudPanel); + $crudPanel ??= Backpack::crud($this); - return; + if ($crudPanel->isInitialized()) { + $crudPanel->setRequest($request); + return $crudPanel; } - //dd($crudPanel); + + $crudPanel->initialized = true; + $crudPanel->setRequest($request); + + $this->triggerControllerHooks(); + + return $crudPanel; + } + + private function triggerControllerHooks() + { LifecycleHook::trigger('crud:before_setup_defaults', [$this]); $this->setupDefaults(); LifecycleHook::trigger('crud:after_setup_defaults', [$this]); LifecycleHook::trigger('crud:before_setup', [$this]); $this->setup(); - LifecycleHook::trigger('crud:after_setup', [$this]); - $this->setupConfigurationForCurrentOperation(); - $this->crud->initialized = true; + $this->setupConfigurationForCurrentOperation(); } /** @@ -116,7 +120,6 @@ public function setupRoutes($segment, $routeName, $controller) protected function setupDefaults() { preg_match_all('/(?<=^|;)setup([^;]+?)Defaults(;|$)/', implode(';', get_class_methods($this)), $matches); - if (count($matches[1])) { foreach ($matches[1] as $methodName) { $this->{'setup'.$methodName.'Defaults'}(); @@ -136,7 +139,6 @@ protected function setupConfigurationForCurrentOperation(?string $operation = nu if (! $operationName) { return; } - $setupClassName = 'setup'.Str::studly($operationName).'Operation'; /* @@ -152,7 +154,6 @@ protected function setupConfigurationForCurrentOperation(?string $operation = nu LifecycleHook::trigger($operationName.':before_setup', [$this]); $this->crud->applyConfigurationFromSettings($operationName); - /* * THEN, run the corresponding setupXxxOperation if it exists. */ diff --git a/src/app/Library/CrudPanel/CrudField.php b/src/app/Library/CrudPanel/CrudField.php index 2877b79f7e..d866b3b584 100644 --- a/src/app/Library/CrudPanel/CrudField.php +++ b/src/app/Library/CrudPanel/CrudField.php @@ -77,7 +77,7 @@ public function __construct($nameOrDefinitionArray) public function crud() { - return app()->make('crud'); + return app('backpack.crud'); } /** diff --git a/src/app/Library/CrudPanel/CrudPanel.php b/src/app/Library/CrudPanel/CrudPanel.php index 334d055da4..6b20d4cac9 100644 --- a/src/app/Library/CrudPanel/CrudPanel.php +++ b/src/app/Library/CrudPanel/CrudPanel.php @@ -35,7 +35,9 @@ use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\Relation; +use Illuminate\Http\Request; use Illuminate\Support\Arr; +use Illuminate\Support\Facades\Route; class CrudPanel { @@ -80,7 +82,7 @@ public function isInitialized() /** * Set the request instance for this CRUD. * - * @param \Illuminate\Http\Request $request + * @param Request $request */ public function setRequest($request = null): self { @@ -92,10 +94,11 @@ public function setRequest($request = null): self /** * Get the request instance for this CRUD. * - * @return \Illuminate\Http\Request + * @return Request */ public function getRequest() { + return $this->request; } diff --git a/src/app/Library/CrudPanel/CrudPanelFacade.php b/src/app/Library/CrudPanel/CrudPanelFacade.php index b24159d9cc..6211dab169 100644 --- a/src/app/Library/CrudPanel/CrudPanelFacade.php +++ b/src/app/Library/CrudPanel/CrudPanelFacade.php @@ -7,7 +7,7 @@ /** * This object allows developers to use CRUD::addField() instead of $this->crud->addField(), * by providing a Facade that leads to the CrudPanel object. That object is stored in Laravel's - * service container as 'crud'. + * service container as 'backpack.crud'. */ /** * @codeCoverageIgnore @@ -36,6 +36,6 @@ class CrudPanelFacade extends Facade */ protected static function getFacadeAccessor() { - return 'crud'; + return 'backpack.crud'; } } diff --git a/src/app/Library/CrudPanel/Traits/AutoSet.php b/src/app/Library/CrudPanel/Traits/AutoSet.php index 1944278ff6..bf3471164b 100644 --- a/src/app/Library/CrudPanel/Traits/AutoSet.php +++ b/src/app/Library/CrudPanel/Traits/AutoSet.php @@ -30,7 +30,7 @@ public function setFromDb($setFields = true, $setColumns = true) ]); } - if ($setColumns && ! in_array($field, $this->model->getHidden()) && ! isset($this->columns()[$field])) { + if ($setColumns && ! in_array($field, $this->getModel()->getHidden()) && ! isset($this->columns()[$field])) { $this->addColumn([ 'name' => $field, 'label' => $this->makeLabel($field), diff --git a/src/app/Library/CrudPanel/Traits/FieldsProtectedMethods.php b/src/app/Library/CrudPanel/Traits/FieldsProtectedMethods.php index 0c458f9e09..0d55ac030b 100644 --- a/src/app/Library/CrudPanel/Traits/FieldsProtectedMethods.php +++ b/src/app/Library/CrudPanel/Traits/FieldsProtectedMethods.php @@ -138,7 +138,7 @@ protected function makeSureFieldHasName($field) */ protected function makeSureFieldHasEntity($field) { - $model = isset($field['baseModel']) ? (new $field['baseModel']) : $this->model; + $model = isset($field['baseModel']) ? (new $field['baseModel']) : $this->getModel(); if (isset($field['entity'])) { return $field; diff --git a/src/app/Library/CrudPanel/Traits/Query.php b/src/app/Library/CrudPanel/Traits/Query.php index a14d3817c5..4210e9a13d 100644 --- a/src/app/Library/CrudPanel/Traits/Query.php +++ b/src/app/Library/CrudPanel/Traits/Query.php @@ -277,7 +277,6 @@ private function getCountFromQuery(Builder $query) } // re-set the previous query bindings - //dump($crudQuery->getColumns(), get_class($crudQuery), get_class($subQuery)); foreach ($crudQuery->getRawBindings() as $type => $binding) { $subQuery->setBindings($binding, $type); } diff --git a/tests/Unit/CrudPanel/CrudPanelFieldsTest.php b/tests/Unit/CrudPanel/CrudPanelFieldsTest.php index 43107a785e..ecd2748694 100644 --- a/tests/Unit/CrudPanel/CrudPanelFieldsTest.php +++ b/tests/Unit/CrudPanel/CrudPanelFieldsTest.php @@ -658,7 +658,7 @@ public function testItAbortsOnUnexpectedEntity() } catch (\Throwable $e) { } $this->assertEquals( - new \Symfony\Component\HttpKernel\Exception\HttpException(500, 'Looks like field doesNotExist is not properly defined. The doesNotExist() relationship doesn\'t seem to exist on the Backpack\CRUD\Tests\Config\Models\TestModel model.', null, ['developer-error-exception']), + new \Symfony\Component\HttpKernel\Exception\HttpException(500, 'Looks like field doesNotExist is not properly defined. The doesNotExist() relationship doesn\'t seem to exist on the Backpack\CRUD\Tests\config\Models\TestModel model.', null, ['developer-error-exception']), $e ); } diff --git a/tests/Unit/CrudPanel/CrudPanelTest.php b/tests/Unit/CrudPanel/CrudPanelTest.php index a467380ebc..b827d4745d 100644 --- a/tests/Unit/CrudPanel/CrudPanelTest.php +++ b/tests/Unit/CrudPanel/CrudPanelTest.php @@ -14,8 +14,7 @@ class CrudPanelTest extends BaseCrudPanel public function testSetModelFromModelClass() { $this->crudPanel->setModel(TestModel::class); - - $this->assertEquals($this->model, $this->crudPanel->model); + $this->assertEquals($this->model, get_class($this->crudPanel->model)); $this->assertInstanceOf(TestModel::class, $this->crudPanel->model); $this->assertInstanceOf(Builder::class, $this->crudPanel->query); } @@ -26,7 +25,7 @@ public function testSetModelFromModelClassName() $this->crudPanel->setModel($modelClassName); - $this->assertEquals($this->model, $this->crudPanel->model); + $this->assertEquals($this->model, get_class($this->crudPanel->model)); $this->assertInstanceOf($modelClassName, $this->crudPanel->model); $this->assertInstanceOf(Builder::class, $this->crudPanel->query); } diff --git a/tests/config/CrudPanel/BaseCrudPanel.php b/tests/config/CrudPanel/BaseCrudPanel.php index 2368ba6dcd..5b7adc7995 100644 --- a/tests/config/CrudPanel/BaseCrudPanel.php +++ b/tests/config/CrudPanel/BaseCrudPanel.php @@ -2,7 +2,9 @@ namespace Backpack\CRUD\Tests\Config\CrudPanel; +use Backpack\CRUD\app\Http\Controllers\Contracts\CrudControllerContract; use Backpack\CRUD\app\Library\CrudPanel\CrudPanel; +use Backpack\CRUD\Backpack; use Backpack\CRUD\Tests\BaseTestClass; use Backpack\CRUD\Tests\config\Models\TestModel; @@ -24,12 +26,10 @@ protected function setUp(): void { parent::setUp(); - $this->app->singleton('crud', function ($app) { - return new CrudPanel($app); - }); - $this->crudPanel = app('crud'); + $this->crudPanel = Backpack::getCrudPanelInstance(); $this->crudPanel->setModel(TestModel::class); - $this->model = $this->crudPanel->getModel(); + $this->crudPanel->setRequest(); + $this->model = TestModel::class; } /** diff --git a/tests/config/Http/CrudControllerTest.php b/tests/config/Http/CrudControllerTest.php index 825c8a6633..e140757b1c 100644 --- a/tests/config/Http/CrudControllerTest.php +++ b/tests/config/Http/CrudControllerTest.php @@ -3,6 +3,7 @@ namespace Backpack\CRUD\Tests\Unit\Http; use Backpack\CRUD\app\Library\CrudPanel\CrudPanel; +use Backpack\CRUD\Backpack; use Backpack\CRUD\Tests\BaseTestClass; /** @@ -19,15 +20,12 @@ class CrudControllerTest extends BaseTestClass * @param \Illuminate\Foundation\Application $app * @return void */ - protected function getEnvironmentSetUp($app) + protected function defineEnvironment($app) { - parent::getEnvironmentSetUp($app); + parent::defineEnvironment($app); - $app->singleton('crud', function ($app) { - return new CrudPanel($app); - }); - - $this->crudPanel = app('crud'); + $this->crudPanel = Backpack::getCrudPanelInstance(); + $this->crudPanel->setRequest(); } public function testSetRouteName() @@ -48,21 +46,22 @@ public function testCrudRequestUpdatesOnEachRequest() { // create a first request $firstRequest = request()->create('admin/users/1/edit', 'GET'); + app()->handle($firstRequest); $firstRequest = app()->request; - + // see if the first global request has been passed to the CRUD object - $this->assertSame($this->crudPanel->getRequest(), $firstRequest); + $this->assertSame(app('crud')->getRequest(), $firstRequest); // create a second request $secondRequest = request()->create('admin/users/1', 'PUT', ['name' => 'foo']); app()->handle($secondRequest); $secondRequest = app()->request; - + // see if the second global request has been passed to the CRUD object - $this->assertSame($this->crudPanel->getRequest(), $secondRequest); + $this->assertSame(app('crud')->getRequest(), $secondRequest); // the CRUD object's request should no longer hold the first request, but the second one - $this->assertNotSame($this->crudPanel->getRequest(), $firstRequest); + $this->assertNotSame(app('crud')->getRequest(), $firstRequest); } } diff --git a/tests/config/Models/TestModel.php b/tests/config/Models/TestModel.php index d4e32f111d..293c4e106e 100644 --- a/tests/config/Models/TestModel.php +++ b/tests/config/Models/TestModel.php @@ -1,6 +1,6 @@ Date: Mon, 24 Mar 2025 13:50:50 +0000 Subject: [PATCH 08/52] Apply fixes from StyleCI [ci skip] [skip ci] --- src/BackpackServiceProvider.php | 12 ++++++------ src/ThemeServiceProvider.php | 2 +- .../Http/Controllers/Auth/VerifyEmailController.php | 2 +- src/app/Http/Controllers/CrudController.php | 3 ++- src/app/Library/CrudPanel/CrudPanel.php | 1 - .../Uploaders/Support/RegisterUploadEvents.php | 2 +- .../Support/Traits/HandleRepeatableUploads.php | 4 ++-- .../Uploaders/Support/UploadersRepository.php | 2 +- .../Library/Validation/Rules/BackpackCustomRule.php | 4 ++-- tests/config/CrudPanel/BaseCrudPanel.php | 1 - tests/config/Http/CrudControllerTest.php | 7 +++---- 11 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/BackpackServiceProvider.php b/src/BackpackServiceProvider.php index e2e10696ef..3c69f0e501 100644 --- a/src/BackpackServiceProvider.php +++ b/src/BackpackServiceProvider.php @@ -107,7 +107,6 @@ public function register() $cruds = Backpack::getCruds(); if (! empty($cruds)) { - $crudPanel = reset($cruds); // Ensure upload events are registered @@ -135,6 +134,7 @@ public function register() return $crudPanel; } + return Backpack::getCrudPanelInstance(); }); @@ -313,7 +313,7 @@ public function loadConfigs() // add the root disk to filesystem configuration app()->config['filesystems.disks.'.config('backpack.base.root_disk_name')] = [ 'driver' => 'local', - 'root' => base_path(), + 'root' => base_path(), ]; /* @@ -332,7 +332,7 @@ public function loadConfigs() [ 'backpack' => [ 'driver' => 'eloquent', - 'model' => config('backpack.base.user_model_fqn'), + 'model' => config('backpack.base.user_model_fqn'), ], ]; @@ -350,8 +350,8 @@ public function loadConfigs() [ 'backpack' => [ 'provider' => 'backpack', - 'table' => $backpackPasswordBrokerTable, - 'expire' => config('backpack.base.password_recovery_token_expiration', 60), + 'table' => $backpackPasswordBrokerTable, + 'expire' => config('backpack.base.password_recovery_token_expiration', 60), 'throttle' => config('backpack.base.password_recovery_throttle_notifications'), ], ]; @@ -360,7 +360,7 @@ public function loadConfigs() app()->config['auth.guards'] = app()->config['auth.guards'] + [ 'backpack' => [ - 'driver' => 'session', + 'driver' => 'session', 'provider' => 'backpack', ], ]; diff --git a/src/ThemeServiceProvider.php b/src/ThemeServiceProvider.php index 107580bb1e..cd1185f9ed 100644 --- a/src/ThemeServiceProvider.php +++ b/src/ThemeServiceProvider.php @@ -14,7 +14,7 @@ class ThemeServiceProvider extends ServiceProvider protected string $packageName = 'theme-name'; protected array $commands = []; protected bool $theme = true; - protected null|string $componentsNamespace = null; + protected ?string $componentsNamespace = null; /** * ------------------------- diff --git a/src/app/Http/Controllers/Auth/VerifyEmailController.php b/src/app/Http/Controllers/Auth/VerifyEmailController.php index 6beb5bc451..20ceb735fc 100644 --- a/src/app/Http/Controllers/Auth/VerifyEmailController.php +++ b/src/app/Http/Controllers/Auth/VerifyEmailController.php @@ -11,7 +11,7 @@ class VerifyEmailController extends Controller { - public null|string $redirectTo = null; + public ?string $redirectTo = null; /** * Create a new controller instance. diff --git a/src/app/Http/Controllers/CrudController.php b/src/app/Http/Controllers/CrudController.php index 3481e563f6..8c5afc806a 100644 --- a/src/app/Http/Controllers/CrudController.php +++ b/src/app/Http/Controllers/CrudController.php @@ -48,7 +48,7 @@ public function __construct() $this->triggerControllerHooks(); $this->initialized = true; - + Backpack::crud($this)->setRequest($request); return $next($request); @@ -61,6 +61,7 @@ public function initializeCrud($request, $crudPanel = null, $operation = null): if ($crudPanel->isInitialized()) { $crudPanel->setRequest($request); + return $crudPanel; } diff --git a/src/app/Library/CrudPanel/CrudPanel.php b/src/app/Library/CrudPanel/CrudPanel.php index 6b20d4cac9..55e624ddc5 100644 --- a/src/app/Library/CrudPanel/CrudPanel.php +++ b/src/app/Library/CrudPanel/CrudPanel.php @@ -98,7 +98,6 @@ public function setRequest($request = null): self */ public function getRequest() { - return $this->request; } diff --git a/src/app/Library/Uploaders/Support/RegisterUploadEvents.php b/src/app/Library/Uploaders/Support/RegisterUploadEvents.php index 3c0d7a72e1..3fedbc7175 100644 --- a/src/app/Library/Uploaders/Support/RegisterUploadEvents.php +++ b/src/app/Library/Uploaders/Support/RegisterUploadEvents.php @@ -35,7 +35,7 @@ public static function handle(CrudField|CrudColumn $crudObject, array $uploaderC /******************************* * Private methods - implementation *******************************/ - private function registerEvents(array|null $subfield = [], ?bool $registerModelEvents = true): void + private function registerEvents(?array $subfield = [], ?bool $registerModelEvents = true): void { if (! empty($subfield)) { $this->registerSubfieldEvent($subfield, $registerModelEvents); diff --git a/src/app/Library/Uploaders/Support/Traits/HandleRepeatableUploads.php b/src/app/Library/Uploaders/Support/Traits/HandleRepeatableUploads.php index 858a519be1..84af1b95b7 100644 --- a/src/app/Library/Uploaders/Support/Traits/HandleRepeatableUploads.php +++ b/src/app/Library/Uploaders/Support/Traits/HandleRepeatableUploads.php @@ -18,7 +18,7 @@ trait HandleRepeatableUploads { public bool $handleRepeatableFiles = false; - public null|string $repeatableContainerName = null; + public ?string $repeatableContainerName = null; /******************************* * Setters - fluently configure the uploader @@ -35,7 +35,7 @@ public function repeats(string $repeatableContainerName): self /******************************* * Getters *******************************/ - public function getRepeatableContainerName(): null|string + public function getRepeatableContainerName(): ?string { return $this->repeatableContainerName; } diff --git a/src/app/Library/Uploaders/Support/UploadersRepository.php b/src/app/Library/Uploaders/Support/UploadersRepository.php index a2da49ea7e..0bbd438324 100644 --- a/src/app/Library/Uploaders/Support/UploadersRepository.php +++ b/src/app/Library/Uploaders/Support/UploadersRepository.php @@ -200,7 +200,7 @@ public function getFieldUploaderInstance(string $requestInputName): UploaderInte /** * Get the upload field macro type for the given object. */ - private function getUploadCrudObjectMacroType(array $crudObject): string|null + private function getUploadCrudObjectMacroType(array $crudObject): ?string { $uploadersGroups = $this->getUploadersGroupsNames(); diff --git a/src/app/Library/Validation/Rules/BackpackCustomRule.php b/src/app/Library/Validation/Rules/BackpackCustomRule.php index 3ba76f0678..2807a69c8b 100644 --- a/src/app/Library/Validation/Rules/BackpackCustomRule.php +++ b/src/app/Library/Validation/Rules/BackpackCustomRule.php @@ -166,7 +166,7 @@ protected function validateOnSubmit(string $attribute, mixed $value): array return $this->validateRules($attribute, $value); } - protected function validateFieldAndFile(string $attribute, null|array $data = null, array|null $customRules = null): array + protected function validateFieldAndFile(string $attribute, ?array $data = null, ?array $customRules = null): array { $fieldErrors = $this->validateFieldRules($attribute, $data, $customRules); @@ -178,7 +178,7 @@ protected function validateFieldAndFile(string $attribute, null|array $data = nu /** * Implementation. */ - public function validateFieldRules(string $attribute, null|array|string|UploadedFile $data = null, array|null $customRules = null): array + public function validateFieldRules(string $attribute, null|array|string|UploadedFile $data = null, ?array $customRules = null): array { $data = $data ?? $this->data; $validationRuleAttribute = $this->getValidationAttributeString($attribute); diff --git a/tests/config/CrudPanel/BaseCrudPanel.php b/tests/config/CrudPanel/BaseCrudPanel.php index 5b7adc7995..772e892d4a 100644 --- a/tests/config/CrudPanel/BaseCrudPanel.php +++ b/tests/config/CrudPanel/BaseCrudPanel.php @@ -2,7 +2,6 @@ namespace Backpack\CRUD\Tests\Config\CrudPanel; -use Backpack\CRUD\app\Http\Controllers\Contracts\CrudControllerContract; use Backpack\CRUD\app\Library\CrudPanel\CrudPanel; use Backpack\CRUD\Backpack; use Backpack\CRUD\Tests\BaseTestClass; diff --git a/tests/config/Http/CrudControllerTest.php b/tests/config/Http/CrudControllerTest.php index e140757b1c..f819660018 100644 --- a/tests/config/Http/CrudControllerTest.php +++ b/tests/config/Http/CrudControllerTest.php @@ -2,7 +2,6 @@ namespace Backpack\CRUD\Tests\Unit\Http; -use Backpack\CRUD\app\Library\CrudPanel\CrudPanel; use Backpack\CRUD\Backpack; use Backpack\CRUD\Tests\BaseTestClass; @@ -46,10 +45,10 @@ public function testCrudRequestUpdatesOnEachRequest() { // create a first request $firstRequest = request()->create('admin/users/1/edit', 'GET'); - + app()->handle($firstRequest); $firstRequest = app()->request; - + // see if the first global request has been passed to the CRUD object $this->assertSame(app('crud')->getRequest(), $firstRequest); @@ -57,7 +56,7 @@ public function testCrudRequestUpdatesOnEachRequest() $secondRequest = request()->create('admin/users/1', 'PUT', ['name' => 'foo']); app()->handle($secondRequest); $secondRequest = app()->request; - + // see if the second global request has been passed to the CRUD object $this->assertSame(app('crud')->getRequest(), $secondRequest); From f9b3117a4d5687e06b31e1c13001dc10f9009e53 Mon Sep 17 00:00:00 2001 From: pxpm Date: Thu, 27 Mar 2025 10:13:14 +0000 Subject: [PATCH 09/52] wip --- src/BackpackManager.php | 2 +- src/app/Library/Datatable/Datatable.php | 5 ++++- src/resources/views/crud/columns/crud_column.blade.php | 6 +----- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/BackpackManager.php b/src/BackpackManager.php index f73435853b..77c39613b4 100644 --- a/src/BackpackManager.php +++ b/src/BackpackManager.php @@ -50,7 +50,7 @@ public function crudFromController(string $controller): CrudPanel $primaryControllerRequest = $this->cruds[array_key_first($this->cruds)]->getRequest(); - $controller->initializeCrud($primaryControllerRequest, 'list'); + $controller->initializeCrud($primaryControllerRequest, $crud, 'list'); return $crud; } diff --git a/src/app/Library/Datatable/Datatable.php b/src/app/Library/Datatable/Datatable.php index ce5c2d090f..3fd3bc1a9c 100644 --- a/src/app/Library/Datatable/Datatable.php +++ b/src/app/Library/Datatable/Datatable.php @@ -7,8 +7,11 @@ class Datatable extends Component { - public function __construct(private CrudPanel $crud) + private CrudPanel $crud; + + public function __construct(private string $controller) { + $this->crud = \Backpack\CRUD\Backpack::crudFromController($controller); } public function render() diff --git a/src/resources/views/crud/columns/crud_column.blade.php b/src/resources/views/crud/columns/crud_column.blade.php index d97d163b57..c6fcd7027a 100644 --- a/src/resources/views/crud/columns/crud_column.blade.php +++ b/src/resources/views/crud/columns/crud_column.blade.php @@ -1,5 +1 @@ -@php -$columnCrud = \Backpack\CRUD\Backpack::crudFromController($column['controller']); -@endphp - - \ No newline at end of file + \ No newline at end of file From e6490ab5e3a80a66b3f12772699a064c62a71c5c Mon Sep 17 00:00:00 2001 From: pxpm Date: Fri, 28 Mar 2025 12:14:26 +0000 Subject: [PATCH 10/52] wip --- .../Http/Controllers/Operations/ListOperation.php | 1 + .../views/crud/columns/crud_column.blade.php | 1 - src/resources/views/crud/columns/datatable.blade.php | 1 + .../views/crud/datatable/datatables_logic.blade.php | 12 ++++++++---- src/resources/views/crud/list.blade.php | 2 +- 5 files changed, 11 insertions(+), 6 deletions(-) delete mode 100644 src/resources/views/crud/columns/crud_column.blade.php create mode 100644 src/resources/views/crud/columns/datatable.blade.php diff --git a/src/app/Http/Controllers/Operations/ListOperation.php b/src/app/Http/Controllers/Operations/ListOperation.php index a7698d18a0..f03770de1c 100644 --- a/src/app/Http/Controllers/Operations/ListOperation.php +++ b/src/app/Http/Controllers/Operations/ListOperation.php @@ -61,6 +61,7 @@ public function index() $this->data['crud'] = $this->crud; $this->data['title'] = $this->crud->getTitle() ?? mb_ucfirst($this->crud->entity_name_plural); + $this->data['controller'] = get_class($this); // load the view from /resources/views/vendor/backpack/crud/ if it exists, otherwise load the one in the package return view($this->crud->getListView(), $this->data); diff --git a/src/resources/views/crud/columns/crud_column.blade.php b/src/resources/views/crud/columns/crud_column.blade.php deleted file mode 100644 index c6fcd7027a..0000000000 --- a/src/resources/views/crud/columns/crud_column.blade.php +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/resources/views/crud/columns/datatable.blade.php b/src/resources/views/crud/columns/datatable.blade.php new file mode 100644 index 0000000000..14566e385b --- /dev/null +++ b/src/resources/views/crud/columns/datatable.blade.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/resources/views/crud/datatable/datatables_logic.blade.php b/src/resources/views/crud/datatable/datatables_logic.blade.php index b061ee9c8d..c5502ad6bd 100644 --- a/src/resources/views/crud/datatable/datatables_logic.blade.php +++ b/src/resources/views/crud/datatable/datatables_logic.blade.php @@ -105,6 +105,7 @@ @endif window.crud = { + updatesUrl: {{ var_export($updatesUrl) }}, exportButtons: JSON.parse('{!! json_encode($crud->get('list.export_buttons')) !!}'), functionsToRunOnDataTablesDrawEvent: [], addFunctionToDataTablesDrawEventQueue: function (functionName) { @@ -126,19 +127,22 @@ functionsToRunOnDataTablesDrawEvent: [], fn.apply(window, args); }, updateUrl : function (url) { - let urlStart = "{{ url($crud->getOperationSetting("datatablesUrl")) }}"; - // compare if url and urlStart are the same, if they are not, just return - if (urlStart !== url) { + if(! window.crud.updatesUrl) { return; } + let urlStart = "{{ url($crud->getOperationSetting("datatablesUrl")) }}"; + // compare if url and urlStart are the same, if they are not, just return let urlEnd = url.replace(urlStart, ''); - console.log(url, urlEnd); + urlEnd = urlEnd.replace('/search', ''); let newUrl = urlStart + urlEnd; let tmpUrl = newUrl.split("?")[0], params_arr = [], queryString = (newUrl.indexOf("?") !== -1) ? newUrl.split("?")[1] : false; + if (urlStart !== tmpUrl) { + return; + } // exclude the persistent-table parameter from url if (queryString !== false) { params_arr = queryString.split("&"); diff --git a/src/resources/views/crud/list.blade.php b/src/resources/views/crud/list.blade.php index 469ea428c3..831469e18f 100644 --- a/src/resources/views/crud/list.blade.php +++ b/src/resources/views/crud/list.blade.php @@ -25,7 +25,7 @@ {{-- THE ACTUAL CONTENT --}}
- +
From 2e58485b94e18c70dbd60906b2e6636f8f4e41b8 Mon Sep 17 00:00:00 2001 From: pxpm Date: Fri, 28 Mar 2025 12:14:36 +0000 Subject: [PATCH 11/52] wip --- src/app/Library/Datatable/Datatable.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/app/Library/Datatable/Datatable.php b/src/app/Library/Datatable/Datatable.php index 3fd3bc1a9c..43f41292a8 100644 --- a/src/app/Library/Datatable/Datatable.php +++ b/src/app/Library/Datatable/Datatable.php @@ -7,17 +7,16 @@ class Datatable extends Component { - private CrudPanel $crud; - - public function __construct(private string $controller) + public function __construct(private string $controller, private ?CrudPanel $crud = null, private bool $updatesUrl = true) { - $this->crud = \Backpack\CRUD\Backpack::crudFromController($controller); + $this->crud ??= \Backpack\CRUD\Backpack::crudFromController($controller); } public function render() { return view('crud::datatable.datatable', [ - 'crud' => $this->crud, + 'crud' => $this->crud, + 'updatesUrl' => $this->updatesUrl, ]); } } From f1fdd91a15b48e04a75db27b71f759f2b5c500a8 Mon Sep 17 00:00:00 2001 From: pxpm Date: Tue, 1 Apr 2025 17:31:55 +0100 Subject: [PATCH 12/52] wip --- src/app/Library/Datatable/Datatable.php | 23 +- src/resources/assets/css/common.css | 916 +++++++++++------- .../views/crud/datatable/datatable.blade.php | 10 +- .../crud/datatable/datatables_logic.blade.php | 620 +++++++----- .../views/crud/inc/export_buttons.blade.php | 6 +- .../views/crud/inc/filters_navbar.blade.php | 47 +- 6 files changed, 1032 insertions(+), 590 deletions(-) diff --git a/src/app/Library/Datatable/Datatable.php b/src/app/Library/Datatable/Datatable.php index 43f41292a8..9b5f78c76e 100644 --- a/src/app/Library/Datatable/Datatable.php +++ b/src/app/Library/Datatable/Datatable.php @@ -7,16 +7,31 @@ class Datatable extends Component { - public function __construct(private string $controller, private ?CrudPanel $crud = null, private bool $updatesUrl = true) - { + public function __construct( + private string $controller, + private ?CrudPanel $crud = null, + private bool $updatesUrl = true, + private ?string $tableId = null, + private array $tableOptions = [] + ) { $this->crud ??= \Backpack\CRUD\Backpack::crudFromController($controller); + $this->tableId = 'crudTable_'.uniqid(); + + // Merge default options with provided options + $this->tableOptions = array_merge([ + 'pageLength' => $this->crud->getDefaultPageLength(), + 'searchDelay' => $this->crud->getOperationSetting('searchDelay'), + 'searchableTable' => $this->crud->getOperationSetting('searchableTable') ?? true, + ], $tableOptions); } public function render() { return view('crud::datatable.datatable', [ - 'crud' => $this->crud, - 'updatesUrl' => $this->updatesUrl, + 'crud' => $this->crud, + 'updatesUrl' => $this->updatesUrl, + 'tableId' => $this->tableId, + 'tableOptions' => $this->tableOptions, ]); } } diff --git a/src/resources/assets/css/common.css b/src/resources/assets/css/common.css index 7c6b25d53d..d78f4f8c5b 100644 --- a/src/resources/assets/css/common.css +++ b/src/resources/assets/css/common.css @@ -1,521 +1,741 @@ -:root { - --table-row-hover: #f2f1ff; +/* +* +* Backpack Crud / public / common.css +* +*/ + +.dataTables_wrapper .dt-buttons .dt-button-collection { + width: auto; } -.sidebar .nav-dropdown-items .nav-dropdown { - padding: 0 0 0 .8rem; +.dataTables_wrapper .dt-buttons .dt-button-collection>div.dropdown-menu { + min-width: 100%; } -.sidebar .nav-dropdown-items .nav-dropdown:not(.open) > a { - font-weight: normal !important; +.dataTables_wrapper .dt-buttons .dt-button-collection>div.dropdown-menu ul.dropdown-menu { + position: relative; + top: 0; + left: 0; + display: block; + min-width: 100%; + padding: 0; + border: 0; } -[dir="rtl"] .sidebar .nav-dropdown-items .nav-dropdown { - padding: 0 .8rem 0 0; +.dataTables_wrapper .dt-buttons .dt-button-collection>div.dropdown-menu ul.dropdown-menu li { + padding: 0; + min-width: 100%; + display: block; } -form .form-group.required > label:not(:empty):not(.form-check-label)::after { - content: ' *'; - color: #ff0000; +.dataTables_wrapper .dt-buttons .dt-button-collection>div.dropdown-menu ul.dropdown-menu li .dropdown-item { + color: #23282c; + min-width: 100%; + width: 100%; + display: block; + text-align: left; + margin: 0; + border-radius: 0; +} + +.dataTables_wrapper .dt-buttons .dt-button-collection>div.dropdown-menu ul.dropdown-menu li .dropdown-item:hover { + background: #f0f3f9; +} + +.dataTables_wrapper .dt-buttons .dt-button-collection>div.dropdown-menu ul.dropdown-menu li .dropdown-item.active { + background: #4285f4; + color: #fff; +} + +.dataTables_wrapper .dt-buttons .dt-button-collection>div.dropdown-menu ul.dropdown-menu li .dropdown-item.active:hover { + background: #4285f4; +} + +.dataTables_wrapper .dt-buttons .dt-button-collection>div.dropdown-menu ul.dropdown-menu li.dropdown-divider { + height: 0; + margin: .5rem 0; + overflow: hidden; + border-top: 1px solid #e4e7ea; +} + +.dataTables_wrapper .dt-buttons .dt-button-collection>div.dropdown-menu ul.dropdown-menu li.buttons-colvis { + padding: 0; } -form .help-block { - margin-top: .25rem; - margin-bottom: .25rem; - color: #73818f; +.dataTables_wrapper .dt-buttons .dt-button-collection>div.dropdown-menu ul.dropdown-menu li.buttons-colvis .dropdown-item { + padding: .3rem 1rem; font-size: 0.9em; } -form .nav-tabs .nav-link:hover { - color: #384c74; +.dataTables_wrapper .dt-buttons .dt-button-collection>div.dropdown-menu ul.dropdown-menu li.buttons-colvis.active .dropdown-item { + background: #4285f4; + color: #fff; } -form .select2-container--bootstrap .select2-selection--single { - padding-top: 8px; - padding-bottom: 8px; - min-height: 38px; +.dataTables_wrapper .dt-buttons .dt-button-collection>div.dropdown-menu ul.dropdown-menu li.buttons-colvis.active .dropdown-item:hover { + background: #4285f4; } -form .select2-container--bootstrap .select2-selection--multiple { - min-height: 38px; +.dataTables_wrapper .dt-buttons .dt-button-collection>div.dropdown-menu ul.dropdown-menu li.buttons-columnVisibility .dropdown-item { + padding: .3rem 1rem; + font-size: 0.9em; } -form .select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field { - min-height: 36px; +.dataTables_wrapper .dt-buttons .dt-button-collection>div.dropdown-menu ul.dropdown-menu li.buttons-columnVisibility.active .dropdown-item { + background: #4285f4; + color: #fff; } -form .select2-container--bootstrap .select2-selection--multiple .select2-selection__choice { - margin-top: 6px; +.dataTables_wrapper .dt-buttons .dt-button-collection>div.dropdown-menu ul.dropdown-menu li.buttons-columnVisibility.active .dropdown-item:hover { + background: #4285f4; } -form .select2-container--bootstrap .select2-selection--multiple .select2-selection__clear { - margin-top: 8px; +.dataTables_wrapper .dt-buttons .dt-button-collection>div.dropdown-menu ul.dropdown-menu li.buttons-colvisRestore .dropdown-item { + padding: .3rem 1rem; + font-size: 0.9em; } -form .select2-container--bootstrap .select2-selection { - border: none !important; +.dataTables_wrapper .dt-buttons .dt-button-collection>div.dropdown-menu ul.dropdown-menu li.buttons-colvisRestore.active .dropdown-item { + background: #4285f4; + color: #fff; } -form .select2.select2-container { - border: 1px solid rgba(0, 40, 100, 0.12) !important; +.dataTables_wrapper .dt-buttons .dt-button-collection>div.dropdown-menu ul.dropdown-menu li.buttons-colvisRestore.active .dropdown-item:hover { + background: #4285f4; } -/*Table - List View*/ -#crudTable_wrapper div.row .col-sm-12 { - position: relative; +.dataTables_wrapper .dt-buttons .dt-button-collection>div.dropdown-menu ul.dropdown-menu li.buttons-reset .dropdown-item { + padding: .3rem 1rem; + font-size: 0.9em; } -#crudTable_processing.dataTables_processing.card { - all: unset; - position: absolute; - background: rgba(255, 255, 255, 0.9); - height: calc(100% - 6px); - width: calc(100% - 20px); - top: 0; - left: 10px; - z-index: 999; - border-radius: 5px; +.dataTables_wrapper .dt-buttons .dt-button-collection>div.dropdown-menu ul.dropdown-menu li.buttons-reset.active .dropdown-item { + background: #4285f4; + color: #fff; } -#crudTable_processing.dataTables_processing.card > img { - margin: 0; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); +.dataTables_wrapper .dt-buttons .dt-button-collection>div.dropdown-menu ul.dropdown-menu li.buttons-reset.active .dropdown-item:hover { + background: #4285f4; } -#crudTable_processing.dataTables_processing.card > div { - display: none !important; +.dataTables_wrapper .dataTables_filter { + text-align: right; } -#crudTable_wrapper #crudTable, -#crudTable_wrapper table.dataTable { - margin-top: 0 !important; +.dataTables_wrapper .dataTables_filter input { + margin-left: 0.5em; + display: inline-block; + width: auto; } -#crudTable_wrapper #crudTable .crud_bulk_actions_line_checkbox { - vertical-align: text-top; +.dataTables_wrapper .dataTables_info { + clear: both; + float: left; + padding-top: 0.755em; } -#crudTable_wrapper #crudTable.dtr-none > tbody > tr > td > div.dtr-control:before, -#crudTable_wrapper table.dataTable.dtr-none > tbody > tr > td > div.dtr-control:before { - background-color: transparent; - color: #636161; - font-family: "Line Awesome Free"; - font-weight: 900; - width: 1rem; - content: "\f142"; - font-size: 21px; - box-shadow: none; - border: none; +.dataTables_wrapper .dataTables_paginate { + float: right; + text-align: right; + padding-top: 0.25em; +} + +.dataTables_wrapper .dataTables_paginate .paginate_button { + box-sizing: border-box; display: inline-block; - position: relative; - top: 0; - left: 0; - margin: 0 0 0 -0.25rem; -} - -#crudTable_wrapper #crudTable .sorting:before, -#crudTable_wrapper #crudTable .sorting_asc:before, -#crudTable_wrapper #crudTable .sorting_desc:before, -#crudTable_wrapper #crudTable .sorting_asc_disabled:before, -#crudTable_wrapper #crudTable .sorting_desc_disabled:before, -#crudTable_wrapper table.dataTable .sorting:before, -#crudTable_wrapper table.dataTable .sorting_asc:before, -#crudTable_wrapper table.dataTable .sorting_desc:before, -#crudTable_wrapper table.dataTable .sorting_asc_disabled:before, -#crudTable_wrapper table.dataTable .sorting_desc_disabled:before { - right: 0.4em; - top: 1em; - content: "\f0d8"; - font: normal normal normal 14px/1 "Line Awesome Free"; - font-weight: 900; -} - -#crudTable_wrapper #crudTable .sorting:after, -#crudTable_wrapper #crudTable .sorting_asc:after, -#crudTable_wrapper #crudTable .sorting_desc:after, -#crudTable_wrapper #crudTable .sorting_asc_disabled:after, -#crudTable_wrapper #crudTable .sorting_desc_disabled:after, -#crudTable_wrapper table.dataTable .sorting:after, -#crudTable_wrapper table.dataTable .sorting_asc:after, -#crudTable_wrapper table.dataTable .sorting_desc:after, -#crudTable_wrapper table.dataTable .sorting_asc_disabled:after, -#crudTable_wrapper table.dataTable .sorting_desc_disabled:after { - right: 0.4em; - content: "\f0d7"; - font: normal normal normal 14px/1 "Line Awesome Free"; - font-weight: 900; -} - -#crudTable_wrapper #crudTable .crud_bulk_actions_checkbox, -#crudTable_wrapper table.dataTable .crud_bulk_actions_checkbox { - margin: 0 0.6rem 0 0.45rem; -} - -#crudTable tr th:first-child, -#crudTable tr td:first-child, -#crudTable table.dataTable tr th:first-child, -#crudTable table.dataTable tr td:first-child { + min-width: 1em; + padding: 0.1em 0.3em; + margin-left: 0px; + text-align: center; + text-decoration: none !important; + cursor: pointer; + color: #333 !important; + border: 1px solid transparent; + border-radius: 2px; + font-size: 0.85em; +} + +.dataTables_wrapper .dataTables_paginate .paginate_button.current, +.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover { + color: #333 !important; + border: 1px solid #979797; + background-color: white; + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(100%, #dcdcdc)); + /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, white 0%, #dcdcdc 100%); + /* Chrome10+,Safari5.1+ */ + background: -moz-linear-gradient(top, white 0%, #dcdcdc 100%); + /* FF3.6+ */ + background: -ms-linear-gradient(top, white 0%, #dcdcdc 100%); + /* IE10+ */ + background: -o-linear-gradient(top, white 0%, #dcdcdc 100%); + /* Opera 11.10+ */ + background: linear-gradient(to bottom, white 0%, #dcdcdc 100%); + /* W3C */ +} + +.dataTables_wrapper .dataTables_paginate .paginate_button.disabled, +.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover, +.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active { + cursor: default; + color: #666 !important; + border: 1px solid transparent; + background: transparent; + box-shadow: none; +} + +.dataTables_wrapper .dataTables_paginate .paginate_button:hover { + color: white !important; + border: 1px solid #111; + background-color: #585858; + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111)); + /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #585858 0%, #111 100%); + /* Chrome10+,Safari5.1+ */ + background: -moz-linear-gradient(top, #585858 0%, #111 100%); + /* FF3.6+ */ + background: -ms-linear-gradient(top, #585858 0%, #111 100%); + /* IE10+ */ + background: -o-linear-gradient(top, #585858 0%, #111 100%); + /* Opera 11.10+ */ + background: linear-gradient(to bottom, #585858 0%, #111 100%); + /* W3C */ +} + +.dataTables_wrapper .dataTables_paginate .paginate_button:active { + outline: none; + background-color: #2b2b2b; + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c)); + /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); + /* Chrome10+,Safari5.1+ */ + background: -moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); + /* FF3.6+ */ + background: -ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); + /* IE10+ */ + background: -o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); + /* Opera 11.10+ */ + background: linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%); + /* W3C */ + box-shadow: inset 0 0 3px #111; +} + +.dataTables_wrapper .dataTables_paginate .ellipsis { + padding: 0 1em; +} + +.dataTables_wrapper tr th:first-child, .dataTables_wrapper tr td:first-child, .dataTables_wrapper table.dataTable tr th:first-child, .dataTables_wrapper table.dataTable tr td:first-child { align-items: center; padding-top: 1rem; padding-bottom: 1rem; padding-left: 0.6rem; } -#crudTable_wrapper .dt-buttons .dt-button-collection, -#crudTable_wrapper tr td .btn-group .dropdown-menu { - max-height: 340px; - overflow-y: auto; +.dataTables_wrapper .dataTables_processing { + position: absolute; + top: 50%; + left: 50%; + width: 100%; + height: 40px; + margin-left: -50%; + margin-top: -25px; + padding-top: 20px; + text-align: center; + font-size: 1.2em; + background-color: white; + background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(25%, rgba(255, 255, 255, 0.9)), color-stop(75%, rgba(255, 255, 255, 0.9)), color-stop(100%, rgba(255, 255, 255, 0))); + background: -webkit-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); + background: -moz-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); + background: -ms-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); + background: -o-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); + background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); } -#crudTable_wrapper .dt-buttons .dt-button-collection { - padding: 0; - border: 0; +.dataTables_wrapper .dataTables_length, +.dataTables_wrapper .dataTables_filter, +.dataTables_wrapper .dataTables_info, +.dataTables_wrapper .dataTables_processing, +.dataTables_wrapper .dataTables_paginate { + color: #333; } -/*/Table - List View/*/ -.navbar-filters { - min-height: 25px; - border-radius: 0; - margin-bottom: 6px; - margin-top: 0; - background: transparent; - border-color: #f4f4f4; - border: none; +.dataTables_wrapper .dataTables_scroll { + clear: both; } -.navbar-filters .navbar-collapse { - padding: 0; - border: 0; +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody { + -webkit-overflow-scrolling: touch; } -.navbar-filters .navbar-toggle { - padding: 10px 15px; - border-radius: 0; +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th, +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td, +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th, +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td { + vertical-align: middle; } -.navbar-filters .navbar-brand { - height: 25px; - padding: 5px 15px; - font-size: 14px; - text-transform: uppercase; +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th>div.dataTables_sizing, +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td>div.dataTables_sizing, +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th>div.dataTables_sizing, +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td>div.dataTables_sizing { + height: 0; + overflow: hidden; + margin: 0 !important; + padding: 0 !important; } -.navbar-filters li { - margin: 0 2px; +.dataTables_wrapper.no-footer .dataTables_scrollBody { + border-bottom: 1px solid #111; } -.navbar-filters li > a { - border-radius: 2px; +.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable, +.dataTables_wrapper.no-footer div.dataTables_scrollBody>table { + border-bottom: none; } -.navbar-filters li > a:active, -.navbar-filters .navbar-nav > .active > a, -.navbar-filters .navbar-nav > .active > a:focus, -.navbar-filters .navbar-nav > .active > a:hover, -.navbar-filters .navbar-nav > .open > a, -.navbar-filters .navbar-nav > .open > a:focus, -.navbar-filters .navbar-nav > .open > a:hover { - background-color: #e4e7ea; - border-radius: 3px; +.dataTables_wrapper:after { + visibility: hidden; + display: block; + content: ""; + clear: both; + height: 0; } -.navbar-filters .nav.navbar-nav { - float: none; -} +@media screen and (max-width: 767px) { -.navbar-filters .backpack-filter label { - color: #868686; - font-weight: 600; - text-transform: uppercase; -} + .dataTables_wrapper .dataTables_info, + .dataTables_wrapper .dataTables_paginate { + float: none; + text-align: center; + } -@media (min-width: 768px) { - .navbar-filters .navbar-nav > li > a { - padding-top: 5px; - padding-bottom: 5px; + .dataTables_wrapper .dataTables_paginate { + margin-top: 0.5em; } } -@media (max-width: 768px) { - .navbar-filters .navbar-nav { - margin: 0; +@media screen and (max-width: 640px) { + + .dataTables_wrapper .dataTables_length, + .dataTables_wrapper .dataTables_filter { + float: none; + text-align: center; } + + .dataTables_wrapper .dataTables_filter { + margin-top: 0.5em; + } +} + +.dt-button.active { + background: #ccc !important; +} + +.dt-button.dropdown-item { + padding: 0.15rem 1rem; + font-size: 0.9em; } .dataTables_filter { - text-align: right; + margin-bottom: 10px; } .dataTables_filter label { - font-weight: normal; - white-space: nowrap; - text-align: left; + margin-bottom: 0; +} + +.dt-buttons { + margin-bottom: 8px; +} + +/* Adjust table headers */ +.dataTable thead th { + font-size: 0.70em; + font-weight: 700; +} + +/* Make export and column visibility buttons smaller */ +.dataTables_wrapper .dt-buttons .btn { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; +} + +.form-group.required>label:not(:empty):not(.form-check-label)::after { + content: ' *'; + color: #ff0000; } -.dataTables_filter input { +.form-group.required>label:empty::after { display: inline-block; - width: auto; - border-radius: 25px; + content: '*'; + color: #ff0000; } -@media (max-width: 576px) { - .dataTables_filter label { - width: 100%; - } +.tab-container .nav-tabs .nav-item .nav-link.active { + background-color: #fff; +} - .dataTables_filter input[type="search"] { - width: 100%; - } +.tab-container .nav-tabs .nav-item .nav-link:not(.active) { + background-color: #f4f4f4; + color: #c3c3c3; } -.pagination > .disabled > a, -.pagination > .disabled > a:focus, -.pagination > .disabled > a:hover, -.pagination > .disabled > span, -.pagination > .disabled > span:focus, -.pagination > .disabled > span:hover { - background: transparent; +.tab-container .tab-pane { + padding: 10px; + border-left: 1px solid #ddd; + border-right: 1px solid #ddd; + border-bottom: 1px solid #ddd; } -.pagination > li > a { - background: transparent; - border: none; - border-radius: 5px; +.hidden { + display: none !important; + visibility: hidden !important; } -.pagination > li > span:hover { - background: white; +.dropdown-menu.show { + z-index: 9999; } -.pagination > li:last-child > a, -.pagination > li:last-child > span, -.pagination > li:first-child > a, -.pagination > li:first-child > span { - border-radius: 5px; +.select2-container--bootstrap .select2-selection--multiple .select2-selection__choice { + margin-bottom: 3px; } -.box-body.table-responsive { - padding-left: 15px; - padding-right: 15px; +.select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field { + padding-left: 0; } -.dt-buttons, -.dtr-modal .details-control, -.modal .details-control { - display: none; +.select2-container--bootstrap .select2-selection--multiple .select2-selection__choice { + padding: 2px 6px; } -.dtr-bs-modal .modal-body { - padding: 0; +.select2-container--bootstrap .select2-selection--multiple .select2-selection__choice__remove { + margin-right: 3px; +} + +.select2-container--bootstrap .select2-selection--multiple .select2-selection__clear { + margin-top: 0; + line-height: 1.5; + padding: 1px 10px; + margin-right: 8px; +} + +.select2-container--bootstrap .select2-selection--single { + padding-top: 7px; + padding-bottom: 8px; +} + +.select2-container--bootstrap .select2-selection--single .select2-selection__arrow { + top: 5px; +} + +.select2-container--bootstrap .select2-selection--single .select2-selection__clear { + margin-right: 30px; +} + +.select2-container--bootstrap .select2-selection--single .select2-selection__placeholder { + color: #999; +} + +.select2-container--bootstrap .select2-results__option { + padding: 6px 12px; +} + +.select2-container--bootstrap .select2-results__option[aria-selected=true] { + background-color: #f5f5f5; + color: #262626; +} + +.select2-container--bootstrap .select2-results__option--highlighted[aria-selected] { + background-color: #3875d7; + color: #fff; } -.dtr-bs-modal .crud_bulk_actions_checkbox { - display: none; +.select2-container--bootstrap .select2-selection--multiple .select2-selection__choice { + color: #3c4858; + border: 1px solid #aaa; + border-radius: 4px; + padding: 0; + padding-right: 5px; + cursor: default; + float: left; + margin-right: 5px; + margin-top: 5px; } -.content-wrapper { - min-height: calc(100% - 98px); +.select2-container--bootstrap .select2-selection--multiple .select2-selection__choice__remove { + color: #999; + cursor: pointer; + display: inline-block; + font-weight: bold; + margin-right: 2px; + padding: 0 5px; } -.fixed .wrapper { - overflow: visible; +.select2-container--bootstrap .select2-selection--multiple .select2-selection__choice__remove:hover { + color: #333; } -/* SELECT 2 */ -.select2-container--bootstrap .select2-selection { - box-shadow: none !important; - border: 1px solid rgba(0, 40, 100, 0.12) !important; +.select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field { + background: transparent; + padding: 0 12px; + height: 36px; + line-height: 1.5; + margin-top: 0; + min-width: 5em; } -.select2-container--bootstrap.select2-container--focus .select2-selection, -.select2-container--bootstrap.select2-container--open .select2-selection { - box-shadow: none !important; +.select2-container--bootstrap.select2-container--focus .select2-selection, .select2-container--bootstrap.select2-container--open .select2-selection { + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); + -webkit-transition: border-color ease-in-out 0.15s, -webkit-box-shadow ease-in-out 0.15s; + -o-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + border-color: #66afe9; } .select2-container--bootstrap .select2-dropdown { - border-color: rgba(0, 40, 100, 0.12) !important; + border-color: #66afe9; + overflow-x: hidden; + margin-top: -1px; } -/* PACE JS */ -.pace { - pointer-events: none; - -webkit-user-select: none; - user-select: none; +.select2-container--bootstrap .select2-results > .select2-results__options { + max-height: 200px; + overflow-y: auto; } -.pace-inactive { - display: none; +.select2-container--bootstrap .select2-selection--single { + height: 36px; + line-height: 1.5; + padding: 6px 24px 6px 12px; } -.pace .pace-progress { - background: var(--tblr-primary); - position: fixed; - z-index: 2000; +.select2-container--bootstrap .select2-selection--single .select2-selection__arrow { + position: absolute; + bottom: 0; + right: 12px; top: 0; - right: 100%; - width: 100%; - height: 2px; + width: 4px; +} + +.select2-container--bootstrap .select2-selection--single .select2-selection__arrow b { + border-color: #999 transparent transparent transparent; + border-style: solid; + border-width: 4px 4px 0 4px; + height: 0; + left: 0; + margin-left: -4px; + margin-top: -2px; + position: absolute; + top: 50%; + width: 0; +} + +.select2-container--bootstrap .select2-selection--single .select2-selection__rendered { + color: #555555; + padding: 0; } -.alert a.alert-link { - color: inherit !important; - font-weight: 400; - text-decoration: underline !important; +.select2-container--bootstrap .select2-selection--single .select2-selection__placeholder { + color: #999; } -/*# sourceMappingURL=backstrap.css.map */ -.noty_theme__backstrap.noty_bar { - margin: 4px 0; +.select2-container--bootstrap .select2-selection--multiple { + min-height: 36px; + padding: 0; + height: auto; +} + +.select2-container--bootstrap .select2-selection--multiple .select2-selection__rendered { + box-sizing: border-box; + display: block; + line-height: 1.5; + list-style: none; + margin: 0; overflow: hidden; - position: relative; - border: 1px solid transparent; - border-radius: .25rem; + padding: 0; + width: 100%; + text-overflow: ellipsis; + white-space: nowrap; } -.noty_theme__backstrap.noty_bar .noty_body { - padding: .75rem 1.25rem; - font-weight: 300; +.select2-container--bootstrap .select2-selection--multiple .select2-selection__placeholder { + color: #999; + float: left; + margin-top: 5px; } -.noty_theme__backstrap.noty_bar .noty_buttons { - padding: 10px; +.select2-container--bootstrap .select2-selection--multiple .select2-selection__choice { + color: #555555; + background: #fff; + border: 1px solid #ccc; + border-radius: 4px; + cursor: default; + float: left; + margin: 5px 0 0 6px; + padding: 0 6px; } -.noty_theme__backstrap.noty_bar .noty_close_button { - font-size: 1.5rem; - font-weight: 700; - line-height: 1; - color: #161C2D; - text-shadow: 0 1px 0 #FFFFFF; - filter: alpha(opacity=20); - opacity: .5; +.select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field { background: transparent; + padding: 0 12px; + height: 34px; + line-height: 1.5; + margin-top: 0; + min-width: 5em; } -.noty_theme__backstrap.noty_bar .noty_close_button:hover { - background: transparent; - text-decoration: none; +.select2-container--bootstrap .select2-selection--multiple .select2-selection__choice__remove { + color: #999; cursor: pointer; - filter: alpha(opacity=50); - opacity: .75; + display: inline-block; + font-weight: bold; + margin-right: 3px; +} + +.select2-container--bootstrap .select2-selection--multiple .select2-selection__choice__remove:hover { + color: #333; } -.noty_theme__backstrap.noty_type__note, -.noty_theme__backstrap.noty_type__notice, -.noty_theme__backstrap.noty_type__alert, -.noty_theme__backstrap.noty_type__notification { - background-color: #FFFFFF; - color: inherit; +.select2-container--bootstrap .select2-selection--multiple .select2-selection__clear { + margin-top: 6px; } -.noty_theme__backstrap.noty_type__warning { - color: #F9FBFD; - background-color: #ffc107; - border-color: #d39e00; +.select2-container--bootstrap .select2-selection--single.input-sm, +.input-group-sm .select2-container--bootstrap .select2-selection--single, +.form-group-sm .select2-container--bootstrap .select2-selection--single { + border-radius: 3px; + font-size: 12px; + height: 30px; + line-height: 1.5; + padding: 5px 22px 5px 10px; } -.noty_theme__backstrap.noty_type__danger, -.noty_theme__backstrap.noty_type__error { - color: #F9FBFD; - background-color: #df4759; - border-color: #cf2438; +.select2-container--bootstrap .select2-selection--single.input-sm .select2-selection__arrow b, +.input-group-sm .select2-container--bootstrap .select2-selection--single .select2-selection__arrow b, +.form-group-sm .select2-container--bootstrap .select2-selection--single .select2-selection__arrow b { + margin-left: -5px; } -.noty_theme__backstrap.noty_type__info, -.noty_theme__backstrap.noty_type__information { - color: #F9FBFD; - background-color: #467FD0; - border-color: #2e66b5; +.select2-container--bootstrap .select2-selection--multiple.input-sm, +.input-group-sm .select2-container--bootstrap .select2-selection--multiple, +.form-group-sm .select2-container--bootstrap .select2-selection--multiple { + min-height: 30px; + border-radius: 3px; } -.noty_theme__backstrap.noty_type__success { - color: #F9FBFD; - background-color: #42ba96; - border-color: #359478; +.select2-container--bootstrap .select2-selection--multiple.input-sm .select2-selection__choice, +.input-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-selection__choice, +.form-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-selection__choice { + font-size: 12px; + line-height: 1.5; + margin: 4px 0 0 5px; + padding: 0 5px; } -.noty_theme__backstrap.noty_type__primary { - color: #F9FBFD; - background-color: #7c69ef; - border-color: #543bea; +.select2-container--bootstrap .select2-selection--multiple.input-sm .select2-search--inline .select2-search__field, +.input-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field, +.form-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field { + padding: 0 10px; + font-size: 12px; + height: 28px; + line-height: 1.5; } -.noty_theme__backstrap.noty_type__secondary { - color: #161C2D; - background-color: #D9E2EF; - border-color: #b5c7e0; +.select2-container--bootstrap .select2-selection--multiple.input-sm .select2-selection__clear, +.input-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-selection__clear, +.form-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-selection__clear { + margin-top: 5px; } -.noty_theme__backstrap.noty_type__light { - color: #161C2D; - background-color: #F1F4F8; - border-color: #cfd9e7; +.select2-container--bootstrap .select2-selection--single.input-lg, +.input-group-lg .select2-container--bootstrap .select2-selection--single, +.form-group-lg .select2-container--bootstrap .select2-selection--single { + border-radius: 6px; + font-size: 18px; + height: 46px; + line-height: 1.3333333; + padding: 10px 31px 10px 16px; } -.noty_theme__backstrap.noty_type__dark { - color: #F9FBFD; - background-color: #161C2D; - border-color: #05070b; +.select2-container--bootstrap .select2-selection--single.input-lg .select2-selection__arrow, +.input-group-lg .select2-container--bootstrap .select2-selection--single .select2-selection__arrow, +.form-group-lg .select2-container--bootstrap .select2-selection--single .select2-selection__arrow { + width: 5px; } -/* Use whole table width for td when displaying empty content message */ -#crudTable_wrapper td.dataTables_empty { - display: table-cell !important; +.select2-container--bootstrap .select2-selection--single.input-lg .select2-selection__arrow b, +.input-group-lg .select2-container--bootstrap .select2-selection--single .select2-selection__arrow b, +.form-group-lg .select2-container--bootstrap .select2-selection--single .select2-selection__arrow b { + border-width: 5px 5px 0 5px; + margin-left: -5px; + margin-top: -2.5px; } -.navbar-filters span.select2-dropdown.select2-dropdown--below { - border: 1px solid var(--tblr-dropdown-border-color) !important; - border-top: none !important; - box-sizing: content-box; - box-shadow: none; +.select2-container--bootstrap .select2-selection--multiple.input-lg, +.input-group-lg .select2-container--bootstrap .select2-selection--multiple, +.form-group-lg .select2-container--bootstrap .select2-selection--multiple { + min-height: 46px; + border-radius: 6px; } -.navbar-filters .select2-container--bootstrap.select2-container--below .select2-selection, -.navbar-filters .select2.select2-container { - border: none !important; +.select2-container--bootstrap .select2-selection--multiple.input-lg .select2-selection__choice, +.input-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-selection__choice, +.form-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-selection__choice { + font-size: 18px; + line-height: 1.3333333; + border-radius: 4px; + margin: 9px 0 0 8px; + padding: 0 10px; } -.navbar-filters div.form-group { - padding-bottom: 0 !important; +.select2-container--bootstrap .select2-selection--multiple.input-lg .select2-search--inline .select2-search__field, +.input-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field, +.form-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field { + padding: 0 16px; + font-size: 18px; + height: 44px; + line-height: 1.3333333; } -.navbar-filters, .dropdown-menu { - --tblr-dropdown-divider-margin-y: .4rem !important; +.select2-container--bootstrap .select2-selection--multiple.input-lg .select2-selection__clear, +.input-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-selection__clear, +.form-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-selection__clear { + margin-top: 10px; } -/* ERRORS */ +.select2-container--bootstrap .select2-selection.input-lg.select2-container--open .select2-selection--single .select2-selection__arrow b { + border-color: transparent transparent #999 transparent; + border-width: 0 5px 5px 5px; +} -.error_number { - font-size: 156px; - font-weight: 600; - line-height: 100px; +.input-group-lg .select2-container--bootstrap .select2-selection.select2-container--open .select2-selection--single .select2-selection__arrow b { + border-color: transparent transparent #999 transparent; + border-width: 0 5px 5px 5px; } -.error_number small { - font-size: 56px; - font-weight: 700; + +.select2-container--bootstrap[dir="rtl"] .select2-selection--single { + padding-left: 24px; + padding-right: 12px; } -.error_number hr { - margin-top: 60px; - margin-bottom: 0; - width: 50px; +.select2-container--bootstrap[dir="rtl"] .select2-selection--single .select2-selection__rendered { + padding-right: 0; + padding-left: 0; + text-align: right; } -.error_title { - margin-top: 40px; - font-size: 36px; - font-weight: 400; +.select2-container--bootstrap[dir="rtl"] .select2-selection--single .select2-selection__clear { + float: left; } -.error_description { - font-size: 24px; - font-weight: 400; +.select2-container--bootstrap[dir="rtl"] .select2-selection--multiple .select2-selection__choice { + float: right; + margin-left: 5px; + margin-right: auto; } -/* Summernote */ -.note-editor.note-frame.fullscreen { - background-color: white; -} \ No newline at end of file +.select2-container--bootstrap[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove { + margin-left: 2px; + margin-right: auto; +} diff --git a/src/resources/views/crud/datatable/datatable.blade.php b/src/resources/views/crud/datatable/datatable.blade.php index dbc0c79930..3d6ce38755 100644 --- a/src/resources/views/crud/datatable/datatable.blade.php +++ b/src/resources/views/crud/datatable/datatable.blade.php @@ -28,9 +28,9 @@ @endif
- getOperationSetting("datatablesUrl")}}')) : []; +let $dtCachedInfo = JSON.parse(localStorage.getItem('DataTables_{{$tableId}}_/{{$crud->getOperationSetting("datatablesUrl")}}')) + ? JSON.parse(localStorage.getItem('DataTables_{{$tableId}}_/{{$crud->getOperationSetting("datatablesUrl")}}')) : []; var $dtDefaultPageLength = {{ $crud->getDefaultPageLength() }}; let $pageLength = @json($crud->getPageLengthMenu()); -let $dtStoredPageLength = parseInt(localStorage.getItem('DataTables_crudTable_/{{$crud->getOperationSetting("datatablesUrl")}}_pageLength')); +let $dtStoredPageLength = parseInt(localStorage.getItem('DataTables_{{$tableId}}_/{{$crud->getOperationSetting("datatablesUrl")}}_pageLength')); if(!$dtStoredPageLength && $dtCachedInfo.length !== 0 && $dtCachedInfo.length !== $dtDefaultPageLength) { - localStorage.removeItem('DataTables_crudTable_/{{$crud->getOperationSetting("datatablesUrl")}}'); + localStorage.removeItem('DataTables_{{$tableId}}_/{{$crud->getOperationSetting("datatablesUrl")}}'); } if($dtCachedInfo.length !== 0 && $pageLength[0].indexOf($dtCachedInfo.length) === -1) { - localStorage.removeItem('DataTables_crudTable_/{{$crud->getRoute()}}'); + localStorage.removeItem('DataTables_{{$tableId}}_/{{$crud->getRoute()}}'); } @@ -96,88 +98,126 @@ saved_list_url = false; } } - @endif + if (saved_list_url && persistentUrl!=window.location.href) { // finally redirect the user. window.location.href = persistentUrl; } @endif -window.crud = { - updatesUrl: {{ var_export($updatesUrl) }}, - exportButtons: JSON.parse('{!! json_encode($crud->get('list.export_buttons')) !!}'), - functionsToRunOnDataTablesDrawEvent: [], - addFunctionToDataTablesDrawEventQueue: function (functionName) { - if (this.functionsToRunOnDataTablesDrawEvent.indexOf(functionName) == -1) { - this.functionsToRunOnDataTablesDrawEvent.push(functionName); - } - }, - responsiveToggle: function(dt) { - $(dt.table().header()).find('th').toggleClass('all'); - dt.responsive.rebuild(); - dt.responsive.recalc(); - }, - executeFunctionByName: function(str, args) { - var arr = str.split('.'); - var fn = window[ arr[0] ]; - - for (var i = 1; i < arr.length; i++) - { fn = fn[ arr[i] ]; } - fn.apply(window, args); - }, - updateUrl : function (url) { - if(! window.crud.updatesUrl) { - return; - } - let urlStart = "{{ url($crud->getOperationSetting("datatablesUrl")) }}"; - // compare if url and urlStart are the same, if they are not, just return - let urlEnd = url.replace(urlStart, ''); - - urlEnd = urlEnd.replace('/search', ''); - let newUrl = urlStart + urlEnd; - let tmpUrl = newUrl.split("?")[0], - params_arr = [], - queryString = (newUrl.indexOf("?") !== -1) ? newUrl.split("?")[1] : false; - - if (urlStart !== tmpUrl) { - return; - } - // exclude the persistent-table parameter from url - if (queryString !== false) { - params_arr = queryString.split("&"); - for (let i = params_arr.length - 1; i >= 0; i--) { - let param = params_arr[i].split("=")[0]; - if (param === 'persistent-table') { - params_arr.splice(i, 1); +// Initialize the global crud object if it doesn't exist +window.crud = window.crud || {}; + +// Initialize the tables object to store multiple table instances +window.crud.tables = window.crud.tables || {}; + +// Create a default table configuration that can be extended for specific tables +window.crud.defaultTableConfig = { + functionsToRunOnDataTablesDrawEvent: [], + addFunctionToDataTablesDrawEventQueue: function (functionName) { + if (this.functionsToRunOnDataTablesDrawEvent.indexOf(functionName) == -1) { + this.functionsToRunOnDataTablesDrawEvent.push(functionName); + } + }, + responsiveToggle: function(dt) { + $(dt.table().header()).find('th').toggleClass('all'); + dt.responsive.rebuild(); + dt.responsive.recalc(); + }, + executeFunctionByName: function(str, args) { + var arr = str.split('.'); + var fn = window[ arr[0] ]; + + for (var i = 1; i < arr.length; i++) + { fn = fn[ arr[i] ]; } + fn.apply(window, args); + }, + updateUrl: function (url) { + if(!this.updatesUrl) { + return; + } + let urlStart = this.urlStart; + // compare if url and urlStart are the same, if they are not, just return + let urlEnd = url.replace(urlStart, ''); + + urlEnd = urlEnd.replace('/search', ''); + let newUrl = urlStart + urlEnd; + let tmpUrl = newUrl.split("?")[0], + params_arr = [], + queryString = (newUrl.indexOf("?") !== -1) ? newUrl.split("?")[1] : false; + + if (urlStart !== tmpUrl) { + return; + } + // exclude the persistent-table parameter from url + if (queryString !== false) { + params_arr = queryString.split("&"); + for (let i = params_arr.length - 1; i >= 0; i--) { + let param = params_arr[i].split("=")[0]; + if (param === 'persistent-table') { + params_arr.splice(i, 1); + } } + newUrl = params_arr.length ? tmpUrl + "?" + params_arr.join("&") : tmpUrl; + } + window.history.pushState({}, '', newUrl); + if (this.persistentTable) { + localStorage.setItem(this.persistentTableSlug + '_list_url', newUrl); } - newUrl = params_arr.length ? tmpUrl + "?" + params_arr.join("&") : tmpUrl; } - window.history.pushState({}, '', newUrl); - @if ($crud->getPersistentTable()) - localStorage.setItem('{{ Str::slug($crud->getOperationSetting("datatablesUrl")) }}_list_url', newUrl); - @endif - }, - dataTableConfiguration: { +}; + +// Create a table-specific configuration +window.crud.tableConfigs = window.crud.tableConfigs || {}; + +// Initialize the current table configuration +window.crud.tableConfigs['{{$tableId}}'] = Object.assign({}, window.crud.defaultTableConfig, { + updatesUrl: {{ var_export($updatesUrl) }}, + exportButtons: JSON.parse('{!! json_encode($crud->get('list.export_buttons')) !!}'), + functionsToRunOnDataTablesDrawEvent: [], + urlStart: "{{ url($crud->getOperationSetting("datatablesUrl")) }}", + persistentTable: {{ $crud->getPersistentTable() ? 'true' : 'false' }}, + persistentTableSlug: '{{ Str::slug($crud->getOperationSetting("datatablesUrl")) }}', + persistentTableDuration: {{ $crud->getPersistentTableDuration() ?: 'null' }}, + subheading: {{ $crud->getSubheading() ? 'true' : 'false' }}, + resetButton: {{ ($crud->getOperationSetting('resetButton') ?? true) ? 'true' : 'false' }}, + responsiveTable: {{ $crud->getResponsiveTable() ? 'true' : 'false' }} +}); + +// For backward compatibility, maintain the global crud object +if (!window.crud.table) { + window.crud.updatesUrl = window.crud.tableConfigs['{{$tableId}}'].updatesUrl; + window.crud.exportButtons = window.crud.tableConfigs['{{$tableId}}'].exportButtons; + window.crud.functionsToRunOnDataTablesDrawEvent = window.crud.tableConfigs['{{$tableId}}'].functionsToRunOnDataTablesDrawEvent; + window.crud.addFunctionToDataTablesDrawEventQueue = window.crud.tableConfigs['{{$tableId}}'].addFunctionToDataTablesDrawEventQueue; + window.crud.responsiveToggle = window.crud.tableConfigs['{{$tableId}}'].responsiveToggle; + window.crud.executeFunctionByName = window.crud.tableConfigs['{{$tableId}}'].executeFunctionByName; + window.crud.updateUrl = window.crud.tableConfigs['{{$tableId}}'].updateUrl; +} + +// Create a table-specific datatable configuration +window.crud.tableConfigs['{{$tableId}}'].dataTableConfiguration = { bInfo: {{ var_export($crud->getOperationSetting('showEntryCount') ?? true) }}, @if ($crud->getResponsiveTable()) responsive: { details: { display: $.fn.dataTable.Responsive.display.modal( { header: function ( row ) { - // show the content of the first column - // as the modal header - // var data = row.data(); - // return data[0]; return ''; } }), type: 'none', target: '.dtr-control', renderer: function ( api, rowIdx, columns ) { + console.log('Responsive Renderer Called'); + console.log('Columns:', columns); + console.log('Row Index:', rowIdx); var data = $.map( columns, function ( col, i ) { - var columnHeading = crud.table.columns().header()[col.columnIndex]; + // Use the table instance from the API + var table = api.table().context[0].oInstance; + var tableId = table.attr('id'); + var columnHeading = window.crud.tables[tableId].columns().header()[col.columnIndex]; // hide columns that have VisibleInModal false if ($(columnHeading).attr('data-visible-in-modal') == 'false') { return ''; @@ -222,17 +262,15 @@ functionsToRunOnDataTablesDrawEvent: [], @if ($crud->getPersistentTable()) stateSave: true, - /* - if developer forced field into table 'visibleInTable => true' we make sure when saving datatables state - that it reflects the developer decision. - */ - stateSaveParams: function(settings, data) { - localStorage.setItem('{{ Str::slug($crud->getOperationSetting("datatablesUrl")) }}_list_url_time', data.time); + // Get the table ID from the settings + var tableId = settings.sTableId; + var table = window.crud.tables[tableId]; + data.columns.forEach(function(item, index) { - var columnHeading = crud.table.columns().header()[index]; + var columnHeading = table.columns().header()[index]; if ($(columnHeading).attr('data-visible-in-table') == 'true') { return item.visible = true; } @@ -313,24 +351,255 @@ functionsToRunOnDataTablesDrawEvent: [], "<'row hidden'<'col-sm-6'i><'col-sm-6 d-print-none'f>>" + "<'table-content row'<'col-sm-12'tr>>" + "<'table-footer row mt-2 d-print-none align-items-center '<'col-sm-12 col-md-4'l><'col-sm-0 col-md-4 text-center'B><'col-sm-12 col-md-4 'p>>", - } -} + }; @include('crud::inc.export_buttons') @endif diff --git a/src/resources/views/crud/inc/filters_navbar.blade.php b/src/resources/views/crud/inc/filters_navbar.blade.php index 2a09073854..66b4c13f5c 100644 --- a/src/resources/views/crud/inc/filters_navbar.blade.php +++ b/src/resources/views/crud/inc/filters_navbar.blade.php @@ -62,16 +62,19 @@ function updatePageUrl(filterName, filterValue, currentUrl = null) { } if(typeof updateDatatablesOnFilterChange !== 'function') { - function updateDatatablesOnFilterChange(filterName, filterValue, update_url = false, debounce = 500) { + function updateDatatablesOnFilterChange(filterName, filterValue, update_url = false, debounce = 500, tableId = 'crudTable') { + // Get the table instance based on the tableId + let table = window.crud.tables[tableId] || window.crud.table; + // behaviour for ajax tables - let new_url = updatePageUrl(filterName, filterValue, crud.table.ajax.url()); - crud.table.ajax.url(new_url); + let new_url = updatePageUrl(filterName, filterValue, table.ajax.url()); + table.ajax.url(new_url); // when we are clearing ALL filters, we would not update the table url here, because this is done PER filter // and we have a function that will do this update for us after all filters had been cleared. if(update_url) { // replace the datatables ajax url with new_url and reload it - callFunctionOnce(function() { refreshDatatablesOnFilterChange(new_url) }, debounce, 'refreshDatatablesOnFilterChange'); + callFunctionOnce(function() { refreshDatatablesOnFilterChange(new_url, tableId) }, debounce, 'refreshDatatablesOnFilterChange_' + tableId); } return new_url; @@ -101,10 +104,13 @@ function callFunctionOnce(func, within = 300, timerId = null) { } if(typeof refreshDatatablesOnFilterChange !== 'function') { - function refreshDatatablesOnFilterChange(url) + function refreshDatatablesOnFilterChange(url, tableId = 'crudTable') { + // Get the table instance based on the tableId + let table = window.crud.tables[tableId] || window.crud.table; + // replace the datatables ajax url with new_url and reload it - crud.table.ajax.url(url).load(); + table.ajax.url(url).load(); } } @@ -161,10 +167,29 @@ function refreshDatatablesOnFilterChange(url) removeFiltersButton.addEventListener('click', function(e) { e.preventDefault(); - document.dispatchEvent(new Event('backpack:filters:cleared', { - detail: { - navbar: navbar, - filters: filters, + // Find the closest datatable to this navbar + let closestTable = null; + let navbarParent = navbar.parentElement; + + // Look for the datatable in the DOM + if (navbarParent) { + // First try to find a table with an ID that starts with the specified prefix + closestTable = navbarParent.querySelector('table[id^="datatable"]'); + + // If not found, try to find any table that might be the datatable + if (!closestTable) { + closestTable = navbarParent.querySelector('table.dataTable'); + } + } + + // Get the table ID if found, otherwise use the default 'crudTable' + let tableId = closestTable ? closestTable.id : 'crudTable'; + + document.dispatchEvent(new CustomEvent('backpack:filters:cleared', { + detail: { + navbar: navbar, + filters: filters, + tableId: tableId } })); @@ -195,4 +220,4 @@ function refreshDatatablesOnFilterChange(url) }); }); -@endpush \ No newline at end of file +@endpush From 693849b9540b7ff5a8f38bd4cda18795af9c6289 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Tue, 1 Apr 2025 16:32:10 +0000 Subject: [PATCH 13/52] Apply fixes from StyleCI [ci skip] [skip ci] --- src/app/Library/Datatable/Datatable.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/app/Library/Datatable/Datatable.php b/src/app/Library/Datatable/Datatable.php index 9b5f78c76e..14247db3d8 100644 --- a/src/app/Library/Datatable/Datatable.php +++ b/src/app/Library/Datatable/Datatable.php @@ -19,8 +19,8 @@ public function __construct( // Merge default options with provided options $this->tableOptions = array_merge([ - 'pageLength' => $this->crud->getDefaultPageLength(), - 'searchDelay' => $this->crud->getOperationSetting('searchDelay'), + 'pageLength' => $this->crud->getDefaultPageLength(), + 'searchDelay' => $this->crud->getOperationSetting('searchDelay'), 'searchableTable' => $this->crud->getOperationSetting('searchableTable') ?? true, ], $tableOptions); } @@ -28,9 +28,9 @@ public function __construct( public function render() { return view('crud::datatable.datatable', [ - 'crud' => $this->crud, - 'updatesUrl' => $this->updatesUrl, - 'tableId' => $this->tableId, + 'crud' => $this->crud, + 'updatesUrl' => $this->updatesUrl, + 'tableId' => $this->tableId, 'tableOptions' => $this->tableOptions, ]); } From 861be11900edfab2cbca33006dd54cfbac2b11de Mon Sep 17 00:00:00 2001 From: pxpm Date: Fri, 4 Apr 2025 14:02:17 +0100 Subject: [PATCH 14/52] wip --- src/resources/assets/css/common.css | 63 +++++--- src/resources/assets/css/responsive-modal.css | 39 +++++ .../crud/datatable/datatables_logic.blade.php | 134 ++++++++++++++---- .../crud/inc/details_row_logic.blade.php | 55 ++++--- 4 files changed, 230 insertions(+), 61 deletions(-) create mode 100644 src/resources/assets/css/responsive-modal.css diff --git a/src/resources/assets/css/common.css b/src/resources/assets/css/common.css index d78f4f8c5b..01ddfea4a8 100644 --- a/src/resources/assets/css/common.css +++ b/src/resources/assets/css/common.css @@ -231,30 +231,61 @@ padding-left: 0.6rem; } -.dataTables_wrapper .dataTables_processing { +.dataTables_processing.card { + all: unset; + position: absolute; + background: rgba(255, 255, 255, 0.9); + height: 100%; + width: 100%; + top: 0; + /*left: 10px;*/ + z-index: 999; + border-radius: 5px; +} + +.dataTables_processing.card > img { + margin: 0; position: absolute; top: 50%; left: 50%; - width: 100%; - height: 40px; - margin-left: -50%; - margin-top: -25px; - padding-top: 20px; - text-align: center; - font-size: 1.2em; - background-color: white; - background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(25%, rgba(255, 255, 255, 0.9)), color-stop(75%, rgba(255, 255, 255, 0.9)), color-stop(100%, rgba(255, 255, 255, 0))); - background: -webkit-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); - background: -moz-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); - background: -ms-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); - background: -o-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); - background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); + transform: translate(-50%, -50%); +} + +#crudTable_wrapper table.dataTable .crud_bulk_actions_line_checkbox { + vertical-align: text-top; +} + +.dataTables_processing.card > div { + display: none !important; +} + +.dt-buttons, +.dtr-modal .details-control, +.modal .details-control { + display: none; +} + +.crud-table.dtr-none > tbody > tr > td > div.dtr-control:before, +.crud-table.dtr-none > tbody > tr > td > div.dtr-control:before { + background-color: transparent; + color: #636161; + font-family: "Line Awesome Free"; + font-weight: 900; + width: 1rem; + content: "\f142"; + font-size: 21px; + box-shadow: none; + border: none; + display: inline-block; + position: relative; + top: 0; + left: 0; + margin: 0 0 0 -0.25rem; } .dataTables_wrapper .dataTables_length, .dataTables_wrapper .dataTables_filter, .dataTables_wrapper .dataTables_info, -.dataTables_wrapper .dataTables_processing, .dataTables_wrapper .dataTables_paginate { color: #333; } diff --git a/src/resources/assets/css/responsive-modal.css b/src/resources/assets/css/responsive-modal.css new file mode 100644 index 0000000000..9605265dae --- /dev/null +++ b/src/resources/assets/css/responsive-modal.css @@ -0,0 +1,39 @@ +/* DataTables Responsive Modal Styling */ +.dtr-bs-modal .modal-body { + padding: 20px; +} + +.dtr-bs-modal .dtr-details-table { + width: 100%; + margin-bottom: 0; +} + +.dtr-bs-modal .dtr-details-table td { + padding: 8px; + vertical-align: top; +} + +.dtr-bs-modal .dtr-details-table tr { + border-bottom: 1px solid #f0f0f0; +} + +.dtr-bs-modal .dtr-details-table tr:last-child { + border-bottom: none; +} + +.dtr-bs-modal .modal-header { + border-bottom: 1px solid #e9ecef; + padding: 15px 20px; +} + +.dtr-bs-modal .modal-footer { + border-top: 1px solid #e9ecef; + padding: 15px 20px; +} + +/* Fix for bullet list appearance */ +.dtr-bs-modal ul { + list-style: none; + padding: 0; + margin: 0; +} diff --git a/src/resources/views/crud/datatable/datatables_logic.blade.php b/src/resources/views/crud/datatable/datatables_logic.blade.php index 1c479b8743..52770add57 100644 --- a/src/resources/views/crud/datatable/datatables_logic.blade.php +++ b/src/resources/views/crud/datatable/datatables_logic.blade.php @@ -15,6 +15,7 @@ @basset('https://cdn.datatables.net/responsive/2.4.0/css/responsive.dataTables.min.css') @basset('https://cdn.datatables.net/fixedheader/3.3.1/js/dataTables.fixedHeader.min.js') @basset('https://cdn.datatables.net/fixedheader/3.3.1/css/fixedHeader.dataTables.min.css') +@basset(base_path('vendor/backpack/crud/src/resources/assets/css/responsive-modal.css'), false) @basset(base_path('vendor/backpack/crud/src/resources/assets/img/spinner.svg'), false) @@ -116,6 +117,11 @@ window.crud.defaultTableConfig = { functionsToRunOnDataTablesDrawEvent: [], addFunctionToDataTablesDrawEventQueue: function (functionName) { + console.log(functionName); + if (typeof functionName !== 'string') { + console.error('Function name must be a string. Received:', functionName); + //return; + } if (this.functionsToRunOnDataTablesDrawEvent.indexOf(functionName) == -1) { this.functionsToRunOnDataTablesDrawEvent.push(functionName); } @@ -126,12 +132,49 @@ functionsToRunOnDataTablesDrawEvent: [], dt.responsive.recalc(); }, executeFunctionByName: function(str, args) { - var arr = str.split('.'); - var fn = window[ arr[0] ]; + console.log('Executing function: ' + str, args); + try { + // First check if the function exists directly in the window object + if (typeof window[str] === 'function') { + console.log('Calling function directly from window: ' + str); + window[str].apply(window, args || []); + return; + } + + // Check if the function name contains parentheses + if (str.indexOf('(') !== -1) { + // Extract the function name and arguments + var funcNameMatch = str.match(/([^(]+)\((.*)\)$/); + if (funcNameMatch) { + var funcName = funcNameMatch[1]; + console.log('Extracted function name: ' + funcName); + + // Handle direct function call + if (typeof window[funcName] === 'function') { + console.log('Calling function directly: ' + funcName); + window[funcName](); + return; + } + } + } + + // Standard method - split by dots for namespaced functions + var arr = str.split('.'); + var fn = window[ arr[0] ]; - for (var i = 1; i < arr.length; i++) - { fn = fn[ arr[i] ]; } - fn.apply(window, args); + for (var i = 1; i < arr.length; i++) { + fn = fn[ arr[i] ]; + } + + if (typeof fn === 'function') { + console.log('Function found, applying with args'); + fn.apply(window, args || []); + } else { + console.error('Function not found: ' + str); + } + } catch (e) { + console.error('Error executing function ' + str + ': ', e); + } }, updateUrl: function (url) { if(!this.updatesUrl) { @@ -205,14 +248,11 @@ functionsToRunOnDataTablesDrawEvent: [], display: $.fn.dataTable.Responsive.display.modal( { header: function ( row ) { return ''; - } + }, }), type: 'none', target: '.dtr-control', renderer: function ( api, rowIdx, columns ) { - console.log('Responsive Renderer Called'); - console.log('Columns:', columns); - console.log('Row Index:', rowIdx); var data = $.map( columns, function ( col, i ) { // Use the table instance from the API var table = api.table().context[0].oInstance; @@ -357,10 +397,7 @@ functionsToRunOnDataTablesDrawEvent: [], -@endif \ No newline at end of file +@endif From 7357fc21fcce89ab3f46da55c797657861b284b3 Mon Sep 17 00:00:00 2001 From: pxpm Date: Mon, 7 Apr 2025 15:24:42 +0100 Subject: [PATCH 15/52] wip --- src/resources/assets/css/common.css | 19 +++++++--- .../views/crud/datatable/datatable.blade.php | 4 +-- .../crud/datatable/datatables_logic.blade.php | 36 ++++++++----------- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/resources/assets/css/common.css b/src/resources/assets/css/common.css index 01ddfea4a8..c17821d24d 100644 --- a/src/resources/assets/css/common.css +++ b/src/resources/assets/css/common.css @@ -128,6 +128,12 @@ width: auto; } +.datatable_search_stack input { + display: block !important; + visibility: visible !important; + opacity: 1 !important; +} + .dataTables_wrapper .dataTables_info { clear: both; float: left; @@ -235,12 +241,13 @@ all: unset; position: absolute; background: rgba(255, 255, 255, 0.9); - height: 100%; - width: 100%; + height: calc(100% - 6px); + width: calc(100% - 20px); top: 0; - /*left: 10px;*/ + left: 10px; z-index: 999; border-radius: 5px; + overflow: hidden; } .dataTables_processing.card > img { @@ -251,10 +258,14 @@ transform: translate(-50%, -50%); } -#crudTable_wrapper table.dataTable .crud_bulk_actions_line_checkbox { +.dataTables_wrapper table.dataTable .crud_bulk_actions_line_checkbox { vertical-align: text-top; } +.dataTables_wrapper div.row .col-sm-12 { + position: relative; +} + .dataTables_processing.card > div { display: none !important; } diff --git a/src/resources/views/crud/datatable/datatable.blade.php b/src/resources/views/crud/datatable/datatable.blade.php index 3d6ce38755..3e9b097eec 100644 --- a/src/resources/views/crud/datatable/datatable.blade.php +++ b/src/resources/views/crud/datatable/datatable.blade.php @@ -10,12 +10,12 @@ @if($crud->getOperationSetting('searchableTable'))
-
+
- +
diff --git a/src/resources/views/crud/datatable/datatables_logic.blade.php b/src/resources/views/crud/datatable/datatables_logic.blade.php index 52770add57..94817e06d1 100644 --- a/src/resources/views/crud/datatable/datatables_logic.blade.php +++ b/src/resources/views/crud/datatable/datatables_logic.blade.php @@ -117,11 +117,6 @@ window.crud.defaultTableConfig = { functionsToRunOnDataTablesDrawEvent: [], addFunctionToDataTablesDrawEventQueue: function (functionName) { - console.log(functionName); - if (typeof functionName !== 'string') { - console.error('Function name must be a string. Received:', functionName); - //return; - } if (this.functionsToRunOnDataTablesDrawEvent.indexOf(functionName) == -1) { this.functionsToRunOnDataTablesDrawEvent.push(functionName); } @@ -132,11 +127,9 @@ functionsToRunOnDataTablesDrawEvent: [], dt.responsive.recalc(); }, executeFunctionByName: function(str, args) { - console.log('Executing function: ' + str, args); try { // First check if the function exists directly in the window object if (typeof window[str] === 'function') { - console.log('Calling function directly from window: ' + str); window[str].apply(window, args || []); return; } @@ -147,11 +140,9 @@ functionsToRunOnDataTablesDrawEvent: [], var funcNameMatch = str.match(/([^(]+)\((.*)\)$/); if (funcNameMatch) { var funcName = funcNameMatch[1]; - console.log('Extracted function name: ' + funcName); // Handle direct function call if (typeof window[funcName] === 'function') { - console.log('Calling function directly: ' + funcName); window[funcName](); return; } @@ -167,13 +158,10 @@ functionsToRunOnDataTablesDrawEvent: [], } if (typeof fn === 'function') { - console.log('Function found, applying with args'); fn.apply(window, args || []); } else { - console.error('Function not found: ' + str); } } catch (e) { - console.error('Error executing function ' + str + ': ', e); } }, updateUrl: function (url) { @@ -531,10 +519,20 @@ class: 'dtr-bs-modal' }; // Function to set up table UI elements -function setupTableUI(tableId, config) { - // move search bar - $(`#${tableId}_filter input`).appendTo($('#datatable_search_stack .input-icon')); - $("#datatable_search_stack input").removeClass('form-control-sm'); +function setupTableUI(tableId, config) { + // Set up the search functionality for the existing input + const searchInput = $(`#datatable_search_stack_${tableId} input.datatable-search-input`); + + if (searchInput.length > 0) { + // Set up the search functionality + searchInput.on('keyup', function() { + window.crud.tables[tableId].search(this.value).draw(); + }); + } else { + console.error(`Search input not found for table: ${tableId}`); + } + + // Remove the original filter div $(`#${tableId}_filter`).remove(); // remove btn-secondary from export and column visibility buttons @@ -600,9 +598,6 @@ function setupTableEvents(tableId, config) { // on DataTable draw event run all functions in the queue $(`#${tableId}`).on('draw.dt', function() { - console.log('DataTable draw event triggered for ' + tableId); - console.log('Functions in queue:', config.functionsToRunOnDataTablesDrawEvent); - console.log(config, window.crud.defaultTableConfig); window.crud.defaultTableConfig.functionsToRunOnDataTablesDrawEvent.forEach(function(functionName) { config.executeFunctionByName(functionName); }); @@ -738,9 +733,6 @@ function updateDatatablesOnFilterChange(filterName, filterValue, shouldUpdateUrl window.crud.initializeTable(tableId); } }); - - // Remove any duplicate inputs that might be left over - $("#datatable_search_stack input").not(':first').remove(); }); function formatActionColumnAsDropdown(tableId) { From 74c0bab40014bb221895722056993989834f1db7 Mon Sep 17 00:00:00 2001 From: pxpm Date: Mon, 7 Apr 2025 16:20:15 +0100 Subject: [PATCH 16/52] wip --- src/app/Library/Datatable/Datatable.php | 17 +++++++---------- .../views/crud/columns/datatable.blade.php | 2 +- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/app/Library/Datatable/Datatable.php b/src/app/Library/Datatable/Datatable.php index 14247db3d8..b5a1d936d3 100644 --- a/src/app/Library/Datatable/Datatable.php +++ b/src/app/Library/Datatable/Datatable.php @@ -12,26 +12,23 @@ public function __construct( private ?CrudPanel $crud = null, private bool $updatesUrl = true, private ?string $tableId = null, - private array $tableOptions = [] + private ?\Closure $configure = null ) { $this->crud ??= \Backpack\CRUD\Backpack::crudFromController($controller); $this->tableId = 'crudTable_'.uniqid(); - // Merge default options with provided options - $this->tableOptions = array_merge([ - 'pageLength' => $this->crud->getDefaultPageLength(), - 'searchDelay' => $this->crud->getOperationSetting('searchDelay'), - 'searchableTable' => $this->crud->getOperationSetting('searchableTable') ?? true, - ], $tableOptions); + // Apply the configuration if provided + if ($this->configure) { + ($this->configure)($this->crud); + } } public function render() { return view('crud::datatable.datatable', [ - 'crud' => $this->crud, + 'crud' => $this->crud, 'updatesUrl' => $this->updatesUrl, - 'tableId' => $this->tableId, - 'tableOptions' => $this->tableOptions, + 'tableId' => $this->tableId, ]); } } diff --git a/src/resources/views/crud/columns/datatable.blade.php b/src/resources/views/crud/columns/datatable.blade.php index 14566e385b..8cf1b81d77 100644 --- a/src/resources/views/crud/columns/datatable.blade.php +++ b/src/resources/views/crud/columns/datatable.blade.php @@ -1 +1 @@ - \ No newline at end of file + From 3ffacd961a37792853ef1cd891424bb33d5dc479 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Mon, 7 Apr 2025 15:20:28 +0000 Subject: [PATCH 17/52] Apply fixes from StyleCI [ci skip] [skip ci] --- src/app/Library/Datatable/Datatable.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/Library/Datatable/Datatable.php b/src/app/Library/Datatable/Datatable.php index b5a1d936d3..1af0327909 100644 --- a/src/app/Library/Datatable/Datatable.php +++ b/src/app/Library/Datatable/Datatable.php @@ -26,9 +26,9 @@ public function __construct( public function render() { return view('crud::datatable.datatable', [ - 'crud' => $this->crud, + 'crud' => $this->crud, 'updatesUrl' => $this->updatesUrl, - 'tableId' => $this->tableId, + 'tableId' => $this->tableId, ]); } } From 60dbf9d32a8dd9234a8b6bfd6380a34ff3068a9a Mon Sep 17 00:00:00 2001 From: pxpm Date: Thu, 24 Apr 2025 10:14:34 +0100 Subject: [PATCH 18/52] wip --- src/BackpackManager.php | 37 +++- src/BackpackServiceProvider.php | 42 +--- src/app/Http/Controllers/CrudController.php | 8 +- src/app/Library/CrudPanel/CrudPanelFacade.php | 2 +- .../CrudPanel/Hooks/LifecycleHooks.php | 12 +- src/app/Library/Datatable/Datatable.php | 25 ++- .../views/crud/datatable/datatable.blade.php | 1 + .../crud/datatable/datatables_logic.blade.php | 188 +++++++----------- 8 files changed, 147 insertions(+), 168 deletions(-) diff --git a/src/BackpackManager.php b/src/BackpackManager.php index 77c39613b4..b29d964818 100644 --- a/src/BackpackManager.php +++ b/src/BackpackManager.php @@ -4,11 +4,14 @@ use Backpack\CRUD\app\Http\Controllers\Contracts\CrudControllerContract; use Backpack\CRUD\app\Library\CrudPanel\CrudPanel; +use Illuminate\Support\Facades\Facade; final class BackpackManager { private array $cruds = []; + private ?string $currentlyActiveCrudController = null; + private CrudPanel $crudPanelInstance; private $requestController = null; @@ -49,12 +52,18 @@ public function crudFromController(string $controller): CrudPanel $crud->setOperation('list'); $primaryControllerRequest = $this->cruds[array_key_first($this->cruds)]->getRequest(); - - $controller->initializeCrud($primaryControllerRequest, $crud, 'list'); + if(! $crud->isInitialized()) { + $controller->initializeCrud($primaryControllerRequest, $crud, 'list'); + } return $crud; } + public function setControllerCrud(string $controller, CrudPanel $crud): void + { + $this->cruds[$controller] = $crud; + } + public function hasCrudController(string $controller): bool { return isset($this->cruds[$controller]); @@ -63,12 +72,32 @@ public function hasCrudController(string $controller): bool public function getControllerCrud(string $controller): CrudPanel { if (! isset($this->cruds[$controller])) { - return $this->crudFromController($this->requestController ?? $controller); + return $this->crudFromController($this->getActiveController() ?? $this->requestController ?? $controller); } - return $this->cruds[$controller]; } + public function getRequestController(): ?string + { + return $this->requestController; + } + + public function setActiveController(string $controller): void + { + Facade::clearResolvedInstance('crud'); + $this->currentlyActiveCrudController = $controller; + } + + public function getActiveController(): ?string + { + return $this->currentlyActiveCrudController; + } + + public function unsetActiveController(): void + { + $this->currentlyActiveCrudController = null; + } + public function getCruds(): array { return $this->cruds; diff --git a/src/BackpackServiceProvider.php b/src/BackpackServiceProvider.php index 3c69f0e501..dbb2924e8d 100644 --- a/src/BackpackServiceProvider.php +++ b/src/BackpackServiceProvider.php @@ -84,12 +84,15 @@ public function register() $this->registerBackpackErrorViews(); - // Bind the CrudPanel object to Laravel's service container $this->app->bind('crud', function ($app) { + if (Backpack::getActiveController()) { + return Backpack::crudFromController(Backpack::getActiveController()); + } + // Prioritize explicit controller context $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); $controller = null; - + foreach ($trace as $step) { if (isset($step['class']) && is_a($step['class'], app\Http\Controllers\Contracts\CrudControllerContract::class, true)) { @@ -97,44 +100,21 @@ public function register() break; } } - + if ($controller) { $crudPanel = Backpack::getControllerCrud($controller); - + return $crudPanel; } - // Fallback to a more robust initialization + $cruds = Backpack::getCruds(); - + if (! empty($cruds)) { $crudPanel = reset($cruds); - - // Ensure upload events are registered - return $crudPanel; - } - - return Backpack::getCrudPanelInstance(); - }); - - // Bind a special CrudPanel object for the CrudPanelFacade - $this->app->bind('backpack.crud', function ($app) { - // Similar logic to 'crud' binding - $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); - $controller = null; - foreach ($trace as $step) { - if (isset($step['class']) && - is_a($step['class'], app\Http\Controllers\Contracts\CrudControllerContract::class, true)) { - $controller = $step['class']; - break; - } - } - - if ($controller) { - $crudPanel = Backpack::getControllerCrud($controller); - + return $crudPanel; } - + return Backpack::getCrudPanelInstance(); }); diff --git a/src/app/Http/Controllers/CrudController.php b/src/app/Http/Controllers/CrudController.php index 8c5afc806a..da6b4a4a1d 100644 --- a/src/app/Http/Controllers/CrudController.php +++ b/src/app/Http/Controllers/CrudController.php @@ -22,8 +22,6 @@ class CrudController extends Controller implements CrudControllerContract { use DispatchesJobs, ValidatesRequests; - //public $crud; - public $data = []; public $initialized = false; @@ -55,14 +53,14 @@ public function __construct() }); } - public function initializeCrud($request, $crudPanel = null, $operation = null): CrudPanel + public function initializeCrud($request, $crudPanel = null, $operation = null): void { $crudPanel ??= Backpack::crud($this); if ($crudPanel->isInitialized()) { $crudPanel->setRequest($request); - return $crudPanel; + Backpack::setControllerCrud(get_class($this), $crudPanel); } $crudPanel->initialized = true; @@ -70,7 +68,7 @@ public function initializeCrud($request, $crudPanel = null, $operation = null): $this->triggerControllerHooks(); - return $crudPanel; + Backpack::setControllerCrud(get_class($this), $crudPanel); } private function triggerControllerHooks() diff --git a/src/app/Library/CrudPanel/CrudPanelFacade.php b/src/app/Library/CrudPanel/CrudPanelFacade.php index 6211dab169..811097d96d 100644 --- a/src/app/Library/CrudPanel/CrudPanelFacade.php +++ b/src/app/Library/CrudPanel/CrudPanelFacade.php @@ -36,6 +36,6 @@ class CrudPanelFacade extends Facade */ protected static function getFacadeAccessor() { - return 'backpack.crud'; + return 'crud'; } } diff --git a/src/app/Library/CrudPanel/Hooks/LifecycleHooks.php b/src/app/Library/CrudPanel/Hooks/LifecycleHooks.php index 336e49e5e8..d8985157af 100644 --- a/src/app/Library/CrudPanel/Hooks/LifecycleHooks.php +++ b/src/app/Library/CrudPanel/Hooks/LifecycleHooks.php @@ -1,6 +1,7 @@ hooks[$hook][] = $callback; + $this->hooks[Backpack::getActiveController() ?? Backpack::getRequestController()][$hook][] = $callback; } } public function trigger(string|array $hooks, array $parameters = []): void { $hooks = is_array($hooks) ? $hooks : [$hooks]; + $controller = Backpack::getActiveController() ?? Backpack::getRequestController(); + foreach ($hooks as $hook) { - if (isset($this->hooks[$hook])) { - foreach ($this->hooks[$hook] as $callback) { + if (isset($this->hooks[$controller][$hook])) { + foreach ($this->hooks[$controller][$hook] as $callback) { if ($callback instanceof \Closure) { $callback(...$parameters); } @@ -30,6 +33,7 @@ public function trigger(string|array $hooks, array $parameters = []): void public function has(string $hook): bool { - return isset($this->hooks[$hook]); + $controller = Backpack::getActiveController() ?? Backpack::getRequestController(); + return isset($this->hooks[$controller][$hook]); } } diff --git a/src/app/Library/Datatable/Datatable.php b/src/app/Library/Datatable/Datatable.php index 1af0327909..ee19383ead 100644 --- a/src/app/Library/Datatable/Datatable.php +++ b/src/app/Library/Datatable/Datatable.php @@ -3,6 +3,7 @@ namespace Backpack\CRUD\app\Library\Datatable; use Backpack\CRUD\app\Library\CrudPanel\CrudPanel; +use Backpack\CRUD\Backpack; use Illuminate\View\Component; class Datatable extends Component @@ -14,21 +15,31 @@ public function __construct( private ?string $tableId = null, private ?\Closure $configure = null ) { - $this->crud ??= \Backpack\CRUD\Backpack::crudFromController($controller); - $this->tableId = 'crudTable_'.uniqid(); - - // Apply the configuration if provided + // Set active controller for proper context + Backpack::setActiveController($controller); + + $this->crud ??= Backpack::crudFromController($controller); + + $this->tableId = $tableId ?? 'crudTable_'.uniqid(); + if ($this->configure) { ($this->configure)($this->crud); } + + if (!$this->crud->getOperationSetting('datatablesUrl')) { + $this->crud->setOperationSetting('datatablesUrl', $this->crud->getRoute()); + } + + // Reset the active controller + Backpack::unsetActiveController($controller); } public function render() { return view('crud::datatable.datatable', [ - 'crud' => $this->crud, + 'crud' => $this->crud, 'updatesUrl' => $this->updatesUrl, - 'tableId' => $this->tableId, + 'tableId' => $this->tableId, ]); } -} +} \ No newline at end of file diff --git a/src/resources/views/crud/datatable/datatable.blade.php b/src/resources/views/crud/datatable/datatable.blade.php index 3e9b097eec..ad29ca737b 100644 --- a/src/resources/views/crud/datatable/datatable.blade.php +++ b/src/resources/views/crud/datatable/datatable.blade.php @@ -37,6 +37,7 @@ class="{{ backpack_theme_config('classes.table') ?? 'table table-striped table-h data-has-line-buttons-as-dropdown="{{ (int) $crud->getOperationSetting('lineButtonsAsDropdown') }}" data-line-buttons-as-dropdown-minimum="{{ (int) $crud->getOperationSetting('lineButtonsAsDropdownMinimum') }}" data-line-buttons-as-dropdown-show-before-dropdown="{{ (int) $crud->getOperationSetting('lineButtonsAsDropdownShowBefore') }}" + data-url-start="{{ url($crud->getOperationSetting('datatablesUrl')) }}" cellspacing="0">
diff --git a/src/resources/views/crud/datatable/datatables_logic.blade.php b/src/resources/views/crud/datatable/datatables_logic.blade.php index 94817e06d1..f8e0227fe3 100644 --- a/src/resources/views/crud/datatable/datatables_logic.blade.php +++ b/src/resources/views/crud/datatable/datatables_logic.blade.php @@ -113,7 +113,6 @@ // Initialize the tables object to store multiple table instances window.crud.tables = window.crud.tables || {}; -// Create a default table configuration that can be extended for specific tables window.crud.defaultTableConfig = { functionsToRunOnDataTablesDrawEvent: [], addFunctionToDataTablesDrawEventQueue: function (functionName) { @@ -384,147 +383,120 @@ functionsToRunOnDataTablesDrawEvent: [], @include('crud::inc.export_buttons') -@endif + }); + + $(`#${tableId}_wrapper .dt-buttons`).appendTo($('#datatable_button_stack')); + $('.dt-buttons').addClass('d-xs-block') + .addClass('d-sm-inline-block') + .addClass('d-md-inline-block') + .addClass('d-lg-inline-block'); + }; + + // Add the function to the draw event queue + window.crud.addFunctionToDataTablesDrawEventQueue('moveExportButtonsToTopRight'); + + @push('after_styles') + @basset('https://cdn.datatables.net/buttons/3.2.0/css/buttons.bootstrap5.min.css') + @endpush +@endif \ No newline at end of file From 641ce127a6dad96f258818a2dfdacab1605d2a1f Mon Sep 17 00:00:00 2001 From: pxpm Date: Wed, 7 May 2025 13:47:56 +0100 Subject: [PATCH 26/52] wip --- src/resources/assets/css/common.css | 60 +++++---------- .../crud/datatable/datatables_logic.blade.php | 73 +++++++++++-------- 2 files changed, 62 insertions(+), 71 deletions(-) diff --git a/src/resources/assets/css/common.css b/src/resources/assets/css/common.css index 0ac4f926be..577030cc21 100644 --- a/src/resources/assets/css/common.css +++ b/src/resources/assets/css/common.css @@ -134,19 +134,19 @@ opacity: 1 !important; } -.dataTables_wrapper .dataTables_info { +.dt-container .dataTables_info { clear: both; float: left; padding-top: 0.755em; } -.dataTables_wrapper .dataTables_paginate { +.dataTables_wrapper .dt-paginate { float: right; text-align: right; padding-top: 0.25em; } -.dataTables_wrapper .dataTables_paginate .paginate_button { +.dt-container .dt-paging .dt-paging-button { box-sizing: border-box; display: inline-block; min-width: 1em; @@ -161,8 +161,8 @@ font-size: 0.85em; } -.dataTables_wrapper .dataTables_paginate .paginate_button.current, -.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover { +.dt-container .dt-paging .dt-paging-button.current, +.dt-container .dt-paging .dt-paging-button.current:hover { color: #333 !important; border: 1px solid #979797; background-color: white; @@ -180,9 +180,9 @@ /* W3C */ } -.dataTables_wrapper .dataTables_paginate .paginate_button.disabled, -.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover, -.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active { +.dt-container .dt-paging .dt-paging-button.disabled, +.dt-container .dt-paging .dt-paging-button.disabled:hover, +.dt-container .dt-paging .dt-paging-button.disabled:active { cursor: default; color: #666 !important; border: 1px solid transparent; @@ -190,33 +190,15 @@ box-shadow: none; } -.dataTables_wrapper .dataTables_paginate .paginate_button:hover { - color: white !important; - border: 1px solid #111; - background-color: #585858; - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111)); - /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(top, #585858 0%, #111 100%); - /* Chrome10+,Safari5.1+ */ - background: -moz-linear-gradient(top, #585858 0%, #111 100%); - /* FF3.6+ */ - background: -ms-linear-gradient(top, #585858 0%, #111 100%); - /* IE10+ */ - background: -o-linear-gradient(top, #585858 0%, #111 100%); - /* Opera 11.10+ */ - background: linear-gradient(to bottom, #585858 0%, #111 100%); - /* W3C */ -} - -.dataTables_wrapper > div.table-footer > div:nth-child(1) > div.dt-length { +table.dataTable > div.table-footer > div:nth-child(1) > div.dt-length { display: flex; } -.dataTables_wrapper > div.table-footer > div:nth-child(1) > div.dt-length select { +table.dataTable > div.table-footer > div:nth-child(1) > div.dt-length select { width: 4rem; } -.dataTables_wrapper > div.table-footer > div:nth-child(1) > div.dt-length > label{ +table.dataTable > div.table-footer > div:nth-child(1) > div.dt-length > label{ white-space: nowrap; } @@ -224,7 +206,7 @@ div.dt-container div.dt-paging ul.pagination { justify-content: flex-end; } -.dataTables_wrapper .dataTables_paginate .paginate_button:active { +table.dataTable .dt-paging .dt-paging-button:active { outline: none; background-color: #2b2b2b; background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c)); @@ -242,18 +224,17 @@ div.dt-container div.dt-paging ul.pagination { box-shadow: inset 0 0 3px #111; } -.dataTables_wrapper .dataTables_paginate .ellipsis { +table.dataTable .dt-paging .ellipsis { padding: 0 1em; } -.dataTables_wrapper tr th:first-child, .dataTables_wrapper tr td:first-child, .dataTables_wrapper table.dataTable tr th:first-child, .dataTables_wrapper table.dataTable tr td:first-child { +table.dataTable tr th:first-child, table.dataTable tr td:first-child, table.dataTable tr th:first-child, table.dataTable tr td:first-child { align-items: center; padding-top: 1rem; padding-bottom: 1rem; padding-left: 0.6rem; } -/* Improved spinner display for DataTables */ .dataTables_processing, .dt-processing { background: rgba(255, 255, 255, 0.8) !important; @@ -261,25 +242,20 @@ div.dt-container div.dt-paging ul.pagination { border: none !important; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1) !important; border-radius: 5px !important; - /* Full table content coverage */ width: 100% !important; height: 100% !important; - /* Position it to cover the entire table content */ position: absolute !important; top: 0 !important; left: 0 !important; - /* Reset transform since we're using top/left 0 */ transform: none !important; margin: 0 !important; padding: 0 !important; z-index: 999 !important; - /* Center the spinner image using flex */ display: flex !important; justify-content: center !important; align-items: center !important; } -/* Make sure the spinner image displays properly */ .dataTables_processing img, .dt-processing img { display: block !important; @@ -290,19 +266,16 @@ div.dt-container div.dt-paging ul.pagination { margin: 0 auto !important; } -/* Hide any text that might be in the processing element */ .dataTables_processing > *:not(img), .dt-processing > *:not(img) { display: none !important; } -/* Ensure it's hidden when processing is done */ .dataTables_processing:not([style*="block"]), .dt-processing:not([style*="block"]) { display: none !important; } -/* Ensure proper positioning context */ .table-content { position: relative !important; } @@ -323,7 +296,6 @@ div.dt-container div.dt-paging ul.pagination { display: none !important; } -.dt-buttons, .dtr-modal .details-control, .modal .details-control { display: none; @@ -435,6 +407,10 @@ div.dt-container div.dt-paging ul.pagination { margin-bottom: 10px; } +.navbar-filters div.form-group { + padding-bottom: 0 !important; +} + .dataTables_filter label { margin-bottom: 0; } diff --git a/src/resources/views/crud/datatable/datatables_logic.blade.php b/src/resources/views/crud/datatable/datatables_logic.blade.php index 1f97deb1f4..322c0a548e 100644 --- a/src/resources/views/crud/datatable/datatables_logic.blade.php +++ b/src/resources/views/crud/datatable/datatables_logic.blade.php @@ -498,39 +498,44 @@ functionsToRunOnDataTablesDrawEvent: [], }; } } + + dataTableConfig.buttons = []; + if (window.crud.exportButtonsConfig) { + dataTableConfig.buttons = window.crud.exportButtonsConfig; + } // Update the Ajax URL with the current URL parameters -if (config.urlStart) { - const currentParams = new URLSearchParams(window.location.search); - const searchParams = currentParams.toString() ? '?' + currentParams.toString() : ''; - - // Get the complete datatablesUrl as a string without relying on PHP parsing - const fullDatatablesUrl = "{!! $crud->getOperationSetting('datatablesUrl') !!}"; - - // Extract query parameters from the full URL - const urlParts = fullDatatablesUrl.split('?'); - let datatableUrlParams = new URLSearchParams(''); - - if (urlParts.length > 1) { - datatableUrlParams = new URLSearchParams(urlParts[1]); - } + if (config.urlStart) { + const currentParams = new URLSearchParams(window.location.search); + const searchParams = currentParams.toString() ? '?' + currentParams.toString() : ''; - // Configure the ajax URL and data - const ajaxUrl = config.urlStart + '/search' + searchParams; - dataTableConfig.ajax = { - "url": ajaxUrl, - "type": "POST", - "data": function(d) { - // Add the totalEntryCount parameter - d.totalEntryCount = "{{$crud->getOperationSetting('totalEntryCount') ?? false}}"; - - // get the table ID from the current table we are initializing - d.datatable_id = tableId; - - return d; + // Get the complete datatablesUrl as a string without relying on PHP parsing + const fullDatatablesUrl = "{!! $crud->getOperationSetting('datatablesUrl') !!}"; + + // Extract query parameters from the full URL + const urlParts = fullDatatablesUrl.split('?'); + let datatableUrlParams = new URLSearchParams(''); + + if (urlParts.length > 1) { + datatableUrlParams = new URLSearchParams(urlParts[1]); } - }; -} + + // Configure the ajax URL and data + const ajaxUrl = config.urlStart + '/search' + searchParams; + dataTableConfig.ajax = { + "url": ajaxUrl, + "type": "POST", + "data": function(d) { + // Add the totalEntryCount parameter + d.totalEntryCount = "{{$crud->getOperationSetting('totalEntryCount') ?? false}}"; + + // get the table ID from the current table we are initializing + d.datatable_id = tableId; + + return d; + } + }; + } // Store the dataTableConfig in the config object for future reference window.crud.tableConfigs[tableId].dataTableConfig = dataTableConfig; @@ -614,6 +619,16 @@ function setupTableUI(tableId, config) { }); } + if (config.exportButtons && window.crud.exportButtonsConfig) { + // Add the export buttons to the DataTable configuration + window.crud.tables[tableId].buttons().add(window.crud.exportButtonsConfig); + + // Initialize the buttons and place them in the correct container + if (typeof window.crud.moveExportButtonsToTopRight === 'function') { + window.crud.moveExportButtonsToTopRight(tableId); + } +} + // move the bottom buttons before pagination $("#bottom_buttons").insertBefore($(`#${tableId}_wrapper .row:last-child`)); } From e94f20ea70de82c38cccbaf84b146ec3fcf0d173 Mon Sep 17 00:00:00 2001 From: pxpm Date: Thu, 8 May 2025 16:27:46 +0100 Subject: [PATCH 27/52] wip --- src/resources/assets/css/common.css | 7 +- .../views/crud/datatable/datatable.blade.php | 33 +- .../crud/datatable/datatables_logic.blade.php | 670 ++++++++---------- 3 files changed, 314 insertions(+), 396 deletions(-) diff --git a/src/resources/assets/css/common.css b/src/resources/assets/css/common.css index 577030cc21..f06d77e30a 100644 --- a/src/resources/assets/css/common.css +++ b/src/resources/assets/css/common.css @@ -351,12 +351,7 @@ table.dataTable tr th:first-child, table.dataTable tr td:first-child, table.data padding: 0 !important; } -.dataTables_wrapper.no-footer .dataTables_scrollBody { - border-bottom: 1px solid #111; -} - -.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable, -.dataTables_wrapper.no-footer div.dataTables_scrollBody>table { +div.dt-scroll-body { border-bottom: none; } diff --git a/src/resources/views/crud/datatable/datatable.blade.php b/src/resources/views/crud/datatable/datatable.blade.php index d4521041d3..81e5de2785 100644 --- a/src/resources/views/crud/datatable/datatable.blade.php +++ b/src/resources/views/crud/datatable/datatable.blade.php @@ -1,21 +1,24 @@ +@php + // Define the table ID - use the provided tableId or default to 'crudTable' + $tableId = $tableId ?? 'crudTable'; +@endphp +
@if ( $crud->buttons()->where('stack', 'top')->count() || $crud->exportButtons())
- @include('crud::inc.button_stack', ['stack' => 'top']) -
@endif
@if($crud->getOperationSetting('searchableTable'))
-
+
- +
@@ -29,7 +32,7 @@
getOperationSetting('lineButtonsAsDropdownMinimum') }}" data-line-buttons-as-dropdown-show-before-dropdown="{{ (int) $crud->getOperationSetting('lineButtonsAsDropdownShowBefore') }}" data-url-start="{{ url($crud->getOperationSetting('datatablesUrl')) }}" + data-responsive-table="{{ $crud->getResponsiveTable() ? 'true' : 'false' }}" + data-persistent-table="{{ $crud->getPersistentTable() ? 'true' : 'false' }}" + data-persistent-table-slug="{{ Str::slug($crud->getOperationSetting('datatablesUrl')) }}" + data-persistent-table-duration="{{ $crud->getPersistentTableDuration() ?: '' }}" + data-subheading="{{ $crud->getSubheading() ? 'true' : 'false' }}" + data-reset-button="{{ ($crud->getOperationSetting('resetButton') ?? true) ? 'true' : 'false' }}" + data-updates-url="{{ var_export($updatesUrl ?? true) }}" + data-export-buttons="{{ json_encode($crud->get('list.export_buttons') ?? []) }}" + data-default-page-length="{{ $crud->getDefaultPageLength() }}" + data-page-length-menu="{{ json_encode($crud->getPageLengthMenu()) }}" + data-show-entry-count="{{ var_export($crud->getOperationSetting('showEntryCount') ?? true) }}" + data-searchable-table="{{ var_export($crud->getOperationSetting('searchableTable') ?? true) }}" + data-search-delay="{{ $crud->getOperationSetting('searchDelay') ?? 500 }}" + data-total-entry-count="{{ var_export($crud->getOperationSetting('totalEntryCount') ?? false) }}" cellspacing="0"> @@ -117,14 +134,14 @@ class="{{ backpack_theme_config('classes.table') ?? 'table table-striped table-h @endif - @section('after_styles') +@section('after_styles') {{-- CRUD LIST CONTENT - crud_list_styles stack --}} @stack('crud_list_styles') @endsection @section('after_scripts') - @include('crud::datatable.datatables_logic', ['tableId' => $tableId ?? 'crudTable']) + @include('crud::datatable.datatables_logic', ['tableId' => $tableId]) {{-- CRUD LIST CONTENT - crud_list_scripts stack --}} @stack('crud_list_scripts') -@endsection +@endsection \ No newline at end of file diff --git a/src/resources/views/crud/datatable/datatables_logic.blade.php b/src/resources/views/crud/datatable/datatables_logic.blade.php index 322c0a548e..ed8f2209e1 100644 --- a/src/resources/views/crud/datatable/datatables_logic.blade.php +++ b/src/resources/views/crud/datatable/datatables_logic.blade.php @@ -3,9 +3,6 @@ // and flush them from session, so we will get them later from localStorage. $backpack_alerts = \Alert::getMessages(); \Alert::flush(); - -// Define the table ID - use the provided tableId or default to 'crudTable' -$tableId = $tableId ?? 'crudTable'; @endphp {{-- DATA TABLES SCRIPT --}} @@ -22,29 +19,8 @@ @endpush -@include('crud::inc.export_buttons') +window.crud.addFunctionToDataTablesDrawEventQueue = function(functionName) { + window.crud.defaultTableConfig.addFunctionToDataTablesDrawEventQueue(functionName); +}; +window.crud.responsiveToggle = window.crud.defaultTableConfig.responsiveToggle; +window.crud.executeFunctionByName = window.crud.defaultTableConfig.executeFunctionByName; +window.crud.updateUrl = window.crud.defaultTableConfig.updateUrl; - -@include('crud::inc.details_row_logic') +@include('crud::inc.export_buttons') +@include('crud::inc.details_row_logic') \ No newline at end of file From 5a719150096bcfed904d0346305cc871ba1331bb Mon Sep 17 00:00:00 2001 From: pxpm Date: Mon, 12 May 2025 10:52:49 +0100 Subject: [PATCH 28/52] fix responsive table --- .../crud/datatable/datatables_logic.blade.php | 67 ++++++++++--------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/src/resources/views/crud/datatable/datatables_logic.blade.php b/src/resources/views/crud/datatable/datatables_logic.blade.php index ed8f2209e1..bf15421671 100644 --- a/src/resources/views/crud/datatable/datatables_logic.blade.php +++ b/src/resources/views/crud/datatable/datatables_logic.blade.php @@ -198,7 +198,8 @@ functionsToRunOnDataTablesDrawEvent: [], config.hasLineButtonsAsDropdown = tableElement.getAttribute('data-has-line-buttons-as-dropdown') === 'true' || tableElement.getAttribute('data-has-line-buttons-as-dropdown') === '1'; config.lineButtonsAsDropdownMinimum = parseInt(tableElement.getAttribute('data-line-buttons-as-dropdown-minimum')) || 3; config.lineButtonsAsDropdownShowBeforeDropdown = parseInt(tableElement.getAttribute('data-line-buttons-as-dropdown-show-before-dropdown')) || 1; - + config.responsiveTable = tableElement.getAttribute('data-responsive-table') === 'true' || tableElement.getAttribute('data-responsive-table') === '1'; + // Apply any custom config if (customConfig && Object.keys(customConfig).length > 0) { Object.assign(config, customConfig); @@ -275,37 +276,37 @@ functionsToRunOnDataTablesDrawEvent: [], lengthMenu: config.pageLengthMenu, aaSorting: [], language: { - emptyTable: "No entries found", - info: "Showing _START_ to _END_ of _TOTAL_ entries", - infoEmpty: "Showing 0 to 0 of 0 entries", - infoFiltered: "(filtered from _MAX_ total entries)", - infoPostFix: "", - thousands: ",", - lengthMenu: "_MENU_ entries per page", - loadingRecords: "Loading...", - "processing": "{{ trans(", - search: "_INPUT_", - searchPlaceholder: "Search...", - zeroRecords: "No matching records found", - paginate: { - first: "«", - last: "»", - next: '', - previous: '' - }, - aria: { - sortAscending: ": activate to sort column ascending", - sortDescending: ": activate to sort column descending" - }, - buttons: { - copy: "Copy", - excel: "Excel", - csv: "CSV", - pdf: "PDF", - print: "Print", - colvis: "Column visibility" - }, - }, + "emptyTable": "{{ trans('backpack::crud.emptyTable') }}", + "info": "{{ trans('backpack::crud.info') }}", + "infoEmpty": "{{ trans('backpack::crud.infoEmpty') }}", + "infoFiltered": "{{ trans('backpack::crud.infoFiltered') }}", + "infoPostFix": "{{ trans('backpack::crud.infoPostFix') }}", + "thousands": "{{ trans('backpack::crud.thousands') }}", + "lengthMenu": "{{ trans('backpack::crud.lengthMenu') }}", + "loadingRecords": "{{ trans('backpack::crud.loadingRecords') }}", + "processing": "{{ trans(", + "search": "_INPUT_", + "searchPlaceholder": "{{ trans('backpack::crud.search') }}...", + "zeroRecords": "{{ trans('backpack::crud.zeroRecords') }}", + "paginate": { + "first": "{{ trans('backpack::crud.paginate.first') }}", + "last": "{{ trans('backpack::crud.paginate.last') }}", + "next": '', + "previous": '' + }, + "aria": { + "sortAscending": "{{ trans('backpack::crud.aria.sortAscending') }}", + "sortDescending": "{{ trans('backpack::crud.aria.sortDescending') }}" + }, + "buttons": { + "copy": "{{ trans('backpack::crud.export.copy') }}", + "excel": "{{ trans('backpack::crud.export.excel') }}", + "csv": "{{ trans('backpack::crud.export.csv') }}", + "pdf": "{{ trans('backpack::crud.export.pdf') }}", + "print": "{{ trans('backpack::crud.export.print') }}", + "colvis": "{{ trans('backpack::crud.export.column_visibility') }}" + }, + }, dom: "<'row hidden'<'col-sm-6'i><'col-sm-6 d-print-none'f>>" + "<'table-content row'<'col-sm-12'tr>>" + @@ -547,6 +548,8 @@ function setupTableEvents(tableId, config) { // on DataTable draw event run all functions in the queue $(`#${tableId}`).on('draw.dt', function() { + $(`#${tableId} tbody tr td[colspan]`).attr('colspan', '1'); + // in datatables 2.0.3 the implementation was changed to use `replaceChildren`, for that reason scripts // that came with the response are no longer executed, like the delete button script or any other ajax // button created by the developer. For that reason, we move them to the end of the body From f4a0aea12f66a9cf3032a1b08070c9697b704bb8 Mon Sep 17 00:00:00 2001 From: pxpm Date: Mon, 12 May 2025 12:21:49 +0100 Subject: [PATCH 29/52] wip --- src/BackpackServiceProvider.php | 4 ++-- src/CrudManager.php | 2 +- src/app/Library/CrudPanel/CrudPanel.php | 8 ++++---- src/app/Library/CrudPanel/CrudPanelFacade.php | 2 +- src/app/Library/CrudPanel/Hooks/LifecycleHooks.php | 3 ++- src/app/Library/Datatable/Datatable.php | 2 +- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/BackpackServiceProvider.php b/src/BackpackServiceProvider.php index fbb4b38383..d0b4740d34 100644 --- a/src/BackpackServiceProvider.php +++ b/src/BackpackServiceProvider.php @@ -87,7 +87,7 @@ public function register() return CrudManager::getCrudPanel(); }); - $this->app->scoped('crud-manager', function ($app) { + $this->app->scoped('CrudManager', function ($app) { return new CrudPanelManager(); }); @@ -337,7 +337,7 @@ public function loadHelpers() */ public function provides() { - return ['widgets', 'BackpackViewNamespaces', 'DatabaseSchema', 'UploadersRepository', 'crud-manager']; + return ['widgets', 'BackpackViewNamespaces', 'DatabaseSchema', 'UploadersRepository', 'CrudManager']; } private function registerBackpackErrorViews() diff --git a/src/CrudManager.php b/src/CrudManager.php index 23add9c02d..6f9ee1f885 100644 --- a/src/CrudManager.php +++ b/src/CrudManager.php @@ -11,6 +11,6 @@ class CrudManager extends Facade { protected static function getFacadeAccessor() { - return 'crud-manager'; + return 'CrudManager'; } } diff --git a/src/app/Library/CrudPanel/CrudPanel.php b/src/app/Library/CrudPanel/CrudPanel.php index 3e1a8fdeae..397dd7cf91 100644 --- a/src/app/Library/CrudPanel/CrudPanel.php +++ b/src/app/Library/CrudPanel/CrudPanel.php @@ -68,7 +68,7 @@ class CrudPanel public $initialized = false; - public $crudController; + public $controller; // The following methods are used in CrudController or your EntityCrudController to manipulate the variables above. @@ -84,7 +84,7 @@ public function isInitialized() public function initialize(string $controller, $request): self { $this->setRequest($request); - $this->setCrudController($controller); + $this->setController($controller); return $this; } @@ -158,9 +158,9 @@ private function getSchema() return $this->getModel()->getConnection()->getSchemaBuilder(); } - public function setCrudController(string $crudController) + public function setController(string $crudController) { - $this->crudController = $crudController; + $this->controller = $crudController; } /** diff --git a/src/app/Library/CrudPanel/CrudPanelFacade.php b/src/app/Library/CrudPanel/CrudPanelFacade.php index 811097d96d..b24159d9cc 100644 --- a/src/app/Library/CrudPanel/CrudPanelFacade.php +++ b/src/app/Library/CrudPanel/CrudPanelFacade.php @@ -7,7 +7,7 @@ /** * This object allows developers to use CRUD::addField() instead of $this->crud->addField(), * by providing a Facade that leads to the CrudPanel object. That object is stored in Laravel's - * service container as 'backpack.crud'. + * service container as 'crud'. */ /** * @codeCoverageIgnore diff --git a/src/app/Library/CrudPanel/Hooks/LifecycleHooks.php b/src/app/Library/CrudPanel/Hooks/LifecycleHooks.php index 9190a258fb..7b8ca0bc73 100644 --- a/src/app/Library/CrudPanel/Hooks/LifecycleHooks.php +++ b/src/app/Library/CrudPanel/Hooks/LifecycleHooks.php @@ -11,8 +11,9 @@ final class LifecycleHooks public function hookInto(string|array $hooks, callable $callback): void { $hooks = is_array($hooks) ? $hooks : [$hooks]; + $controller = CrudManager::getActiveController() ?? CrudManager::getRequestController(); foreach ($hooks as $hook) { - $this->hooks[CrudManager::getActiveController() ?? CrudManager::getRequestController()][$hook][] = $callback; + $this->hooks[$controller][$hook][] = $callback; } } diff --git a/src/app/Library/Datatable/Datatable.php b/src/app/Library/Datatable/Datatable.php index e61319be9a..ff9b2931be 100644 --- a/src/app/Library/Datatable/Datatable.php +++ b/src/app/Library/Datatable/Datatable.php @@ -61,7 +61,7 @@ private function storeDatatableConfig() if ($parentCrud && $parentCrud->getCurrentEntry()) { $parentEntry = $parentCrud->getCurrentEntry(); - $parentController = $parentCrud->crudController; + $parentController = $parentCrud->controller; $cacheKey = 'datatable_config_'.$this->tableId; // Store the controller class, parent entry, element type and name From 7cc007d568c1a5e98b60956ed3748e6d8a97da2b Mon Sep 17 00:00:00 2001 From: pxpm Date: Mon, 12 May 2025 12:29:05 +0100 Subject: [PATCH 30/52] wip --- src/app/Library/CrudPanel/CrudButton.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app/Library/CrudPanel/CrudButton.php b/src/app/Library/CrudPanel/CrudButton.php index cc95173ba1..98f0ce667d 100644 --- a/src/app/Library/CrudPanel/CrudButton.php +++ b/src/app/Library/CrudPanel/CrudButton.php @@ -2,8 +2,6 @@ namespace Backpack\CRUD\app\Library\CrudPanel; -use Backpack\CRUD\app\Http\Controllers\CrudController; -use Backpack\CRUD\Backpack; use Backpack\CRUD\ViewNamespaces; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Support\Traits\Conditionable; From e3b5759c08148dde583b974ca67c303efd3f15a8 Mon Sep 17 00:00:00 2001 From: pxpm Date: Mon, 12 May 2025 16:38:21 +0100 Subject: [PATCH 31/52] fix pagination --- src/app/Http/Controllers/Operations/ListOperation.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/Http/Controllers/Operations/ListOperation.php b/src/app/Http/Controllers/Operations/ListOperation.php index fec9c3dae8..83003bdb19 100644 --- a/src/app/Http/Controllers/Operations/ListOperation.php +++ b/src/app/Http/Controllers/Operations/ListOperation.php @@ -110,10 +110,10 @@ public function search() $this->crud->applyDatatableOrder(); $entries = $this->crud->getEntries(); - + $requestTotalEntryCount = request()->get('totalEntryCount') ? (int) request()->get('totalEntryCount') : null; // if show entry count is disabled we use the "simplePagination" technique to move between pages. if ($this->crud->getOperationSetting('showEntryCount')) { - $totalEntryCount = (int) (request()->get('totalEntryCount') ?: $this->crud->getTotalQueryCount()); + $totalEntryCount = (int) ($requestTotalEntryCount ?: $this->crud->getTotalQueryCount()); $filteredEntryCount = $this->crud->getFilteredQueryCount() ?? $totalEntryCount; } else { $totalEntryCount = $length; From 878ac24b842acfecbb2a1e419b0b52e6a7fed9a1 Mon Sep 17 00:00:00 2001 From: pxpm Date: Tue, 20 May 2025 13:04:03 +0100 Subject: [PATCH 32/52] wip --- src/resources/assets/css/common.css | 4 +- .../inc/bulk_actions_checkbox.blade.php | 58 +++---- .../views/crud/datatable/datatable.blade.php | 5 +- .../crud/datatable/datatables_logic.blade.php | 30 ++-- .../crud/inc/details_row_logic.blade.php | 145 ++++++++++-------- 5 files changed, 135 insertions(+), 107 deletions(-) diff --git a/src/resources/assets/css/common.css b/src/resources/assets/css/common.css index f06d77e30a..a51d3cebed 100644 --- a/src/resources/assets/css/common.css +++ b/src/resources/assets/css/common.css @@ -259,8 +259,8 @@ table.dataTable tr th:first-child, table.dataTable tr td:first-child, table.data .dataTables_processing img, .dt-processing img { display: block !important; - width: 60px !important; - height: 60px !important; + width: 30px !important; + height: 30px !important; opacity: 1 !important; visibility: visible !important; margin: 0 auto !important; diff --git a/src/resources/views/crud/columns/inc/bulk_actions_checkbox.blade.php b/src/resources/views/crud/columns/inc/bulk_actions_checkbox.blade.php index 4774ada5b6..7b64f03443 100644 --- a/src/resources/views/crud/columns/inc/bulk_actions_checkbox.blade.php +++ b/src/resources/views/crud/columns/inc/bulk_actions_checkbox.blade.php @@ -6,14 +6,15 @@ - +@endif @bassetBlock('backpack/crud/operations/list/bulk-actions-checkbox.js') + window.crud.addFunctionToDataTablesDrawEventQueue('addOrRemoveCrudCheckedItem'); + window.crud.addFunctionToDataTablesDrawEventQueue('markCheckboxAsCheckedIfPreviouslySelected'); + window.crud.addFunctionToDataTablesDrawEventQueue('addBulkActionMainCheckboxesFunctionality'); + window.crud.addFunctionToDataTablesDrawEventQueue('enableOrDisableBulkButtons'); + @endBassetBlock -@endif diff --git a/src/resources/views/crud/datatable/datatable.blade.php b/src/resources/views/crud/datatable/datatable.blade.php index 81e5de2785..ece44657c8 100644 --- a/src/resources/views/crud/datatable/datatable.blade.php +++ b/src/resources/views/crud/datatable/datatable.blade.php @@ -29,7 +29,6 @@ @if ($crud->filtersEnabled()) @include('crud::inc.filters_navbar') @endif -
getSubheading() ? 'true' : 'false' }}" data-reset-button="{{ ($crud->getOperationSetting('resetButton') ?? true) ? 'true' : 'false' }}" data-updates-url="{{ var_export($updatesUrl ?? true) }}" - data-export-buttons="{{ json_encode($crud->get('list.export_buttons') ?? []) }}" + data-has-export-buttons="{{ var_export($crud->get('list.exportButtons') ?? false) }}" data-default-page-length="{{ $crud->getDefaultPageLength() }}" data-page-length-menu="{{ json_encode($crud->getPageLengthMenu()) }}" data-show-entry-count="{{ var_export($crud->getOperationSetting('showEntryCount') ?? true) }}" @@ -141,6 +140,8 @@ class="{{ backpack_theme_config('classes.table') ?? 'table table-striped table-h @section('after_scripts') @include('crud::datatable.datatables_logic', ['tableId' => $tableId]) + @include('crud::inc.export_buttons') + @include('crud::inc.details_row_logic') {{-- CRUD LIST CONTENT - crud_list_scripts stack --}} @stack('crud_list_scripts') diff --git a/src/resources/views/crud/datatable/datatables_logic.blade.php b/src/resources/views/crud/datatable/datatables_logic.blade.php index bf15421671..740836a04d 100644 --- a/src/resources/views/crud/datatable/datatables_logic.blade.php +++ b/src/resources/views/crud/datatable/datatables_logic.blade.php @@ -182,11 +182,9 @@ functionsToRunOnDataTablesDrawEvent: [], // Parse complex JSON structures from data attributes try { - config.exportButtons = JSON.parse(tableElement.getAttribute('data-export-buttons') || '[]'); config.pageLengthMenu = JSON.parse(tableElement.getAttribute('data-page-length-menu') || '[[10, 25, 50, 100], [10, 25, 50, 100]]'); } catch (e) { console.error(`Error parsing JSON data attributes for table ${tableId}:`, e); - config.exportButtons = []; config.pageLengthMenu = [[10, 25, 50, 100], [10, 25, 50, 100]]; } @@ -199,7 +197,7 @@ functionsToRunOnDataTablesDrawEvent: [], config.lineButtonsAsDropdownMinimum = parseInt(tableElement.getAttribute('data-line-buttons-as-dropdown-minimum')) || 3; config.lineButtonsAsDropdownShowBeforeDropdown = parseInt(tableElement.getAttribute('data-line-buttons-as-dropdown-show-before-dropdown')) || 1; config.responsiveTable = tableElement.getAttribute('data-responsive-table') === 'true' || tableElement.getAttribute('data-responsive-table') === '1'; - + config.exportButtons = tableElement.getAttribute('data-has-export-buttons') === 'true'; // Apply any custom config if (customConfig && Object.keys(customConfig).length > 0) { Object.assign(config, customConfig); @@ -407,12 +405,11 @@ functionsToRunOnDataTablesDrawEvent: [], } // Configure export buttons if present - if (config.exportButtons && config.exportButtons.length > 0) { - if (window.crud.exportButtonsConfig) { - dataTableConfig.buttons = window.crud.exportButtonsConfig; - } + if (config.exportButtons) { + dataTableConfig.buttons = window.crud.exportButtonsConfig; } + // Configure ajax for server-side processing if (config.urlStart) { const currentParams = new URLSearchParams(window.location.search); @@ -509,17 +506,19 @@ function setupTableUI(tableId, config) { } }); } - - if (config.exportButtons && config.exportButtons.length > 0 && window.crud.exportButtonsConfig) { + console.log('config.exportButtons', config.exportButtons); + console.log(window.crud); + if (config.exportButtons && window.crud.exportButtonsConfig) { // Add the export buttons to the DataTable configuration - window.crud.tables[tableId].buttons().add(window.crud.exportButtonsConfig); - + new $.fn.dataTable.Buttons(window.crud.tables[tableId], { + buttons: window.crud.exportButtonsConfig + }); // Initialize the buttons and place them in the correct container if (typeof window.crud.moveExportButtonsToTopRight === 'function') { window.crud.moveExportButtonsToTopRight(tableId); } } - + console.log('Table initialized:', tableId); // move the bottom buttons before pagination $("#bottom_buttons").insertBefore($(`#${tableId}_wrapper .row:last-child`)); } @@ -562,12 +561,14 @@ function setupTableEvents(tableId, config) { // Run global functions window.crud.defaultTableConfig.functionsToRunOnDataTablesDrawEvent.forEach(function(functionName) { + console.log('Running function:', functionName); config.executeFunctionByName(functionName); }); // Run table-specific functions if (config.functionsToRunOnDataTablesDrawEvent && config.functionsToRunOnDataTablesDrawEvent.length) { config.functionsToRunOnDataTablesDrawEvent.forEach(function(functionName) { + console.log('Running table specific function:', functionName); config.executeFunctionByName(functionName); }); } @@ -740,7 +741,4 @@ function formatActionColumnAsDropdown(tableId) { } }); } - - -@include('crud::inc.export_buttons') -@include('crud::inc.details_row_logic') \ No newline at end of file + \ No newline at end of file diff --git a/src/resources/views/crud/inc/details_row_logic.blade.php b/src/resources/views/crud/inc/details_row_logic.blade.php index 24d4806175..66661dbcb4 100644 --- a/src/resources/views/crud/inc/details_row_logic.blade.php +++ b/src/resources/views/crud/inc/details_row_logic.blade.php @@ -1,73 +1,98 @@ @if ($crud->get('list.detailsRow')) - @endif From dae5da8f3d36213e0147cd015f2da96f592cb5fa Mon Sep 17 00:00:00 2001 From: pxpm Date: Wed, 21 May 2025 16:57:08 +0100 Subject: [PATCH 33/52] wip --- src/app/Library/Datatable/Datatable.php | 15 +- src/resources/assets/css/common.css | 431 ++++++++++++++++-- .../inc/bulk_actions_checkbox.blade.php | 343 ++++++++++---- .../views/crud/datatable/datatable.blade.php | 2 +- .../crud/datatable/datatables_logic.blade.php | 44 +- .../crud/inc/details_row_logic.blade.php | 102 +++-- .../views/crud/inc/export_buttons.blade.php | 2 +- .../views/crud/inc/filters_navbar.blade.php | 2 +- 8 files changed, 763 insertions(+), 178 deletions(-) diff --git a/src/app/Library/Datatable/Datatable.php b/src/app/Library/Datatable/Datatable.php index ff9b2931be..63e21df9f1 100644 --- a/src/app/Library/Datatable/Datatable.php +++ b/src/app/Library/Datatable/Datatable.php @@ -24,11 +24,7 @@ public function __construct( $this->crud ??= CrudManager::crudFromController($controller); - $controllerPart = str_replace('\\', '_', $this->controller); - $typePart = $this->type ?? 'default'; - $namePart = $this->name ?? 'default'; - $uniqueId = md5($controllerPart.'_'.$typePart.'_'.$namePart); - $this->tableId = 'crudTable_'.$uniqueId; + $this->tableId = $this->generateTableId(); if ($this->configure) { // Apply the configuration @@ -46,6 +42,15 @@ public function __construct( CrudManager::unsetActiveController($controller); } + private function generateTableId(): string + { + $controllerPart = str_replace('\\', '_', $this->controller); + $typePart = $this->type ?? 'default'; + $namePart = $this->name ?? 'default'; + $uniqueId = md5($controllerPart.'_'.$typePart.'_'.$namePart); + return 'crudTable_'.$uniqueId; + } + /** * Store the datatable configuration in the cache for later use in Ajax requests. */ diff --git a/src/resources/assets/css/common.css b/src/resources/assets/css/common.css index a51d3cebed..b823d9df8d 100644 --- a/src/resources/assets/css/common.css +++ b/src/resources/assets/css/common.css @@ -1,8 +1,402 @@ -/* -* -* Backpack Crud / public / common.css -* -*/ +:root { + --table-row-hover: #f2f1ff; +} + +.sidebar .nav-dropdown-items .nav-dropdown { + padding: 0 0 0 .8rem; +} + +.sidebar .nav-dropdown-items .nav-dropdown:not(.open) > a { + font-weight: normal !important; +} + +[dir="rtl"] .sidebar .nav-dropdown-items .nav-dropdown { + padding: 0 .8rem 0 0; +} + +form .form-group.required > label:not(:empty):not(.form-check-label)::after { + content: ' *'; + color: #ff0000; +} + +form .help-block { + margin-top: .25rem; + margin-bottom: .25rem; + color: #73818f; + font-size: 0.9em; +} + +form .nav-tabs .nav-link:hover { + color: #384c74; +} + +form .select2-container--bootstrap .select2-selection--single { + padding-top: 8px; + padding-bottom: 8px; + min-height: 38px; +} + +form .select2-container--bootstrap .select2-selection--multiple { + min-height: 38px; +} + +form .select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field { + min-height: 36px; +} + +form .select2-container--bootstrap .select2-selection--multiple .select2-selection__choice { + margin-top: 6px; +} + +form .select2-container--bootstrap .select2-selection--multiple .select2-selection__clear { + margin-top: 8px; +} + +form .select2-container--bootstrap .select2-selection { + border: none !important; +} + +form .select2.select2-container { + border: 1px solid rgba(0, 40, 100, 0.12) !important; +} + +/*Table - List View*/ +.dataTables_wrapper div.row .col-sm-12 { + position: relative; +} +.navbar-filters { + min-height: 25px; + border-radius: 0; + margin-bottom: 6px; + margin-top: 0; + background: transparent; + border-color: #f4f4f4; + border: none; +} + +.navbar-filters .navbar-collapse { + padding: 0; + border: 0; +} + +.navbar-filters .navbar-toggle { + padding: 10px 15px; + border-radius: 0; +} + +.navbar-filters .navbar-brand { + height: 25px; + padding: 5px 15px; + font-size: 14px; + text-transform: uppercase; +} + +.navbar-filters li { + margin: 0 2px; +} + +.navbar-filters li > a { + border-radius: 2px; +} + +.navbar-filters li > a:active, +.navbar-filters .navbar-nav > .active > a, +.navbar-filters .navbar-nav > .active > a:focus, +.navbar-filters .navbar-nav > .active > a:hover, +.navbar-filters .navbar-nav > .open > a, +.navbar-filters .navbar-nav > .open > a:focus, +.navbar-filters .navbar-nav > .open > a:hover { + background-color: #e4e7ea; + border-radius: 3px; +} + +.navbar-filters .nav.navbar-nav { + float: none; +} + +.navbar-filters .backpack-filter label { + color: #868686; + font-weight: 600; + text-transform: uppercase; +} + +@media (min-width: 768px) { + .navbar-filters .navbar-nav > li > a { + padding-top: 5px; + padding-bottom: 5px; + } +} + +@media (max-width: 768px) { + .navbar-filters .navbar-nav { + margin: 0; + } +} + +.dataTables_filter { + text-align: right; +} + +.dataTables_filter label { + font-weight: normal; + white-space: nowrap; + text-align: left; +} + +.dataTables_filter input { + display: inline-block; + width: auto; + border-radius: 25px; +} + +@media (max-width: 576px) { + .dataTables_filter label { + width: 100%; + } + + .dataTables_filter input[type="search"] { + width: 100%; + } +} + +.pagination > .disabled > a, +.pagination > .disabled > a:focus, +.pagination > .disabled > a:hover, +.pagination > .disabled > span, +.pagination > .disabled > span:focus, +.pagination > .disabled > span:hover { + background: transparent; +} + +.pagination > li > a { + background: transparent; + border: none; + border-radius: 5px; +} + +.pagination > li > span:hover { + background: white; +} + +.pagination > li:last-child > a, +.pagination > li:last-child > span, +.pagination > li:first-child > a, +.pagination > li:first-child > span { + border-radius: 5px; +} + +.box-body.table-responsive { + padding-left: 15px; + padding-right: 15px; +} + +.dt-buttons, +.dtr-modal .details-control, +.modal .details-control { + display: none; +} + +.dtr-bs-modal .modal-body { + padding: 0; +} + +.dtr-bs-modal .crud_bulk_actions_checkbox { + display: none; +} + +.content-wrapper { + min-height: calc(100% - 98px); +} + +.fixed .wrapper { + overflow: visible; +} + +/* SELECT 2 */ +.select2-container--bootstrap .select2-selection { + box-shadow: none !important; + border: 1px solid rgba(0, 40, 100, 0.12) !important; +} + +/* PACE JS */ + +.pace { + pointer-events: none; + -webkit-user-select: none; + user-select: none; +} + +.pace-inactive { + display: none; +} + +.pace .pace-progress { + background: var(--tblr-primary); + position: fixed; + z-index: 2000; + top: 0; + right: 100%; + width: 100%; + height: 2px; +} + +.alert a.alert-link { + color: inherit !important; + font-weight: 400; + text-decoration: underline !important; +} + +.noty_theme__backstrap.noty_bar { + margin: 4px 0; + overflow: hidden; + position: relative; + border: 1px solid transparent; + border-radius: .25rem; +} + +.noty_theme__backstrap.noty_bar .noty_body { + padding: .75rem 1.25rem; + font-weight: 300; +} + +.noty_theme__backstrap.noty_bar .noty_buttons { + padding: 10px; +} + +.noty_theme__backstrap.noty_bar .noty_close_button { + font-size: 1.5rem; + font-weight: 700; + line-height: 1; + color: #161C2D; + text-shadow: 0 1px 0 #FFFFFF; + filter: alpha(opacity=20); + opacity: .5; + background: transparent; +} + +.noty_theme__backstrap.noty_bar .noty_close_button:hover { + background: transparent; + text-decoration: none; + cursor: pointer; + filter: alpha(opacity=50); + opacity: .75; +} + +.noty_theme__backstrap.noty_type__note, +.noty_theme__backstrap.noty_type__notice, +.noty_theme__backstrap.noty_type__alert, +.noty_theme__backstrap.noty_type__notification { + background-color: #FFFFFF; + color: inherit; +} + +.noty_theme__backstrap.noty_type__warning { + color: #F9FBFD; + background-color: #ffc107; + border-color: #d39e00; +} + +.noty_theme__backstrap.noty_type__danger, +.noty_theme__backstrap.noty_type__error { + color: #F9FBFD; + background-color: #df4759; + border-color: #cf2438; +} + +.noty_theme__backstrap.noty_type__info, +.noty_theme__backstrap.noty_type__information { + color: #F9FBFD; + background-color: #467FD0; + border-color: #2e66b5; +} + +.noty_theme__backstrap.noty_type__success { + color: #F9FBFD; + background-color: #42ba96; + border-color: #359478; +} + +.noty_theme__backstrap.noty_type__primary { + color: #F9FBFD; + background-color: #7c69ef; + border-color: #543bea; +} + +.noty_theme__backstrap.noty_type__secondary { + color: #161C2D; + background-color: #D9E2EF; + border-color: #b5c7e0; +} + +.noty_theme__backstrap.noty_type__light { + color: #161C2D; + background-color: #F1F4F8; + border-color: #cfd9e7; +} + +.noty_theme__backstrap.noty_type__dark { + color: #F9FBFD; + background-color: #161C2D; + border-color: #05070b; +} + +/* Use whole table width for td when displaying empty content message */ +.crudTable_wrapper td.dataTables_empty { + display: table-cell !important; +} + +.navbar-filters span.select2-dropdown.select2-dropdown--below { + border: 1px solid var(--tblr-dropdown-border-color) !important; + border-top: none !important; + box-sizing: content-box; + box-shadow: none; +} + +.navbar-filters .select2-container--bootstrap.select2-container--below .select2-selection, +.navbar-filters .select2.select2-container { + border: none !important; +} + +.navbar-filters div.form-group { + padding-bottom: 0 !important; +} + +.navbar-filters, .dropdown-menu { + --tblr-dropdown-divider-margin-y: .4rem !important; +} + +/* ERRORS */ +.error_number { + font-size: 156px; + font-weight: 600; + line-height: 100px; +} +.error_number small { + font-size: 56px; + font-weight: 700; +} + +.error_number hr { + margin-top: 60px; + margin-bottom: 0; + width: 50px; +} + +.error_title { + margin-top: 40px; + font-size: 36px; + font-weight: 400; +} + +.error_description { + font-size: 24px; + font-weight: 400; +} + +/* Summernote */ +.note-editor.note-frame.fullscreen { + background-color: white; +} .dataTables_wrapper .dt-buttons .dt-button-collection { width: auto; @@ -426,33 +820,6 @@ div.dt-scroll-body { font-size: 0.875rem; } -.form-group.required>label:not(:empty):not(.form-check-label)::after { - content: ' *'; - color: #ff0000; -} - -.form-group.required>label:empty::after { - display: inline-block; - content: '*'; - color: #ff0000; -} - -.tab-container .nav-tabs .nav-item .nav-link.active { - background-color: #fff; -} - -.tab-container .nav-tabs .nav-item .nav-link:not(.active) { - background-color: #f4f4f4; - color: #c3c3c3; -} - -.tab-container .tab-pane { - padding: 10px; - border-left: 1px solid #ddd; - border-right: 1px solid #ddd; - border-bottom: 1px solid #ddd; -} - .hidden { display: none !important; visibility: hidden !important; diff --git a/src/resources/views/crud/columns/inc/bulk_actions_checkbox.blade.php b/src/resources/views/crud/columns/inc/bulk_actions_checkbox.blade.php index 7b64f03443..38d3163652 100644 --- a/src/resources/views/crud/columns/inc/bulk_actions_checkbox.blade.php +++ b/src/resources/views/crud/columns/inc/bulk_actions_checkbox.blade.php @@ -7,125 +7,300 @@ @endif +@push('after_scripts') @bassetBlock('backpack/crud/operations/list/bulk-actions-checkbox.js') - - @endBassetBlock +@endBassetBlock +@endpush \ No newline at end of file diff --git a/src/resources/views/crud/datatable/datatable.blade.php b/src/resources/views/crud/datatable/datatable.blade.php index ece44657c8..c4eed959d6 100644 --- a/src/resources/views/crud/datatable/datatable.blade.php +++ b/src/resources/views/crud/datatable/datatable.blade.php @@ -27,7 +27,7 @@ {{-- Backpack List Filters --}} @if ($crud->filtersEnabled()) - @include('crud::inc.filters_navbar') + @include('crud::inc.filters_navbar', ['componentId' => $tableId]) @endif
get('list.detailsRow')) +@if($crud->get('list.detailsRow')) -@endif + + // Also run immediately for any tables already in the DOM + document.addEventListener('DOMContentLoaded', function() { + if (typeof window.crud !== 'undefined') { + if (window.crud.tables) { + // For multiple tables + Object.keys(window.crud.tables).forEach(tableId => { + window.registerDetailsRowButtonAction(tableId); + }); + } + } + }); + + + +@endif \ No newline at end of file diff --git a/src/resources/views/crud/inc/export_buttons.blade.php b/src/resources/views/crud/inc/export_buttons.blade.php index d01551664f..6399a8033d 100644 --- a/src/resources/views/crud/inc/export_buttons.blade.php +++ b/src/resources/views/crud/inc/export_buttons.blade.php @@ -157,7 +157,7 @@ }; // Add the function to the draw event queue - window.crud.addFunctionToDataTablesDrawEventQueue('moveExportButtonsToTopRight'); + window.crud.defaultTableConfig.addFunctionToDataTablesDrawEventQueue('moveExportButtonsToTopRight'); @push('after_styles') @basset('https://cdn.datatables.net/buttons/3.2.0/css/buttons.bootstrap5.min.css') diff --git a/src/resources/views/crud/inc/filters_navbar.blade.php b/src/resources/views/crud/inc/filters_navbar.blade.php index 66b4c13f5c..9d8b5f81c2 100644 --- a/src/resources/views/crud/inc/filters_navbar.blade.php +++ b/src/resources/views/crud/inc/filters_navbar.blade.php @@ -1,4 +1,4 @@ -
+ + + {{-- Table columns --}} + @foreach ($crud->columns() as $column) + @php + $exportOnlyColumn = $column['exportOnlyColumn'] ?? false; + $visibleInTable = $column['visibleInTable'] ?? ($exportOnlyColumn ? false : true); + $visibleInModal = $column['visibleInModal'] ?? ($exportOnlyColumn ? false : true); + $visibleInExport = $column['visibleInExport'] ?? true; + $forceExport = $column['forceExport'] ?? (isset($column['exportOnlyColumn']) ? true : false); + @endphp + + @endforeach + + @if ( $crud->buttons()->where('stack', 'line')->count() ) + + @endif + + + + + + + {{-- Table columns --}} + @foreach ($crud->columns() as $column) + + @endforeach + + @if ( $crud->buttons()->where('stack', 'line')->count() ) + + @endif + + +
if developer forced column to be in the table with 'visibleInTable => true' + data-visible => regular visibility of the column + data-can-be-visible-in-table => prevents the column to be visible into the table (export-only) + data-visible-in-modal => if column appears on responsive modal + data-visible-in-export => if this column is exportable + data-force-export => force export even if columns are hidden + --}} + + data-visible="{{ $exportOnlyColumn ? 'false' : var_export($visibleInTable) }}" + data-visible-in-table="{{ var_export($visibleInTable) }}" + data-can-be-visible-in-table="{{ $exportOnlyColumn ? 'false' : 'true' }}" + data-visible-in-modal="{{ var_export($visibleInModal) }}" + data-visible-in-export="{{ $exportOnlyColumn ? 'true' : ($visibleInExport ? 'true' : 'false') }}" + data-force-export="{{ var_export($forceExport) }}" + > + {{-- Bulk checkbox --}} + @if($loop->first && $crud->getOperationSetting('bulkActions')) + {!! View::make('crud::columns.inc.bulk_actions_checkbox')->render() !!} + @endif + {!! $column['label'] !!} + {{ trans('backpack::crud.actions') }}
+ {{-- Bulk checkbox --}} + @if($loop->first && $crud->getOperationSetting('bulkActions')) + {!! View::make('crud::columns.inc.bulk_actions_checkbox')->render() !!} + @endif + {!! $column['label'] !!} + {{ trans('backpack::crud.actions') }}
+
+ +@if ( $crud->buttons()->where('stack', 'bottom')->count() ) +
+ @include('crud::inc.button_stack', ['stack' => 'bottom']) + +
+@endif + +@section('after_styles') + {{-- CRUD LIST CONTENT - crud_list_styles stack --}} + @stack('crud_list_styles') +@endsection + +@section('after_scripts') + @include('crud::components.datatable.datatable_logic', ['tableId' => $tableId]) + @include('crud::inc.export_buttons') + @include('crud::inc.details_row_logic') + + {{-- CRUD LIST CONTENT - crud_list_scripts stack --}} + @stack('crud_list_scripts') +@endsection diff --git a/src/resources/views/crud/components/datatable/datatable_logic.blade.php b/src/resources/views/crud/components/datatable/datatable_logic.blade.php new file mode 100644 index 0000000000..c5e5719dd7 --- /dev/null +++ b/src/resources/views/crud/components/datatable/datatable_logic.blade.php @@ -0,0 +1,750 @@ +@php +// as it is possible that we can be redirected with persistent table we save the alerts in a variable +// and flush them from session, so we will get them later from localStorage. +$backpack_alerts = \Alert::getMessages(); +\Alert::flush(); +@endphp + +{{-- DATA TABLES SCRIPT --}} +@basset("https://cdn.datatables.net/2.1.8/js/dataTables.min.js") +@basset("https://cdn.datatables.net/2.1.8/js/dataTables.bootstrap5.min.js") +@basset("https://cdn.datatables.net/responsive/3.0.3/js/dataTables.responsive.min.js") +@basset('https://cdn.datatables.net/fixedheader/4.0.1/js/dataTables.fixedHeader.min.js') +@basset(base_path('vendor/backpack/crud/src/resources/assets/img/spinner.svg'), false) + +@push('before_styles') + @basset('https://cdn.datatables.net/2.1.8/css/dataTables.bootstrap5.min.css') + @basset("https://cdn.datatables.net/responsive/3.0.3/css/responsive.dataTables.min.css") + @basset('https://cdn.datatables.net/fixedheader/4.0.1/css/fixedHeader.dataTables.min.css') +@endpush + + \ No newline at end of file diff --git a/src/resources/views/crud/datatable/datatable.blade.php b/src/resources/views/crud/datatable/datatable.blade.php index 266bd2f008..0ffa6dc390 100644 --- a/src/resources/views/crud/datatable/datatable.blade.php +++ b/src/resources/views/crud/datatable/datatable.blade.php @@ -139,7 +139,7 @@ class="{{ backpack_theme_config('classes.table') ?? 'table table-striped table-h @endsection @section('after_scripts') - @include('crud::datatable.datatable_logic', ['tableId' => $tableId]) + @include('crud::components.datatable.datatable_logic', ['tableId' => $tableId]) @include('crud::inc.export_buttons') @include('crud::inc.details_row_logic') diff --git a/src/resources/views/crud/list.blade.php b/src/resources/views/crud/list.blade.php index 831469e18f..c5be3eb89e 100644 --- a/src/resources/views/crud/list.blade.php +++ b/src/resources/views/crud/list.blade.php @@ -25,7 +25,7 @@ {{-- THE ACTUAL CONTENT --}}
- +
From bd958bb695890a5fcec44b8bca2801b93cb82d18 Mon Sep 17 00:00:00 2001 From: pxpm Date: Fri, 6 Jun 2025 14:27:27 +0100 Subject: [PATCH 50/52] wip --- src/BackpackServiceProvider.php | 1 + .../Controllers/Operations/ListOperation.php | 2 +- .../views/crud/widgets/datatable.blade.php | 18 ++++++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/resources/views/crud/widgets/datatable.blade.php diff --git a/src/BackpackServiceProvider.php b/src/BackpackServiceProvider.php index a7b3a0ed5e..c88cef27bc 100644 --- a/src/BackpackServiceProvider.php +++ b/src/BackpackServiceProvider.php @@ -77,6 +77,7 @@ public function register() $this->loadViewsWithFallbacks('crud'); $this->loadViewsWithFallbacks('ui', 'backpack.ui'); $this->loadViewNamespace('widgets', 'backpack.ui::widgets'); + ViewNamespaces::addFor('widgets', "crud::widgets"); $this->loadViewComponents(); diff --git a/src/app/Http/Controllers/Operations/ListOperation.php b/src/app/Http/Controllers/Operations/ListOperation.php index f899dc48e3..d6082fc4bd 100644 --- a/src/app/Http/Controllers/Operations/ListOperation.php +++ b/src/app/Http/Controllers/Operations/ListOperation.php @@ -78,7 +78,7 @@ public function search() // If there's a config closure in the cache for this CRUD, run that configuration closure. // This is done in order to allow the developer to configure the datatable component. - \Backpack\CRUD\app\Library\Datatable\Datatable::applyCachedSetupClosure($this->crud); + \Backpack\CRUD\app\View\Components\Datatable::applyCachedSetupClosure($this->crud); $this->crud->applyUnappliedFilters(); diff --git a/src/resources/views/crud/widgets/datatable.blade.php b/src/resources/views/crud/widgets/datatable.blade.php new file mode 100644 index 0000000000..5676af6c7e --- /dev/null +++ b/src/resources/views/crud/widgets/datatable.blade.php @@ -0,0 +1,18 @@ +@includeWhen(!empty($widget['wrapper']), backpack_view('widgets.inc.wrapper_start')) +
+ @if (isset($widget['content']['header'])) +
+
{!! $widget['content']['header'] !!}
+
+ @endif +
+ + {!! $widget['content']['body'] ?? '' !!} + +
+ +
+ +
+
+@includeWhen(!empty($widget['wrapper']), backpack_view('widgets.inc.wrapper_end')) \ No newline at end of file From 762f5131c8ac9043f9bbf87cf29ddd5152a45ec5 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Fri, 6 Jun 2025 13:27:48 +0000 Subject: [PATCH 51/52] Apply fixes from StyleCI [ci skip] [skip ci] --- src/BackpackServiceProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BackpackServiceProvider.php b/src/BackpackServiceProvider.php index c88cef27bc..621196948c 100644 --- a/src/BackpackServiceProvider.php +++ b/src/BackpackServiceProvider.php @@ -77,7 +77,7 @@ public function register() $this->loadViewsWithFallbacks('crud'); $this->loadViewsWithFallbacks('ui', 'backpack.ui'); $this->loadViewNamespace('widgets', 'backpack.ui::widgets'); - ViewNamespaces::addFor('widgets', "crud::widgets"); + ViewNamespaces::addFor('widgets', 'crud::widgets'); $this->loadViewComponents(); From a974b4586d18f44dc9e5ba2f1a9790e180b579a6 Mon Sep 17 00:00:00 2001 From: Pedro Martins Date: Mon, 9 Jun 2025 14:03:05 +0100 Subject: [PATCH 52/52] Create setup cache class and create the datatable cache class (#5807) Co-authored-by: StyleCI Bot --- .../Controllers/Operations/ListOperation.php | 3 +- src/app/Library/Support/DatatableCache.php | 203 ++++++++++++++++++ src/app/Library/Support/SetupCache.php | 114 ++++++++++ src/app/View/Components/Datatable.php | 124 +---------- 4 files changed, 329 insertions(+), 115 deletions(-) create mode 100644 src/app/Library/Support/DatatableCache.php create mode 100644 src/app/Library/Support/SetupCache.php diff --git a/src/app/Http/Controllers/Operations/ListOperation.php b/src/app/Http/Controllers/Operations/ListOperation.php index d6082fc4bd..f04a542568 100644 --- a/src/app/Http/Controllers/Operations/ListOperation.php +++ b/src/app/Http/Controllers/Operations/ListOperation.php @@ -3,6 +3,7 @@ namespace Backpack\CRUD\app\Http\Controllers\Operations; use Backpack\CRUD\app\Library\CrudPanel\Hooks\Facades\LifecycleHook; +use Backpack\CRUD\app\Library\Support\DatatableCache; use Illuminate\Support\Facades\Route; trait ListOperation @@ -78,7 +79,7 @@ public function search() // If there's a config closure in the cache for this CRUD, run that configuration closure. // This is done in order to allow the developer to configure the datatable component. - \Backpack\CRUD\app\View\Components\Datatable::applyCachedSetupClosure($this->crud); + DatatableCache::applyFromRequest($this->crud); $this->crud->applyUnappliedFilters(); diff --git a/src/app/Library/Support/DatatableCache.php b/src/app/Library/Support/DatatableCache.php new file mode 100644 index 0000000000..9912436a38 --- /dev/null +++ b/src/app/Library/Support/DatatableCache.php @@ -0,0 +1,203 @@ +cachePrefix = 'datatable_config_'; + $this->cacheDuration = 60; // 1 hour + } + + /** + * Cache setup closure for a datatable component. + * + * @param string $tableId The table ID to use as cache key + * @param string $controllerClass The controller class + * @param \Closure|null $setup The setup closure + * @param string|null $name The element name + * @param CrudPanel $crud The CRUD panel instance to update with datatable_id + * @return bool Whether the operation was successful + */ + public function cacheForComponent(string $tableId, string $controllerClass, ?\Closure $setup = null, ?string $name = null, ?CrudPanel $crud = null): bool + { + if (! $setup) { + return false; + } + + $cruds = CrudManager::getCrudPanels(); + $parentCrud = reset($cruds); + + if ($parentCrud && $parentCrud->getCurrentEntry()) { + $parentEntry = $parentCrud->getCurrentEntry(); + $parentController = $parentCrud->controller; + + // Store in cache + $this->store( + $tableId, + $controllerClass, + $parentController, + $parentEntry, + $name + ); + + // Set the datatable_id in the CRUD panel if provided + if ($crud) { + $crud->set('list.datatable_id', $tableId); + } + + return true; + } + + return false; + } + + public static function applyAndStoreSetupClosure( + string $tableId, + string $controllerClass, + \Closure $setupClosure, + ?string $name = null, + ?CrudPanel $crud = null, + $parentEntry = null + ): bool { + $instance = new self(); + // Cache the setup closure for the datatable component + if ($instance->applySetupClosure($crud, $controllerClass, $setupClosure, $parentEntry)) { + // Apply the setup closure to the CrudPanel instance + return $instance->cacheForComponent($tableId, $controllerClass, $setupClosure, $name, $crud); + } + + return false; + } + + /** + * Apply cached setup to a CRUD instance using the request's datatable_id. + * + * @param CrudPanel $crud The CRUD panel instance + * @return bool Whether the operation was successful + */ + public static function applyFromRequest(CrudPanel $crud): bool + { + $instance = new self(); + // Check if the request has a datatable_id parameter + $tableId = request('datatable_id'); + + if (! $tableId) { + \Log::debug('Missing datatable_id in request parameters'); + + return false; + } + + return $instance->apply($tableId, $crud); + } + + /** + * Apply a setup closure to a CrudPanel instance. + * + * @param CrudPanel $crud The CRUD panel instance + * @param string $controllerClass The controller class + * @param \Closure $setupClosure The setup closure + * @param mixed $entry The entry to pass to the setup closure + * @return bool Whether the operation was successful + */ + public function applySetupClosure(CrudPanel $crud, string $controllerClass, \Closure $setupClosure, $entry = null): bool + { + $originalSetup = $setupClosure; + $modifiedSetup = function ($crud, $entry) use ($originalSetup, $controllerClass) { + CrudManager::setActiveController($controllerClass); + + // Run the original closure + return ($originalSetup)($crud, $entry); + }; + + try { + // Execute the modified closure + ($modifiedSetup)($crud, $entry); + + return true; + } finally { + // Clean up + CrudManager::unsetActiveController(); + } + } + + /** + * Prepare datatable data for storage in the cache. + * + * @param string $controllerClass The controller class + * @param string $parentController The parent controller + * @param mixed $parentEntry The parent entry + * @param string|null $elementName The element name + * @return array The data to be cached + */ + protected function prepareDataForStorage(...$args): array + { + [$controllerClass, $parentController, $parentEntry, $elementName] = $args; + + return [ + 'controller' => $controllerClass, + 'parentController' => $parentController, + 'parent_entry' => $parentEntry, + 'element_name' => $elementName, + 'operations' => CrudManager::getInitializedOperations($parentController), + ]; + } + + /** + * Apply data from the cache to configure a datatable. + * + * @param array $cachedData The cached data + * @param CrudPanel $crud The CRUD panel instance + * @return bool Whether the operation was successful + */ + protected function applyFromCache($cachedData, ...$args): bool + { + [$crud] = $args; + + try { + // Initialize operations for the parent controller + $this->initializeOperations($cachedData['parentController'], $cachedData['operations']); + $entry = $cachedData['parent_entry']; + $elementName = $cachedData['element_name']; + + $widgets = Widget::collection(); + $found = false; + + foreach ($widgets as $widget) { + if ($widget['type'] === 'datatable' && + (isset($widget['name']) && $widget['name'] === $elementName) && + (isset($widget['setup']) && $widget['setup'] instanceof \Closure)) { + $this->applySetupClosure($crud, $cachedData['controller'], $widget['setup'], $entry); + $found = true; + break; + } + } + + return $found; + } catch (\Exception $e) { + \Log::error('Error applying cached datatable config: '.$e->getMessage(), [ + 'exception' => $e, + ]); + + return false; + } + } + + /** + * Initialize operations for a parent controller. + */ + private function initializeOperations(string $parentController, $operations): void + { + $parentCrud = CrudManager::setupCrudPanel($parentController); + + foreach ($operations as $operation) { + $parentCrud->initialized = false; + CrudManager::setupCrudPanel($parentController, $operation); + } + } +} diff --git a/src/app/Library/Support/SetupCache.php b/src/app/Library/Support/SetupCache.php new file mode 100644 index 0000000000..b7aa259636 --- /dev/null +++ b/src/app/Library/Support/SetupCache.php @@ -0,0 +1,114 @@ +cachePrefix.$identifier; + } + + /** + * Store data in the cache. + */ + public function store($identifier, ...$args) + { + $cacheKey = $this->generateCacheKey($identifier); + $data = $this->prepareDataForStorage(...$args); + + if ($data !== false && $data !== null) { + Cache::forget($cacheKey); + Cache::put($cacheKey, $data, now()->addMinutes($this->cacheDuration)); + + return true; + } + + return false; + } + + /** + * Apply cached data. + */ + public function apply($identifier, ...$args) + { + $cacheKey = $this->generateCacheKey($identifier); + $cachedData = Cache::get($cacheKey); + + if (! $cachedData) { + return false; + } + + return $this->applyFromCache($cachedData, ...$args); + } + + /** + * Get cached data without applying it. + */ + public function get($identifier) + { + $cacheKey = $this->generateCacheKey($identifier); + + return Cache::get($cacheKey); + } + + /** + * Check if cache exists for the given identifier. + */ + public function has($identifier): bool + { + $cacheKey = $this->generateCacheKey($identifier); + + return Cache::has($cacheKey); + } + + /** + * Remove cached data. + */ + public function forget($identifier): bool + { + $cacheKey = $this->generateCacheKey($identifier); + + return Cache::forget($cacheKey); + } + + /** + * Set the cache prefix. + */ + public function setCachePrefix(string $prefix): self + { + $this->cachePrefix = $prefix; + + return $this; + } + + /** + * Set the cache duration in minutes. + */ + public function setCacheDuration(int $minutes): self + { + $this->cacheDuration = $minutes; + + return $this; + } + + /** + * Prepare data for storage in the cache. + * This method should be implemented by child classes. + */ + abstract protected function prepareDataForStorage(...$args); + + /** + * Apply data from the cache. + * This method should be implemented by child classes. + */ + abstract protected function applyFromCache($cachedData, ...$args); +} diff --git a/src/app/View/Components/Datatable.php b/src/app/View/Components/Datatable.php index e89a52a36b..b906c1be04 100644 --- a/src/app/View/Components/Datatable.php +++ b/src/app/View/Components/Datatable.php @@ -3,9 +3,8 @@ namespace Backpack\CRUD\app\View\Components; use Backpack\CRUD\app\Library\CrudPanel\CrudPanel; -use Backpack\CRUD\app\Library\Widget; +use Backpack\CRUD\app\Library\Support\DatatableCache; use Backpack\CRUD\CrudManager; -use Illuminate\Support\Facades\Cache; use Illuminate\View\Component; class Datatable extends Component @@ -27,9 +26,15 @@ public function __construct( $this->tableId = $this->generateTableId(); if ($this->setup) { - // Apply the configuration using the shared method - $this->applySetupClosure($this->crud, $this->controller, $this->setup, $this->getParentCrudEntry()); - $this->cacheSetupClosure(); + // Apply the configuration using DatatableCache + DatatableCache::applyAndStoreSetupClosure( + $this->tableId, + $this->controller, + $this->setup, + $this->name, + $this->crud, + $this->getParentCrudEntry() + ); } if (! $this->crud->has('list.datatablesUrl')) { @@ -40,27 +45,6 @@ public function __construct( CrudManager::unsetActiveController(); } - private function applySetupClosure(CrudPanel $crud, string $controllerClass, \Closure $setupClosure, $entry = null) - { - $originalSetup = $setupClosure; - $modifiedSetup = function ($crud, $entry) use ($originalSetup, $controllerClass) { - CrudManager::setActiveController($controllerClass); - - // Run the original closure - return ($originalSetup)($crud, $entry); - }; - - try { - // Execute the modified closure - ($modifiedSetup)($crud, $entry); - - return true; - } finally { - // Clean up - CrudManager::unsetActiveController(); - } - } - private function getParentCrudEntry() { $cruds = CrudManager::getCrudPanels(); @@ -87,94 +71,6 @@ private function generateTableId(): string return 'crudTable_'.$uniqueId; } - /** - * Store the datatable configuration in the cache for later use in Ajax requests. - */ - private function cacheSetupClosure() - { - if (! $this->setup) { - return; - } - - $controllerClass = $this->controller; - $cruds = CrudManager::getCrudPanels(); - $parentCrud = reset($cruds); - - if ($parentCrud && $parentCrud->getCurrentEntry()) { - $parentEntry = $parentCrud->getCurrentEntry(); - $parentController = $parentCrud->controller; - $cacheKey = 'datatable_config_'.$this->tableId; - - Cache::forget($cacheKey); - - // Store the controller class, parent entry, element type and name - Cache::put($cacheKey, [ - 'controller' => $controllerClass, - 'parentController' => $parentController, - 'parent_entry' => $parentEntry, - 'element_name' => $this->name, - 'operations' => CrudManager::getInitializedOperations($parentController), - ], now()->addHours(1)); - - $this->crud->set('list.datatable_id', $this->tableId); - } - } - - public static function applyCachedSetupClosure($crud) - { - $tableId = request('datatable_id'); - - if (! $tableId) { - \Log::debug('Missing datatable_id in request parameters'); - - return false; - } - - $cacheKey = 'datatable_config_'.$tableId; - $cachedData = Cache::get($cacheKey); - - if (! $cachedData) { - return false; - } - - try { - // Get the parent crud instance - self::initializeOperations($cachedData['parentController'], $cachedData['operations']); - $entry = $cachedData['parent_entry']; - $elementName = $cachedData['element_name']; - - $widgets = Widget::collection(); - - foreach ($widgets as $widget) { - if ($widget['type'] === 'datatable' && - (isset($widget['name']) && $widget['name'] === $elementName) && - (isset($widget['setup']) && $widget['setup'] instanceof \Closure)) { - $instance = new self($cachedData['controller']); - - $instance->applySetupClosure($crud, $cachedData['controller'], $widget['setup'], $entry); - } - } - - return false; - } catch (\Exception $e) { - \Log::error('Error applying cached datatable config: '.$e->getMessage(), [ - 'exception' => $e, - ]); - } - - return false; - } - - private static function initializeOperations(string $parentController, $operations) - { - $parentCrud = CrudManager::setupCrudPanel($parentController); - - foreach ($operations as $operation) { - $parentCrud->initialized = false; - CrudManager::setupCrudPanel($parentController, $operation); - } - } - public function render() { return view('crud::components.datatable.datatable', [