Skip to content

Commit 5087c1d

Browse files
tabacituStyleCIBot
andauthored
add Datagrid and Datalist components (#5810)
Co-authored-by: StyleCI Bot <[email protected]>
1 parent f6fb8c9 commit 5087c1d

File tree

12 files changed

+324
-43
lines changed

12 files changed

+324
-43
lines changed

src/BackpackServiceProvider.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Illuminate\Routing\Router;
1212
use Illuminate\Support\Collection;
1313
use Illuminate\Support\Facades\Blade;
14+
use Illuminate\Support\Facades\File;
1415
use Illuminate\Support\ServiceProvider;
1516
use Illuminate\Support\Str;
1617
use Illuminate\View\Compilers\BladeCompiler;
@@ -80,6 +81,7 @@ public function register()
8081
ViewNamespaces::addFor('widgets', 'crud::widgets');
8182

8283
$this->loadViewComponents();
84+
$this->registerDynamicBladeComponents();
8385

8486
$this->registerBackpackErrorViews();
8587

@@ -322,6 +324,38 @@ public function loadViewComponents()
322324
});
323325
}
324326

327+
/**
328+
* Register dynamic Blade components from the Components directory.
329+
*
330+
* Any Blade component classes that are in that directory will be registered
331+
* as dynamic components with the 'bp-{component-name}' prefix.
332+
*/
333+
private function registerDynamicBladeComponents()
334+
{
335+
$path = __DIR__.'/app/View/Components';
336+
$namespace = 'Backpack\\CRUD\\app\\View\\Components';
337+
338+
if (! is_dir($path)) {
339+
return;
340+
}
341+
342+
foreach (File::allFiles($path) as $file) {
343+
$relativePath = str_replace(
344+
['/', '.php'],
345+
['\\', ''],
346+
Str::after($file->getRealPath(), realpath($path).DIRECTORY_SEPARATOR)
347+
);
348+
349+
$class = $namespace.'\\'.$relativePath;
350+
351+
// Check if the class exists and is a subclass of Illuminate\View\Component
352+
// This ensures that only valid Blade components are registered.
353+
if (class_exists($class) && is_subclass_of($class, \Illuminate\View\Component::class)) {
354+
Blade::component('bp-'.Str::kebab(class_basename($class)), $class);
355+
}
356+
}
357+
}
358+
325359
/**
326360
* Load the Backpack helper methods, for convenience.
327361
*/

src/ViewNamespaces.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,25 @@ public static function getViewPathsFor(string $domain, string $viewName)
8989
return $item.'.'.$viewName;
9090
}, self::getFor($domain));
9191
}
92+
93+
/**
94+
* Get view paths for a domain and view name with fallback support.
95+
* This is useful for @includeFirst() calls that need a guaranteed fallback.
96+
*
97+
* @param string $domain (eg. fields, filters, buttons, columns)
98+
* @param string $viewName (eg. text, select, checkbox)
99+
* @param string|null $fallbackViewPath (eg. 'crud::columns.text')
100+
* @return array
101+
*/
102+
public static function getViewPathsWithFallbackFor(string $domain, string $viewName, ?string $fallbackViewPath = null)
103+
{
104+
$paths = self::getViewPathsFor($domain, $viewName);
105+
106+
// Add fallback if provided and not already in the list
107+
if ($fallbackViewPath && ! in_array($fallbackViewPath, $paths)) {
108+
$paths[] = $fallbackViewPath;
109+
}
110+
111+
return $paths;
112+
}
92113
}

