With pages

# Основы

MoonShine предоставляет возможность кастомизировать crud страниц ModelResource, для этого необходимо, при создании ресурса через команду, выбрать тип ресурса
Model resource with pages.

В результате будет создан класс ресурса модели и дополнительные классы для индексной, детальной и страницы с формой.
Располагаться классы страниц по умолчанию будут в директории app/MoonShine/Pages.

В созданном ресурсе модели в методе pages() будут зарегистрированы crud страницы.

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

Для указания типа страницы в ModelResource используется enum class PageType

Доступны следующие типы страниц:

  • INDEX - индексная страница,
  • FORM - страница с формой,
  • DETAIL - детальная страница.
use MoonShine\Enums\PageType;
 
//...
 
PageType::INDEX;
PageType::FORM;
PageType::DETAIL;

# Добавление полей

Поля в MoonShine используются не только для ввода данных, но и для их вывода.
Метод fields() в классе crud страницы позволяет указать необходимые поля.

namespace App\MoonShine\Pages\Post;
 
use MoonShine\Pages\Crud\IndexPage;
 
class PostIndexPage extends IndexPage
{
public function fields(): array
{
return [
ID::make(),
Text::make('Title'),
];
}
 
//...
}

# Основные компоненты

В админ-панели MoonShine можно быстро изменить основной компонент на странице.

IndexPage

Метод itemsComponent() позволяет изменить основной компонент индексной страницы.

itemsComponent(iterable $items, Fields $fields)
  • $items - значения полей,
  • $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',
]);
});
}
}

Пример индексной страницы с компонентом CardsBuilder в разделе Recipes

DetailPage

Метод detailComponent() позволяет изменить основной компонент детальной страницы.

detailComponent(?Model $item, Fields $fields)
  • $item - Eloquent Model
  • $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

Метод formComponent() позволяет изменить основной компонент на странице с формой.

formComponent(
string $action,
?Model $item,
Fields $fields,
bool $isAsync = false,
): MoonShineRenderable
  • $action - обработчик,
  • $item - Eloquent Model,
  • $fields - поля,
  • $isAsync - асинхронный режим.
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']);
}
}

# Слои на странице

Для удобства все crud страницы разбиты на три слоя, которые отвечают за отображение определенной области на странице.

  • TopLayer - по умолчанию используется для вывода метрик на индексной странице и для дополнительных кнопок на странице редактирования
  • MainLayer - по умолчанию данный слой используется для вывода основной информации используя FormBuilder и TableBuilder
  • BottomLayer - по умолчанию используется для вывода дополнительной информации

Для кастомизации слоев используются соответствующие методы: topLayer(), mainLayer() и bottomLayer(). Методы должны возвращать массив Компонентов .

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()
];
}
 
//...
}

Если необходимо через ресурс или страницу получить доступ к компонентам определенного слоя, то воспользуйтесь методом getLayerComponents

use MoonShine\Enums\Layer;
use MoonShine\Enums\PageType;
 
// ...
 
// Resource
$this->getPages()
->formPage()
->getLayerComponents(Layer::BOTTOM);
 
// Page
$this->getLayerComponents(Layer::BOTTOM);

Если необходимо через ресурс добавить компонент для указанной страницы в нужный слой, то воспользуйтесь методом onBoot ресурса и pushToLayer страницы

protected function onBoot(): void
{
$this->getPages()
->formPage()
->pushToLayer(
layer: Layer::BOTTOM,
component: Permissions::make(
'Permissions',
$this,
)
);
}