Upon a successful request, the form updates the table and resets the values.
Block::make([
FormBuilder::make(route('form-table.store'))
->fields([
Text::make('Title')
])
->name('main-form')
->async(asyncEvents: ['table-updated-main-table','form-reset-main-form'])
]),
TableBuilder::make()
->fields([
ID::make(),
Text::make('Title'),
Textarea::make('Body'),
])
->creatable()
->items(Post::query()->paginate())
->name('main-table')
->async()
Block::make([
FormBuilder::make(route('form-table.store'))
->fields([
Text::make('Title')
])
->name('main-form')
->async(asyncEvents: ['table-updated-main-table','form-reset-main-form'])
]),
TableBuilder::make()
->fields([
ID::make(),
Text::make('Title'),
Textarea::make('Body'),
])
->creatable()
->items(Post::query()->paginate())
->name('main-table')
->async()
Block::make([
FormBuilder::make(route('form-table.store'))
->fields([
Text::make('Title')
])
->name('main-form')
->async(asyncEvents: ['table-updated-main-table','form-reset-main-form'])
]),
TableBuilder::make()
->fields([
ID::make(),
Text::make('Title'),
Textarea::make('Body'),
])
->creatable()
->items(Post::query()->paginate())
->name('main-table')
->async()
Block::make([
FormBuilder::make(route('form-table.store'))
->fields([
Text::make('Title')
])
->name('main-form')
->async(asyncEvents: ['table-updated-main-table','form-reset-main-form'])
]),
TableBuilder::make()
->fields([
ID::make(),
Text::make('Title'),
Textarea::make('Body'),
])
->creatable()
->items(Post::query()->paginate())
->name('main-table')
->async()
Block::make([
FormBuilder::make(route('form-table.store'))
->fields([
Text::make('Title')
])
->name('main-form')
->async(asyncEvents: ['table-updated-main-table','form-reset-main-form'])
]),
TableBuilder::make()
->fields([
ID::make(),
Text::make('Title'),
Textarea::make('Body'),
])
->creatable()
->items(Post::query()->paginate())
->name('main-table')
->async()
Let's also look at how to add your own events
<div x-data=""
@my-event.window="alert()"
>
</div>
<div x-data=""
@my-event.window="alert()"
>
</div>
<div x-data=""
@my-event.window="alert()"
>
</div>
<div x-data=""
@my-event.window="alert()"
>
</div>
<div x-data=""
@my-event.window="alert()"
>
</div>
<div x-data="my"
@my-event.window="asyncRequest"
>
</div>
<script>
document.addEventListener("alpine:init", () => {
Alpine.data("my", () => ({
init() {
},
asyncRequest() {
this.$event.preventDefault()
}
}))
})
</script>
<div x-data="my"
@my-event.window="asyncRequest"
>
</div>
<script>
document.addEventListener("alpine:init", () => {
Alpine.data("my", () => ({
init() {
},
asyncRequest() {
this.$event.preventDefault()
// this.$el
// this.$root
}
}))
})
</script>
<div x-data="my"
@my-event.window="asyncRequest"
>
</div>
<script>
document.addEventListener("alpine:init", () => {
Alpine.data("my", () => ({
init() {
},
asyncRequest() {
this.$event.preventDefault()
// this.$el
// this.$root
}
}))
})
</script>
<div x-data="my"
@my-event.window="asyncRequest"
>
</div>
<script>
document.addEventListener("alpine:init", () => {
Alpine.data("my", () => ({
init() {
},
asyncRequest() {
this.$event.preventDefault()
// this.$el
// this.$root
}
}))
})
</script>
<div x-data="my"
@my-event.window="asyncRequest"
>
</div>
<script>
document.addEventListener("alpine:init", () => {
Alpine.data("my", () => ({
init() {
},
asyncRequest() {
this.$event.preventDefault()
// this.$el
// this.$root
}
}))
})
</script>
FormBuilder::make(route('form-table.store'))
->fields([
Text::make('Title')
])
->name('main-form')
->async(asyncEvents: ['my-event'])
FormBuilder::make(route('form-table.store'))
->fields([
Text::make('Title')
])
->name('main-form')
->async(asyncEvents: ['my-event'])
FormBuilder::make(route('form-table.store'))
->fields([
Text::make('Title')
])
->name('main-form')
->async(asyncEvents: ['my-event'])
FormBuilder::make(route('form-table.store'))
->fields([
Text::make('Title')
])
->name('main-form')
->async(asyncEvents: ['my-event'])
FormBuilder::make(route('form-table.store'))
->fields([
Text::make('Title')
])
->name('main-form')
->async(asyncEvents: ['my-event'])
We also recommend that you familiarize yourself with AlpineJs and use the full power of this js framework.
You can use its reactivity, let's see how to conveniently create a component.
<div x-data="myComponent">
</div>
<script>
document.addEventListener("alpine:init", () => {
Alpine.data("myComponent", () => ({
init() {
},
}))
})
</script>
<div x-data="myComponent">
</div>
<script>
document.addEventListener("alpine:init", () => {
Alpine.data("myComponent", () => ({
init() {
},
}))
})
</script>
<div x-data="myComponent">
</div>
<script>
document.addEventListener("alpine:init", () => {
Alpine.data("myComponent", () => ({
init() {
},
}))
})
</script>
<div x-data="myComponent">
</div>
<script>
document.addEventListener("alpine:init", () => {
Alpine.data("myComponent", () => ({
init() {
},
}))
})
</script>
<div x-data="myComponent">
</div>
<script>
document.addEventListener("alpine:init", () => {
Alpine.data("myComponent", () => ({
init() {
},
}))
})
</script>
Let's add one compiled using Vite build.
public function indexButtons(): array
{
$resource = new CommentResource();
return [
ActionButton::make('Custom button', static fn ($data): string => to_page(
page: $resource->formPage(),
resource: $resource,
params: ['resourceItem' => $data->getKey()]
))
];
}
public function indexButtons(): array
{
$resource = new CommentResource();
return [
ActionButton::make('Custom button', static fn ($data): string => to_page(
page: $resource->formPage(),
resource: $resource,
params: ['resourceItem' => $data->getKey()]
))
];
}
public function indexButtons(): array
{
$resource = new CommentResource();
return [
ActionButton::make('Custom button', static fn ($data): string => to_page(
page: $resource->formPage(),
resource: $resource,
params: ['resourceItem' => $data->getKey()]
))
];
}
public function indexButtons(): array
{
$resource = new CommentResource();
return [
ActionButton::make('Custom button', static fn ($data): string => to_page(
page: $resource->formPage(),
resource: $resource,
params: ['resourceItem' => $data->getKey()]
))
];
}
public function indexButtons(): array
{
$resource = new CommentResource();
return [
ActionButton::make('Custom button', static fn ($data): string => to_page(
page: $resource->formPage(),
resource: $resource,
params: ['resourceItem' => $data->getKey()]
))
];
}
Let's add custom buttons to the index table.
public function indexButtons(): array
{
$resource = new CommentResource();
return [
ActionButton::make('Custom button', static fn ($data): string => to_page(
page: $resource->formPage(),
resource: $resource,
params: ['resourceItem' => $data->getKey()]
))
];
}
public function indexButtons(): array
{
$resource = new CommentResource();
return [
ActionButton::make('Custom button', static fn ($data): string => to_page(
page: $resource->formPage(),
resource: $resource,
params: ['resourceItem' => $data->getKey()]
))
];
}
public function indexButtons(): array
{
$resource = new CommentResource();
return [
ActionButton::make('Custom button', static fn ($data): string => to_page(
page: $resource->formPage(),
resource: $resource,
params: ['resourceItem' => $data->getKey()]
))
];
}
public function indexButtons(): array
{
$resource = new CommentResource();
return [
ActionButton::make('Custom button', static fn ($data): string => to_page(
page: $resource->formPage(),
resource: $resource,
params: ['resourceItem' => $data->getKey()]
))
];
}
public function indexButtons(): array
{
$resource = new CommentResource();
return [
ActionButton::make('Custom button', static fn ($data): string => to_page(
page: $resource->formPage(),
resource: $resource,
params: ['resourceItem' => $data->getKey()]
))
];
}
An example of implementing the HasOne relationship through the Template
field.
use MoonShine\Fields\Template;
public function fields(): array
{
return [
Template::make('Comment')
->changeFill(fn (Article $data) => $data->comment)
->changePreview(fn($data) => $data?->id ?? '-')
->fields((new CommentResource())->getFormFields())
->changeRender(function (?Comment $data, Template $field) {
$fields = $field->preparedFields();
$fields->fill($data?->toArray() ?? [], $data ?? new Comment());
return Components::make($fields);
})
->onAfterApply(function (Article $item, array $value) {
$item->comment()->updateOrCreate([
'id' => $value['id']
], $value);
return $item;
})
];
}
use MoonShine\Fields\Template;
//...
public function fields(): array
{
return [
Template::make('Comment')
->changeFill(fn (Article $data) => $data->comment)
->changePreview(fn($data) => $data?->id ?? '-')
->fields((new CommentResource())->getFormFields())
->changeRender(function (?Comment $data, Template $field) {
$fields = $field->preparedFields();
$fields->fill($data?->toArray() ?? [], $data ?? new Comment());
return Components::make($fields);
})
->onAfterApply(function (Article $item, array $value) {
$item->comment()->updateOrCreate([
'id' => $value['id']
], $value);
return $item;
})
];
}
//...
use MoonShine\Fields\Template;
//...
public function fields(): array
{
return [
Template::make('Comment')
->changeFill(fn (Article $data) => $data->comment)
->changePreview(fn($data) => $data?->id ?? '-')
->fields((new CommentResource())->getFormFields())
->changeRender(function (?Comment $data, Template $field) {
$fields = $field->preparedFields();
$fields->fill($data?->toArray() ?? [], $data ?? new Comment());
return Components::make($fields);
})
->onAfterApply(function (Article $item, array $value) {
$item->comment()->updateOrCreate([
'id' => $value['id']
], $value);
return $item;
})
];
}
//...
use MoonShine\Fields\Template;
//...
public function fields(): array
{
return [
Template::make('Comment')
->changeFill(fn (Article $data) => $data->comment)
->changePreview(fn($data) => $data?->id ?? '-')
->fields((new CommentResource())->getFormFields())
->changeRender(function (?Comment $data, Template $field) {
$fields = $field->preparedFields();
$fields->fill($data?->toArray() ?? [], $data ?? new Comment());
return Components::make($fields);
})
->onAfterApply(function (Article $item, array $value) {
$item->comment()->updateOrCreate([
'id' => $value['id']
], $value);
return $item;
})
];
}
//...
use MoonShine\Fields\Template;
//...
public function fields(): array
{
return [
Template::make('Comment')
->changeFill(fn (Article $data) => $data->comment)
->changePreview(fn($data) => $data?->id ?? '-')
->fields((new CommentResource())->getFormFields())
->changeRender(function (?Comment $data, Template $field) {
$fields = $field->preparedFields();
$fields->fill($data?->toArray() ?? [], $data ?? new Comment());
return Components::make($fields);
})
->onAfterApply(function (Article $item, array $value) {
$item->comment()->updateOrCreate([
'id' => $value['id']
], $value);
return $item;
})
];
}
//...
You can change page breadcrumbs directly from the resource.
namespace App\MoonShine\Resources;
use App\Models\Post;
use MoonShine\Resources\ModelResource;
class PostResource extends ModelResource
{
protected function onBoot(): void
{
$this->formPage()
->setBreadcrumbs([
'#' => $this->title()
]);
}
}
namespace App\MoonShine\Resources;
use App\Models\Post;
use MoonShine\Resources\ModelResource;
class PostResource extends ModelResource
{
//...
protected function onBoot(): void
{
$this->formPage()
->setBreadcrumbs([
'#' => $this->title()
]);
}
//...
}
namespace App\MoonShine\Resources;
use App\Models\Post;
use MoonShine\Resources\ModelResource;
class PostResource extends ModelResource
{
//...
protected function onBoot(): void
{
$this->formPage()
->setBreadcrumbs([
'#' => $this->title()
]);
}
//...
}
namespace App\MoonShine\Resources;
use App\Models\Post;
use MoonShine\Resources\ModelResource;
class PostResource extends ModelResource
{
//...
protected function onBoot(): void
{
$this->formPage()
->setBreadcrumbs([
'#' => $this->title()
]);
}
//...
}
namespace App\MoonShine\Resources;
use App\Models\Post;
use MoonShine\Resources\ModelResource;
class PostResource extends ModelResource
{
//...
protected function onBoot(): void
{
$this->formPage()
->setBreadcrumbs([
'#' => $this->title()
]);
}
//...
}
Let's change the display of elements on the index page through the CardsBuilder component.
class MoonShineUserIndexPage extends IndexPage
{
public function listComponentName(): string
{
return 'index-cards';
}
public function listEventName(): string
{
return 'cards-updated';
}
protected function itemsComponent(iterable $items, Fields $fields): MoonShineRenderable
{
return CardsBuilder::make($items, $fields)
->cast($this->getResource()->getModelCast())
->name($this->listComponentName())
->async()
->overlay()
->title('email')
->subtitle('name')
->url(fn ($user) => $this->getResource()->formPageUrl($user))
->thumbnail(fn ($user) => asset($user->avatar))
->buttons($this->getResource()->getIndexItemButtons());
}
}
class MoonShineUserIndexPage extends IndexPage
{
public function listComponentName(): string
{
return 'index-cards';
}
public function listEventName(): string
{
return 'cards-updated';
}
protected function itemsComponent(iterable $items, Fields $fields): MoonShineRenderable
{
return CardsBuilder::make($items, $fields)
->cast($this->getResource()->getModelCast())
->name($this->listComponentName())
->async()
->overlay()
->title('email')
->subtitle('name')
->url(fn ($user) => $this->getResource()->formPageUrl($user))
->thumbnail(fn ($user) => asset($user->avatar))
->buttons($this->getResource()->getIndexItemButtons());
}
}
class MoonShineUserIndexPage extends IndexPage
{
public function listComponentName(): string
{
return 'index-cards';
}
public function listEventName(): string
{
return 'cards-updated';
}
protected function itemsComponent(iterable $items, Fields $fields): MoonShineRenderable
{
return CardsBuilder::make($items, $fields)
->cast($this->getResource()->getModelCast())
->name($this->listComponentName())
->async()
->overlay()
->title('email')
->subtitle('name')
->url(fn ($user) => $this->getResource()->formPageUrl($user))
->thumbnail(fn ($user) => asset($user->avatar))
->buttons($this->getResource()->getIndexItemButtons());
}
}
class MoonShineUserIndexPage extends IndexPage
{
public function listComponentName(): string
{
return 'index-cards';
}
public function listEventName(): string
{
return 'cards-updated';
}
protected function itemsComponent(iterable $items, Fields $fields): MoonShineRenderable
{
return CardsBuilder::make($items, $fields)
->cast($this->getResource()->getModelCast())
->name($this->listComponentName())
->async()
->overlay()
->title('email')
->subtitle('name')
->url(fn ($user) => $this->getResource()->formPageUrl($user))
->thumbnail(fn ($user) => asset($user->avatar))
->buttons($this->getResource()->getIndexItemButtons());
}
}
class MoonShineUserIndexPage extends IndexPage
{
public function listComponentName(): string
{
return 'index-cards';
}
public function listEventName(): string
{
return 'cards-updated';
}
protected function itemsComponent(iterable $items, Fields $fields): MoonShineRenderable
{
return CardsBuilder::make($items, $fields)
->cast($this->getResource()->getModelCast())
->name($this->listComponentName())
->async()
->overlay()
->title('email')
->subtitle('name')
->url(fn ($user) => $this->getResource()->formPageUrl($user))
->thumbnail(fn ($user) => asset($user->avatar))
->buttons($this->getResource()->getIndexItemButtons());
}
}
Let's create a sorting for the CardsBuilder component:
Select::make('Sorts')->options([
'created_at' => 'Date',
'id' => 'ID',
])
->onChangeMethod('reSort', events: ['cards-updated-cards'])
->setValue(session('sort_column') ?: 'created_at'),
CardsBuilder::make(
items: Article::query()->with('author')
->when(
session('sort_column'),
fn($q) => $q->orderBy(session('sort_column'), session('sort_direction', 'asc')),
fn($q) => $q->latest()
)
->paginate()
)
->name('cards')
->async()
->cast(ModelCast::make(Article::class))
->title('title')
->url(fn($data) => (new ArticleResource())->formPageUrl($data))
->overlay()
->columnSpan(4) ,
public function reSort(MoonShineRequest $request): void
{
session()->put('sort_column', $request->get('value'));
session()->put('sort_direction', 'ASC');
}
Select::make('Sorts')->options([
'created_at' => 'Date',
'id' => 'ID',
])
->onChangeMethod('reSort', events: ['cards-updated-cards'])
->setValue(session('sort_column') ?: 'created_at'),
CardsBuilder::make(
items: Article::query()->with('author')
->when(
session('sort_column'),
fn($q) => $q->orderBy(session('sort_column'), session('sort_direction', 'asc')),
fn($q) => $q->latest()
)
->paginate()
)
->name('cards')
->async()
->cast(ModelCast::make(Article::class))
->title('title')
->url(fn($data) => (new ArticleResource())->formPageUrl($data))
->overlay()
->columnSpan(4) ,
// ...
public function reSort(MoonShineRequest $request): void
{
session()->put('sort_column', $request->get('value'));
session()->put('sort_direction', 'ASC');
}
Select::make('Sorts')->options([
'created_at' => 'Date',
'id' => 'ID',
])
->onChangeMethod('reSort', events: ['cards-updated-cards'])
->setValue(session('sort_column') ?: 'created_at'),
CardsBuilder::make(
items: Article::query()->with('author')
->when(
session('sort_column'),
fn($q) => $q->orderBy(session('sort_column'), session('sort_direction', 'asc')),
fn($q) => $q->latest()
)
->paginate()
)
->name('cards')
->async()
->cast(ModelCast::make(Article::class))
->title('title')
->url(fn($data) => (new ArticleResource())->formPageUrl($data))
->overlay()
->columnSpan(4) ,
// ...
public function reSort(MoonShineRequest $request): void
{
session()->put('sort_column', $request->get('value'));
session()->put('sort_direction', 'ASC');
}
Select::make('Sorts')->options([
'created_at' => 'Date',
'id' => 'ID',
])
->onChangeMethod('reSort', events: ['cards-updated-cards'])
->setValue(session('sort_column') ?: 'created_at'),
CardsBuilder::make(
items: Article::query()->with('author')
->when(
session('sort_column'),
fn($q) => $q->orderBy(session('sort_column'), session('sort_direction', 'asc')),
fn($q) => $q->latest()
)
->paginate()
)
->name('cards')
->async()
->cast(ModelCast::make(Article::class))
->title('title')
->url(fn($data) => (new ArticleResource())->formPageUrl($data))
->overlay()
->columnSpan(4) ,
// ...
public function reSort(MoonShineRequest $request): void
{
session()->put('sort_column', $request->get('value'));
session()->put('sort_direction', 'ASC');
}
Select::make('Sorts')->options([
'created_at' => 'Date',
'id' => 'ID',
])
->onChangeMethod('reSort', events: ['cards-updated-cards'])
->setValue(session('sort_column') ?: 'created_at'),
CardsBuilder::make(
items: Article::query()->with('author')
->when(
session('sort_column'),
fn($q) => $q->orderBy(session('sort_column'), session('sort_direction', 'asc')),
fn($q) => $q->latest()
)
->paginate()
)
->name('cards')
->async()
->cast(ModelCast::make(Article::class))
->title('title')
->url(fn($data) => (new ArticleResource())->formPageUrl($data))
->overlay()
->columnSpan(4) ,
// ...
public function reSort(MoonShineRequest $request): void
{
session()->put('sort_column', $request->get('value'));
session()->put('sort_direction', 'ASC');
}
Implementation via asyncMethod of the method for changing the pivot field on the index page:
public function fields(): array
{
return [
Grid::make([
Column::make([
ID::make()->sortable(),
Text::make('Team title')->required(),
Number::make('Team number'),
BelongsTo::make('Tournament', resource: new TournamentResource())
->searchable(),
]),
Column::make([
BelongsToMany::make('Users', resource: new UserResource())
->fields([
Switcher::make('Approved')
->updateOnPreview(MoonShineRouter::asyncMethodClosure(
'updatePivot',
params: fn($data) => ['parent' => $data->pivot->tournamen_team_id]
)),
])
->searchable(),
])
])
];
}
public function updatePivot(MoonShineRequest $request): MoonShineJsonResponse
{
$item = TournamentTeam::query()
->findOrFail($request->get('parent'));
$column = (string) $request->str('field')->remove('pivot.');
$item->users()->updateExistingPivot($request->get('resourceItem'), [
$column => $request->get('value'),
]);
return MoonShineJsonResponse::make()
->toast('Success');
}
public function fields(): array
{
return [
Grid::make([
Column::make([
ID::make()->sortable(),
Text::make('Team title')->required(),
Number::make('Team number'),
BelongsTo::make('Tournament', resource: new TournamentResource())
->searchable(),
]),
Column::make([
BelongsToMany::make('Users', resource: new UserResource())
->fields([
Switcher::make('Approved')
->updateOnPreview(MoonShineRouter::asyncMethodClosure(
'updatePivot',
params: fn($data) => ['parent' => $data->pivot->tournamen_team_id]
)),
])
->searchable(),
])
])
];
}
public function updatePivot(MoonShineRequest $request): MoonShineJsonResponse
{
$item = TournamentTeam::query()
->findOrFail($request->get('parent'));
$column = (string) $request->str('field')->remove('pivot.');
$item->users()->updateExistingPivot($request->get('resourceItem'), [
$column => $request->get('value'),
]);
return MoonShineJsonResponse::make()
->toast('Success');
}
public function fields(): array
{
return [
Grid::make([
Column::make([
ID::make()->sortable(),
Text::make('Team title')->required(),
Number::make('Team number'),
BelongsTo::make('Tournament', resource: new TournamentResource())
->searchable(),
]),
Column::make([
BelongsToMany::make('Users', resource: new UserResource())
->fields([
Switcher::make('Approved')
->updateOnPreview(MoonShineRouter::asyncMethodClosure(
'updatePivot',
params: fn($data) => ['parent' => $data->pivot->tournamen_team_id]
)),
])
->searchable(),
])
])
];
}
public function updatePivot(MoonShineRequest $request): MoonShineJsonResponse
{
$item = TournamentTeam::query()
->findOrFail($request->get('parent'));
$column = (string) $request->str('field')->remove('pivot.');
$item->users()->updateExistingPivot($request->get('resourceItem'), [
$column => $request->get('value'),
]);
return MoonShineJsonResponse::make()
->toast('Success');
}
public function fields(): array
{
return [
Grid::make([
Column::make([
ID::make()->sortable(),
Text::make('Team title')->required(),
Number::make('Team number'),
BelongsTo::make('Tournament', resource: new TournamentResource())
->searchable(),
]),
Column::make([
BelongsToMany::make('Users', resource: new UserResource())
->fields([
Switcher::make('Approved')
->updateOnPreview(MoonShineRouter::asyncMethodClosure(
'updatePivot',
params: fn($data) => ['parent' => $data->pivot->tournamen_team_id]
)),
])
->searchable(),
])
])
];
}
public function updatePivot(MoonShineRequest $request): MoonShineJsonResponse
{
$item = TournamentTeam::query()
->findOrFail($request->get('parent'));
$column = (string) $request->str('field')->remove('pivot.');
$item->users()->updateExistingPivot($request->get('resourceItem'), [
$column => $request->get('value'),
]);
return MoonShineJsonResponse::make()
->toast('Success');
}
public function fields(): array
{
return [
Grid::make([
Column::make([
ID::make()->sortable(),
Text::make('Team title')->required(),
Number::make('Team number'),
BelongsTo::make('Tournament', resource: new TournamentResource())
->searchable(),
]),
Column::make([
BelongsToMany::make('Users', resource: new UserResource())
->fields([
Switcher::make('Approved')
->updateOnPreview(MoonShineRouter::asyncMethodClosure(
'updatePivot',
params: fn($data) => ['parent' => $data->pivot->tournamen_team_id]
)),
])
->searchable(),
])
])
];
}
public function updatePivot(MoonShineRequest $request): MoonShineJsonResponse
{
$item = TournamentTeam::query()
->findOrFail($request->get('parent'));
$column = (string) $request->str('field')->remove('pivot.');
$item->users()->updateExistingPivot($request->get('resourceItem'), [
$column => $request->get('value'),
]);
return MoonShineJsonResponse::make()
->toast('Success');
}
The HasMany connection stores file data that needs to be saved in a directory by parent id.
use App\Models\PostImage;
use MoonShine\Fields\ID;
use MoonShine\Fields\Image;
use MoonShine\Fields\Relationships\BelongsTo;
use MoonShine\Resources\ModelResource;
use MoonShine\Traits\Resource\ResourceWithParent;
class PostImageResource extends ModelResource
{
use ResourceWithParent;
public string $model = PostImage::class;
protected function getParentResourceClassName(): string
{
return PostResource::class;
}
protected function getParentRelationName(): string
{
return 'post';
}
public function fields(): array
{
return [
ID::make(),
Image::make('Path')
->when(
$parentId = $this->getParentId(),
fn(Image $image) => $image->dir('post_images/'.$parentId)
)
,
BelongsTo::make('Post', 'post', resource: new PostResource())
];
}
}
use App\Models\PostImage;
use MoonShine\Fields\ID;
use MoonShine\Fields\Image;
use MoonShine\Fields\Relationships\BelongsTo;
use MoonShine\Resources\ModelResource;
use MoonShine\Traits\Resource\ResourceWithParent;
class PostImageResource extends ModelResource
{
use ResourceWithParent;
public string $model = PostImage::class;
protected function getParentResourceClassName(): string
{
return PostResource::class;
}
protected function getParentRelationName(): string
{
return 'post';
}
public function fields(): array
{
return [
ID::make(),
Image::make('Path')
->when(
$parentId = $this->getParentId(),
fn(Image $image) => $image->dir('post_images/'.$parentId)
)
,
BelongsTo::make('Post', 'post', resource: new PostResource())
];
}
//...
}
use App\Models\PostImage;
use MoonShine\Fields\ID;
use MoonShine\Fields\Image;
use MoonShine\Fields\Relationships\BelongsTo;
use MoonShine\Resources\ModelResource;
use MoonShine\Traits\Resource\ResourceWithParent;
class PostImageResource extends ModelResource
{
use ResourceWithParent;
public string $model = PostImage::class;
protected function getParentResourceClassName(): string
{
return PostResource::class;
}
protected function getParentRelationName(): string
{
return 'post';
}
public function fields(): array
{
return [
ID::make(),
Image::make('Path')
->when(
$parentId = $this->getParentId(),
fn(Image $image) => $image->dir('post_images/'.$parentId)
)
,
BelongsTo::make('Post', 'post', resource: new PostResource())
];
}
//...
}
use App\Models\PostImage;
use MoonShine\Fields\ID;
use MoonShine\Fields\Image;
use MoonShine\Fields\Relationships\BelongsTo;
use MoonShine\Resources\ModelResource;
use MoonShine\Traits\Resource\ResourceWithParent;
class PostImageResource extends ModelResource
{
use ResourceWithParent;
public string $model = PostImage::class;
protected function getParentResourceClassName(): string
{
return PostResource::class;
}
protected function getParentRelationName(): string
{
return 'post';
}
public function fields(): array
{
return [
ID::make(),
Image::make('Path')
->when(
$parentId = $this->getParentId(),
fn(Image $image) => $image->dir('post_images/'.$parentId)
)
,
BelongsTo::make('Post', 'post', resource: new PostResource())
];
}
//...
}
use App\Models\PostImage;
use MoonShine\Fields\ID;
use MoonShine\Fields\Image;
use MoonShine\Fields\Relationships\BelongsTo;
use MoonShine\Resources\ModelResource;
use MoonShine\Traits\Resource\ResourceWithParent;
class PostImageResource extends ModelResource
{
use ResourceWithParent;
public string $model = PostImage::class;
protected function getParentResourceClassName(): string
{
return PostResource::class;
}
protected function getParentRelationName(): string
{
return 'post';
}
public function fields(): array
{
return [
ID::make(),
Image::make('Path')
->when(
$parentId = $this->getParentId(),
fn(Image $image) => $image->dir('post_images/'.$parentId)
)
,
BelongsTo::make('Post', 'post', resource: new PostResource())
];
}
//...
}
Sometimes it is necessary to display the TinyMce field in the preview with a limited number of characters. To do this, you can use the changePreview()
method.
public function fields(): array
{
return [
TinyMce::make('Description')
->changePreview(fn(string $text) => str($text)->stripTags()->limit(10))
];
}
}
public function fields(): array
{
return [
TinyMce::make('Description')
->changePreview(fn(string $text) => str($text)->stripTags()->limit(10))
];
}
//...
}
public function fields(): array
{
return [
TinyMce::make('Description')
->changePreview(fn(string $text) => str($text)->stripTags()->limit(10))
];
}
//...
}
public function fields(): array
{
return [
TinyMce::make('Description')
->changePreview(fn(string $text) => str($text)->stripTags()->limit(10))
];
}
//...
}
public function fields(): array
{
return [
TinyMce::make('Description')
->changePreview(fn(string $text) => str($text)->stripTags()->limit(10))
];
}
//...
}
- To solve this problem, you need to block the
onApply()
method and moved the logic to onAfterApply()
. This will get the parent model on the creation page. We will have access to the model and we will be able to work with its relationships.
- The
onAfterApply()
method stores and retrieves old and current values, also cleaning deleted files.
- After deleting the parent record, the
onAfterDestroy()
method deletes the downloaded files.
use MoonShine\Fields\Image;
Image::make('Images', 'images')
->multiple()
->removable()
->changeFill(function (Model $data, Image $field) {
return DB::table('images')->pluck('file');
})
->onApply(function (Model $data) {
return $data;
})
->onAfterApply(function (Model $data, false|array $values, Image $field) {
if($values !== false) {
foreach ($values as $value) {
DB::table('images')->insert([
'file' => $field->store($value),
]);
}
}
foreach ($field->toValue()->diff($field->getRemainingValues()) as $removed) {
DB::table('images')->where('file', $removed)->delete();
Storage::disk('public')->delete($removed);
}
return $data;
})
->onAfterDestroy(function (Model $data, mixed $values, Image $field) {
foreach ($values as $value) {
Storage::disk('public')->delete($value);
}
return $data;
})
use MoonShine\Fields\Image;
//...
Image::make('Images', 'images')
->multiple()
->removable()
->changeFill(function (Model $data, Image $field) {
// return $data->images->pluck('file');
// or raw
return DB::table('images')->pluck('file');
})
->onApply(function (Model $data) {
// block onApply
return $data;
})
->onAfterApply(function (Model $data, false|array $values, Image $field) {
// $field->getRemainingValues(); values that remained in the form taking into account deletions
// $field->toValue(); current images
// $field->toValue()->diff($field->getRemainingValues()) deleted images
if($values !== false) {
foreach ($values as $value) {
DB::table('images')->insert([
'file' => $field->store($value),
]);
}
}
foreach ($field->toValue()->diff($field->getRemainingValues()) as $removed) {
DB::table('images')->where('file', $removed)->delete();
Storage::disk('public')->delete($removed);
}
// or $field->removeExcludedFiles();
return $data;
})
->onAfterDestroy(function (Model $data, mixed $values, Image $field) {
foreach ($values as $value) {
Storage::disk('public')->delete($value);
}
return $data;
})
//...
use MoonShine\Fields\Image;
//...
Image::make('Images', 'images')
->multiple()
->removable()
->changeFill(function (Model $data, Image $field) {
// return $data->images->pluck('file');
// or raw
return DB::table('images')->pluck('file');
})
->onApply(function (Model $data) {
// block onApply
return $data;
})
->onAfterApply(function (Model $data, false|array $values, Image $field) {
// $field->getRemainingValues(); values that remained in the form taking into account deletions
// $field->toValue(); current images
// $field->toValue()->diff($field->getRemainingValues()) deleted images
if($values !== false) {
foreach ($values as $value) {
DB::table('images')->insert([
'file' => $field->store($value),
]);
}
}
foreach ($field->toValue()->diff($field->getRemainingValues()) as $removed) {
DB::table('images')->where('file', $removed)->delete();
Storage::disk('public')->delete($removed);
}
// or $field->removeExcludedFiles();
return $data;
})
->onAfterDestroy(function (Model $data, mixed $values, Image $field) {
foreach ($values as $value) {
Storage::disk('public')->delete($value);
}
return $data;
})
//...
use MoonShine\Fields\Image;
//...
Image::make('Images', 'images')
->multiple()
->removable()
->changeFill(function (Model $data, Image $field) {
// return $data->images->pluck('file');
// or raw
return DB::table('images')->pluck('file');
})
->onApply(function (Model $data) {
// block onApply
return $data;
})
->onAfterApply(function (Model $data, false|array $values, Image $field) {
// $field->getRemainingValues(); values that remained in the form taking into account deletions
// $field->toValue(); current images
// $field->toValue()->diff($field->getRemainingValues()) deleted images
if($values !== false) {
foreach ($values as $value) {
DB::table('images')->insert([
'file' => $field->store($value),
]);
}
}
foreach ($field->toValue()->diff($field->getRemainingValues()) as $removed) {
DB::table('images')->where('file', $removed)->delete();
Storage::disk('public')->delete($removed);
}
// or $field->removeExcludedFiles();
return $data;
})
->onAfterDestroy(function (Model $data, mixed $values, Image $field) {
foreach ($values as $value) {
Storage::disk('public')->delete($value);
}
return $data;
})
//...
use MoonShine\Fields\Image;
//...
Image::make('Images', 'images')
->multiple()
->removable()
->changeFill(function (Model $data, Image $field) {
// return $data->images->pluck('file');
// or raw
return DB::table('images')->pluck('file');
})
->onApply(function (Model $data) {
// block onApply
return $data;
})
->onAfterApply(function (Model $data, false|array $values, Image $field) {
// $field->getRemainingValues(); values that remained in the form taking into account deletions
// $field->toValue(); current images
// $field->toValue()->diff($field->getRemainingValues()) deleted images
if($values !== false) {
foreach ($values as $value) {
DB::table('images')->insert([
'file' => $field->store($value),
]);
}
}
foreach ($field->toValue()->diff($field->getRemainingValues()) as $removed) {
DB::table('images')->where('file', $removed)->delete();
Storage::disk('public')->delete($removed);
}
// or $field->removeExcludedFiles();
return $data;
})
->onAfterDestroy(function (Model $data, mixed $values, Image $field) {
foreach ($values as $value) {
Storage::disk('public')->delete($value);
}
return $data;
})
//...
The code comments out the relation option and provides an example of natively obtaining file paths from another table.
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use MoonShine\Fields\Image;
Image::make('Images', 'images')
->multiple()
->removable()
->changeFill(function (Model $data, Image $field) {
return DB::table('images')->pluck('file');
})
->onApply(function (Model $data) {
return $data;
})
->onAfterApply(function (Model $data, false|array $values, Image $field) {
if($values !== false) {
foreach ($values as $value) {
DB::table('images')->insert([
'file' => $field->store($value),
]);
}
}
foreach ($field->toValue()->diff($field->getRemainingValues()) as $removed) {
DB::table('images')->where('file', $removed)->delete();
Storage::disk('public')->delete($removed);
}
return $data;
})
->onAfterDestroy(function (Model $data, mixed $values, Image $field) {
foreach ($values as $value) {
Storage::disk('public')->delete($value);
}
return $data;
})
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use MoonShine\Fields\Image;
// ...
Image::make('Images', 'images')
->multiple()
->removable()
->changeFill(function (Model $data, Image $field) {
// return $data->images->pluck('file');
// or raw
return DB::table('images')->pluck('file');
})
->onApply(function (Model $data) {
// block onApply
return $data;
})
->onAfterApply(function (Model $data, false|array $values, Image $field) {
// $field->getRemainingValues(); values that remained in the form taking into account deletions
// $field->toValue(); current images
// $field->toValue()->diff($field->getRemainingValues()) deleted images
if($values !== false) {
foreach ($values as $value) {
DB::table('images')->insert([
'file' => $field->store($value),
]);
}
}
foreach ($field->toValue()->diff($field->getRemainingValues()) as $removed) {
DB::table('images')->where('file', $removed)->delete();
Storage::disk('public')->delete($removed);
}
// or $field->removeExcludedFiles();
return $data;
})
->onAfterDestroy(function (Model $data, mixed $values, Image $field) {
foreach ($values as $value) {
Storage::disk('public')->delete($value);
}
return $data;
})
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use MoonShine\Fields\Image;
// ...
Image::make('Images', 'images')
->multiple()
->removable()
->changeFill(function (Model $data, Image $field) {
// return $data->images->pluck('file');
// or raw
return DB::table('images')->pluck('file');
})
->onApply(function (Model $data) {
// block onApply
return $data;
})
->onAfterApply(function (Model $data, false|array $values, Image $field) {
// $field->getRemainingValues(); values that remained in the form taking into account deletions
// $field->toValue(); current images
// $field->toValue()->diff($field->getRemainingValues()) deleted images
if($values !== false) {
foreach ($values as $value) {
DB::table('images')->insert([
'file' => $field->store($value),
]);
}
}
foreach ($field->toValue()->diff($field->getRemainingValues()) as $removed) {
DB::table('images')->where('file', $removed)->delete();
Storage::disk('public')->delete($removed);
}
// or $field->removeExcludedFiles();
return $data;
})
->onAfterDestroy(function (Model $data, mixed $values, Image $field) {
foreach ($values as $value) {
Storage::disk('public')->delete($value);
}
return $data;
})
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use MoonShine\Fields\Image;
// ...
Image::make('Images', 'images')
->multiple()
->removable()
->changeFill(function (Model $data, Image $field) {
// return $data->images->pluck('file');
// or raw
return DB::table('images')->pluck('file');
})
->onApply(function (Model $data) {
// block onApply
return $data;
})
->onAfterApply(function (Model $data, false|array $values, Image $field) {
// $field->getRemainingValues(); values that remained in the form taking into account deletions
// $field->toValue(); current images
// $field->toValue()->diff($field->getRemainingValues()) deleted images
if($values !== false) {
foreach ($values as $value) {
DB::table('images')->insert([
'file' => $field->store($value),
]);
}
}
foreach ($field->toValue()->diff($field->getRemainingValues()) as $removed) {
DB::table('images')->where('file', $removed)->delete();
Storage::disk('public')->delete($removed);
}
// or $field->removeExcludedFiles();
return $data;
})
->onAfterDestroy(function (Model $data, mixed $values, Image $field) {
foreach ($values as $value) {
Storage::disk('public')->delete($value);
}
return $data;
})
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use MoonShine\Fields\Image;
// ...
Image::make('Images', 'images')
->multiple()
->removable()
->changeFill(function (Model $data, Image $field) {
// return $data->images->pluck('file');
// or raw
return DB::table('images')->pluck('file');
})
->onApply(function (Model $data) {
// block onApply
return $data;
})
->onAfterApply(function (Model $data, false|array $values, Image $field) {
// $field->getRemainingValues(); values that remained in the form taking into account deletions
// $field->toValue(); current images
// $field->toValue()->diff($field->getRemainingValues()) deleted images
if($values !== false) {
foreach ($values as $value) {
DB::table('images')->insert([
'file' => $field->store($value),
]);
}
}
foreach ($field->toValue()->diff($field->getRemainingValues()) as $removed) {
DB::table('images')->where('file', $removed)->delete();
Storage::disk('public')->delete($removed);
}
// or $field->removeExcludedFiles();
return $data;
})
->onAfterDestroy(function (Model $data, mixed $values, Image $field) {
foreach ($values as $value) {
Storage::disk('public')->delete($value);
}
return $data;
})
namespace App\MoonShine\Resources;
use MoonShine\Resources\ModelResource;
class PostResource extends ModelResource
{
public function filters(): array
{
return [
Select::make('Activity status', 'active')
->options([
'0' => 'Only NOT active',
'1' => 'Only active',
])
->nullable()
->onApply(fn(Builder $query, $value) => $query->where('active', $value)),
];
}
}
namespace App\MoonShine\Resources;
use MoonShine\Resources\ModelResource;
class PostResource extends ModelResource
{
//...
public function filters(): array
{
return [
Select::make('Activity status', 'active')
->options([
'0' => 'Only NOT active',
'1' => 'Only active',
])
->nullable()
->onApply(fn(Builder $query, $value) => $query->where('active', $value)),
];
}
//...
}
namespace App\MoonShine\Resources;
use MoonShine\Resources\ModelResource;
class PostResource extends ModelResource
{
//...
public function filters(): array
{
return [
Select::make('Activity status', 'active')
->options([
'0' => 'Only NOT active',
'1' => 'Only active',
])
->nullable()
->onApply(fn(Builder $query, $value) => $query->where('active', $value)),
];
}
//...
}
namespace App\MoonShine\Resources;
use MoonShine\Resources\ModelResource;
class PostResource extends ModelResource
{
//...
public function filters(): array
{
return [
Select::make('Activity status', 'active')
->options([
'0' => 'Only NOT active',
'1' => 'Only active',
])
->nullable()
->onApply(fn(Builder $query, $value) => $query->where('active', $value)),
];
}
//...
}
namespace App\MoonShine\Resources;
use MoonShine\Resources\ModelResource;
class PostResource extends ModelResource
{
//...
public function filters(): array
{
return [
Select::make('Activity status', 'active')
->options([
'0' => 'Only NOT active',
'1' => 'Only active',
])
->nullable()
->onApply(fn(Builder $query, $value) => $query->where('active', $value)),
];
}
//...
}
Metrics with form parameters
$startDate = request()->date('_form.start_date');
$endDate = request()->date('_form.end_date');
FormBuilder::make()
->dispatchEvent(AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'metrics'))
->fields([
Flex::make([
Date::make('Start date'),
Date::make('End date'),
]),
]),
Fragment::make([
FlexibleRender::make("$startDate - $endDate"),
LineChartMetric::make('Orders')
->line([
'Profit' => Order::query()
->selectRaw('SUM(price) as sum, DATE_FORMAT(created_at, "%d.%m.%Y") as date')
->whereBetween('created_at', [$startDate, $endDate])
->groupBy('date')
->pluck('sum', 'date')
->toArray(),
])
->line([
'Avg' => Order::query()
->selectRaw('AVG(price) as avg, DATE_FORMAT(created_at, "%d.%m.%Y") as date')
->whereBetween('created_at', [$startDate, $endDate])
->groupBy('date')
-> pluck('avg', 'date')
->toArray(),
], '#EC4176'),
])->name('metrics'),
$startDate = request()->date('_form.start_date');
$endDate = request()->date('_form.end_date');
FormBuilder::make()
->dispatchEvent(AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'metrics'))
->fields([
Flex::make([
Date::make('Start date'),
Date::make('End date'),
]),
]),
Fragment::make([
FlexibleRender::make("$startDate - $endDate"),
LineChartMetric::make('Orders')
->line([
'Profit' => Order::query()
->selectRaw('SUM(price) as sum, DATE_FORMAT(created_at, "%d.%m.%Y") as date')
->whereBetween('created_at', [$startDate, $endDate])
->groupBy('date')
->pluck('sum', 'date')
->toArray(),
])
->line([
'Avg' => Order::query()
->selectRaw('AVG(price) as avg, DATE_FORMAT(created_at, "%d.%m.%Y") as date')
->whereBetween('created_at', [$startDate, $endDate])
->groupBy('date')
-> pluck('avg', 'date')
->toArray(),
], '#EC4176'),
])->name('metrics'),
$startDate = request()->date('_form.start_date');
$endDate = request()->date('_form.end_date');
FormBuilder::make()
->dispatchEvent(AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'metrics'))
->fields([
Flex::make([
Date::make('Start date'),
Date::make('End date'),
]),
]),
Fragment::make([
FlexibleRender::make("$startDate - $endDate"),
LineChartMetric::make('Orders')
->line([
'Profit' => Order::query()
->selectRaw('SUM(price) as sum, DATE_FORMAT(created_at, "%d.%m.%Y") as date')
->whereBetween('created_at', [$startDate, $endDate])
->groupBy('date')
->pluck('sum', 'date')
->toArray(),
])
->line([
'Avg' => Order::query()
->selectRaw('AVG(price) as avg, DATE_FORMAT(created_at, "%d.%m.%Y") as date')
->whereBetween('created_at', [$startDate, $endDate])
->groupBy('date')
-> pluck('avg', 'date')
->toArray(),
], '#EC4176'),
])->name('metrics'),
$startDate = request()->date('_form.start_date');
$endDate = request()->date('_form.end_date');
FormBuilder::make()
->dispatchEvent(AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'metrics'))
->fields([
Flex::make([
Date::make('Start date'),
Date::make('End date'),
]),
]),
Fragment::make([
FlexibleRender::make("$startDate - $endDate"),
LineChartMetric::make('Orders')
->line([
'Profit' => Order::query()
->selectRaw('SUM(price) as sum, DATE_FORMAT(created_at, "%d.%m.%Y") as date')
->whereBetween('created_at', [$startDate, $endDate])
->groupBy('date')
->pluck('sum', 'date')
->toArray(),
])
->line([
'Avg' => Order::query()
->selectRaw('AVG(price) as avg, DATE_FORMAT(created_at, "%d.%m.%Y") as date')
->whereBetween('created_at', [$startDate, $endDate])
->groupBy('date')
-> pluck('avg', 'date')
->toArray(),
], '#EC4176'),
])->name('metrics'),
$startDate = request()->date('_form.start_date');
$endDate = request()->date('_form.end_date');
FormBuilder::make()
->dispatchEvent(AlpineJs::event(JsEvent::FRAGMENT_UPDATED, 'metrics'))
->fields([
Flex::make([
Date::make('Start date'),
Date::make('End date'),
]),
]),
Fragment::make([
FlexibleRender::make("$startDate - $endDate"),
LineChartMetric::make('Orders')
->line([
'Profit' => Order::query()
->selectRaw('SUM(price) as sum, DATE_FORMAT(created_at, "%d.%m.%Y") as date')
->whereBetween('created_at', [$startDate, $endDate])
->groupBy('date')
->pluck('sum', 'date')
->toArray(),
])
->line([
'Avg' => Order::query()
->selectRaw('AVG(price) as avg, DATE_FORMAT(created_at, "%d.%m.%Y") as date')
->whereBetween('created_at', [$startDate, $endDate])
->groupBy('date')
-> pluck('avg', 'date')
->toArray(),
], '#EC4176'),
])->name('metrics'),