Pages are the core of the MoonShine architecture.
All key functionality is defined directly in page classes, which provides flexibility and modularity.
When creating a resource, classes are also created for the index pages (IndexPage), detailed view (DetailPage) and form (FormPage).
These pages will be registered with the resource in the pages() method.
namespace App\MoonShine\Resources;
use App\MoonShine\Resources\Post\Pages\PostIndexPage;
use App\MoonShine\Resources\Post\Pages\PostFormPage;
use App\MoonShine\Resources\Post\Pages\PostDetailPage;
use MoonShine\Laravel\Resources\ModelResource;
class PostResource extends ModelResource
{
protected function pages(): array
{
return [
PostIndexPage::class,
PostFormPage::class,
PostDetailPage::class,
];
}
}
namespaces
namespace App\MoonShine\Resources;
use App\MoonShine\Resources\Post\Pages\PostIndexPage;
use App\MoonShine\Resources\Post\Pages\PostFormPage;
use App\MoonShine\Resources\Post\Pages\PostDetailPage;
use MoonShine\Laravel\Resources\ModelResource;
class PostResource extends ModelResource
{
// ...
protected function pages(): array
{
return [
PostIndexPage::class,
PostFormPage::class,
PostDetailPage::class,
];
}
}
namespace App\MoonShine\Resources;
use App\MoonShine\Resources\Post\Pages\PostIndexPage;
use App\MoonShine\Resources\Post\Pages\PostFormPage;
use App\MoonShine\Resources\Post\Pages\PostDetailPage;
use MoonShine\Laravel\Resources\ModelResource;
class PostResource extends ModelResource
{
// ...
protected function pages(): array
{
return [
PostIndexPage::class,
PostFormPage::class,
PostDetailPage::class,
];
}
}
namespaces
namespace App\MoonShine\Resources;
use App\MoonShine\Resources\Post\Pages\PostIndexPage;
use App\MoonShine\Resources\Post\Pages\PostFormPage;
use App\MoonShine\Resources\Post\Pages\PostDetailPage;
use MoonShine\Laravel\Resources\ModelResource;
class PostResource extends ModelResource
{
// ...
protected function pages(): array
{
return [
PostIndexPage::class,
PostFormPage::class,
PostDetailPage::class,
];
}
}
namespace App\MoonShine\Resources;
use App\MoonShine\Resources\Post\Pages\PostIndexPage;
use App\MoonShine\Resources\Post\Pages\PostFormPage;
use App\MoonShine\Resources\Post\Pages\PostDetailPage;
use MoonShine\Laravel\Resources\ModelResource;
class PostResource extends ModelResource
{
// ...
protected function pages(): array
{
return [
PostIndexPage::class,
PostFormPage::class,
PostDetailPage::class,
];
}
}
IndexPage is the main section of the resource and is responsible for displaying the list of elements.
Lazy mode
Lazy mode delays loading the index table until it becomes visible on the page.
protected bool $isLazy = true;
protected bool $isLazy = true;
protected bool $isLazy = true;
protected bool $isLazy = true;
protected bool $isLazy = true;
Metrics
The metrics() method allows you to define metrics to display on the list page
(more details in the Metrics section).
Filters
In the filters() method you can specify a list of fields to form the filter form
(more details in the Filters section).
Query Tags
The queryTags() method allows you to add quick filtering buttons based on preset conditions
(more details in the Query Tags section).
Handlers
The handlers() method for registering event handlers
(more details in the Handlers section).
Main component
To modify an existing component, use the modifyListComponent() method
(more details in the Basics section).
To completely replace the main index page component, use your own class
(more details in the Main components section below).
##FormPage
FormPage is responsible for creating and editing elements.
You can add validation for resource form fields using Laravel's standard validation rules.
Validation rules
The rules() method allows you to define validation rules for fields.
protected function rules(DataWrapperContract $item): array
{
return [
'title' => ['required', 'string', 'min:5'],
'content' => ['required', 'string'],
'email' => ['sometimes', 'email'],
];
}
protected function rules(DataWrapperContract $item): array
{
return [
'title' => ['required', 'string', 'min:5'],
'content' => ['required', 'string'],
'email' => ['sometimes', 'email'],
];
}
protected function rules(DataWrapperContract $item): array
{
return [
'title' => ['required', 'string', 'min:5'],
'content' => ['required', 'string'],
'email' => ['sometimes', 'email'],
];
}
protected function rules(DataWrapperContract $item): array
{
return [
'title' => ['required', 'string', 'min:5'],
'content' => ['required', 'string'],
'email' => ['sometimes', 'email'],
];
}
protected function rules(DataWrapperContract $item): array
{
return [
'title' => ['required', 'string', 'min:5'],
'content' => ['required', 'string'],
'email' => ['sometimes', 'email'],
];
}
Validation messages
The validationMessages() method allows you to override validation error messages.
protected function rules(DataWrapperContract $item): array
{
return [
'title' => ['required', 'string', 'min:5'],
];
}
public function validationMessages(): array
{
return [
'title.required' => 'Title is required',
'title.min' => 'Title must contain at least :min characters',
];
}
protected function rules(DataWrapperContract $item): array
{
return [
'title' => ['required', 'string', 'min:5'],
];
}
public function validationMessages(): array
{
return [
'title.required' => 'Title is required',
'title.min' => 'Title must contain at least :min characters',
];
}
protected function rules(DataWrapperContract $item): array
{
return [
'title' => ['required', 'string', 'min:5'],
];
}
public function validationMessages(): array
{
return [
'title.required' => 'Title is required',
'title.min' => 'Title must contain at least :min characters',
];
}
protected function rules(DataWrapperContract $item): array
{
return [
'title' => ['required', 'string', 'min:5'],
];
}
public function validationMessages(): array
{
return [
'title.required' => 'Title is required',
'title.min' => 'Title must contain at least :min characters',
];
}
protected function rules(DataWrapperContract $item): array
{
return [
'title' => ['required', 'string', 'min:5'],
];
}
public function validationMessages(): array
{
return [
'title.required' => 'Title is required',
'title.min' => 'Title must contain at least :min characters',
];
}
Preparing data for validation
The prepareForValidation() method allows you to change data before validation.
public function prepareForValidation(): void
{
request()->merge([
'slug' => request()
->string('slug')
->lower()
->value(),
]);
}
public function prepareForValidation(): void
{
request()->merge([
'slug' => request()
->string('slug')
->lower()
->value(),
]);
}
public function prepareForValidation(): void
{
request()->merge([
'slug' => request()
->string('slug')
->lower()
->value(),
]);
}
public function prepareForValidation(): void
{
request()->merge([
'slug' => request()
->string('slug')
->lower()
->value(),
]);
}
public function prepareForValidation(): void
{
request()->merge([
'slug' => request()
->string('slug')
->lower()
->value(),
]);
}
Precognitive validation
The $isPrecognitive property allows you to enable precognitive validation for the form.
protected bool $isPrecognitive = true;
protected bool $isPrecognitive = true;
protected bool $isPrecognitive = true;
protected bool $isPrecognitive = true;
protected bool $isPrecognitive = true;
Precognitive validation allows you to validate form fields in real time as you enter data.
Main component
To modify an existing component, use the modifyFormComponent() method
(more details in the Basics section).
To completely replace the main form page component, use your own class
(more details in the Main components section below).
DetailPage is responsible for detailed display of the element.
Main component
To modify an existing component, use the modifyDetailComponent() method
(more details in the Basics section).
To completely replace the main form page component, use your own class
(more details in the Main components section below).
To specify the page type in ModelResource, the enum class PageType is used.
use MoonShine\Support\Enums\PageType;
PageType::INDEX;
PageType::FORM;
PageType::DETAIL;
namespaces
use MoonShine\Support\Enums\PageType;
PageType::INDEX;
PageType::FORM;
PageType::DETAIL;
use MoonShine\Support\Enums\PageType;
PageType::INDEX;
PageType::FORM;
PageType::DETAIL;
namespaces
use MoonShine\Support\Enums\PageType;
PageType::INDEX;
PageType::FORM;
PageType::DETAIL;
use MoonShine\Support\Enums\PageType;
PageType::INDEX;
PageType::FORM;
PageType::DETAIL;
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 main information using FormBuilder
and TableBuilder,
BottomLayer - used to display additional information.
To configure layers, the corresponding methods are used: topLayer(), mainLayer() and bottomLayer().
Methods must return an array of Components.
use MoonShine\Laravel\Pages\Crud\IndexPage;
use MoonShine\UI\Components\Heading;
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()
];
}
}
namespaces
use MoonShine\Laravel\Pages\Crud\IndexPage;
use MoonShine\UI\Components\Heading;
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()
];
}
}
use MoonShine\Laravel\Pages\Crud\IndexPage;
use MoonShine\UI\Components\Heading;
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()
];
}
}
namespaces
use MoonShine\Laravel\Pages\Crud\IndexPage;
use MoonShine\UI\Components\Heading;
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()
];
}
}
use MoonShine\Laravel\Pages\Crud\IndexPage;
use MoonShine\UI\Components\Heading;
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 specific layer, then use the getLayerComponents() method.
use MoonShine\Support\Enums\Layer;
$this->getFormPage()->getLayerComponents(Layer::BOTTOM);
$this->getLayerComponents(Layer::BOTTOM);
namespaces
use MoonShine\Support\Enums\Layer;
// Resource
$this->getFormPage()->getLayerComponents(Layer::BOTTOM);
// Page
$this->getLayerComponents(Layer::BOTTOM);
use MoonShine\Support\Enums\Layer;
// Resource
$this->getFormPage()->getLayerComponents(Layer::BOTTOM);
// Page
$this->getLayerComponents(Layer::BOTTOM);
namespaces
use MoonShine\Support\Enums\Layer;
// Resource
$this->getFormPage()->getLayerComponents(Layer::BOTTOM);
// Page
$this->getLayerComponents(Layer::BOTTOM);
use MoonShine\Support\Enums\Layer;
// Resource
$this->getFormPage()->getLayerComponents(Layer::BOTTOM);
// Page
$this->getLayerComponents(Layer::BOTTOM);
If you need to add a component for a specified page to the desired layer via a resource,
then use the onLoad() method of the resource and the pushToLayer() method of the page.
use MoonShine\Permissions\Components\Permissions;
use MoonShine\Support\Enums\Layer;
protected function onLoad(): void
{
$this->getFormPage()
->pushToLayer(
layer: Layer::BOTTOM,
component: Permissions::make(
'Permissions',
$this,
)
);
}
namespaces
use MoonShine\Permissions\Components\Permissions;
use MoonShine\Support\Enums\Layer;
protected function onLoad(): void
{
$this->getFormPage()
->pushToLayer(
layer: Layer::BOTTOM,
component: Permissions::make(
'Permissions',
$this,
)
);
}
use MoonShine\Permissions\Components\Permissions;
use MoonShine\Support\Enums\Layer;
protected function onLoad(): void
{
$this->getFormPage()
->pushToLayer(
layer: Layer::BOTTOM,
component: Permissions::make(
'Permissions',
$this,
)
);
}
namespaces
use MoonShine\Permissions\Components\Permissions;
use MoonShine\Support\Enums\Layer;
protected function onLoad(): void
{
$this->getFormPage()
->pushToLayer(
layer: Layer::BOTTOM,
component: Permissions::make(
'Permissions',
$this,
)
);
}
use MoonShine\Permissions\Components\Permissions;
use MoonShine\Support\Enums\Layer;
protected function onLoad(): void
{
$this->getFormPage()
->pushToLayer(
layer: Layer::BOTTOM,
component: Permissions::make(
'Permissions',
$this,
)
);
}
The main component of the page is specified by a class that implements one of the namespace interfaces MoonShine\Crud\Contracts\PageComponents.
This allows you to completely replace a component, encapsulate the logic, and reuse it between pages and resources.
Available interfaces:
DefaultListComponentContract - the main component of the index page (list of elements),
DefaultDetailComponentContract - the main component of the detail page,
DefaultFormContract - the main form component.
The class must implement the __invoke() method, which returns a component that implements the MoonShine\Contracts\UI\ComponentContract interface.
IndexPage
To change the index page component, you need to create a class that implements the DefaultListComponentContract interface:
use MoonShine\Contracts\Core\DependencyInjection\CoreContract;
use MoonShine\Contracts\Core\DependencyInjection\FieldsContract;
use MoonShine\Contracts\UI\ComponentContract;
use MoonShine\Contracts\UI\TableBuilderContract;
use MoonShine\Core\Traits\WithCore;
use MoonShine\Crud\Contracts\Page\IndexPageContract;
use MoonShine\Crud\Contracts\PageComponents\DefaultListComponentContract;
use MoonShine\UI\Components\Table\TableBuilder;
final class ArticleListComponent implements DefaultListComponentContract
{
use WithCore;
public function __construct(CoreContract $core) {
$this->setCore($core);
}
public function __invoke(
IndexPageContract $page,
iterable $items,
FieldsContract $fields
): ComponentContract
{
$resource = $page->getResource();
return TableBuilder::make(items: $items)
->name($page->getListComponentName())
->fields($fields)
->cast($resource->getCaster())
->withNotFound()
->buttons($page->getButtons())
->when($page->isAsync(), function (TableBuilderContract $table) use($page): void {
$table->async(
url: fn (): string
=> $page->getRouter()->getEndpoints()->component(
name: $table->getName(),
additionally: $this->getCore()->getRequest()->getRequest()->getQueryParams(),
),
)->pushState();
})
->when($page->isLazy(), function (TableBuilderContract $table) use($resource): void {
$table->lazy()->whenAsync(
fn (TableBuilderContract $t): TableBuilderContract
=> $t->items(
$resource->getItems(),
),
);
})
->when(
! \is_null($resource->getItemsResolver()),
function (TableBuilderContract $table) use($resource): void {
$table->itemsResolver(
$resource->getItemsResolver(),
);
},
);
}
}
namespaces
use MoonShine\Contracts\Core\DependencyInjection\CoreContract;
use MoonShine\Contracts\Core\DependencyInjection\FieldsContract;
use MoonShine\Contracts\UI\ComponentContract;
use MoonShine\Contracts\UI\TableBuilderContract;
use MoonShine\Core\Traits\WithCore;
use MoonShine\Crud\Contracts\Page\IndexPageContract;
use MoonShine\Crud\Contracts\PageComponents\DefaultListComponentContract;
use MoonShine\UI\Components\Table\TableBuilder;
final class ArticleListComponent implements DefaultListComponentContract
{
use WithCore;
public function __construct(CoreContract $core) {
$this->setCore($core);
}
/**
* @param iterable<array-key, mixed> $items
*/
public function __invoke(
IndexPageContract $page,
iterable $items,
FieldsContract $fields
): ComponentContract
{
$resource = $page->getResource();
return TableBuilder::make(items: $items)
->name($page->getListComponentName())
->fields($fields)
->cast($resource->getCaster())
->withNotFound()
->buttons($page->getButtons())
->when($page->isAsync(), function (TableBuilderContract $table) use($page): void {
$table->async(
url: fn (): string
=> $page->getRouter()->getEndpoints()->component(
name: $table->getName(),
additionally: $this->getCore()->getRequest()->getRequest()->getQueryParams(),
),
)->pushState();
})
->when($page->isLazy(), function (TableBuilderContract $table) use($resource): void {
$table->lazy()->whenAsync(
fn (TableBuilderContract $t): TableBuilderContract
=> $t->items(
$resource->getItems(),
),
);
})
->when(
! \is_null($resource->getItemsResolver()),
function (TableBuilderContract $table) use($resource): void {
$table->itemsResolver(
$resource->getItemsResolver(),
);
},
);
}
}
use MoonShine\Contracts\Core\DependencyInjection\CoreContract;
use MoonShine\Contracts\Core\DependencyInjection\FieldsContract;
use MoonShine\Contracts\UI\ComponentContract;
use MoonShine\Contracts\UI\TableBuilderContract;
use MoonShine\Core\Traits\WithCore;
use MoonShine\Crud\Contracts\Page\IndexPageContract;
use MoonShine\Crud\Contracts\PageComponents\DefaultListComponentContract;
use MoonShine\UI\Components\Table\TableBuilder;
final class ArticleListComponent implements DefaultListComponentContract
{
use WithCore;
public function __construct(CoreContract $core) {
$this->setCore($core);
}
/**
* @param iterable<array-key, mixed> $items
*/
public function __invoke(
IndexPageContract $page,
iterable $items,
FieldsContract $fields
): ComponentContract
{
$resource = $page->getResource();
return TableBuilder::make(items: $items)
->name($page->getListComponentName())
->fields($fields)
->cast($resource->getCaster())
->withNotFound()
->buttons($page->getButtons())
->when($page->isAsync(), function (TableBuilderContract $table) use($page): void {
$table->async(
url: fn (): string
=> $page->getRouter()->getEndpoints()->component(
name: $table->getName(),
additionally: $this->getCore()->getRequest()->getRequest()->getQueryParams(),
),
)->pushState();
})
->when($page->isLazy(), function (TableBuilderContract $table) use($resource): void {
$table->lazy()->whenAsync(
fn (TableBuilderContract $t): TableBuilderContract
=> $t->items(
$resource->getItems(),
),
);
})
->when(
! \is_null($resource->getItemsResolver()),
function (TableBuilderContract $table) use($resource): void {
$table->itemsResolver(
$resource->getItemsResolver(),
);
},
);
}
}
namespaces
use MoonShine\Contracts\Core\DependencyInjection\CoreContract;
use MoonShine\Contracts\Core\DependencyInjection\FieldsContract;
use MoonShine\Contracts\UI\ComponentContract;
use MoonShine\Contracts\UI\TableBuilderContract;
use MoonShine\Core\Traits\WithCore;
use MoonShine\Crud\Contracts\Page\IndexPageContract;
use MoonShine\Crud\Contracts\PageComponents\DefaultListComponentContract;
use MoonShine\UI\Components\Table\TableBuilder;
final class ArticleListComponent implements DefaultListComponentContract
{
use WithCore;
public function __construct(CoreContract $core) {
$this->setCore($core);
}
/**
* @param iterable<array-key, mixed> $items
*/
public function __invoke(
IndexPageContract $page,
iterable $items,
FieldsContract $fields
): ComponentContract
{
$resource = $page->getResource();
return TableBuilder::make(items: $items)
->name($page->getListComponentName())
->fields($fields)
->cast($resource->getCaster())
->withNotFound()
->buttons($page->getButtons())
->when($page->isAsync(), function (TableBuilderContract $table) use($page): void {
$table->async(
url: fn (): string
=> $page->getRouter()->getEndpoints()->component(
name: $table->getName(),
additionally: $this->getCore()->getRequest()->getRequest()->getQueryParams(),
),
)->pushState();
})
->when($page->isLazy(), function (TableBuilderContract $table) use($resource): void {
$table->lazy()->whenAsync(
fn (TableBuilderContract $t): TableBuilderContract
=> $t->items(
$resource->getItems(),
),
);
})
->when(
! \is_null($resource->getItemsResolver()),
function (TableBuilderContract $table) use($resource): void {
$table->itemsResolver(
$resource->getItemsResolver(),
);
},
);
}
}
use MoonShine\Contracts\Core\DependencyInjection\CoreContract;
use MoonShine\Contracts\Core\DependencyInjection\FieldsContract;
use MoonShine\Contracts\UI\ComponentContract;
use MoonShine\Contracts\UI\TableBuilderContract;
use MoonShine\Core\Traits\WithCore;
use MoonShine\Crud\Contracts\Page\IndexPageContract;
use MoonShine\Crud\Contracts\PageComponents\DefaultListComponentContract;
use MoonShine\UI\Components\Table\TableBuilder;
final class ArticleListComponent implements DefaultListComponentContract
{
use WithCore;
public function __construct(CoreContract $core) {
$this->setCore($core);
}
/**
* @param iterable<array-key, mixed> $items
*/
public function __invoke(
IndexPageContract $page,
iterable $items,
FieldsContract $fields
): ComponentContract
{
$resource = $page->getResource();
return TableBuilder::make(items: $items)
->name($page->getListComponentName())
->fields($fields)
->cast($resource->getCaster())
->withNotFound()
->buttons($page->getButtons())
->when($page->isAsync(), function (TableBuilderContract $table) use($page): void {
$table->async(
url: fn (): string
=> $page->getRouter()->getEndpoints()->component(
name: $table->getName(),
additionally: $this->getCore()->getRequest()->getRequest()->getQueryParams(),
),
)->pushState();
})
->when($page->isLazy(), function (TableBuilderContract $table) use($resource): void {
$table->lazy()->whenAsync(
fn (TableBuilderContract $t): TableBuilderContract
=> $t->items(
$resource->getItems(),
),
);
})
->when(
! \is_null($resource->getItemsResolver()),
function (TableBuilderContract $table) use($resource): void {
$table->itemsResolver(
$resource->getItemsResolver(),
);
},
);
}
}
__invoke() method arguments:
$page - object of the index page on which the component is located,
$items - list elements to display,
$fields - fields that will be displayed in the list.
Now in the page class in the $component property you need to override the component to display the list:
use MoonShine\Crud\Contracts\PageComponents\DefaultListComponentContract;
use MoonShine\Laravel\Pages\Crud\IndexPage;
class ArticleIndexPage extends IndexPage
{
protected string $component = ArticleListComponent::class;
}
namespaces
use MoonShine\Crud\Contracts\PageComponents\DefaultListComponentContract;
use MoonShine\Laravel\Pages\Crud\IndexPage;
class ArticleIndexPage extends IndexPage
{
/**
* @var class-string<DefaultListComponentContract>
*/
protected string $component = ArticleListComponent::class;
}
use MoonShine\Crud\Contracts\PageComponents\DefaultListComponentContract;
use MoonShine\Laravel\Pages\Crud\IndexPage;
class ArticleIndexPage extends IndexPage
{
/**
* @var class-string<DefaultListComponentContract>
*/
protected string $component = ArticleListComponent::class;
}
namespaces
use MoonShine\Crud\Contracts\PageComponents\DefaultListComponentContract;
use MoonShine\Laravel\Pages\Crud\IndexPage;
class ArticleIndexPage extends IndexPage
{
/**
* @var class-string<DefaultListComponentContract>
*/
protected string $component = ArticleListComponent::class;
}
use MoonShine\Crud\Contracts\PageComponents\DefaultListComponentContract;
use MoonShine\Laravel\Pages\Crud\IndexPage;
class ArticleIndexPage extends IndexPage
{
/**
* @var class-string<DefaultListComponentContract>
*/
protected string $component = ArticleListComponent::class;
}
You can also change the list component using the getItemsComponent() method:
use MoonShine\Contracts\Core\DependencyInjection\FieldsContract;
use MoonShine\Contracts\UI\ComponentContract;
getItemsComponent(iterable $items, FieldsContract $fields): ComponentContract
namespaces
use MoonShine\Contracts\Core\DependencyInjection\FieldsContract;
use MoonShine\Contracts\UI\ComponentContract;
getItemsComponent(iterable $items, FieldsContract $fields): ComponentContract
use MoonShine\Contracts\Core\DependencyInjection\FieldsContract;
use MoonShine\Contracts\UI\ComponentContract;
getItemsComponent(iterable $items, FieldsContract $fields): ComponentContract
namespaces
use MoonShine\Contracts\Core\DependencyInjection\FieldsContract;
use MoonShine\Contracts\UI\ComponentContract;
getItemsComponent(iterable $items, FieldsContract $fields): ComponentContract
use MoonShine\Contracts\Core\DependencyInjection\FieldsContract;
use MoonShine\Contracts\UI\ComponentContract;
getItemsComponent(iterable $items, FieldsContract $fields): ComponentContract
$items - field values,
$fields - fields.
Example of an index page with the CardsBuilder component in the Recipes section.
DetailPage
To change the detail view page component, you need to create a class that implements the DefaultDetailComponentContract interface:
use MoonShine\Contracts\Core\DependencyInjection\FieldsContract;
use MoonShine\Contracts\Core\TypeCasts\DataWrapperContract;
use MoonShine\Contracts\UI\ComponentContract;
use MoonShine\Crud\Contracts\Page\DetailPageContract;
use MoonShine\Crud\Contracts\PageComponents\DefaultDetailComponentContract;
use MoonShine\UI\Components\Table\TableBuilder;
final class ArticleDetailComponent implements DefaultDetailComponentContract
{
public function __invoke(
DetailPageContract $page,
?DataWrapperContract $item,
FieldsContract $fields,
): ComponentContract {
$resource = $page->getResource();
return TableBuilder::make($fields)
->cast($resource->getCaster())
->items([$item])
->vertical(
title: $resource->isDetailInModal() ? 3 : 2,
value: $resource->isDetailInModal() ? 9 : 10,
)
->simple()
->preview()
->class('table-divider');
}
}
namespaces
use MoonShine\Contracts\Core\DependencyInjection\FieldsContract;
use MoonShine\Contracts\Core\TypeCasts\DataWrapperContract;
use MoonShine\Contracts\UI\ComponentContract;
use MoonShine\Crud\Contracts\Page\DetailPageContract;
use MoonShine\Crud\Contracts\PageComponents\DefaultDetailComponentContract;
use MoonShine\UI\Components\Table\TableBuilder;
final class ArticleDetailComponent implements DefaultDetailComponentContract
{
public function __invoke(
DetailPageContract $page,
?DataWrapperContract $item,
FieldsContract $fields,
): ComponentContract {
$resource = $page->getResource();
return TableBuilder::make($fields)
->cast($resource->getCaster())
->items([$item])
->vertical(
title: $resource->isDetailInModal() ? 3 : 2,
value: $resource->isDetailInModal() ? 9 : 10,
)
->simple()
->preview()
->class('table-divider');
}
}
use MoonShine\Contracts\Core\DependencyInjection\FieldsContract;
use MoonShine\Contracts\Core\TypeCasts\DataWrapperContract;
use MoonShine\Contracts\UI\ComponentContract;
use MoonShine\Crud\Contracts\Page\DetailPageContract;
use MoonShine\Crud\Contracts\PageComponents\DefaultDetailComponentContract;
use MoonShine\UI\Components\Table\TableBuilder;
final class ArticleDetailComponent implements DefaultDetailComponentContract
{
public function __invoke(
DetailPageContract $page,
?DataWrapperContract $item,
FieldsContract $fields,
): ComponentContract {
$resource = $page->getResource();
return TableBuilder::make($fields)
->cast($resource->getCaster())
->items([$item])
->vertical(
title: $resource->isDetailInModal() ? 3 : 2,
value: $resource->isDetailInModal() ? 9 : 10,
)
->simple()
->preview()
->class('table-divider');
}
}
namespaces
use MoonShine\Contracts\Core\DependencyInjection\FieldsContract;
use MoonShine\Contracts\Core\TypeCasts\DataWrapperContract;
use MoonShine\Contracts\UI\ComponentContract;
use MoonShine\Crud\Contracts\Page\DetailPageContract;
use MoonShine\Crud\Contracts\PageComponents\DefaultDetailComponentContract;
use MoonShine\UI\Components\Table\TableBuilder;
final class ArticleDetailComponent implements DefaultDetailComponentContract
{
public function __invoke(
DetailPageContract $page,
?DataWrapperContract $item,
FieldsContract $fields,
): ComponentContract {
$resource = $page->getResource();
return TableBuilder::make($fields)
->cast($resource->getCaster())
->items([$item])
->vertical(
title: $resource->isDetailInModal() ? 3 : 2,
value: $resource->isDetailInModal() ? 9 : 10,
)
->simple()
->preview()
->class('table-divider');
}
}
use MoonShine\Contracts\Core\DependencyInjection\FieldsContract;
use MoonShine\Contracts\Core\TypeCasts\DataWrapperContract;
use MoonShine\Contracts\UI\ComponentContract;
use MoonShine\Crud\Contracts\Page\DetailPageContract;
use MoonShine\Crud\Contracts\PageComponents\DefaultDetailComponentContract;
use MoonShine\UI\Components\Table\TableBuilder;
final class ArticleDetailComponent implements DefaultDetailComponentContract
{
public function __invoke(
DetailPageContract $page,
?DataWrapperContract $item,
FieldsContract $fields,
): ComponentContract {
$resource = $page->getResource();
return TableBuilder::make($fields)
->cast($resource->getCaster())
->items([$item])
->vertical(
title: $resource->isDetailInModal() ? 3 : 2,
value: $resource->isDetailInModal() ? 9 : 10,
)
->simple()
->preview()
->class('table-divider');
}
}
__invoke() method arguments:
$page - object of the detailed page on which the component is located,
$item - object with data,
$fields - fields that will be displayed in the component.
Now in the page class in the $component property you need to override the component for detailed viewing:
use MoonShine\Crud\Contracts\PageComponents\DefaultDetailComponentContract;
use MoonShine\Laravel\Pages\Crud\DetailPage;
class ArticleDetailPage extends DetailPage
{
protected string $component = ArticleDetailComponent::class;
}
namespaces
use MoonShine\Crud\Contracts\PageComponents\DefaultDetailComponentContract;
use MoonShine\Laravel\Pages\Crud\DetailPage;
class ArticleDetailPage extends DetailPage
{
/**
* @var class-string<DefaultDetailComponentContract>
*/
protected string $component = ArticleDetailComponent::class;
}
use MoonShine\Crud\Contracts\PageComponents\DefaultDetailComponentContract;
use MoonShine\Laravel\Pages\Crud\DetailPage;
class ArticleDetailPage extends DetailPage
{
/**
* @var class-string<DefaultDetailComponentContract>
*/
protected string $component = ArticleDetailComponent::class;
}
namespaces
use MoonShine\Crud\Contracts\PageComponents\DefaultDetailComponentContract;
use MoonShine\Laravel\Pages\Crud\DetailPage;
class ArticleDetailPage extends DetailPage
{
/**
* @var class-string<DefaultDetailComponentContract>
*/
protected string $component = ArticleDetailComponent::class;
}
use MoonShine\Crud\Contracts\PageComponents\DefaultDetailComponentContract;
use MoonShine\Laravel\Pages\Crud\DetailPage;
class ArticleDetailPage extends DetailPage
{
/**
* @var class-string<DefaultDetailComponentContract>
*/
protected string $component = ArticleDetailComponent::class;
}
You can also change the main component of the detail view page using the getDetailComponent() method:
use MoonShine\Contracts\UI\ComponentContract;
getDetailComponent(bool $withoutFragment = false): ComponentContract
namespaces
use MoonShine\Contracts\UI\ComponentContract;
getDetailComponent(bool $withoutFragment = false): ComponentContract
use MoonShine\Contracts\UI\ComponentContract;
getDetailComponent(bool $withoutFragment = false): ComponentContract
namespaces
use MoonShine\Contracts\UI\ComponentContract;
getDetailComponent(bool $withoutFragment = false): ComponentContract
use MoonShine\Contracts\UI\ComponentContract;
getDetailComponent(bool $withoutFragment = false): ComponentContract
$withoutFragment - flag of whether the component should be wrapped in a Fragment.
FormPage
To change a page component with an element edit form, you need to create a class that implements the DefaultFormContract interface:
use MoonShine\Contracts\Core\DependencyInjection\CoreContract;
use MoonShine\Contracts\Core\DependencyInjection\FieldsContract;
use MoonShine\Contracts\Core\TypeCasts\DataWrapperContract;
use MoonShine\Contracts\UI\FormBuilderContract;
use MoonShine\Core\Traits\WithCore;
use MoonShine\Crud\Collections\Fields;
use MoonShine\Crud\Contracts\Page\FormPageContract;
use MoonShine\Crud\Contracts\PageComponents\DefaultFormContract;
use MoonShine\Support\AlpineJs;
use MoonShine\Support\Enums\JsEvent;
use MoonShine\UI\Components\FormBuilder;
use MoonShine\UI\Fields\Hidden;
final class ArticleForm implements DefaultFormContract
{
use WithCore;
public function __construct(CoreContract $core) {
$this->setCore($core);
}
public function __invoke(
FormPageContract $page,
string $action,
?DataWrapperContract $item,
FieldsContract $fields,
bool $isAsync = true,
): FormBuilderContract
{
$resource = $page->getResource();
return FormBuilder::make($action)
->cast($resource->getCaster())
->fill($item)
->fields([
...$fields
->when(
! \is_null($item),
static fn (Fields $fields): Fields
=> $fields->push(
Hidden::make('_method')->setValue('PUT'),
),
)
->toArray(),
])
->when(
! $page->hasErrorsAbove(),
fn (FormBuilderContract $form): FormBuilderContract => $form->errorsAbove($page->hasErrorsAbove()),
)
->when(
$isAsync,
fn (FormBuilderContract $formBuilder): FormBuilderContract
=> $formBuilder
->async(
events: array_filter([
$resource->getListEventName(
$this->getCore()->getRequest()->getScalar('_component_name', 'default'),
$isAsync && $resource->isItemExists() ? array_filter([
'page' => $this->getCore()->getRequest()->getScalar('page'),
'sort' => $this->getCore()->getRequest()->getScalar('sort'),
]) : [],
),
! $resource->isItemExists() && $resource->isCreateInModal()
? AlpineJs::event(JsEvent::FORM_RESET, $resource->getUriKey())
: null,
]),
),
)
->when(
$page->isPrecognitive() || ($this->getCore()->getCrudRequest()->isFragmentLoad('crud-form') && ! $isAsync),
static fn (FormBuilderContract $form): FormBuilderContract => $form->precognitive(),
)
->name($resource->getUriKey())
->submit(
$this->getCore()->getTranslator()->get('moonshine::ui.save'),
['class' => 'btn-primary btn-lg'],
)
->buttons($page->getFormButtons());
}
}
namespaces
use MoonShine\Contracts\Core\DependencyInjection\CoreContract;
use MoonShine\Contracts\Core\DependencyInjection\FieldsContract;
use MoonShine\Contracts\Core\TypeCasts\DataWrapperContract;
use MoonShine\Contracts\UI\FormBuilderContract;
use MoonShine\Core\Traits\WithCore;
use MoonShine\Crud\Collections\Fields;
use MoonShine\Crud\Contracts\Page\FormPageContract;
use MoonShine\Crud\Contracts\PageComponents\DefaultFormContract;
use MoonShine\Support\AlpineJs;
use MoonShine\Support\Enums\JsEvent;
use MoonShine\UI\Components\FormBuilder;
use MoonShine\UI\Fields\Hidden;
final class ArticleForm implements DefaultFormContract
{
use WithCore;
public function __construct(CoreContract $core) {
$this->setCore($core);
}
public function __invoke(
FormPageContract $page,
string $action,
?DataWrapperContract $item,
FieldsContract $fields,
bool $isAsync = true,
): FormBuilderContract
{
$resource = $page->getResource();
return FormBuilder::make($action)
->cast($resource->getCaster())
->fill($item)
->fields([
/** @phpstan-ignore argument.templateType */
...$fields
->when(
! \is_null($item),
static fn (Fields $fields): Fields
=> $fields->push(
Hidden::make('_method')->setValue('PUT'),
),
)
->toArray(),
])
->when(
! $page->hasErrorsAbove(),
fn (FormBuilderContract $form): FormBuilderContract => $form->errorsAbove($page->hasErrorsAbove()),
)
->when(
$isAsync,
fn (FormBuilderContract $formBuilder): FormBuilderContract
=> $formBuilder
->async(
events: array_filter([
$resource->getListEventName(
$this->getCore()->getRequest()->getScalar('_component_name', 'default'),
$isAsync && $resource->isItemExists() ? array_filter([
'page' => $this->getCore()->getRequest()->getScalar('page'),
'sort' => $this->getCore()->getRequest()->getScalar('sort'),
]) : [],
),
! $resource->isItemExists() && $resource->isCreateInModal()
? AlpineJs::event(JsEvent::FORM_RESET, $resource->getUriKey())
: null,
]),
),
)
->when(
$page->isPrecognitive() || ($this->getCore()->getCrudRequest()->isFragmentLoad('crud-form') && ! $isAsync),
static fn (FormBuilderContract $form): FormBuilderContract => $form->precognitive(),
)
->name($resource->getUriKey())
->submit(
$this->getCore()->getTranslator()->get('moonshine::ui.save'),
['class' => 'btn-primary btn-lg'],
)
->buttons($page->getFormButtons());
}
}
use MoonShine\Contracts\Core\DependencyInjection\CoreContract;
use MoonShine\Contracts\Core\DependencyInjection\FieldsContract;
use MoonShine\Contracts\Core\TypeCasts\DataWrapperContract;
use MoonShine\Contracts\UI\FormBuilderContract;
use MoonShine\Core\Traits\WithCore;
use MoonShine\Crud\Collections\Fields;
use MoonShine\Crud\Contracts\Page\FormPageContract;
use MoonShine\Crud\Contracts\PageComponents\DefaultFormContract;
use MoonShine\Support\AlpineJs;
use MoonShine\Support\Enums\JsEvent;
use MoonShine\UI\Components\FormBuilder;
use MoonShine\UI\Fields\Hidden;
final class ArticleForm implements DefaultFormContract
{
use WithCore;
public function __construct(CoreContract $core) {
$this->setCore($core);
}
public function __invoke(
FormPageContract $page,
string $action,
?DataWrapperContract $item,
FieldsContract $fields,
bool $isAsync = true,
): FormBuilderContract
{
$resource = $page->getResource();
return FormBuilder::make($action)
->cast($resource->getCaster())
->fill($item)
->fields([
/** @phpstan-ignore argument.templateType */
...$fields
->when(
! \is_null($item),
static fn (Fields $fields): Fields
=> $fields->push(
Hidden::make('_method')->setValue('PUT'),
),
)
->toArray(),
])
->when(
! $page->hasErrorsAbove(),
fn (FormBuilderContract $form): FormBuilderContract => $form->errorsAbove($page->hasErrorsAbove()),
)
->when(
$isAsync,
fn (FormBuilderContract $formBuilder): FormBuilderContract
=> $formBuilder
->async(
events: array_filter([
$resource->getListEventName(
$this->getCore()->getRequest()->getScalar('_component_name', 'default'),
$isAsync && $resource->isItemExists() ? array_filter([
'page' => $this->getCore()->getRequest()->getScalar('page'),
'sort' => $this->getCore()->getRequest()->getScalar('sort'),
]) : [],
),
! $resource->isItemExists() && $resource->isCreateInModal()
? AlpineJs::event(JsEvent::FORM_RESET, $resource->getUriKey())
: null,
]),
),
)
->when(
$page->isPrecognitive() || ($this->getCore()->getCrudRequest()->isFragmentLoad('crud-form') && ! $isAsync),
static fn (FormBuilderContract $form): FormBuilderContract => $form->precognitive(),
)
->name($resource->getUriKey())
->submit(
$this->getCore()->getTranslator()->get('moonshine::ui.save'),
['class' => 'btn-primary btn-lg'],
)
->buttons($page->getFormButtons());
}
}
namespaces
use MoonShine\Contracts\Core\DependencyInjection\CoreContract;
use MoonShine\Contracts\Core\DependencyInjection\FieldsContract;
use MoonShine\Contracts\Core\TypeCasts\DataWrapperContract;
use MoonShine\Contracts\UI\FormBuilderContract;
use MoonShine\Core\Traits\WithCore;
use MoonShine\Crud\Collections\Fields;
use MoonShine\Crud\Contracts\Page\FormPageContract;
use MoonShine\Crud\Contracts\PageComponents\DefaultFormContract;
use MoonShine\Support\AlpineJs;
use MoonShine\Support\Enums\JsEvent;
use MoonShine\UI\Components\FormBuilder;
use MoonShine\UI\Fields\Hidden;
final class ArticleForm implements DefaultFormContract
{
use WithCore;
public function __construct(CoreContract $core) {
$this->setCore($core);
}
public function __invoke(
FormPageContract $page,
string $action,
?DataWrapperContract $item,
FieldsContract $fields,
bool $isAsync = true,
): FormBuilderContract
{
$resource = $page->getResource();
return FormBuilder::make($action)
->cast($resource->getCaster())
->fill($item)
->fields([
/** @phpstan-ignore argument.templateType */
...$fields
->when(
! \is_null($item),
static fn (Fields $fields): Fields
=> $fields->push(
Hidden::make('_method')->setValue('PUT'),
),
)
->toArray(),
])
->when(
! $page->hasErrorsAbove(),
fn (FormBuilderContract $form): FormBuilderContract => $form->errorsAbove($page->hasErrorsAbove()),
)
->when(
$isAsync,
fn (FormBuilderContract $formBuilder): FormBuilderContract
=> $formBuilder
->async(
events: array_filter([
$resource->getListEventName(
$this->getCore()->getRequest()->getScalar('_component_name', 'default'),
$isAsync && $resource->isItemExists() ? array_filter([
'page' => $this->getCore()->getRequest()->getScalar('page'),
'sort' => $this->getCore()->getRequest()->getScalar('sort'),
]) : [],
),
! $resource->isItemExists() && $resource->isCreateInModal()
? AlpineJs::event(JsEvent::FORM_RESET, $resource->getUriKey())
: null,
]),
),
)
->when(
$page->isPrecognitive() || ($this->getCore()->getCrudRequest()->isFragmentLoad('crud-form') && ! $isAsync),
static fn (FormBuilderContract $form): FormBuilderContract => $form->precognitive(),
)
->name($resource->getUriKey())
->submit(
$this->getCore()->getTranslator()->get('moonshine::ui.save'),
['class' => 'btn-primary btn-lg'],
)
->buttons($page->getFormButtons());
}
}
use MoonShine\Contracts\Core\DependencyInjection\CoreContract;
use MoonShine\Contracts\Core\DependencyInjection\FieldsContract;
use MoonShine\Contracts\Core\TypeCasts\DataWrapperContract;
use MoonShine\Contracts\UI\FormBuilderContract;
use MoonShine\Core\Traits\WithCore;
use MoonShine\Crud\Collections\Fields;
use MoonShine\Crud\Contracts\Page\FormPageContract;
use MoonShine\Crud\Contracts\PageComponents\DefaultFormContract;
use MoonShine\Support\AlpineJs;
use MoonShine\Support\Enums\JsEvent;
use MoonShine\UI\Components\FormBuilder;
use MoonShine\UI\Fields\Hidden;
final class ArticleForm implements DefaultFormContract
{
use WithCore;
public function __construct(CoreContract $core) {
$this->setCore($core);
}
public function __invoke(
FormPageContract $page,
string $action,
?DataWrapperContract $item,
FieldsContract $fields,
bool $isAsync = true,
): FormBuilderContract
{
$resource = $page->getResource();
return FormBuilder::make($action)
->cast($resource->getCaster())
->fill($item)
->fields([
/** @phpstan-ignore argument.templateType */
...$fields
->when(
! \is_null($item),
static fn (Fields $fields): Fields
=> $fields->push(
Hidden::make('_method')->setValue('PUT'),
),
)
->toArray(),
])
->when(
! $page->hasErrorsAbove(),
fn (FormBuilderContract $form): FormBuilderContract => $form->errorsAbove($page->hasErrorsAbove()),
)
->when(
$isAsync,
fn (FormBuilderContract $formBuilder): FormBuilderContract
=> $formBuilder
->async(
events: array_filter([
$resource->getListEventName(
$this->getCore()->getRequest()->getScalar('_component_name', 'default'),
$isAsync && $resource->isItemExists() ? array_filter([
'page' => $this->getCore()->getRequest()->getScalar('page'),
'sort' => $this->getCore()->getRequest()->getScalar('sort'),
]) : [],
),
! $resource->isItemExists() && $resource->isCreateInModal()
? AlpineJs::event(JsEvent::FORM_RESET, $resource->getUriKey())
: null,
]),
),
)
->when(
$page->isPrecognitive() || ($this->getCore()->getCrudRequest()->isFragmentLoad('crud-form') && ! $isAsync),
static fn (FormBuilderContract $form): FormBuilderContract => $form->precognitive(),
)
->name($resource->getUriKey())
->submit(
$this->getCore()->getTranslator()->get('moonshine::ui.save'),
['class' => 'btn-primary btn-lg'],
)
->buttons($page->getFormButtons());
}
}
__invoke() method arguments:
$page - object of the page on which the component is located,
$action - form handler,
$item - object with data,
$fields - fields that will be displayed in the component.
Now in the page class in the $component property you need to override the form component:
use MoonShine\Crud\Contracts\PageComponents\DefaultFormContract;
use MoonShine\Laravel\Pages\Crud\FormPage;
class ArticleFormPage extends FormPage
{
protected string $component = ArticleForm::class;
}
namespaces
use MoonShine\Crud\Contracts\PageComponents\DefaultFormContract;
use MoonShine\Laravel\Pages\Crud\FormPage;
class ArticleFormPage extends FormPage
{
/**
* @var class-string<DefaultFormContract>
*/
protected string $component = ArticleForm::class;
}
use MoonShine\Crud\Contracts\PageComponents\DefaultFormContract;
use MoonShine\Laravel\Pages\Crud\FormPage;
class ArticleFormPage extends FormPage
{
/**
* @var class-string<DefaultFormContract>
*/
protected string $component = ArticleForm::class;
}
namespaces
use MoonShine\Crud\Contracts\PageComponents\DefaultFormContract;
use MoonShine\Laravel\Pages\Crud\FormPage;
class ArticleFormPage extends FormPage
{
/**
* @var class-string<DefaultFormContract>
*/
protected string $component = ArticleForm::class;
}
use MoonShine\Crud\Contracts\PageComponents\DefaultFormContract;
use MoonShine\Laravel\Pages\Crud\FormPage;
class ArticleFormPage extends FormPage
{
/**
* @var class-string<DefaultFormContract>
*/
protected string $component = ArticleForm::class;
}
You can also use the getFormComponent() method to change the main component on the form page.
use MoonShine\Contracts\UI\ComponentContract;
getFormComponent(bool $withoutFragment = false): ComponentContract
namespaces
use MoonShine\Contracts\UI\ComponentContract;
getFormComponent(bool $withoutFragment = false): ComponentContract
use MoonShine\Contracts\UI\ComponentContract;
getFormComponent(bool $withoutFragment = false): ComponentContract
namespaces
use MoonShine\Contracts\UI\ComponentContract;
getFormComponent(bool $withoutFragment = false): ComponentContract
use MoonShine\Contracts\UI\ComponentContract;
getFormComponent(bool $withoutFragment = false): ComponentContract
$withoutFragment - flag of whether the component should be wrapped in a Fragment.
We do not recommend using CRUD pages to arbitrary URL.
However, if you understand their logic well, you can use CRUD pages on non-standard routes, emulating the necessary URL.
class HomeController extends Controller
{
public function __invoke(FormArticlePage $page, ArticleResource $resource)
{
return $page->simulateRoute($page, $resource);
}
}
class HomeController extends Controller
{
public function __invoke(FormArticlePage $page, ArticleResource $resource)
{
return $page->simulateRoute($page, $resource);
}
}
class HomeController extends Controller
{
public function __invoke(FormArticlePage $page, ArticleResource $resource)
{
return $page->simulateRoute($page, $resource);
}
}
class HomeController extends Controller
{
public function __invoke(FormArticlePage $page, ArticleResource $resource)
{
return $page->simulateRoute($page, $resource);
}
}
class HomeController extends Controller
{
public function __invoke(FormArticlePage $page, ArticleResource $resource)
{
return $page->simulateRoute($page, $resource);
}
}