In CrudResource (ModelResource) on the indexPage as well as on the DetailPage, TableBuilder is used to display the main data,
so we recommend you also study the documentation section TableBuilder.
// torchlight! {"summaryCollapsedIndicator": "namespaces"}
// [tl! collapse:2]
use MoonShine\Support\ListOf;
use MoonShine\UI\Components\ActionButton;
protected function indexButtons(): ListOf
{
return parent::indexButtons()
->add(ActionButton::make('Link', '/endpoint'));
}
namespaces
useMoonShine\Support\ListOf;
useMoonShine\UI\Components\ActionButton;
protectedfunctionindexButtons():ListOf
{
returnparent::indexButtons()
->add(ActionButton::make('Link', '/endpoint'));
}
use MoonShine\Support\ListOf;
use MoonShine\UI\Components\ActionButton;
protected function indexButtons(): ListOf
{
return parent::indexButtons()
->add(ActionButton::make('Link', '/endpoint'));
}
Before the main buttons:
// torchlight! {"summaryCollapsedIndicator": "namespaces"}
// [tl! collapse:2]
use MoonShine\Support\ListOf;
use MoonShine\UI\Components\ActionButton;
protected function indexButtons(): ListOf
{
return parent::indexButtons()
->prepend(ActionButton::make('Link', '/endpoint'));
}
use MoonShine\Support\ListOf;
use MoonShine\UI\Components\ActionButton;
protected function indexButtons(): ListOf
{
return parent::indexButtons()
->prepend(ActionButton::make('Link', '/endpoint'));
}
Remove the delete button:
// torchlight! {"summaryCollapsedIndicator": "namespaces"}
// [tl! collapse:2]
use MoonShine\Support\ListOf;
use MoonShine\UI\Components\ActionButton;
protected function indexButtons(): ListOf
{
return parent::indexButtons()
->except(fn(ActionButton $btn) => $btn->getName() === 'resource-delete-button');
}
use MoonShine\Support\ListOf;
use MoonShine\UI\Components\ActionButton;
protected function indexButtons(): ListOf
{
return parent::indexButtons()
->except(fn(ActionButton $btn) => $btn->getName() === 'resource-delete-button');
}
Clear the button set and add your own:
// torchlight! {"summaryCollapsedIndicator": "namespaces"}
// [tl! collapse:2]
use MoonShine\Support\ListOf;
use MoonShine\UI\Components\ActionButton;
protected function indexButtons(): ListOf
{
parent::indexButtons()
->empty()
->add(ActionButton::make('Link', '/endpoint'));
}
namespaces
useMoonShine\Support\ListOf;
useMoonShine\UI\Components\ActionButton;
protectedfunctionindexButtons():ListOf
{
parent::indexButtons()
->empty()
->add(ActionButton::make('Link', '/endpoint'));
}
use MoonShine\Support\ListOf;
use MoonShine\UI\Components\ActionButton;
protected function indexButtons(): ListOf
{
parent::indexButtons()
->empty()
->add(ActionButton::make('Link', '/endpoint'));
}
The same approach is used for the table on the detail page, only through the method detailButtons().
For bulk actions, you need to add the bulk() method.
// torchlight! {"summaryCollapsedIndicator": "namespaces"}
// [tl! collapse:2]
use MoonShine\Support\ListOf;
use MoonShine\UI\Components\ActionButton;
protected function indexButtons(): ListOf
{
return parent::indexButtons()
->add(ActionButton::make('Link', '/endpoint')->bulk());
}
use MoonShine\Support\ListOf;
use MoonShine\UI\Components\ActionButton;
protected function indexButtons(): ListOf
{
return parent::indexButtons()
->add(ActionButton::make('Link', '/endpoint')->bulk());
}
By default, all buttons in the table are displayed in a line, but you can change the behavior and display them through a drop-down list.
To do this, change the $indexButtonsInDropdown property in the resource:
use MoonShine\UI\Fields\Text;
protected function indexFields(): iterable
{
return [
// ...
Text::make('Title')
->customWrapperAttributes(['width' => '20%']);
];
}
You can also customize tr and td for the table with data through the resource.
To do this, you need to use the corresponding methods trAttributes() and tdAttributes(),
to which you need to pass a closure that returns an array of attributes for the table component.
// torchlight! {"summaryCollapsedIndicator": "namespaces"}
// [tl! collapse:6]
namespace App\MoonShine\Resources;
use Closure;
use MoonShine\Contracts\Core\TypeCasts\DataWrapperContract;
use MoonShine\Laravel\Resources\ModelResource;
use MoonShine\UI\Fields\Text;
class PostResource extends ModelResource
{
// ...
protected function tdAttributes(): Closure
{
return fn(?DataWrapperContract $data, int $row, int $cell) => [
'width' => '20%'
];
}
protected function trAttributes(): Closure
{
return fn(?DataWrapperContract $data, int $row) => [
'data-tr' => $row
];
}
}
You can allow users to independently determine which columns to display in the table while retaining their selection.
To do this, you need to set the parameter $columnSelection for the resource.
If you do not plan to display the total number of pages, use Simple Pagination.
This avoids additional queries for the total number of records in the database.
In the resource, async mode is used by default.
This mode allows for pagination, filtering, and sorting without page reloads.
However, if you want to disable async mode, you can use the property $isAsync.
namespace App\MoonShine\Resources;
use MoonShine\UI\Fields\ID;
use MoonShine\UI\Fields\Switcher;
use MoonShine\UI\Fields\Text;
use MoonShine\Laravel\Resources\ModelResource;
use MoonShine\Support\AlpineJs;
use MoonShine\Support\Enums\JsEvent;
class PostResource extends ModelResource
{
// ...
protected function indexFields(): iterable
{
return [
ID::make(),
Text::make('Title'),
Switcher::make('Active')
->updateOnPreview(
events: [AlpineJs::event(JsEvent::TABLE_ROW_UPDATED, 'index-table-{row-id}')]
)
];
}
}
The withUpdateRow() method is also available, which helps simplify the assignment of events.
// torchlight! {"summaryCollapsedIndicator": "namespaces"}
// [tl! collapse:6]
namespace App\MoonShine\Resources;
use MoonShine\UI\Fields\ID;
use MoonShine\UI\Fields\Switcher;
use MoonShine\UI\Fields\Text;
use MoonShine\Laravel\Resources\ModelResource;
class PostResource extends ModelResource
{
// ...
protected function indexFields(): iterable
{
return [
ID::make(),
Text::make('Title'),
Switcher::make('Active')
->withUpdateRow($this->getListComponentName())
];
}
}
namespaces
namespaceApp\MoonShine\Resources;
useMoonShine\UI\Fields\ID;
useMoonShine\UI\Fields\Switcher;
useMoonShine\UI\Fields\Text;
useMoonShine\Laravel\Resources\ModelResource;
classPostResourceextendsModelResource
{
// ...
protectedfunctionindexFields():iterable
{
return [
ID::make(),
Text::make('Title'),
Switcher::make('Active')
->withUpdateRow($this->getListComponentName())
];
}
}
namespace App\MoonShine\Resources;
use MoonShine\UI\Fields\ID;
use MoonShine\UI\Fields\Switcher;
use MoonShine\UI\Fields\Text;
use MoonShine\Laravel\Resources\ModelResource;
class PostResource extends ModelResource
{
// ...
protected function indexFields(): iterable
{
return [
ID::make(),
Text::make('Title'),
Switcher::make('Active')
->withUpdateRow($this->getListComponentName())
];
}
}
You can completely replace or modify the resource's TableBuilder for both the index and detail pages.
Use the modifyListComponent() or modifyDetailComponent() methods for this.
// torchlight! {"summaryCollapsedIndicator": "namespaces"}
// [tl! collapse:1]
use MoonShine\Contracts\UI\ComponentContract;
public function modifyListComponent(ComponentContract $component): ComponentContract
{
return parent::modifyListComponent($component)->customAttributes([
'data-my-attr' => 'value'
]);
}
use MoonShine\Contracts\UI\ComponentContract;
public function modifyListComponent(ComponentContract $component): ComponentContract
{
return parent::modifyListComponent($component)->customAttributes([
'data-my-attr' => 'value'
]);
}
// torchlight! {"summaryCollapsedIndicator": "namespaces"}
// [tl! collapse:1]
use MoonShine\Contracts\UI\ComponentContract;
public function modifyDetailComponent(ComponentContract $component): ComponentContract
{
return parent::modifyDetailComponent($component)->customAttributes([
'data-my-attr' => 'value'
]);
}
use MoonShine\Contracts\UI\ComponentContract;
public function modifyDetailComponent(ComponentContract $component): ComponentContract
{
return parent::modifyDetailComponent($component)->customAttributes([
'data-my-attr' => 'value'
]);
}
If it is not enough to just automatically output fields in thead, tbody, and tfoot,
you can override or extend this logic based on the resource methods thead(), tbody(), tfoot().
// torchlight! {"summaryCollapsedIndicator": "namespaces"}
// [tl! collapse:5]
use Closure;
use MoonShine\Contracts\UI\Collection\TableRowsContract;
use MoonShine\Contracts\UI\TableRowContract;
use MoonShine\UI\Collections\TableCells;
use MoonShine\UI\Collections\TableRows;
protected function thead(): null|TableRowsContract|Closure
{
return static fn(TableRowContract $default) => TableRows::make([$default])->pushRow(
TableCells::make()->pushCell(
'td content'
)
);
}
protected function tbody(): null|TableRowsContract|Closure
{
return static fn(TableRowsContract $default) => $default->pushRow(
TableCells::make()->pushCell(
'td content'
)
);
}
protected function tfoot(): null|TableRowsContract|Closure
{
return static fn(?TableRowContract $default) => TableRows::make([$default])->pushRow(
TableCells::make()->pushCell(
'td content'
)
);
}
use Closure;
use MoonShine\Contracts\UI\Collection\TableRowsContract;
use MoonShine\Contracts\UI\TableRowContract;
use MoonShine\UI\Collections\TableCells;
use MoonShine\UI\Collections\TableRows;
use MoonShine\UI\Components\Table\TableBuilder;
use MoonShine\UI\Components\Table\TableRow;
protected function tfoot(): null|TableRowsContract|Closure
{
return static function(?TableRowContract $default, TableBuilder $table) {
$cells = TableCells::make();
$cells->pushCell('Balance:');
$cells->pushCell('$1000');
return TableRows::make([TableRow::make($cells), $default]);
};
}