На этой неделе команда MoonShine выпустила первый в 2024 году релиз v.2.6.0 с кодовым именем "Jet Punch"! Давайте взглянем на самое интересное в этом обновлении!
Реактивность полей (beta)
Фича дает возможность реактивно изменять поля. Не только их значения, но и содержимое. Пока работает в бета-режиме.
Давайте рассмотрим пример формирования slug-поля на основе заголовка. Slug будет генерироваться в процессе ввода текста:
FormBuilder::make()
->name('my-form')
->fields([
Text::make('Title')
->reactive(function(Fields $fields, ?string $value): Fields {
return tap($fields, static fn ($fields) => $fields
->findByColumn('slug')
?->setValue(str($value ?? '')->slug()->value())
);
}),
Text::make('Slug')->reactive(),
])
Также для slug-поля появился метод для быстрого формирования логики из примера выше:
FormBuilder::make()
->name('reactive')
->fields([
Text::make('Title')->reactive(),
Slug::make('Slug')->from('title')->live(),
])
В данном случае slug всего лишь пример использования этого мощного функционала.
CardsBuilder
Пример использования для вывода элементов в виде карточек:
CardsBuilder::make(items: Article::query()->with('author')->latest()->paginate())
->name('cards')
->async()
->fields([
BelongsTo::make('Author', resource: new MoonShineUserResource())->badge(),
Date::make('Created at')->format('d.m.Y')
])
->cast(ModelCast::make(Article::class))
->title('title')
->thumbnail(fn($data) => $data->author?->avatar ? Storage::url($data->author->avatar) : '')
->subtitle(fn() => 'Subtitle')
->url(fn($data) => (new ArticleResource())->formPageUrl($data))
->overlay()
->header(fn() => Badge::make('New', 'green'))
->content('Custom content')
->buttons([
ActionButton::make('Go to', '#'),
ActionButton::make('Go to 2', '#'),
])
->columnSpan(4)
Также можно изменить компонент самой карточки:
->customComponent(function (Article $article, int $index, CardsBuilder $builder) {
return Badge::make($index + 1 . "." . $article->title, 'green');
})
Ну и событие изменения cards-updated-
Методы onChange
С этого момента метод updateOnPreview является сахаром метода onChangeUrl.
А новые методы onChangeUrl или onChangeMethod присутствуют для всех полей, кроме полей отношений HasOne и HasMany.
C помощью методов onChangeMethod и onChangeUrl вы сможете добавить логику при изменении значений:
Switcher::make('Name')->onChangeMethod('someMethod')
// ...
public function someMethod(MoonShineRequest $request): void
{
// Logic
}
Большой пример, где мы меняем сортировку для компонента CardsBuilder:
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');
}
Методы для быстрого изменения основных компонентов страниц
Теперь можно быстро изменить только основной компонент страницы. Например, изменить на индексной странице отображение через компонент TableBuilder на CardsBuilder, а также изменить события изменения:
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());
}
}
Тоже самое и для FormPage:
protected function detailComponent(?Model $item, Fields $fields): MoonShineRenderable
А также DetailPage:
protected function formComponent(
string $action,
?Model $item,
Fields $fields,
bool $isAsync = false,
): MoonShineRenderable
Новые директивы и класс помощник AlpineJs
Blade-директивы для быстрого объявления событий:
@defineEvent('table-updated', 'index', 'asyncRequest') // @table-updated-index.window="asyncRequest"
@defineEventWhen(true, 'table-updated', 'index', 'asyncRequest')
AlpineJs класс-помощник, для формирования событий и многое другое:
AlpineJs::event(JsEvent::TABLE_UPDATED, 'index', 'asyncRequest'); // table-updated-index
AlpineJs::eventBlade(JsEvent::TABLE_UPDATED, 'index', 'asyncRequest'); // @table-updated-index.window="asyncRequest"
Новые компоненты
Card, Badge, Boolean, Breadcrumbs, Color, Dropdown, Files, Thumbnails, Icon, ProgressBar, Spinner, Title, Url.
Компоненты Modals
В предыдущих версиях MoonShine была небольшая проблема с Modals, которая заключалась в том, что содержимое является строкой, оно изолировано и мы не имели доступ к компонентам внутри. Тем самым при передаче формы могли возникнуть проблемы с asyncSearch. Благодаря этому PR появилась возможность передавать компоненты, а модалки реализует интерфейс HasFields:
$modal = Modal::make(
'Image Modal',
components: PageComponents::make([$image])
)->name('image-modal');
Исправлены баги 🐞
- Проблемы с layout-wrapper
- Enum с null
- Octane issue
- Default-значение для asyncSearch
- и многое другое
https://github.com/moonshine-software/moonshine/compare/2.5.0...2.6.0