Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/BackpackServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Illuminate\Routing\Router;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\File;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;
use Illuminate\View\Compilers\BladeCompiler;
Expand Down Expand Up @@ -80,6 +81,7 @@ public function register()
ViewNamespaces::addFor('widgets', 'crud::widgets');

$this->loadViewComponents();
$this->registerDynamicBladeComponents();

$this->registerBackpackErrorViews();

Expand Down Expand Up @@ -322,6 +324,38 @@ public function loadViewComponents()
});
}

/**
* Register dynamic Blade components from the Components directory.
*
* Any Blade component classes that are in that directory will be registered
* as dynamic components with the 'bp-{component-name}' prefix.
*/
private function registerDynamicBladeComponents()
{
$path = __DIR__.'/app/View/Components';
$namespace = 'Backpack\\CRUD\\app\\View\\Components';

if (! is_dir($path)) {
return;
}

foreach (File::allFiles($path) as $file) {
$relativePath = str_replace(
['/', '.php'],
['\\', ''],
Str::after($file->getRealPath(), realpath($path).DIRECTORY_SEPARATOR)
);

$class = $namespace.'\\'.$relativePath;

// Check if the class exists and is a subclass of Illuminate\View\Component
// This ensures that only valid Blade components are registered.
if (class_exists($class) && is_subclass_of($class, \Illuminate\View\Component::class)) {
Blade::component('bp-'.Str::kebab(class_basename($class)), $class);
}
}
}

/**
* Load the Backpack helper methods, for convenience.
*/
Expand Down
21 changes: 21 additions & 0 deletions src/ViewNamespaces.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,25 @@ public static function getViewPathsFor(string $domain, string $viewName)
return $item.'.'.$viewName;
}, self::getFor($domain));
}

/**
* Get view paths for a domain and view name with fallback support.
* This is useful for @includeFirst() calls that need a guaranteed fallback.
*
* @param string $domain (eg. fields, filters, buttons, columns)
* @param string $viewName (eg. text, select, checkbox)
* @param string|null $fallbackViewPath (eg. 'crud::columns.text')
* @return array
*/
public static function getViewPathsWithFallbackFor(string $domain, string $viewName, ?string $fallbackViewPath = null)
{
$paths = self::getViewPathsFor($domain, $viewName);

// Add fallback if provided and not already in the list
if ($fallbackViewPath && ! in_array($fallbackViewPath, $paths)) {
$paths[] = $fallbackViewPath;
}

return $paths;
}
}
37 changes: 37 additions & 0 deletions src/app/View/Components/Datagrid.php
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use EditableColumns inside this component?! What happens then?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They don't work - they throw a JS error. Buuuut. I don't think it's because of the component itself... because they don't work inside the ShowOperation either!

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace Backpack\CRUD\app\View\Components;

use Illuminate\View\Component;

class Datagrid extends Component
{
public $entry;
public $crud;
public $columns;
public $displayButtons;

/**
* Create a new component instance.
*/
public function __construct($entry, $crud = null, $columns = [], $displayButtons = true)
{
$this->columns = $columns ?? $crud?->columns() ?? [];
$this->entry = $entry;
$this->crud = $crud;
$this->displayButtons = $displayButtons;
}

/**
* Get the view / contents that represent the component.
*/
public function render()
{
// if no columns are set, don't load any view
if (empty($this->columns)) {
return '';
}

return view('crud::components.datagrid');
}
}
37 changes: 37 additions & 0 deletions src/app/View/Components/Datalist.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace Backpack\CRUD\app\View\Components;

use Illuminate\View\Component;

class Datalist extends Component
{
public $entry;
public $crud;
public $columns;
public $displayButtons;

/**
* Create a new component instance.
*/
public function __construct($entry, $crud = null, $columns = [], $displayButtons = true)
{
$this->columns = $columns ?? $crud?->columns() ?? [];
$this->entry = $entry;
$this->crud = $crud;
$this->displayButtons = $displayButtons;
}

/**
* Get the view / contents that represent the component.
*/
public function render()
{
// if no columns are set, don't load any view
if (empty($this->columns)) {
return '';
}

return view('crud::components.datalist');
}
}
3 changes: 3 additions & 0 deletions src/config/backpack/operations/show.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
// To override per Controller use $this->crud->setShowContentClass('class-string')
'contentClass' => 'col-md-12',

// Which component to use for displaying the Show page?
'component' => 'bp-datagrid', // options: bp-datagrid, bp-datalist, or a custom component alias

// Automatically add all columns from the db table?
'setFromDb' => true,

Expand Down
105 changes: 105 additions & 0 deletions src/resources/assets/css/common.css
Original file line number Diff line number Diff line change
Expand Up @@ -1180,3 +1180,108 @@ div.dt-scroll-body {
margin-left: 2px;
margin-right: auto;
}

/* DataGrid Component */

/* Base mobile-first layout: 1 column */
.bp-datagrid {
display: grid;
grid-gap: 1.5rem;
grid-template-columns: 1fr;
}

.bp-datagrid-title {
font-size: .625rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: .04em;
line-height: 1rem;
color: #66626c;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tabacitu put this color in a CSS variable

margin-bottom: 0.25rem;
}

/* Breakpoint 1: Small screens (≥768px) — 6 columns */
@media (min-width: 768px) {
.bp-datagrid {
grid-template-columns: repeat(6, 1fr);
}

.bp-datagrid-item.size-1 {
grid-column: span 1 / span 1;
}

.bp-datagrid-item.size-2 {
grid-column: span 2 / span 2;
}

.bp-datagrid-item.size-3 {
grid-column: span 3 / span 3;
}

.bp-datagrid-item.size-4 {
grid-column: span 4 / span 4;
}

.bp-datagrid-item.size-5 {
grid-column: span 5 / span 5;
}

.bp-datagrid-item.size-6 {
grid-column: span 6 / span 6;
}
}

