Recipes

Form and events

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()

Let's also look at how to add your own events

<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()
 
// this.$el
// this.$root
}
}))
})
</script>
FormBuilder::make(route('form-table.store'))
->fields([
Text::make('Title')
])
->name('main-form')
->async(asyncEvents: ['my-event'])

View component with AlpineJs

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>

Vite build connection

Let's add one compiled using Vite build.

use Illuminate\Support\Facades\Vite;
 
class MoonShineServiceProvider extends MoonShineApplicationServiceProvider
{
//...
 
public function boot(): void
{
parent::boot();
 
moonShineAssets()->add([
Vite::asset('resources/css/app.css'),
Vite::asset('resources/js/app.js'),
]);
}
 
//...
}

Custom buttons

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

HasOne through the Template field

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

Changing breadcrumbs from a resource

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

Index page via CardsBuilder

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

Сортировка для CardsBuilder

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');
}

updateOnPreview for pivot fields

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');
}

Parent ID in HasMany

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