Table

# Properties

namespace App\MoonShine\Resources;
 
use App\Models\Post;
use MoonShine\Resources\ModelResource;
 
class PostResource extends ModelResource
{
protected string $sortColumn = ''; // Default sort field
 
protected string $sortDirection = 'DESC'; // Default sort type
 
protected int $itemsPerPage = 25; // Number of elements per page
 
//...
}

# Buttons

To add buttons to the table, use ActionButton and the indexButtons or buttons methods in the resource

public function indexButtons(): array
{
return [
ActionButton::make('Link', '/endpoint'),
];
}

An example of creating custom buttons for the index table in the section Recipes

For bulk actions you need to add the bulk method

public function indexButtons(): array
{
return [
ActionButton::make('Link', '/endpoint')->bulk(),
];
}

You can also use the buttons method, but in this case the buttons will be on all other pages of the resource

public function buttons(): array
{
return [
ActionButton::make('Link', '/endpoint'),
];
}

# Attributes

Through model resources, it is possible to customize the data table tr and td.
To do this, you must use the appropriate trAttributes() and tdAttributes() methods, which need to pass a closure that returns attributes for the table component.

namespace App\MoonShine\Resources;
 
use App\Models\Post;
use Closure;
use Illuminate\View\ComponentAttributeBag;
use MoonShine\Fields\Text;
use MoonShine\Resources\ModelResource;
 
class PostResource extends ModelResource
{
protected string $model = Post::class;
 
protected string $title = 'Posts';
 
//...
 
public function trAttributes(): Closure
{
return function (
Model $item,
int $row,
ComponentAttributeBag $attr
): ComponentAttributeBag {
if ($item->id === 1 | $row === 2) {
$attr->setAttributes([
'class' => 'bgc-green'
]);
}
 
return $attr;
};
}
 
public function tdAttributes(): Closure
{
return function (
Model $item,
int $row,
int $cell,
ComponentAttributeBag $attr = null
): ComponentAttributeBag {
if ($cell === 6) {
$attr->setAttributes([
'class' => 'bgc-red'
]);
}
 
return $attr;
};
}
 
//...
}

# Click Actions

By default, nothing will happen when clicking tr, but you can change the behavior to go to edit, select or go to detailed view

// Resource property
// ClickAction::SELECT, ClickAction::DETAIL, ClickAction::EDIT
 
protected ?ClickAction $clickAction = ClickAction::SELECT;

# Sticky table header

The stickyTable model resource property allows you to fix the header when scrolling a table with a large number of elements.

namespace App\MoonShine\Resources;
 
use MoonShine\Resources\ModelResource;
 
class PostResource extends ModelResource
{
protected string $model = Post::class;
 
protected string $title = 'Posts';
 
protected bool $stickyTable = true;
 
// ...
}

# Simple pagination

If you don't plan to display the total number of pages, use Simple Pagination. This will avoid additional queries for the total number of records in the database.

namespace App\MoonShine\Resources;
 
use App\Models\Post;
use MoonShine\Resources\ModelResource;
 
class PostResource extends ModelResource
{
protected string $model = Post::class;
 
protected string $title = 'Posts';
 
protected bool $simplePaginate = true;
 
// ...
}

# Disabling pagination

If you don't plan to use pagination, you can turn it off.

namespace App\MoonShine\Resources;
 
use App\Models\Post;
use MoonShine\Resources\ModelResource;
 
class PostResource extends ModelResource
{
protected string $model = Post::class;
 
protected string $title = 'Posts';
 
protected bool $usePagination = false;
 
// ...
}

# Asynchronous mode

Switch mode without reboot for filtering, sorting and pagination

namespace App\MoonShine\Resources;
 
use App\Models\Post;
use MoonShine\Resources\ModelResource;
 
class PostResource extends ModelResource
{
protected string $model = Post::class;
 
protected string $title = 'Posts';
 
protected bool $isAsync = true;
 
// ...
}

# Updating a row

You can update a row of a table asynchronously; to do this, you need to trigger the event:

table-row-updated-{{componentName}}-{{row-key}}
  • {{componentName}} - name of the component;
  • {{row-key}} - row key.

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.

The presence of the ID field and asynchronous mode are required.

namespace App\MoonShine\Resources;
 
use App\Models\Post;
use MoonShine\Enums\JsEvent;
use MoonShine\Fields\ID;
use MoonShine\Fields\Switcher;
use MoonShine\Fields\Text;
use MoonShine\Fields\Textarea;
use MoonShine\Resources\ModelResource;
use MoonShine\Support\AlpineJs;
 
class PostResource extends ModelResource
{
protected string $model = Post::class;
 
protected string $title = 'Posts';
 
protected bool $isAsync = true;
 
//...
 
public function fields(): array
{
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 to help simplify event assignment:

TableBuilder::make()
->fields([
ID::make()->sortable(),
Text::make('Title'),
Textarea::make('Body'),
Switcher::make('Active')
->withUpdateRow('main-table')
])
->items($this->fetch())
->name('main-table')
->async(),

# Column display

You can let users decide which columns to display in the table, saving the choice. To do this, you need to set the resource parameter $columnSelection.

namespace App\MoonShine\Resources;
 
use App\Models\Post;
use MoonShine\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 selection, use the columnSelection() method.

public function columnSelection(bool $active = true)
namespace App\MoonShine\Resources;
 
use App\Models\Post;
use MoonShine\Fields\ID;
use MoonShine\Fields\Text;
use MoonShine\Fields\Textarea;
use MoonShine\Resources\ModelResource;
 
class PostResource extends ModelResource
{
protected string $model = Post::class;
 
protected string $title = 'Posts';
 
protected bool $columnSelection = true;
 
//...
 
public function fields(): array
{
return [
ID::make()
->columnSelection(false),
Text::make('Title'),
Textarea::make('Body'),
];
}
 
//...
}

# Modify

You can replace thead or tbody or tfoot, and also add elements to the table in tbody before and after the first row.

thead()
namespace App\MoonShine\Resources;
 
use MoonShine\Fields\Fields;
use MoonShine\Resources\ModelResource;
 
class PostResource extends ModelResource
{
// ...
 
public function thead(): ?Closure
{
return static fn(Fields $headFields): string => '<tr><th>Title</th></tr>';
}
}
tbody()
namespace App\MoonShine\Resources;
 
use Illuminate\Support\Collection;
use MoonShine\Resources\ModelResource;
 
class PostResource extends ModelResource
{
// ...
 
public function tbody(): ?Closure
{
return static fn(Collection $rows): string => '<tr><td>Content</td></tr>';
}
}
tfoot()
namespace App\MoonShine\Resources;
 
use MoonShine\ActionButtons\ActionButtons;
use MoonShine\Resources\ModelResource;
 
class PostResource extends ModelResource
{
// ...
 
public function tfoot(): ?Closure
{
return static fn(ActionButtons $bulkButtons): string => '<tr><td>Footer</td></tr>';
}
}
tbodyBefore()
namespace App\MoonShine\Resources;
 
use Illuminate\Support\Collection;
use MoonShine\Resources\ModelResource;
 
class PostResource extends ModelResource
{
// ...
 
public function tbodyBefore(): ?Closure
{
return static fn(Collection $rows): string => '<tr><td>Before</td></tr>';
}
}
tbodyAfter()
namespace App\MoonShine\Resources;
 
use Illuminate\Support\Collection;
use MoonShine\Resources\ModelResource;
 
class PostResource extends ModelResource
{
// ...
 
public function tbodyAfter(): ?Closure
{
return static fn(Collection $rows): string => '<tr><td>After</td></tr>';
}
}