- Basics
- Sorting
- Buttons
- Attributes
- Click Actions
- Sticky Header
- Column Display
- Pagination
- Async Mode
- Modifiers
Basics
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.
Sorting
namespace App\MoonShine\Resources; use App\Models\Post;use MoonShine\Laravel\Resources\ModelResource;use MoonShine\Support\Enums\SortDirection; class PostResource extends ModelResource{ protected string $sortColumn = 'created_at'; // Default sort field protected SortDirection $sortDirection = SortDirection::DESC; // Default sort type //...}
Buttons
To add buttons to the table, you can use ActionButton
and the methods indexButtons
or customIndexButtons
, as well as detailButtons
and customDetailButtons
for the detail page.
protected function customIndexButtons(): ListOf{ return parent::customIndexButtons()->add(ActionButton::make('Link', '/endpoint'));}
When using the customIndexButtons
method, all your buttons will be added before the main CRUD
buttons. However, if you need to replace the main buttons or add new ones after the main buttons, you can use the indexButtons
method.
After the main buttons:
protected function indexButtons(): ListOf{ return parent::indexButtons()->add(ActionButton::make('Link', '/endpoint'));}
Before the main buttons:
protected function indexButtons(): ListOf{ return parent::indexButtons()->prepend(ActionButton::make('Link', '/endpoint'));}
Remove the delete button:
protected function indexButtons(): ListOf{ return parent::indexButtons()->except(fn(ActionButton $btn) => $btn->getName() === 'resource-delete-button');}
Clear the button set and add your own:
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 methods detailButtons
and customDetailButtons
.
For bulk actions, you need to add the bulk
method.
protected function customIndexButtons(): ListOf{ return parent::customIndexButtons()->add(ActionButton::make('Link', '/endpoint')->bulk());}
Attributes
To add attributes for the td
element of the table, you can use the customWrapperAttributes
method on the field that represents the cell you need.
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.
namespace App\MoonShine\Resources; use App\Models\Post;use Closure;use MoonShine\UI\Fields\Text;use MoonShine\Laravel\Resources\ModelResource;use MoonShine\Contracts\Core\TypeCasts\DataWrapperContract; 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 ]; } //...}
Click Actions
By default, clicking on tr
does nothing, but you can change the behavior to navigate to editing, selection, or to the detailed view.
// ClickAction::SELECT, ClickAction::DETAIL, ClickAction::EDIT protected ?ClickAction $clickAction = ClickAction::SELECT;
Sticky Header
The model resource property stickyTable
allows you to fix the header when scrolling a table with a large number of elements.
namespace App\MoonShine\Resources; use MoonShine\Laravle\Resources\ModelResource; class PostResource extends ModelResource{ protected string $model = Post::class; protected string $title = 'Posts'; protected bool $stickyTable = true; // ...}
Column Display
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.
namespace App\MoonShine\Resources; use App\Models\Post;use MoonShine\Laravel\Resources\ModelResource; class PostResource extends ModelResource{ protected string $model = Post::class; protected string $title = 'Posts'; protected bool $columnSelection = true; //...}
If you need to exclude fields from the selection, use the columnSelection()
method.
public function columnSelection(bool $active = true)
namespace App\MoonShine\Resources; use App\Models\Post;use MoonShine\UI\Fields\ID;use MoonShine\UI\Fields\Text;use MoonShine\UI\Fields\Textarea;use MoonShine\Laravel\Resources\ModelResource; class PostResource extends ModelResource{ protected string $model = Post::class; protected string $title = 'Posts'; protected bool $columnSelection = true; //... protected function indexFields(): iterable { return [ ID::make() ->columnSelection(false), Text::make('Title'), Textarea::make('Body'), ]; } //...}
Pagination
To change the number of items per page, use the property $itemsPerPage
.
namespace App\MoonShine\Resources; use App\Models\Post;use MoonShine\Laravel\Resources\ModelResource; class PostResource extends ModelResource{ // .. protected int $itemsPerPage = 25; //...}
Cursor
When dealing with a large volume of data, the best solution is to use cursor pagination.
namespace App\MoonShine\Resources; use App\Models\Post;use MoonShine\Laravel\Resources\ModelResource; class PostResource extends ModelResource{ // .. protected bool $cursorPaginate = true; //...}
Simple
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.
namespace App\MoonShine\Resources; use App\Models\Post;use MoonShine\Laravel\Resources\ModelResource; class PostResource extends ModelResource{ // ... protected bool $simplePaginate = true; // ...}
![img] (/storage/docs-assets/3.x/screenshots/resource_simple_paginate_dark.png)
Disable Pagination
If you do not plan to use pagination, it can be disabled.
namespace App\MoonShine\Resources; use App\Models\Post;use MoonShine\Laravel\Resources\ModelResource; class PostResource extends ModelResource{ // ... protected bool $usePagination = false; // ...}
Async Mode
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 App\Models\Post;use MoonShine\Laravel\Resources\ModelResource; class PostResource extends ModelResource{ // ... protected bool $isAsync = false; // ...}
Updating a row
You can asynchronously update a row in the table by triggering the event:
table-row-updated-{{componentName}}-{{row-id}}
-
{{componentName}}
- the name of the component; -
{{row-id}}
- the key of the row item.
To add an event, you can use the helper class:
AlpineJs::event(JsEvent::TABLE_ROW_UPDATED, 'main-table-{row-id}')
-
{row-id}
- shortcode for the id of the current model record.
namespace App\MoonShine\Resources; use App\Models\Post;use MoonShine\Support\Enums\JsEvent;use MoonShine\UI\Fields\ID;use MoonShine\UI\Fields\Switcher;use MoonShine\UI\Fields\Text;use MoonShine\UI\Fields\Textarea;use MoonShine\Laravel\Resources\ModelResource;use MoonShine\Support\AlpineJs; class PostResource extends ModelResource{ //... protected function fields(): iterable { return [ ID::make(), Text::make('Title'), Textarea::make('Body'), 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:
namespace App\MoonShine\Resources; use App\Models\Post;use MoonShine\UI\Fields\ID;use MoonShine\UI\Fields\Switcher;use MoonShine\UI\Fields\Text;use MoonShine\UI\Fields\Textarea;use MoonShine\Laravel\Resources\ModelResource; class PostResource extends ModelResource{ //... protected function fields(): iterable { return [ ID::make(), Text::make('Title'), Textarea::make('Body'), Switcher::make('Active') ->withUpdateRow($this->getListComponentName()) ]; } //...}
Modifiers
Components
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.
public function modifyListComponent(ComponentContract $component): ComponentContract{ return parent::modifyListComponent($component)->customAttributes([ 'data-my-attr' => 'value' ]);}
public function modifyDetailComponent(MoonShineRenderable $component): MoonShineRenderable{ return parent::modifyDetailComponent($component)->customAttributes([ 'data-my-attr' => 'value' ]);}
Elements thead, tbody, tfoot
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()
.
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' ) );}
Example of adding an additional row in tfoot
protected function tfoot(): null|TableRowsContract|Closure{ return static function(?TableRowContract $default, TableBuilder $table) { $cells = TableCells::make(); $cells->pushCell('Balancе:'); $cells->pushCell('1000 р.'); return TableRows::make([TableRow::make($cells), $default]); };}