- Концепция
- Режимы работы
- Цикл жизни поля
- Изменить preview
- Изменить наполнение
- Смена режима отображения
- Процесс применения полей
Концепция
Полям отводится важнейшая роль в админ-панели MoonShine.
Они используются в FormBuilder
для построения форм, в TableBuilder
для создания таблиц, а также в формировании фильтра для ModelResource
(CrudResource).
Их можно использовать в ваших кастомных страницах и даже вне админ-панели как в виде объектов, так и непосредственно в Blade.
Создать экземпляр поля очень просто.
Для этого есть удобный метод make()
и для базового использования достаточно указать label и name поля.
use MoonShine\UI\Fields\Text; Text::make('Title')Text::make('Title', 'title')
Чаще всего поля используются внутри FormBuilder
, где за счет самого FormBuilder
они на основе реквеста могут изменять исходный объект.
Режимы работы
Сложность понимания полей MoonShine обусловлена наличием нескольких визуальных состояний.
Режим по умолчанию
Поля являются элементами формы, поэтому их состояние по умолчанию при рендере является просто HTML элементом формы.
Например, для поля Text
визуальное состояние по умолчанию будет <input type="text" .../>
.
Режим Preview
Этот режим служит для отображения значения поля.
При выводе поля через TableBuilder
, нам не нужно его редактировать, мы просто хотим показать его содержимое.
Давайте рассмотрим поле Image
, его preview
вид будет иметь img
миниатюру или карусель изображений если режим multiple.
Тем самым каждое поле выглядит по-разному как и разнообразие элементов формы, но также и выглядят по-разному в режиме preview так как у них разные назначения.
Это имеет преимущество в том, что нам, разработчикам, не нужно беспокоиться о том, как отобразить, например, поле Date
.
Под капотом MoonShine выполнит форматирование, экранирует текстовые поля для обеспечения безопасности или просто сделает вывод более эстетичным.
Режим Raw
Возможно, при использовании панели, вам и не придётся использовать этот режим. Его суть в том, что он просто выведет значение поля, которое было ему присвоено изначально без дополнительных модификаций.
Режим идеально подходит для экспорта, чтобы в итоге отобразить исходное содержимое для дальнейшего импорта.
Цикл жизни поля
В процессе объявления полей вы можете менять визуальные состояния каждого из них, но прежде чем мы взглянем на примеры, давайте кратко рассмотрим базовый цикл жизни поля.
Цикл через FormBuilder
- поле объявлено в ресурсе,
- поле попадает в
FormBuilder
, FormBuilder
наполняет поле,FormBuilder
рендерит поле,- при реквесте
FormBuilder
вызывает поля и сохраняет за счет них исходный объект.
Цикл через TableBuilder
- поле объявлено в ресурсе,
- поле попадает в
TableBuilder
, TableBuilder
включает поле в режим Preview,TableBuilder
итерирует исходные данные и трансформирует их вTableRow
предварительно наполнив каждое поле данными,TableBuilder
рендерит себя и каждый свой row вместе с полями.
Цикл через экспорт
- поле объявлено в ресурсе в методе для экспорта,
- поле попадает в
Handler
, Handler
включает поле в режим Raw,Handler
итерирует исходные данные, наполняет ими поля и генерирует таблицу для экспорта на основе сырых значений полей.
Поля в MoonShine не привязаны к модели (за исключением поля Slug
и полей отношений), поэтому спектр их применения ограничивается только вашей фантазией.
В процессе взаимодействия с полями вы можете столкнуться с рядом задач по их модификации. Все они будут связаны с циклами и состояниями описанными выше, рассмотрим их.
Изменить preview
Допустим, Вы используете поле Select
с опциями в виде ссылок на изображения и хотите в режиме preview выводить не ссылки, а сразу рендерить изображения.
Результат можно достигнут за счет метода changePreview()
.
Ваш код будет выглядеть следующим образом:
use MoonShine\UI\Components\Carousel;use MoonShine\UI\Fields\Select; Select::make('Links')->options([ '/images/1.png' => 'Picture 1', '/images/2.png' => 'Picture 2',]) ->multiple() // Поле может иметь несколько значений ->fill(['/images/1.png', '/images/2.png']) // Мы наполнили поле, указали какие значения выбраны ->changePreview( fn(?array $values, Select $ctx) => Carousel::make($values) ) // изменили состояние preview
В итоге вы получите карусель изображений на основе значений Select
, вы можете вернуть компонент или любую строку.
Изменить наполнение
В предыдущем примере мы использовали метод fill()
для наполнения поля Select
.
Но, если мы используем его в готовом ModelResource
или FormBuilder
, то поле будет наполнено за нас и данные, переданные в метод fill()
, будут перезаписаны.
В ваших же задачах может возникнуть ситуация, когда вам потребуется изменить логику наполнения, интегрироваться в этот процесс.
В этом случае вам помогут методы changeFill()
и afterFill()
.
Давайте рассмотрим всё тот же пример с Select
и изображениями, но при этом преобразуем относительный путь в полный URL.
В данном случае наполнение происходит автоматически, эти действия сделает за нас FormBuilder
и ModelResource
, мы же только изменим процесс.
use MoonShine\UI\Components\Carousel;use MoonShine\UI\Fields\Select; Select::make('Images')->options([ '/images/1.png' => 'Picture 1', '/images/2.png' => 'Picture 2',]) ->multiple() ->changeFill( fn(Article $article, Select $ctx) => $article->images ->map(fn($value) => "https://cutcode.dev$value") ->toArray() ) ->changePreview( fn(?array $values, Select $ctx) => Carousel::make($values) ),
Данный метод принимает полный объект, который был передан FormBuilder
в поля и поскольку мы рассматривали контекст с ModelResource
, то исходные данные у нас были Model
- Article
.
В процессе мы вернули значения, необходимые для поля, но изменив содержимое.
Мы использовали changePreview()
из предыдущего шага для демонстрации результата.
Рассмотрим ещё один пример наполнения.
Допустим, нам нужно при выводе Select
в таблицу проверить его значение на определенное условие и добавить класс на ячейку, если оно выполнено.
Нам нужно получить итоговое значение, которым наполнен Select
, и нам важно, чтобы наполнение обязательно уже произошло (так как условный метод when()
вызывается до наполнения и нам не подходит).
use MoonShine\UI\Components\Carousel;use MoonShine\UI\Fields\Select; Select::make('Links')->options([ '/images/1.png' => 'Picture 1', '/images/2.png' => 'Picture 2',]) ->multiple() ->afterFill( function(Select $ctx) { if(collect($ctx->toValue())->every(fn($value) => str_contains($value, 'cutcode.dev'))) { return $ctx->customWrapperAttributes(['class' => 'full-url']); } return $ctx; } ) ->changePreview( fn(?array $values, Select $ctx) => Carousel::make($values) ),
Построитель полей обладает широкими возможностями и вы можете менять любые состояния прям на лету.
Давайте рассмотрим редкий случай изменения визуального состояния по умолчанию, хотя мы и не рекомендуем этого делать и лучше будет создать отдельный класс поля для этих задач, чтобы вынести логику и переиспользовать поле в дальнейшем.
Но всё же представим, что из поля Select
по каким-то причинам мы хотим сделать поле Text
:
use MoonShine\UI\Fields\Select; Select::make('Links')->options([ '/images/1.png' => 'Picture 1', '/images/2.png' => 'Picture 2',]) ->multiple() ->changeRender( fn(?array $values, Select $ctx) => Text::make($ctx->getLabel())->fill(implode(',', $values)) )
Смена режима отображения
Как мы уже поняли, поля имеют разные визуальные состояния: в FormBuilder
по умолчанию это будет элемент формы, в TableBuilder
различное отображение значения, а скажем в экспорте - просто исходное значение.
Но давайте представим ситуацию, что нам необходимо в TableBuilder
вывести поле не в режиме preview, а в режиме по умолчанию или наоборот внутри FormBuilder
вывести в preview режиме или вообще в исходном:
Text::make('Title')->defaultMode()
Независимо от того, где мы будем выводить это поле - оно всегда будет в режиме по умолчанию, в виде элемента формы:
Text::make('Title')->previewMode()
То же самое, только всегда будет в режиме preview.
Ну и режим с исходным состоянием напоследок:
Text::make('Title')->rawMode()
Раз уж мы с вами затронули тему rawMode
и уже обсуждали процесс изменения наполнения, давайте также взглянем на метод, который позволяет модифицировать исходное значение.
Например, мы используем поле для экспорта и нам не нужно выполнять последующий импорт, необходимо отобразить значение для менеджера в понятном формате:
use MoonShine\Laravel\Fields\Relationships\BelongsTo; BelongsTo::make('User') ->modifyRawValue( fn(int $rawUserId, Article $model, BelongsTo $ctx) => $model->user->name )
Давайте также представим ситуацию, что вам необходимо делать экспорт в понятном для менеджеров формате, но при этом также в дальнейшем импортировать этот файл. Каким бы не был умным MoonShine, он не поймет, что значение "Иван Иванов" нужно найти в таблице users по полю name и взять только id. Эту задачу можно решить следующим образом:
use MoonShine\Laravel\Fields\Relationships\BelongsTo; BelongsTo::make('User') ->fromRaw( fn(string $name) => User::where('name', $name)->value('id') )
Процесс применения полей
Мы уже знаем, что поля работают с любыми данными и это не обязательно Model
.
Но поля также могут модифицировать поступающие в них данные.
По-простому это можно назвать сохранением, но мы не используем этот термин, так как не всегда поля сохраняют.
Например, исходными данными может быть QueryBuilder
, а поля будут выступать в роли фильтров и тогда они будут модифицировать запрос QueryBuilder
или любой другой кейс.
Поэтому правильнее будет сказать, что поля "применяются" (apply).
Цикл жизни применения поля (на примере сохранения модели)
FormBuilder
принимает исходный объект, пусть это будет модель User,- итерирует поля, передавая в них объект
User
и вызывая метод поляapply()
, - поля внутри
apply()
берут значение из реквеста на основе своего свойства column, - поля на основе своего свойства column модифицируют это поле модели
User
и возвращают её обратно, - после
FormBuilder
вызовет методsave()
модели, - также перед
apply()
методом поля будет вызван методbeforeApply()
, если требуется что-нибудь сделать с объектом до основного применения, - после метода
save()
модели у полей будет вызван методafterApply()
(что в данном кейсе хорошо подойдет для полей отношений, чтобы у них был исходный объект который уже сохранен в бд).
Цикл жизни применения поля (на примере фильтрации)
FormBuilder
принимает исходный объектQueryBuilder
,- итерирует поля передавая в них объект
QueryBuilder
и вызывая метод поляapply()
, - поля на основе своего свойства column модифицируют
QueryBuilder
и возвращают его обратно, - после объект
QueryBuilder
будет использован при выводе данных.
При использовании MoonShine в реальных условиях вы можете столкнуться с ситуацией, когда вам потребуется изменить логику применения или добавить логику до или после основного применения поля.
Построитель полей позволяет легко достичь этих целей на лету:
use Illuminate\Database\Eloquent\Model;use Illuminate\Support\Facades\Storage;use MoonShine\UI\Fields\Text; Text::make('Thumbnail by link', 'thumbnail') ->onApply(function(Model $item, $value, Text $field) { $path = 'thumbnail.jpg'; if ($value) { $item->thumbnail = Storage::put($path, file_get_contents($value)); } return $item; })
Тем самым мы просто добавили ссылку в текстовое поле, но не сохранили её как есть, а загрузили и положили в storage и вернули итоговый путь.
Также нам доступны методы onBeforeApply()
и onAfterApply()
.
Далее давайте взглянем на интерфейс полей более детально, а также на каждое поле отдельно.