- Basics
- Basic Usage
- Basic Methods
- View Methods
- Additional Features
- Attribute Configuration
- Async Loading
- Type Cast
- Using in Blade
Basics
TableBuilder
is a tool in MoonShine for creating customizable tables for displaying data. It is used on index and detail CRUD pages, as well as for relationship fields such as HasMany
, BelongsToMany
, RelationRepeater
, and Json
fields.
use MoonShine\UI\Components\Table\TableBuilder; TableBuilder::make(iterable $fields = [], iterable $items = [])
<x-moonshine::table :columns="[ '#', 'First', 'Last', 'Email' ]" :values="[ [1, fake()->firstName(), fake()->lastName(), fake()->safeEmail()], [2, fake()->firstName(), fake()->lastName(), fake()->safeEmail()], [3, fake()->firstName(), fake()->lastName(), fake()->safeEmail()] ]"/>
Basic Usage
Example of using TableBuilder
:
TableBuilder::make() ->items([ ['id' => 1, 'title' => 'Hello world'] ]) ->fields([ ID::make()->sortable(), Text::make('Title', 'title'), ])
Basic Methods
Fields
Fields for TableBuilder
simplify the filling of data and displaying table cells. By default, fields are displayed in preview
mode. The fields
method defines the table fields, each field is a table cell (td
):
->fields([ ID::make()->sortable(), Text::make('Title', 'title'),])
If you need to specify attributes for td
, use the customWrapperAttributes
method:
->fields([ ID::make()->sortable(), Text::make('Title', 'title')->customWrapperAttributes(['class' => 'my-class']),])
Items
The items
method sets the data for the table:
->items($this->getCollection())
Paginator
The paginator
method sets the paginator for the table. You need to pass an object that implements the MoonShine\Contracts\Core\Paginator\PaginatorContract
interface:
If you need to specify a paginator for QueryBuilder, you can use the built-in ModelCaster
, as in the example below:
->paginator( (new ModelCaster(Article::class)) ->paginatorCast( Article::query()->paginate() ))
The paginator can also be specified through the items
method.
Simple Paginator
The simple()
method applies a simplified pagination style to the table:
->simple()
Buttons
The buttons
method adds action buttons:
->buttons([ ActionButton::make('Delete', fn() => route('name.delete')), ActionButton::make('Edit', fn() => route('name.edit'))->showInDropdown(), ActionButton::make('Go to home', fn() => route('home'))->blank()->canSee(fn($data) => $data->active), ActionButton::make('Mass Delete', fn() => route('name.mass_delete'))->bulk(),])
To specify bulk actions on table items, the bulk
method should be set on ActionButton
:
->buttons([ ActionButton::make('Mass Delete', fn() => route('name.mass_delete'))->bulk(),])
View Methods
Vertical Display
The vertical()
method displays the table in vertical format (used on DetailPage
):
->vertical()
If you want to change the attributes of the columns in vertical mode, use the title
or value
parameters:
/** @param TableBuilder $component */public function modifyDetailComponent(ComponentContract $component): ComponentContract{ return $component->vertical( title: fn(FieldContract $field, Column $default, TableBuilder $ctx): ComponentContract => $default->columnSpan(2), value: fn(FieldContract $field, Column $default, TableBuilder $ctx): ComponentContract => $default->columnSpan(10), );}
-
title
- Column with the header -
value
- Column with the value
You can also pass an integer value to specify the columns:
$component->vertical( title: 2, value: 10,)
Editable Table
The editable()
method makes the table editable, switching all fields to defaultMode
(form mode):
->editable()
Preview Mode
The preview()
method disables the display of buttons and sorting for the table:
->preview()
With "Not Found" Notification
By default, if the table has no data, it will be empty, but you can display a message saying "No records found yet." To do this, use the withNotFound
method:
TableBuilder::make() ->withNotFound()
Row Customization
Fields accelerate the process and fill the table independently, constructing the table header with field headers and sorts, the body of the table with data output through fields, and the footer of the table with bulk actions. However, sometimes there may be a need to specify rows manually or add additional ones. For this task, methods are provided for the corresponding sections of the table: headRows
(thead
), rows
(tbody
), footRows
(tfoot
).
// tbodyTableBuilder::make() ->rows( static fn(TableRowsContract $default) => $default->pushRow( TableCells::make()->pushCell( 'td content' ) ) ) // theadTableBuilder::make() ->headRows( static fn(TableRowContract $default) => TableRows::make([$default])->pushRow( TableCells::make()->pushCell( 'td content' ) ) ) // tfootTableBuilder::make() ->footRows( static fn(?TableRowContract $default) => TableRows::make([$default])->pushRow( TableCells::make()->pushCell( 'td content' ) ) )
Note that for footRows
, a ?TableRowContract
is passed, and the value of $default
will be passed as null
if there are no bulk action buttons. The null
value can be specified in the $items
list in TableRows::make
, and it will be ignored.
TableRows
and TableCells
are collections of components with additional functionality for quickly adding a row or cell to the table.
TableRows::make()->pushRow( TableCellsContract $cells, int|string|null $key = null, ?Closure $builder = null)
-
$cells
- a collection of cells, -
$key
- a unique key fortr
for bulk actions and row update events, -
$builder
- access to TableBuilder.
TableCells::make()->pushCell( Closure|string $content, ?int $index = null, ?Closure $builder = null, array $attributes = [])
-
$content
- content of the cell, -
$index
- ordinal number of the cell, -
$builder
- access to TableBuilder, -
$attributes
- HTML attributes of the cell.
TableCells
also has additional helper methods.
pushFields
for quick generation of cells based on fields:
TableCells::make()->pushFields( FieldsContract $fields, ?Closure $builder = null, int $startIndex = 0)
-
$fields
- collection of fields, -
$builder
- access to TableBuilder, -
$startIndex
- starting index (since there may have already been cells added to the table previously)
Conditional methods pushWhen
and pushCellWhen
are also available.
Additional Features
Adding New Rows
The creatable()
method allows adding new rows, making the table dynamic:
->creatable(reindex: true, limit: 5, label: 'Add', icon: 'plus', attributes: ['class' => 'my-class'])
creatable( bool $reindex = true, ?int $limit = null, ?string $label = null, ?string $icon = null, array $attributes = [], ?ActionButtonContract $button = null)
-
$reindex
- editing mode with dynamic name, -
$limit
- the number of records that can be added, -
$label
- button name, -
$icon
- button icon, -
$attributes
- additional attributes, -
$button
- custom add button.
In add mode, it is necessary for the last element to be empty (skeleton for a new record)!
If there are fields in the table in editing mode with dynamic name, you need to add the method or parameter reindex
:
TableBuilder::make() ->creatable(reindex: true) TableBuilder::make() ->creatable() ->reindex()
Example with specifying a custom add button:
TableBuilder::make() ->creatable( button: ActionButton::make('Foo', '#') )
Reindexing
The reindex()
method allows reindexing the table elements, adding an index to all name
attributes of form elements. Example: The field Text::make('Title', 'title')
in the first row of the table will look like <input name="title[1]">
. In creatable
or removable
mode, when adding/removing a new row, all name
attributes will be reindexed considering the ordinal number.
->reindex()
Drag and Drop Sorting
The reorderable()
method adds the ability to sort rows by dragging:
->reorderable(url: '/reorder-url', key: 'id', group: 'group-name')
-
$url
- handler URL, -
$key
- item key, -
$group
- grouping (if required).
Sticky Header
The sticky()
method makes the table header fixed:
->sticky()
Column Selection
The columnSelection()
method adds the ability to select displayed columns:
->columnSelection()
If you need to disable the display selection for certain fields, use the columnSelection
method on the field with the parameter set to false
:
TableBuilder::make() ->fields([ Text::make('Title') ->columnSelection(false), Text::make('Text') ]) ->columnSelection()
When using columnSelection
, the name
parameter of the TableBuilder
component must be unique across all pages. This is because data is stored in localStorage
based on the value of the component's name
.
Search
The searchable()
method adds the search function for the table:
->searchable()
Click Action
The clickAction()
method sets an action to be performed on clicking the row: In the example below, clicking the table row will trigger a click on the edit button.
->clickAction(ClickAction::EDIT)
If you use custom buttons or have overridden the default buttons, you may also need to specify a button selector:
->clickAction(ClickAction::EDIT, '.edit-button')
Types of ClickAction:
-
ClickAction::SELECT
- select a row for bulk actions, -
ClickAction::EDIT
- go to edit, -
ClickAction::DETAIL
- go to detailed view.
Save State in URL
The pushState()
method saves the state of the table in the URL:
->pushState()
Modify Row Checkbox
The modifyRowCheckbox()
method allows modifying the bulk action checkbox. The example below demonstrates selecting the active checkbox by default:
->modifyRowCheckbox( fn(Checkbox $checkbox, DataWrapperContract $data, TableBuilder $ctx) => $data->getKey() === 2 ? $checkbox->customAttributes(['checked' => true]) : $checkbox)
Attribute Configuration
TableBuilder provides methods for configuring HTML attributes:
->trAttributes(fn(?DataWrapperContract $data, int $row): array => ['class' => $row % 2 ? 'bg-gray-100' : ''])->tdAttributes(fn(?DataWrapperContract $data, int $row, int $cell): array => ['class' => $cell === 0 ? 'font-bold' : ''])->headAttributes(['class' => 'bg-blue-500 text-white'])->bodyAttributes(['class' => 'text-sm'])->footAttributes(['class' => 'bg-gray-200'])->customAttributes(['class' => 'custom-table'])
Async Loading
The async()
method configures asynchronous loading of the table:
The async
method must be after the name
method
->async( Closure|string|null $url = null, string|array|null $events = null, ?AsyncCallback $callback = null,)
-
$url
- URL of the asynchronous request (the response must return TableBuilder), -
$events
- events that will be triggered after a successful response, -
$callback
- JS callback that can be added as a wrapper for the response.
After a successful request, you can trigger events by adding the events
parameter.
use MoonShine\Support\AlpineJs;use MoonShine\Support\Enums\JsEvent; TableBuilder::make() ->name('crud') ->async(events: [ AlpineJs::event(JsEvent::FORM_RESET, 'main-form'), AlpineJs::event(JsEvent::TOAST, params: ['text' => 'Success', 'type' => 'success']), ])
Event list for TableBuilder:
-
JsEvent::TABLE_UPDATED
- table update, -
JsEvent::TABLE_REINDEX
- table reindexing (seereindex()
) -
JsEvent::TABLE_ROW_UPDATED
- table row update (AlpineJs::event(JsEvent::TABLE_ROW_UPDATED, "{component-name}-{row-id}")
)
For more information on js events, refer to the Events section.
All parameters of the async
method are optional, and by default, TableBuilder
will automatically set URL based on the current page.
In the process of using TableBuilder in async
mode, there may arise a task where you use it outside the admin panel on pages that are not declared in the MoonShine system. Then you will need to specify your own URL and implement a response with the HTML table. Let's consider an implementation example:
TableBuilder::make()->name('my-table')->async(route('undefined-page.component', [ '_namespace' => self::class, '_component_name' => 'my-table']))
Controller
<?php declare(strict_types=1); namespace App\MoonShine\Controllers; use Illuminate\Contracts\View\View;use MoonShine\Laravel\MoonShineRequest;use MoonShine\Laravel\Http\Controllers\MoonShineController; final class UndefinedPageController extends MoonShineController{ public function component(MoonShineRequest $request): View { $page = app($request->input('_namespace')); $component = $page->getComponents()->findByName( $request->getComponentName() ); return $component->render(); }}
Lazy and whenAsync Methods
If you need to send a request to update the TableBuilder
component immediately upon page load, you must add the lazy()
method. Additionally, the lazy()
and whenAsync()
methods in combination can solve the problem of lazy loading data or loading data from an external source.
TableBuilder::make() ->name('dashboard-table') ->fields([ ID::make(), Slug::make('Slug'), Text::make('Title'), Preview::make('Image')->image() ]) ->async() ->lazy() ->whenAsync( fn(TableBuilder $table) => $table->items( Http::get('https://jsonplaceholder.org/posts')->json() ) ),
The whenAsync()
method checks if the current request is asynchronous to get the current TableBuilder
component. An example interaction with the methods where the loading of the table occurs by clicking a button:
ActionButton::make('Reload') ->async(events: [AlpineJs::event(JsEvent::TABLE_UPDATED, 'my-table')]), TableBuilder::make() ->name('my-table') ->fields([ ID::make(), Slug::make('Slug'), Text::make('Title'), Preview::make('Image')->image() ]) ->async() ->lazy() ->whenAsync( fn(TableBuilder $table) => $table->items( Http::get('https://jsonplaceholder.org/posts')->json() ) ), ->withNotFound()
Type Cast
If you use data in the table without cast
, you must specify what your data has as a key. Otherwise, some features, such as bulk operations, will not work.
Example:
TableBuilder::make() ->castKeyName('id') ->name('my-table') ->fields([ ID::make(), Text::make('Title') ]) ->items([ ['id' => 3,'title' => 'Hello world'] ]) ->buttons([ ActionButton::make('Mass Delete') ->bulk() ]),
The cast
method is used to cast values in the table to a certain type. Since by default, fields work with primitive types:
use MoonShine\Laravel\TypeCasts\ModelCaster; TableBuilder::make() ->cast(new ModelCaster(User::class))
In this example, we cast the data to the model format of User
using ModelCaster
.
For more detailed information, refer to the TypeCasts section.
Using in Blade
Basics
Styled tables can be created using the moonshine::table
component.
<x-moonshine::table :columns="[ '#', 'First', 'Last', 'Email' ]" :values="[ [1, fake()->firstName(), fake()->lastName(), fake()->safeEmail()], [2, fake()->firstName(), fake()->lastName(), fake()->safeEmail()], [3, fake()->firstName(), fake()->lastName(), fake()->safeEmail()] ]"/>
Simple View
The simple
parameter allows creating a simplified view of the table.
<x-moonshine::table :simple="true" :columns="[ '#', 'First', 'Last', 'Email' ]" :values="[ [1, fake()->firstName(), fake()->lastName(), fake()->safeEmail()], [2, fake()->firstName(), fake()->lastName(), fake()->safeEmail()], [3, fake()->firstName(), fake()->lastName(), fake()->safeEmail()] ]"/>
Sticky Header
If the table contains a large number of items, you can sticky the head while scrolling the table.
<x-moonshine::table :sticky="true" :columns="[ '#', 'First', 'Last', 'Email' ]" :values="[ [1, fake()->firstName(), fake()->lastName(), fake()->safeEmail()], [2, fake()->firstName(), fake()->lastName(), fake()->safeEmail()], [3, fake()->firstName(), fake()->lastName(), fake()->safeEmail()] ]"/>
With "Not Found" Notification
The notfound
parameter allows displaying a message when there are no items in the table.
<x-moonshine::table :columns="[ '#', 'First', 'Last', 'Email' ]" :notfound="true"/>
Slots
The table can be formed using slots.
<x-moonshine::table> <x-slot:thead class="text-center"> <th colspan="4">Header</th> </x-slot:thead> <x-slot:tbody> <tr> <th>1</th> <th>{{ fake()->firstName() }}</th> <th>{{ fake()->lastName() }}</th> <th>{{ fake()->safeEmail() }}</th> </tr> <tr> <th>2</th> <th>{{ fake()->firstName() }}</th> <th>{{ fake()->lastName() }}</th> <th>{{ fake()->safeEmail() }}</th> </tr> <tr> <th>3</th> <th>{{ fake()->firstName() }}</th> <th>{{ fake()->lastName() }}</th> <th>{{ fake()->safeEmail() }}</th> </tr> </x-slot:tbody> <x-slot:tfoot class="text-center"> <td colspan="4">Footer</td> </x-slot:tfoot></x-moonshine::table>
Styling
For styling the table, there are pre-defined classes that can be used for tr
/ td
.
Available classes:
- bgc-purple
- bgc-pink
- bgc-blue
- bgc-green
- bgc-yellow
- bgc-red
- bgc-gray
- bgc-primary
- bgc-secondary
- bgc-success
- bgc-warning
- bgc-error
- bgc-info
<x-moonshine::table> <x-slot:thead class="bgc-secondary text-center"> <th colspan="3">Header</th> </x-slot:thead> <x-slot:tbody> <tr> <th class="bgc-pink">{{ fake()->firstName() }}</th> <th class="bgc-gray">{{ fake()->lastName() }}</th> <th class="bgc-purple">{{ fake()->safeEmail() }}</th> </tr> <tr> <th class="bgc-green">{{ fake()->firstName() }}</th> <th class="bgc-red">{{ fake()->lastName() }}</th> <th class="bgc-yellow">{{ fake()->safeEmail() }}</th> </tr> </x-slot:tbody></x-moonshine::table>
TableBuilder
in MoonShine
offers a wide range of capabilities for creating flexible and functional tables in the admin panel.