Basics
MoonShine
provides the ability to configure CRUD
pages in ModelResource
. To do this, you need to choose the resource type Model resource with pages
when creating a resource via the command.
This will create a model resource class and additional classes for the index, detail view, and form pages. The page classes will, by default, be located in the app/MoonShine/Pages
directory.
In the created model resource, CRUD
pages will be registered in the pages()
method.
namespace App\MoonShine\Resources; use App\Models\Post;use App\MoonShine\Pages\Post\PostIndexPage;use App\MoonShine\Pages\Post\PostFormPage;use App\MoonShine\Pages\Post\PostDetailPage;use MoonShine\Laravel\Resources\ModelResource; class PostResource extends ModelResource{ protected string $model = Post::class; protected string $title = 'Posts'; //... protected function pages(): array { return [ PostIndexPage::class, PostFormPage::class, PostDetailPage::class, ]; } //...}
Page Types
To specify the page type in ModelResource
, the enum
class PageType
is used.
The following page types are available:
-
INDEX
- index page, -
FORM
- form page, -
DETAIL
- detail view page.
use MoonShine\Support\Enums; //... PageType::INDEX;PageType::FORM;PageType::DETAIL;
Adding Fields
Fields in MoonShine
are used not only for data input but also for output.
The fields()
method in the CRUD
page class allows you to specify the necessary fields.
namespace App\MoonShine\Pages\Post; use MoonShine\Laravel\Pages\Crud\IndexPage; class PostIndexPage extends IndexPage{ protected function fields(): iterable { return [ ID::make(), Text::make('Title'), ]; } //...}
Main Components
In the MoonShine
admin panel, you can quickly change the main component on the page.
IndexPage
The getItemsComponent()
method allows you to change the main component of the index page.
getItemsComponent(iterable $items, Fields $fields): ComponentContract
-
$items
- field values, -
$fields
- fields.
class ArticleIndexPage extends IndexPage{ // ... protected function getItemsComponent(iterable $items, Fields $fields): ComponentContract { return TableBuilder::make(items: $items) ->name($this->getListComponentName()) ->fields($fields) ->cast($this->getResource()->getCaster()) ->withNotFound() ->when( ! is_null($head = $this->getResource()->getHeadRows()), fn (TableBuilderContract $table): TableBuilderContract => $table->headRows($head) ) ->when( ! is_null($body = $this->getResource()->getRows()), fn (TableBuilderContract $table): TableBuilderContract => $table->rows($body) ) ->when( ! is_null($foot = $this->getResource()->getFootRows()), fn (TableBuilderContract $table): TableBuilderContract => $table->footRows($foot) ) ->when( ! is_null($this->getResource()->getTrAttributes()), fn (TableBuilderContract $table): TableBuilderContract => $table->trAttributes( $this->getResource()->getTrAttributes() ) ) ->when( ! is_null($this->getResource()->getTdAttributes()), fn (TableBuilderContract $table): TableBuilderContract => $table->tdAttributes( $this->getResource()->getTdAttributes() ) ) ->buttons($this->getResource()->getIndexButtons()) ->clickAction($this->getResource()->getClickAction()) ->when($this->getResource()->isAsync(), static function (TableBuilderContract $table): void { $table->async()->pushState(); }) ->when($this->getResource()->isStickyTable(), function (TableBuilderContract $table): void { $table->sticky(); }) ->when($this->getResource()->isColumnSelection(), function (TableBuilderContract $table): void { $table->columnSelection(); }); }}
Example of an index page with the CardsBuilder
component in the Recipes section.
DetailPage
The getDetailComponent()
method allows you to change the main component of the detail page.
getDetailComponent(?DataWrapperContract $item, Fields $fields): ComponentContract
-
$item
- data -
$fields
- fields
class ArticleDetailPage extends DetailPage{ // ... protected function getDetailComponent(?DataWrapperContract $item, Fields $fields): ComponentContract { return TableBuilder::make($fields) ->cast($this->getResource()->getCaster()) ->items([$item]) ->vertical() ->simple() ->preview(); }}
FormPage
The getFormComponent()
method allows you to change the main component on the form page.
getFormComponent( string $action, ?DataWrapperContract $item, Fields $fields, bool $isAsync = true,): ComponentContract
-
$action
- endpoint, -
$item
- data, -
$fields
- fields, -
$isAsync
- asynchronous mode.
class ArticleFormPage extends FormPage{ // ... protected function getFormComponent( string $action, ?DataWrapperContract $item, Fields $fields, bool $isAsync = true, ): ComponentContract { $resource = $this->getResource(); return FormBuilder::make($action) ->cast($this->getResource()->getCaster()) ->fill($item) ->fields([ ...$fields ->when( ! is_null($item), static fn (Fields $fields): Fields => $fields->push( Hidden::make('_method')->setValue('PUT') ) ) ->when( ! $resource->isItemExists() && ! $resource->isCreateInModal(), static fn (Fields $fields): Fields => $fields->push( Hidden::make('_force_redirect')->setValue(true) ) ) ->toArray(), ]) ->when( ! $resource->hasErrorsAbove(), fn (FormBuilderContract $form): FormBuilderContract => $form->errorsAbove($resource->hasErrorsAbove()) ) ->when( $isAsync, static fn (FormBuilderContract $formBuilder): FormBuilderContract => $formBuilder ->async(events: array_filter([ $resource->getListEventName( request()->input('_component_name', 'default'), $isAsync && $resource->isItemExists() ? array_filter([ 'page' => request()->input('page'), 'sort' => request()->input('sort'), ]) : [] ), ! $resource->isItemExists() && $resource->isCreateInModal() ? AlpineJs::event(JsEvent::FORM_RESET, $resource->getUriKey()) : null, ])) ) ->when( $resource->isPrecognitive() || (moonshineRequest()->isFragmentLoad('crud-form') && ! $isAsync), static fn (FormBuilderContract $form): FormBuilderContract => $form->precognitive() ) ->when( $resource->isSubmitShowWhen(), static fn (FormBuilderContract $form): FormBuilderContract => $form->submitShowWhenAttribute() ) ->name($resource->getUriKey()) ->submit(__('moonshine::ui.save'), ['class' => 'btn-primary btn-lg']) ->buttons($resource->getFormBuilderButtons()); }}
Layers on the Page
For convenience, all CRUD pages are divided into three layers, which are responsible for displaying a certain area on the page.
-
TopLayer
- used for displaying metrics on the index page and for additional buttons on the edit page -
MainLayer
- this layer is used for displaying main information using FormBuilder and TableBuilder -
BottomLayer
- used for displaying additional information
To configure the layers, the corresponding methods are used: topLayer()
, mainLayer()
, and bottomLayer()
. The methods must return an array of Components.
class PostIndexPage extends IndexPage{ //... protected function topLayer(): array { return [ Heading::make('Custom top'), ...parent::topLayer() ]; } protected function mainLayer(): array { return [ Heading::make('Custom main'), ...parent::mainLayer() ]; } protected function bottomLayer(): array { return [ Heading::make('Custom bottom'), ...parent::bottomLayer() ]; } //...}
If you need to access components of a specific layer from a resource or page, use the getLayerComponents
method.
use MoonShine\Support\Enums\Layer;use MoonShine\Support\Enums\PageType; // ... // Resource$this->getFormPage()->getLayerComponents(Layer::BOTTOM); // Page$this->getLayerComponents(Layer::BOTTOM);
If you need to add a component to a specified page in the desired layer from a resource, use the resource's onLoad
method and the page's pushToLayer
.
protected function onLoad(): void{ $this->getFormPage()->pushToLayer( layer: Layer::BOTTOM, component: Permissions::make( 'Permissions', $this, ) );}