Basics
MoonShine provides the ability to customize the ModelResource pages crud, for this it is necessary, when creating a resource through the command, select resource type Model resource with pages
.
This will create a model resource class and additional classes for the index, detail, and form pages. Page classes will be located by default 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\Resources\ModelResource; class PostResource extends ModelResource{ protected string $model = Post::class; protected string $title = 'Posts'; //... public function pages(): array { return [ PostIndexPage::make($this->title()), PostFormPage::make( $this->getItemID() ? __('moonshine::ui.edit') : __('moonshine::ui.add') ), PostDetailPage::make(__('moonshine::ui.show')), ]; } //...}
PageType
To specify the page type in ModelResource, use enum class PageType
.
The following page types are available:
-
INDEX
- index page, -
FORM
- form page, -
DETAIL
- detail page.
use MoonShine\Enums\PageType; //... PageType::INDEX;PageType::FORM;PageType::DETAIL;
Adding fields
Fields in MoonShine are used not only for data input, but also for their output.
The fields()
method in the page crud class allows you to specify the required fields.
namespace App\MoonShine\Pages\Post; use MoonShine\Pages\Crud\IndexPage; class PostIndexPage extends IndexPage{ public function fields(): array { 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 itemsComponent()
method allows you to change the main component of the index page.
itemsComponent(iterable $items, Fields $fields)
-
$items
- field values, -
$fields
- fields.
use MoonShine\Components\TableBuilder;use MoonShine\Contracts\MoonShineRenderable;use MoonShine\Fields\Fields;use MoonShine\Pages\Crud\IndexPage; class ArticleIndexPage extends IndexPage{ // ... protected function itemsComponent(iterable $items, Fields $fields): MoonShineRenderable { return TableBuilder::make(items: $items) ->name($this->listComponentName()) ->fields($fields) ->cast($this->getResource()->getModelCast()) ->withNotFound() ->when( ! is_null($this->getResource()->trAttributes()), fn (TableBuilder $table): TableBuilder => $table->trAttributes( $this->getResource()->trAttributes() ) ) ->when( ! is_null($this->getResource()->tdAttributes()), fn (TableBuilder $table): TableBuilder => $table->tdAttributes( $this->getResource()->tdAttributes() ) ) ->buttons($this->getResource()->getIndexItemButtons()) ->customAttributes([ 'data-click-action' => $this->getResource()->getClickAction(), ]) ->when($this->getResource()->isAsync(), function (TableBuilder $table): void { $table->async()->customAttributes([ 'data-pushstate' => 'true', ]); }); }}
Example index page with CardsBuilder component in section Recipes
- DetailPage
The detailComponent()
method allows you to change the main component of a detail page.
detailComponent(?Model $item, Fields $fields)
-$item
- Eloquent Model
-$fields
- fields
use Illuminate\Database\Eloquent\Model;use Illuminate\View\ComponentAttributeBag;use MoonShine\Components\TableBuilder;use MoonShine\Contracts\MoonShineRenderable;use MoonShine\Fields\Fields;use MoonShine\Pages\Crud\DetailPage; class ArticleDetailPage extends DetailPage{ // ... protected function detailComponent(?Model $item, Fields $fields): MoonShineRenderable { return TableBuilder::make($fields) ->cast($this->getResource()->getModelCast()) ->items([$item]) ->vertical() ->simple() ->preview() ->tdAttributes(fn ( $data, int $row, int $cell, ComponentAttributeBag $attributes ): ComponentAttributeBag => $attributes->when( $cell === 0, fn (ComponentAttributeBag $attr): ComponentAttributeBag => $attr->merge([ 'class' => 'font-semibold', 'width' => '20%', ]) )); }}
FormPage
The formComponent()
method allows you to change the main component on the page with the form.
formComponent( string $action, ?Model $item, Fields $fields, bool $isAsync = false,): MoonShineRenderable
-$action
- action,
-$item
- Eloquent Model,
-$fields
- fields,
-$isAsync
- asynchronous mode.
use Illuminate\Database\Eloquent\Model;use Illuminate\View\ComponentAttributeBag;use MoonShine\Components\FormBuilder;use MoonShine\Contracts\MoonShineRenderable;use MoonShine\Enums\JsEvent;use MoonShine\Fields\Fields;use MoonShine\Fields\Hidden;use MoonShine\Pages\Crud\FormPage;use MoonShine\Support\AlpineJs; class ArticleFormPage extends FormPage{ // ... protected function formComponent( string $action, ?Model $item, Fields $fields, bool $isAsync = false, ): MoonShineRenderable { $resource = $this->getResource(); return FormBuilder::make($action) ->fillCast( $item, $resource->getModelCast() ) ->fields( $fields ->when( ! is_null($item), fn (Fields $fields): Fields => $fields->push( Hidden::make('_method')->setValue('PUT') ) ) ->when( ! $item?->exists && ! $resource->isCreateInModal(), fn (Fields $fields): Fields => $fields->push( Hidden::make('_force_redirect')->setValue(true) ) ) ->toArray() ) ->when( $isAsync, fn (FormBuilder $formBuilder): FormBuilder => $formBuilder ->async(asyncEvents: [ $resource->listEventName(request('_component_name', 'default')), AlpineJs::event(JsEvent::FORM_RESET, 'crud'), ]) ) ->when( $resource->isPrecognitive() || (moonshineRequest()->isFragmentLoad('crud-form') && ! $isAsync), fn (FormBuilder $form): FormBuilder => $form->precognitive() ) ->name('crud') ->submit(__('moonshine::ui.save'), ['class' => 'btn-primary btn-lg']); }}
Layers on a page
For convenience, all crud pages are divided into three layers, which are responsible for displaying a specific area on the page.
-
TopLayer
- used to display metrics on the index page and for additional buttons on the edit page -
MainLayer
- this layer is used to display basic information using FormBuilder and TableBuilder -
BottomLayer
- used to display additional information
To customize layers, the corresponding methods are used: topLayer()
, mainLayer()
, and bottomLayer()
. Methods must return Components an array.
namespace App\MoonShine\Pages\Post; use MoonShine\Decorations\Heading;use MoonShine\Pages\Crud\IndexPage; 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 the components of a certain layer through a resource or page, then use the getLayerComponents
method.
use MoonShine\Enums\Layer;use MoonShine\Enums\PageType; // ... // Resource$this->getPages() ->findByType(PageType::FORM) ->getLayerComponents(Layer::BOTTOM); // Page$this->getLayerComponents(Layer::BOTTOM);
If you need to add a component for the specified page to the desired layer through a resource, then use the onBoot
method resource and page pushToLayer
.
protected function onBoot(): void{ $this->getPages() ->findByUri(PageType::FORM->value) ->pushToLayer( layer: Layer::BOTTOM, component: Permissions::make( 'Permissions', $this, ) );}