Skip to content

Commit c134823

Browse files
pxpmStyleCIBottabacitu
authored
DataFormModal component (#5863)
Co-authored-by: StyleCI Bot <[email protected]> Co-authored-by: Cristian Tabacitu <[email protected]> Co-authored-by: Cristian Tăbăcitu <[email protected]>
1 parent 8a9e968 commit c134823

24 files changed

+971
-236
lines changed

src/CrudPanelManager.php

Lines changed: 250 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,31 +58,273 @@ public function getCrudPanel(CrudControllerContract|string $controller): CrudPan
5858
*/
5959
public function setupCrudPanel(string $controller, ?string $operation = null): CrudPanel
6060
{
61+
// Resolve potential active controller and ensure we have an instance
6162
$controller = $this->getActiveController() ?? $controller;
62-
6363
$controller = is_string($controller) ? app($controller) : $controller;
6464

6565
$crud = $this->getCrudPanel($controller);
6666

6767
// Use provided operation or default to 'list'
6868
$operation = $operation ?? 'list';
69-
$crud->setOperation($operation);
7069

70+
$shouldIsolate = $this->shouldIsolateOperation($controller::class, $operation);
71+
72+
// primary controller request is used when doing a full initialization
7173
$primaryControllerRequest = $this->cruds[array_key_first($this->cruds)]->getRequest();
74+
75+
// If the panel is already initialized but a different operation is requested
76+
// and we don't need to isolate that operation, do a simple setup and return early.
77+
if ($crud->isInitialized() && $crud->getOperation() !== $operation && ! $shouldIsolate) {
78+
return $this->performSimpleOperationSwitch($controller, $operation, $crud);
79+
}
80+
81+
// If the panel (or this specific operation) hasn't been initialized yet,
82+
// perform the required initialization (full or operation-specific).
7283
if (! $crud->isInitialized() || ! $this->isOperationInitialized($controller::class, $operation)) {
73-
self::setActiveController($controller::class);
84+
return $this->performInitialization($controller, $operation, $crud, $primaryControllerRequest, $shouldIsolate);
85+
}
86+
87+
// Already initialized and operation matches: nothing to do.
88+
return $this->cruds[$controller::class];
89+
}
90+
91+
/**
92+
* Perform a lightweight operation switch when the panel is initialized and
93+
* isolation is not required.
94+
*/
95+
private function performSimpleOperationSwitch($controller, string $operation, CrudPanel $crud): CrudPanel
96+
{
97+
self::setActiveController($controller::class);
98+
99+
$crud->setOperation($operation);
100+
$this->setupSpecificOperation($controller, $operation, $crud);
101+
102+
// Mark this operation as initialized
103+
$this->storeInitializedOperation($controller::class, $operation);
104+
105+
self::unsetActiveController();
106+
107+
return $this->cruds[$controller::class];
108+
}
109+
110+
/**
111+
* Perform full or operation-specific initialization when needed.
112+
*/
113+
private function performInitialization($controller, string $operation, CrudPanel $crud, $primaryControllerRequest, bool $shouldIsolate): CrudPanel
114+
{
115+
self::setActiveController($controller::class);
116+
117+
// If the panel isn't initialized at all, do full initialization
118+
if (! $crud->isInitialized()) {
119+
// Set the operation for full initialization
120+
$crud->setOperation($operation);
74121
$crud->initialized = false;
75-
self::setActiveController($controller::class);
76122
$controller->initializeCrudPanel($primaryControllerRequest, $crud);
77-
self::unsetActiveController();
78-
$crud = $this->cruds[$controller::class];
79-
80-
return $this->cruds[$controller::class];
123+
} else {
124+
// Panel is initialized, just setup this specific operation
125+
if ($shouldIsolate) {
126+
$this->setupIsolatedOperation($controller, $operation, $crud);
127+
} else {
128+
// Set the operation for standard setup
129+
$crud->setOperation($operation);
130+
$this->setupSpecificOperation($controller, $operation, $crud);
131+
}
81132
}
82133

134+
// Mark this operation as initialized
135+
$this->storeInitializedOperation($controller::class, $operation);
136+
137+
self::unsetActiveController();
138+
83139
return $this->cruds[$controller::class];
84140
}
85141

142+
/**
143+
* Determine if an operation should be isolated to prevent state interference.
144+
*
145+
* @param string $controller
146+
* @param string $operation
147+
* @return bool
148+
*/
149+
private function shouldIsolateOperation(string $controller, string $operation): bool
150+
{
151+
$currentCrud = $this->cruds[$controller] ?? null;
152+
if (! $currentCrud) {
153+
return false;
154+
}
155+
156+
$currentOperation = $currentCrud->getOperation();
157+
158+
// If operations don't differ, no need to isolate
159+
if (! $currentOperation || $currentOperation === $operation) {
160+
return false;
161+
}
162+
163+
// Check backtrace for components implementing IsolatesOperationSetup
164+
$backtrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 10);
165+
166+
foreach ($backtrace as $trace) {
167+
if (isset($trace['object'])) {
168+
$object = $trace['object'];
169+
170+
// If we find a component that implements the marker interface,
171+
// it signals that the operation setup should be isolated.
172+
if ($object instanceof \Backpack\CRUD\app\View\Components\Contracts\IsolatesOperationSetup) {
173+
return true;
174+
}
175+
}
176+
}
177+
178+
return true;
179+
}
180+
181+
/**
182+
* Setup an operation in isolation without affecting the main CRUD panel state.
183+
* This creates a temporary context for operation setup without state interference.
184+
*
185+
* @param object $controller The controller instance
186+
* @param string $operation The operation to setup
187+
* @param CrudPanel $crud The CRUD panel instance
188+
*/
189+
private function setupIsolatedOperation($controller, string $operation, CrudPanel $crud): void
190+
{
191+
// Store the complete current state
192+
$originalOperation = $crud->getOperation();
193+
$originalSettings = $crud->settings();
194+
$originalColumns = $crud->columns(); // Use the direct method, not operation setting
195+
$originalRoute = $crud->route ?? null;
196+
$originalEntityName = $crud->entity_name ?? null;
197+
$originalEntityNamePlural = $crud->entity_name_plural ?? null;
198+
199+
// Store operation-specific settings generically
200+
$originalOperationSettings = $this->extractOperationSettings($crud, $originalOperation);
201+
202+
// Temporarily setup the requested operation
203+
$crud->setOperation($operation);
204+
205+
// Use the controller's own method to setup the operation properly
206+
$reflection = new \ReflectionClass($controller);
207+
$method = $reflection->getMethod('setupConfigurationForCurrentOperation');
208+
$method->setAccessible(true);
209+
$method->invoke($controller, $operation);
210+
211+
// Completely restore the original state
212+
$crud->setOperation($originalOperation);
213+
214+
// CRITICAL: Properly restore columns by clearing and re-adding them
215+
// This is essential to preserve list operation columns
216+
$crud->removeAllColumns();
217+
foreach ($originalColumns as $column) {
218+
$crud->addColumn($column);
219+
}
220+
221+
// Restore all original settings one by one, but skip complex objects
222+
foreach ($originalSettings as $key => $value) {
223+
try {
224+
// Skip complex objects that Laravel generates dynamically
225+
if (is_object($value) && (
226+
$value instanceof \Illuminate\Routing\UrlGenerator ||
227+
$value instanceof \Illuminate\Http\Request ||
228+
$value instanceof \Illuminate\Contracts\Foundation\Application ||
229+
$value instanceof \Closure ||
230+
method_exists($value, '__toString') === false
231+
)) {
232+
continue;
233+
}
234+
235+
$crud->set($key, $value);
236+
} catch (\Exception $e) {
237+
// Silently continue with restoration
238+
}
239+
}
240+
241+
// Restore operation-specific settings generically
242+
$this->restoreOperationSettings($crud, $originalOperation, $originalOperationSettings);
243+
244+
// Restore core properties if they were changed
245+
if ($originalRoute !== null) {
246+
$crud->route = $originalRoute;
247+
}
248+
if ($originalEntityName !== null) {
249+
$crud->entity_name = $originalEntityName;
250+
}
251+
if ($originalEntityNamePlural !== null) {
252+
$crud->entity_name_plural = $originalEntityNamePlural;
253+
}
254+
}
255+
256+
/**
257+
* Extract all settings for a specific operation.
258+
*
259+
* @param CrudPanel $crud The CRUD panel instance
260+
* @param string $operation The operation name
261+
* @return array Array of operation-specific settings
262+
*/
263+
private function extractOperationSettings(CrudPanel $crud, string $operation): array
264+
{
265+
$settings = $crud->settings();
266+
$operationSettings = [];
267+
$operationPrefix = $operation.'.';
268+
269+
foreach ($settings as $key => $value) {
270+
if (str_starts_with($key, $operationPrefix)) {
271+
$operationSettings[$key] = $value;
272+
}
273+
}
274+
275+
return $operationSettings;
276+
}
277+
278+
/**
279+
* Restore all settings for a specific operation.
280+
*
281+
* @param CrudPanel $crud The CRUD panel instance
282+
* @param string $operation The operation name
283+
* @param array $operationSettings The settings to restore
284+
*/
285+
private function restoreOperationSettings(CrudPanel $crud, string $operation, array $operationSettings): void
286+
{
287+
foreach ($operationSettings as $key => $value) {
288+
try {
289+
// Skip complex objects that Laravel generates dynamically
290+
if (is_object($value) && (
291+
$value instanceof \Illuminate\Routing\UrlGenerator ||
292+
$value instanceof \Illuminate\Http\Request ||
293+
$value instanceof \Illuminate\Contracts\Foundation\Application ||
294+
$value instanceof \Closure ||
295+
method_exists($value, '__toString') === false
296+
)) {
297+
continue;
298+
}
299+
300+
$crud->set($key, $value);
301+
} catch (\Exception $e) {
302+
// Silently continue with restoration
303+
}
304+
}
305+
}
306+
307+
/**
308+
* Setup a specific operation without reinitializing the entire CRUD panel.
309+
*
310+
* @param object $controller The controller instance
311+
* @param string $operation The operation to setup
312+
* @param CrudPanel $crud The CRUD panel instance
313+
*/
314+
private function setupSpecificOperation($controller, string $operation, CrudPanel $crud): void
315+
{
316+
// Setup the specific operation using the existing CrudController infrastructure
317+
$crud->setOperation($operation);
318+
319+
$controller->setup();
320+
321+
// Use the controller's own method to setup the operation properly
322+
$reflection = new \ReflectionClass($controller);
323+
$method = $reflection->getMethod('setupConfigurationForCurrentOperation');
324+
$method->setAccessible(true);
325+
$method->invoke($controller, $operation);
326+
}
327+
86328
/**
87329
* Check if a specific operation has been initialized for a controller.
88330
*/

