На этой неделе команда MoonShine выпустила релиз v.2.5.0 с кодовым именем "Mojito Supreme"! Давайте взглянем на самое интересное в этом обновлении!
AsyncMethod
Самая главная фича релиза - это возможность указывать в ActionButton и FormBuilder просто название метода в ресурсе или на странице и асинхронно его вызывать.
Основаная идея - сэкономить время для простой логики, где не хочется создавать отдельный контроллер.
Давайте взглянем на примеры:
ActionButton::make('Click me')->method('updateSomething')
FormBuilder::make()->asyncMethod('updateSomething')
// With toast
public function updateSomething(MoonShineRequest $request)
{
// $request->getResource();
// $request->getResource()->getItem();
// $request->getPage();
MoonShineUI::toast('MyMessage', 'success');
return back();
}
// Exception
public function updateSomething(MoonShineRequest $request)
{
throw new \Exception('My message');
}
// Custom json response
public function updateSomething(MoonShineRequest $request)
{
return response()->json(['message' => 'MyMessage']);
}
Поле-портянка Template
Поле, которое по сути не имеет никакой начинки, и вы можете его конструировать налету, прямо в процессе объявления. В примере ниже мы реализовали HasOne отношение прямо в основной форме, при этом изменили и процесс сохранения и визуальное отображение. Полный простор для фантазии!
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;
})
Кастомизация для ActionButtons где только можно
Теперь вы можете кастомизировать кнопки добавлени или удаления я в таблицах. Давайте рассмотрим несколько примеров.
Кнопка создания для TableBuilder:
TableBuilder::make()
->fields([
ID::make(),
Text::make('Title'),
])
->creatable(
limit: 3,
label: 'New',
icon: 'heroicons.outline.pencil',
attributes: ['class' => 'my-class']
)
Изменили заголовок, иконку и добавили атрибуты. Также Вы можете заменить кнопку на свою:
TableBuilder::make()
->fields([
ID::make(),
Text::make('Title'),
])
->creatable(
button: ActionButton::make('Foo', '#')
)
Давайте также взглянем на поле Json, где мы можем кастомизировать кнопку добавить, также можем изменить кнопку удалить или добавить свой набор кнопок:
Json::make('Data', 'data.content')->fields([
Text::make('Title'),
Image::make('Image'),
Text::make('Value'),
])->creatable(
button: ActionButton::make(
'New', '#',
)->primary()
),
Json::make('Data', 'data.content')->fields([
Text::make('Title'),
Image::make('Image'),
Text::make('Value'),
])->buttons([
ActionButton::make('', '#')
->icon('heroicons.outline.trash')
->onClick(fn() => 'remove()', 'prevent')
->customAttributes(['class' => 'btn-secondary'])
->showInLine()
])
Давайте рассмотрим еще примеры по кнопкам удаления в других полях:
Image::make('Thumbnail')
->removable(attributes: ['@click.prevent' => '$event.target.closest(`.x-removeable`).remove()'])
Json::make('Data', 'data.content')->fields([
Text::make('Title'),
Image::make('Image'),
Text::make('Value'),
])
->removable(attributes: ['@click.prevent' => 'customAsyncRemove'])
->creatable(),
BelongsToMany возможность добавить свои кнопки и checkAll метод
Проще всего будет рассмотреть пример, где мы добавляем две дополнительные кнопки, которые активируют все элементы, либо наоборот:
BelongsToMany::make('Categories')->buttons([
ActionButton::make('Check all', '')
->onClick(fn() => 'checkAll', 'prevent'),
ActionButton::make('Uncheck all', '')
->onClick(fn() => 'uncheckAll', 'prevent')
])
А также сахар, где под капотом пример выше (так как думаю он будет иметь популярность):
BelongsToMany::make('Categories')->withCheckAll()
Ну и кнопку добавления также можно кастомизировать под себя:
BelongsToMany::make('Categories')
->creatable(
button: ActionButton::make('Custom button', '')
)
Хлебные крошки
Появилась возможность изменять хлебные крошки прямо из ресурса и для ViewPage:
protected function onBoot(): void
{
$this->formPage()
->setBreadcrumbs([
'#' => $this->getItem()?->user?->name
]);
}
Автозакрытие модалок
По умолчанию модальные окна при успешном сохранении формы закрывались, но теперь это поведение можно контролировать (autoClose)
Modal::make(
'Demo modal',
static fn() => FormBuilder::make(route('alert.post'))
->fields([
Text::make('Text'),
])
->submit('Send', ['class' => 'btn-primary'])
->async(),
)
->name('demo-modal')
->autoClose(false), //Modal window don't close when form is submitted
ActionButton::make('Open modal', '#')
->onClick(fn() => "\$dispatch('modal-toggled-demo-modal')", 'prevent')
ActionButton::make('AsyncTest', '#')
->inModal(fn (): string => 'Go test',
fn (): string => (string) FormBuilder::make(route('alert.post'), method: 'GET')
->fields([
Text::make('Text'),
])
->submit('Отправить', ['class' => 'btn-primary'])
->async(),
autoClose: false //Modal window don't close when form is submitted
),
Routes
Сахар для роутов
$resource->url(); // First page of the resource
$resource->route($name, $key, $params); // Advanced method for obtaining routes
$resource->indexPageUrl(); // index page
$resource->indexPageUrl(['query-tag' => $tag->uri()]); // query tag
$resource->formPageUrl(); // create page
$resource->formPageUrl(1); // edit page by int
$resource->formPageUrl($item); // edit page by Model
$resource->detailPageUrl(1); // detail page by int
$resource->detailPageUrl($item); // detail page by Model
$resource->asyncMethodUrl('updateSomething'); // ANY
$resource->fragmentLoadUrl('table-index', $resource->formPage());
// CRUD
$resource->route('crud.update', $data->getKey()); // PUT
$resource->route('crud.store')); // POST
$resource->route('crud.destroy', $data->getKey()); // DELETE
$resource->route('crud.massDelete'); // DELETE
// Handlers
$resource->route('handler', query: ['handlerUri' => $export->uriKey()]);
$page->url(); // page url
$page->route($params); // Advanced method for obtaining routes
$page->asyncMethodUrl('updateSomething'); // ANY
$page->fragmentLoadUrl('table-index');
Генерация тестов
При генерации ресурса также появилась опция с генерацией тестового класса (Pest также поддерживаются)
php artisan make:moonshine NameOfResource --test
php artisan make:moonshine NameOfResource --pest
Визуальные правки и небольшие улучшения
- Поля отношений в Block на детальной странице Pull request
- Новые компоненты Components и FieldsGroupPull request
- Возможность выровнять Tabs по левому и правому краю, либо по центру @k-rustemuly in Pull request
- Обновились до Vite 5Pull request
- Удалили последений border в таблицах Pull request
Исправлены баги 🐞
- Breaking Change Очень важные изменения в классе MoonShine Pull request
- Json и Template поля поддерживают asyncSearch в дочерних поляхPull request
- Исправлена проблема полноэкранного режима TinyMce с TopBar Pull request
- Проблема перезаписи при публикации ассетов Pull request
- MongoDB Models fixPull request
https://github.com/moonshine-software/moonshine/compare/2.4.0...2.5.0