src/app/Library/CrudPanel/CrudButton.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,10 +297,10 @@ public function section($stack)
297297
* @param object|null $entry The eloquent Model for the current entry or null if no current entry.
298298
* @return \Illuminate\Contracts\View\View
299299
*/
300-
public function getHtml($entry = null)
300+
public function getHtml($entry = null, ?CrudPanel $crud = null)
301301
{
302302
$button = $this;
303-
$crud = $this->crud();
303+
$crud = $crud ?? $this->crud();
304304

305305
if ($this->type == 'model_function') {
306306
if (is_null($entry)) {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace Backpack\CRUD\app\View\Components;
4+
5+
class Datagrid extends ShowComponent
6+
{
7+
/**
8+
* Get the view name for the component.
9+
*/
10+
protected function getViewName(): string
11+
{
12+
return 'crud::components.datagrid';
13+
}
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace Backpack\CRUD\app\View\Components;
4+
5+
class Datalist extends ShowComponent
6+
{
7+
/**
8+
* Get the view name for the component.
9+
*/
10+
protected function getViewName(): string
11+
{
12+
return 'crud::components.datalist';
13+
}
14+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
3+
namespace Backpack\CRUD\app\View\Components;
4+
5+
use Backpack\CRUD\app\Library\CrudPanel\CrudPanel;
6+
use Backpack\CRUD\CrudManager;
7+
use Illuminate\Database\Eloquent\Model;
8+
use Illuminate\Support\Collection;
9+
use Illuminate\View\Component;
10+
11+
abstract class ShowComponent extends Component
12+
{
13+
/**
14+
* Create a new component instance.
15+
*/
16+
public function __construct(
17+
public Model $entry,
18+
public ?string $controller = null,
19+
public ?string $operation = 'show',
20+
public ?\Closure $setup = null,
21+
public ?CrudPanel $crud = null,
22+
public array|Collection $columns = [],
23+
public bool $displayButtons = true
24+
) {
25+
$this->setPropertiesFromController();
26+
}
27+
28+
/**
29+
* Set properties from the controller context.
30+
*
31+
* This method initializes the CrudPanel and sets the active controller.
32+
* It also applies any setup closure provided.
33+
*/
34+
protected function setPropertiesFromController(): void
35+
{
36+
// If no CrudController is provided, do nothing
37+
if (! $this->controller) {
38+
return;
39+
}
40+
41+
// If no CrudPanel is provided, try to get it from the CrudManager
42+
$this->crud ??= CrudManager::setupCrudPanel($this->controller, $this->operation);
43+
44+
// Set active controller for proper context
45+
CrudManager::setActiveController($this->controller);
46+
47+
// If a setup closure is provided, apply it
48+
if ($this->setup) {
49+
if (! empty($columns)) {
50+
throw new \Exception('You cannot define both setup closure and columns for a '.class_basename(static::class).' component.');
51+
}
52+
53+
($this->setup)($this->crud, $this->entry);
54+
}
55+
56+
$this->columns = ! empty($columns) ? $columns : $this->crud?->getOperationSetting('columns', $this->operation) ?? [];
57+
58+
// Reset the active controller
59+
CrudManager::unsetActiveController();
60+
}
61+
62+
/**
63+
* Get the view name for the component.
64+
* This method must be implemented by child classes.
65+
*/
66+
abstract protected function getViewName(): string;
67+
68+
/**
69+
* Get the view / contents that represent the component.
70+
*/
71+
public function render()
72+
{
73+
// if no columns are set, don't load any view
74+
if (empty($this->columns)) {
75+
return '';
76+
}
77+
78+
return view($this->getViewName());
79+
}
80+
}

src/config/backpack/operations/show.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
// To override per Controller use $this->crud->setShowContentClass('class-string')
1212
'contentClass' => 'col-md-12',
1313

14+
// Which component to use for displaying the Show page?
15+
'component' => 'bp-datagrid', // options: bp-datagrid, bp-datalist, or a custom component alias
16+
1417
// Automatically add all columns from the db table?
1518
'setFromDb' => true,
1619

src/resources/assets/css/common.css

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1180,3 +1180,108 @@ div.dt-scroll-body {
11801180
margin-left: 2px;
11811181
margin-right: auto;
11821182
}
1183+
1184+
/* DataGrid Component */
1185+
1186+
/* Base mobile-first layout: 1 column */
1187+
.bp-datagrid {
1188+
display: grid;
1189+
grid-gap: 1.5rem;
1190+
grid-template-columns: 1fr;
1191+
}
1192+
1193+
.bp-datagrid-title {
1194+
font-size: .625rem;
1195+
font-weight: 600;
1196+
text-transform: uppercase;
1197+
letter-spacing: .04em;
1198+
line-height: 1rem;
1199+
color: #66626c;
1200+
margin-bottom: 0.25rem;
1201+
}
1202+
1203+
/* Breakpoint 1: Small screens (≥768px) — 6 columns */
1204+
@media (min-width: 768px) {
1205+
.bp-datagrid {
1206+
grid-template-columns: repeat(6, 1fr);
1207+
}
1208+
1209+
.bp-datagrid-item.size-1 {
1210+
grid-column: span 1 / span 1;
1211+
}
1212+
1213+
.bp-datagrid-item.size-2 {
1214+
grid-column: span 2 / span 2;
1215+
}
1216+
1217+
.bp-datagrid-item.size-3 {
1218+
grid-column: span 3 / span 3;
1219+
}
1220+
1221+
.bp-datagrid-item.size-4 {
1222+
grid-column: span 4 / span 4;
1223+
}
1224+
1225+
.bp-datagrid-item.size-5 {
1226+
grid-column: span 5 / span 5;
1227+
}
1228+
1229+
.bp-datagrid-item.size-6 {
1230+
grid-column: span 6 / span 6;
1231+
}
1232+
}
1233+
1234+
/* Breakpoint 2: Large screens (≥1024px) — 12 columns */
1235+
@media (min-width: 1024px) {
1236+
.bp-datagrid {
1237+
grid-template-columns: repeat(12, 1fr);
1238+
}
1239+
1240+
.bp-datagrid-item.size-1 {
1241+
grid-column: span 1 / span 1;
1242+
}
1243+
1244+
.bp-datagrid-item.size-2 {
1245+
grid-column: span 2 / span 2;
1246+
}
1247+
1248+
.bp-datagrid-item.size-3 {
1249+
grid-column: span 3 / span 3;
1250+
}
1251+
1252+
.bp-datagrid-item.size-4 {
1253+
grid-column: span 4 / span 4;
1254+
}
1255+
1256+
.bp-datagrid-item.size-5 {
1257+
grid-column: span 5 / span 5;
1258+
}
1259+
1260+
.bp-datagrid-item.size-6 {
1261+
grid-column: span 6 / span 6;
1262+
}
1263+
1264+
.bp-datagrid-item.size-7 {
1265+
grid-column: span 7 / span 7;
1266+
}
1267+
1268+
.bp-datagrid-item.size-8 {
1269+
grid-column: span 8 / span 8;
1270+
}
1271+
1272+
.bp-datagrid-item.size-9 {
1273+
grid-column: span 9 / span 9;
1274+
}
1275+
1276+
.bp-datagrid-item.size-10 {
1277+
grid-column: span 10 / span 10;
1278+
}
1279+
1280+
.bp-datagrid-item.size-11 {
1281+
grid-column: span 11 / span 11;
1282+
}
1283+
1284+
.bp-datagrid-item.size-12 {
1285+
grid-column: span 12 / span 12;
1286+
}
1287+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<div class="bp-datagrid p-3">
2+
@foreach($columns as $column)
3+
<div class="bp-datagrid-item size-{{ $column['size'] ?? '3' }}">
4+
<div class="bp-datagrid-title">{!! $column['label'] !!}</div>
5+
<div class="bp-datagrid-content">
6+
@includeFirst(\Backpack\CRUD\ViewNamespaces::getViewPathsWithFallbackFor('columns', $column['type'], 'crud::columns.text'))
7+
</div>
8+
</div>
9+
@endforeach
10+
11+
@if($displayButtons && $crud && $crud->buttons()->where('stack', 'line')->count())
12+
<div class="bp-datagrid-item size-12">
13+
<div class="bp-datagrid-title">{{ trans('backpack::crud.actions') }}</div>
14+
<div class="bp-datagrid-content">
15+
@include('crud::inc.button_stack', ['stack' => 'line'])
16+
</div>
17+
</div>
18+
@endif
19+
</div>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<table class="table table-striped m-0 p-0">
2+
<tbody>
3+
@foreach($columns as $column)
4+
<tr>
5+
<td @if($loop->index === 0) class="border-top-0" @endif>
6+
<strong>{!! $column['label'] !!}@if(!empty($column['label'])):@endif</strong>
7+
</td>
8+
<td @if($loop->index === 0) class="border-top-0" @endif>
9+
@includeFirst(\Backpack\CRUD\ViewNamespaces::getViewPathsWithFallbackFor('columns', $column['type'], 'crud::columns.text'))
10+
</td>
11+
</tr>
12+
@endforeach
13+
14+
@if($displayButtons && $crud && $crud->buttons()->where('stack', 'line')->count())
15+
<tr>
16+
<td>
17+
<strong>{{ trans('backpack::crud.actions') }}</strong>
18+
</td>
19+
<td>
20+
@include('crud::inc.button_stack', ['stack' => 'line'])
21+
</td>
22+
</tr>
23+
@endif
24+
</tbody>
25+
</table>

0 commit comments

Comments
 (0)