src/app/Http/Controllers/Operations/CreateOperation.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ protected function setupCreateDefaults()
3737
$this->crud->allowAccess('create');
3838

3939
LifecycleHook::hookInto('create:before_setup', function () {
40+
$this->crud->loadDefaultOperationSettingsFromConfig();
4041
$this->crud->setupDefaultSaveActions();
4142
});
4243

src/app/Library/CrudPanel/Traits/Read.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,8 +275,14 @@ public function getDefaultPageLength()
275275
*/
276276
public function addCustomPageLengthToPageLengthMenu()
277277
{
278-
$values = $this->getOperationSetting('pageLengthMenu')[0];
279-
$labels = $this->getOperationSetting('pageLengthMenu')[1];
278+
$pageLengthMenu = $this->getOperationSetting('pageLengthMenu');
279+
280+
if (is_null($pageLengthMenu)) {
281+
return;
282+
}
283+
284+
$values = $pageLengthMenu[0];
285+
$labels = $pageLengthMenu[1];
280286

281287
if (array_search($this->getDefaultPageLength(), $values) === false) {
282288
for ($i = 0; $i < count($values); $i++) {

src/app/Library/CrudPanel/Traits/Search.php

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
use Backpack\CRUD\ViewNamespaces;
66
use Carbon\Carbon;
7-
use Validator;
7+
use Illuminate\Support\Facades\Validator;
88

99
trait Search
1010
{
@@ -275,14 +275,14 @@ public function getRowViews($entry, $rowNumber = false)
275275
->render();
276276
}
277277

278-
// add the bulk actions checkbox to the first column
279-
if ($this->getOperationSetting('bulkActions')) {
278+
// add the bulk actions checkbox to the first column - but only if we have columns
279+
if ($this->getOperationSetting('bulkActions') && ! empty($row_items)) {
280280
$bulk_actions_checkbox = \View::make('crud::columns.inc.bulk_actions_checkbox', ['entry' => $entry])->render();
281281
$row_items[0] = $bulk_actions_checkbox.$row_items[0];
282282
}
283283

284-
// add the details_row button to the first column
285-
if ($this->getOperationSetting('detailsRow')) {
284+
// add the details_row button to the first column - but only if we have columns
285+
if ($this->getOperationSetting('detailsRow') && ! empty($row_items)) {
286286
$details_row_button = \View::make('crud::columns.inc.details_row_button')
287287
->with('crud', $this)
288288
->with('entry', $entry)
@@ -291,7 +291,7 @@ public function getRowViews($entry, $rowNumber = false)
291291
$row_items[0] = $details_row_button.$row_items[0];
292292
}
293293

294-
if ($this->getResponsiveTable()) {
294+
if ($this->getResponsiveTable() && ! empty($row_items)) {
295295
$responsiveTableTrigger = '<div class="dtr-control d-none cursor-pointer"></div>';
296296
$row_items[0] = $responsiveTableTrigger.$row_items[0];
297297
}
@@ -398,16 +398,18 @@ public function getEntriesAsJsonForDatatables($entries, $totalRows, $filteredRow
398398
{
399399
$rows = [];
400400

401-
foreach ($entries as $row) {
401+
foreach ($entries as $index => $row) {
402402
$rows[] = $this->getRowViews($row, $startIndex === false ? false : ++$startIndex);
403403
}
404404

405-
return [
405+
$result = [
406406
'draw' => (isset($this->getRequest()['draw']) ? (int) $this->getRequest()['draw'] : 0),
407407
'recordsTotal' => $totalRows,
408408
'recordsFiltered' => $filteredRows,
409409
'data' => $rows,
410410
];
411+
412+
return $result;
411413
}
412414

413415
/**

0 commit comments

Comments
 (0)