/* Breakpoint 2: Large screens (≥1024px) — 12 columns */
@media (min-width: 1024px) {
.bp-datagrid {
grid-template-columns: repeat(12, 1fr);
}

.bp-datagrid-item.size-1 {
grid-column: span 1 / span 1;
}

.bp-datagrid-item.size-2 {
grid-column: span 2 / span 2;
}

.bp-datagrid-item.size-3 {
grid-column: span 3 / span 3;
}

.bp-datagrid-item.size-4 {
grid-column: span 4 / span 4;
}

.bp-datagrid-item.size-5 {
grid-column: span 5 / span 5;
}

.bp-datagrid-item.size-6 {
grid-column: span 6 / span 6;
}

.bp-datagrid-item.size-7 {
grid-column: span 7 / span 7;
}

.bp-datagrid-item.size-8 {
grid-column: span 8 / span 8;
}

.bp-datagrid-item.size-9 {
grid-column: span 9 / span 9;
}

.bp-datagrid-item.size-10 {
grid-column: span 10 / span 10;
}

.bp-datagrid-item.size-11 {
grid-column: span 11 / span 11;
}

.bp-datagrid-item.size-12 {
grid-column: span 12 / span 12;
}
}
19 changes: 19 additions & 0 deletions src/resources/views/crud/components/datagrid.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<div class="bp-datagrid p-3">
@foreach($columns as $column)
<div class="bp-datagrid-item size-{{ $column['size'] ?? '3' }}">
<div class="bp-datagrid-title">{!! $column['label'] !!}</div>
<div class="bp-datagrid-content">
@includeFirst(\Backpack\CRUD\ViewNamespaces::getViewPathsWithFallbackFor('columns', $column['type'], 'crud::columns.text'))
</div>
</div>
@endforeach

@if($displayButtons ?? $crud && $crud->buttons()->where('stack', 'line')->count())
<div class="bp-datagrid-item size-12">
<div class="bp-datagrid-title">{{ trans('backpack::crud.actions') }}</div>
<div class="bp-datagrid-content">
@include('crud::inc.button_stack', ['stack' => 'line'])
</div>
</div>
@endif
</div>
25 changes: 25 additions & 0 deletions src/resources/views/crud/components/datalist.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<table class="table table-striped m-0 p-0">
<tbody>
@foreach($columns as $column)
<tr>
<td @if($loop->index === 0) class="border-top-0" @endif>
<strong>{!! $column['label'] !!}@if(!empty($column['label'])):@endif</strong>
</td>
<td @if($loop->index === 0) class="border-top-0" @endif>
@includeFirst(\Backpack\CRUD\ViewNamespaces::getViewPathsWithFallbackFor('columns', $column['type'], 'crud::columns.text'))
</td>
</tr>
@endforeach

@if($displayButtons && $crud && $crud->buttons()->where('stack', 'line')->count())
<tr>
<td>
<strong>{{ trans('backpack::crud.actions') }}</strong>
</td>
<td>
@include('crud::inc.button_stack', ['stack' => 'line'])
</td>
</tr>
@endif
</tbody>
</table>
2 changes: 0 additions & 2 deletions src/resources/views/crud/inc/show_tabbed_table.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
<div class="card">
@include('crud::inc.show_table', ['columns' => $columnsWithoutTab, 'displayActionsColumn' => false])
</div>
@else
@include('crud::inc.show_table', ['columns' => $columnsWithoutTab])
@endif

<div class="tab-container {{ $horizontalTabs ? '' : 'container'}} mb-2">
Expand Down
46 changes: 7 additions & 39 deletions src/resources/views/crud/inc/show_table.blade.php
Copy link
Member Author

@tabacitu tabacitu Jun 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unhappy with this file because it's now called show_table.blade.php and it doesn't ACTUALLY load a table, it loads either a table or a grid (that we call datalist and datagrid).

We can probably

  • (A) rename this to show_component;
  • (B) scrap this file entirely and use the component directly in the operation blade file and in the show_tabbed_table.blade.php file... which prooobably should be called show_tabs or something like that;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's keep it like this for now, to keep changes to a minimum. We can rename/refactor in separate PRs if we think it's worth it.

Original file line number Diff line number Diff line change
@@ -1,39 +1,7 @@
@if(count($columns))
<table class="table table-striped m-0 p-0">
<tbody>
@foreach($columns as $column)
<tr>
<td @if($loop->index === 0) class="border-top-0" @endif>
<strong>{!! $column['label'] !!}@if(!empty($column['label'])):@endif</strong>
</td>
<td @if($loop->index === 0) class="border-top-0" @endif>
@php
// create a list of paths to column blade views
// including the configured view_namespaces
$columnPaths = array_map(function($item) use ($column) {
return $item.'.'.$column['type'];
}, \Backpack\CRUD\ViewNamespaces::getFor('columns'));

// but always fall back to the stock 'text' column
// if a view doesn't exist
if (!in_array('crud::columns.text', $columnPaths)) {
$columnPaths[] = 'crud::columns.text';
}
@endphp
@includeFirst($columnPaths)
</td>
</tr>
@endforeach
@if($crud->buttons()->where('stack', 'line')->count() && ($displayActionsColumn ?? true))
<tr>
<td>
<strong>{{ trans('backpack::crud.actions') }}</strong>
</td>
<td>
@include('crud::inc.button_stack', ['stack' => 'line'])
</td>
</tr>
@endif
</tbody>
</table>
@endif
<x-dynamic-component
:component="$crud->getOperationSetting('component')"
:entry="$entry"
:crud="$crud"
:columns="$columns"
:display-buttons="$displayActionsColumn ?? true"
/>