- Описание
- Создание поля
- Отображение поля
- Атрибуты
- Модифицирование значения поля
- Редактирование в режиме preview
- Ассеты
- Трейт Macroable
- Реактивность
- Динамическое отображение
- Кастомное поле
Описание
Все поля наследуют базовый класс Field, который предоставляет базовые методы работы с полями.
Создание поля
Для создания экземпляра поля используется статический метод make().
make(Closure|string|null $label = null,?string $column = null,?Closure $formatted = null)make(Closure|string|null $label = null,?string $column = null,?Closure $formatted = null)
$label- заголовок поля,$column- связь столбца в базе и атрибутаnameу поля ввода (например:description><input name="description">). Если это поле отношения, то используется название отношения (например: countries),$formatted- замыкание для форматирования значения поля в режиме preview (дляBelongsToиBelongsToManyформатирует значения для выбора).
В $label можно добавлять html теги, они не будут экранироваться.
Если не указать $column, то поле в базе данных будет определено автоматически на основе $label (только для английского языка).
Пример замыкания $formatted для форматирования значения:
use MoonShine\UI\Fields\Text;Text::make('Name','first_name',fn($item) => $item->first_name . ' ' . $item->last_name)use MoonShine\UI\Fields\Text;Text::make('Name','first_name',fn($item) => $item->first_name . ' ' . $item->last_name)
Поля не поддерживающие $formatted: Json, File, Range, RangeSlider, DateRange, Select, Enum, HasOne, HasMany.
Отображение поля
Label
Если необходимо изменить label после создания экземпляра поля, можно воспользоваться методом setLabel().
setLabel(Closure|string $label)setLabel(Closure|string $label)
// torchlight! {"summaryCollapsedIndicator": "namespaces"}// [tl! collapse:2]use MoonShine\UI\Fields\Field;use MoonShine\UI\Fields\Slug;Slug::make('Slug')->setLabel(fn(Field $field) => $field->getData()?->exists? 'Slug (do not change)': 'Slug')// torchlight! {"summaryCollapsedIndicator": "namespaces"}// [tl! collapse:2]use MoonShine\UI\Fields\Field;use MoonShine\UI\Fields\Slug;Slug::make('Slug')->setLabel(fn(Field $field) => $field->getData()?->exists? 'Slug (do not change)': 'Slug')
Для перевода label необходимо в качестве названия передать ключ перевода и добавить метод translatable().
translatable(string $key = '')translatable(string $key = '')
Text::make('ui.Title')->translatable()Text::make('Title')->translatable('ui')Text::make('ui.Title')->translatable()Text::make('Title')->translatable('ui')
Text::make(fn() => __('Title'))Text::make(fn() => __('Title'))
insideLabel()
Для оборачивания поля в тег <label> можно использовать метод insideLabel().
Text::make('Name')->insideLabel()Text::make('Name')->insideLabel()
beforeLabel()
Для отображения label после поля ввода можно использовать метод beforeLabel().
Text::make('Name')->beforeLabel()Text::make('Name')->beforeLabel()
Подсказка
Подсказка с описанием можно создать с помощью метода hint().
hint(string $hint)hint(string $hint)
Number::make('Rating')->hint('From 0 to 5')->min(0)->max(5)->stars()Number::make('Rating')->hint('From 0 to 5')->min(0)->max(5)->stars()
Ссылка
Полю можно добавить ссылку (например с инструкциями) link().
link(string|Closure $link,string|Closure $name = '',?string $icon = null,bool $withoutIcon = false,bool $blank = false)link(string|Closure $link,string|Closure $name = '',?string $icon = null,bool $withoutIcon = false,bool $blank = false)
Text::make('Link')->link('https://cutcode.dev', 'CutCode', blank: true)Text::make('Link')->link('https://cutcode.dev', 'CutCode', blank: true)
Badge
Для отображения поля в режиме preview в виде badge, необходимо воспользоваться методом badge().
badge(string|Color|Closure|null $color = null)badge(string|Color|Closure|null $color = null)
Доступные цвета:
primary secondary success warning error info purple pink blue green yellow red gray
use MoonShine\Support\Enums\Color;Text::make('Title')->badge(Color::PRIMARY)use MoonShine\Support\Enums\Color;Text::make('Title')->badge(Color::PRIMARY)
use MoonShine\UI\Fields\Field;Text::make('Title')->badge(fn($status, Field $field) => 'green')use MoonShine\UI\Fields\Field;Text::make('Title')->badge(fn($status, Field $field) => 'green')
Горизонтально отображение
Метод horizontal() позволяет отображать название и поле горизонтально.
horizontal()horizontal()
Text::make('Title')->horizontal()Text::make('Title')->horizontal()
Обертка
Поля при отображении в формах используют специальную обертку для заголовков, подсказок, ссылок и тд.
Иногда может возникнуть ситуация, когда требуется отобразить поле без дополнительных элементов.
Метод withoutWrapper() позволяет отключить создание обёртки.
withoutWrapper(mixed $condition = null)withoutWrapper(mixed $condition = null)
Text::make('Title')->withoutWrapper()Text::make('Title')->withoutWrapper()
Text wrap
Если поля в режиме предварительного просмотра не помещаются в область, выделенную им по ширине, они выходят за ее пределы.
Но этим поведением можно управлять с помощью метода textWrap(), определяющего, как именно обрезать текст.
Clamp:
Text::make('Field')->textWrap(TextWrap::CLAMP)Text::make('Field')->textWrap(TextWrap::CLAMP)
Ellipsis:
Text::make('Field')->textWrap(TextWrap::ELLIPSIS)Text::make('Field')->textWrap(TextWrap::ELLIPSIS)
Disable:
Text::make('Field')->withoutTextWrap()Text::make('Field')->withoutTextWrap()
По умолчанию поле Text имеет Elipsis, а Textarea имеет Clamp.
Сортировка
Для возможности сортировки поля в таблицах (на главной странице) необходимо добавить метод sortable().
sortable(Closure|string|null $callback = null)sortable(Closure|string|null $callback = null)
Text::make('Title')->sortable()Text::make('Title')->sortable()
Метод sortable() в качестве параметра может принимать название поля в базе данных или замыкание.
// torchlight! {"summaryCollapsedIndicator": "namespaces"}// [tl! collapse:3]use Illuminate\Contracts\Database\Eloquent\Builder;use MoonShine\Laravel\Fields\Relationships\BelongsTo;use MoonShine\UI\Fields\Text;BelongsTo::make('Author')->sortable('author_id'),Text::make('Title')->sortable(function (Builder $query, string $column, string $direction) {$query->orderBy($column, $direction);})// torchlight! {"summaryCollapsedIndicator": "namespaces"}// [tl! collapse:3]use Illuminate\Contracts\Database\Eloquent\Builder;use MoonShine\Laravel\Fields\Relationships\BelongsTo;use MoonShine\UI\Fields\Text;BelongsTo::make('Author')->sortable('author_id'),Text::make('Title')->sortable(function (Builder $query, string $column, string $direction) {$query->orderBy($column, $direction);})
Режимы отображения
Подробнее о режимах отображения поля можно прочитать в разделе Основы > Смена режима отображения.
Режим "Default"
Чтобы поле вне зависимости от контекста всегда работало в режиме "Default" (отображение "input" поля), необходимо использовать метод defaultMode().
Text::make('Title')->defaultMode()Text::make('Title')->defaultMode()
Режим "Preview"
Чтобы поле вне зависимости от контекста всегда работало в режиме "Preview", необходимо использовать метод previewMode().
Text::make('Title')->previewMode()Text::make('Title')->previewMode()
Режим "RawMode"
Чтобы поле вне зависимости от контекста всегда работало в режиме "RawMode" (отображение исходного состояния), необходимо использовать метод rawMode().
Text::make('Title')->rawMode()Text::make('Title')->rawMode()
Атрибуты
Основные html атрибуты, такие как required, disabled и readonly, у поля необходимо задавать через соответствующие методы.
Required
required(Closure|bool|null $condition = null)required(Closure|bool|null $condition = null)
Text::make('Title')->required()Text::make('Title')->required()
Disabled
disabled(Closure|bool|null $condition = null)disabled(Closure|bool|null $condition = null)
Text::make('Title')->disabled()Text::make('Title')->disabled()
Readonly
readonly(Closure|bool|null $condition = null)readonly(Closure|bool|null $condition = null)
Text::make('Title')->readonly()Text::make('Title')->readonly()
Другие атрибуты
Чтобы указать любые другие атрибуты, используется метод customAttributes().
Поля являются компонентами, подробнее об атрибутах читайте в разделе Атрибуты компонентов.
customAttributes(array $attributes,bool $override = false)customAttributes(array $attributes,bool $override = false)
$attributes- массив атрибутов,$override- для добавления атрибутов к полю, используетсяmerge. Если атрибут, который вы хотите добавить к полю, уже присутствует, то он добавлен не будет.$override = trueпозволяет изменить данное поведение и перезаписать уже добавленный атрибут.
Password::make('Title')->customAttributes(['autocomplete' => 'off'])Password::make('Title')->customAttributes(['autocomplete' => 'off'])
Атрибуты для wrapper поля
Метод customWrapperAttributes() позволяет добавить атрибуты для обертки поля.
customWrapperAttributes(array $attributes)customWrapperAttributes(array $attributes)
Password::make('Title')->customWrapperAttributes(['class' => 'mt-8'])Password::make('Title')->customWrapperAttributes(['class' => 'mt-8'])
Модифицирование атрибута "name"
Так как атрибут name генерируется на основе вложенности и имеет сложную логику формирования, то для его изменения требуется воспользоваться методом setNameAttribute().
Text::make('Name')->setNameAttribute('custom_name')Text::make('Name')->setNameAttribute('custom_name')
wrapName
Для того чтобы добавить wrapper для значения атрибута name, используется метод wrapName().
Text::make('Name')->wrapName('options')Text::make('Name')->wrapName('options')
В результате атрибут name примет вид <input name="options[name]>. Это особенно полезно для настройки фильтров.
virtualName
Иногда необходимо хранить по одному полю ввода два значения Field. Например, по условию отображение одно из полей может становиться невидимым, но присутствовать в DOM и отправляться вместе с запросом.
// это поле отображается в DOM на одном условииFile::make('image')// это поле отображается в DOM на другом условииFile::make('image')// это поле отображается в DOM на одном условииFile::make('image')// это поле отображается в DOM на другом условииFile::make('image')
Для того чтобы изменить атрибут name у этих полей, используется метод virtualName().
File::make('image')->virtualColumn('image_1')// ...File::make('image')->virtualColumn('image_2')File::make('image')->virtualColumn('image_1')// ...File::make('image')->virtualColumn('image_2')
Далее, например в onApply() методе, мы обрабатываем эти поля по своему усмотрению.
Модифицирование значения поля
Значение по умолчанию
Для указания значения по умолчанию используется метод default().
default(mixed $default)default(mixed $default)
Text::make('Name')->default('Default value')Text::make('Name')->default('Default value')
Enum::make('Status')->attach(ColorEnum::class)->default(ColorEnum::from('B')->value)Enum::make('Status')->attach(ColorEnum::class)->default(ColorEnum::from('B')->value)
Nullable
Если необходимо у поля по умолчанию сохранять NULL, то необходимо использовать метод nullable().
nullable(Closure|bool|null $condition = null)nullable(Closure|bool|null $condition = null)
Password::make('Title')->nullable()Password::make('Title')->nullable()
Изменение отображения
Когда необходимо изменить view с помощью fluent interface можно воспользоваться методом customView().
customView(string $view, array $data = [])customView(string $view, array $data = [])
Text::make('Title')->customView('fields.my-custom-input')Text::make('Title')->customView('fields.my-custom-input')
Метод changePreview() позволяет переопределить view для превью (везде кроме формы).
// torchlight! {"summaryCollapsedIndicator": "namespaces"}// [tl! collapse:2]use MoonShine\UI\Components\Thumbnails;use MoonShine\UI\Fields\Text;Text::make('Thumbnail')->changePreview(function (?string $value, Text $field) {return Thumbnails::make($value);})// torchlight! {"summaryCollapsedIndicator": "namespaces"}// [tl! collapse:2]use MoonShine\UI\Components\Thumbnails;use MoonShine\UI\Fields\Text;Text::make('Thumbnail')->changePreview(function (?string $value, Text $field) {return Thumbnails::make($value);})
Хук до рендера
Если вам необходимо получить доступ к полю непосредственно перед рендером, для этого можно воспользоваться методом onBeforeRender().
/*** @param Closure(static $ctx): void $onBeforeRender*/onBeforeRender(Closure $onBeforeRender)/*** @param Closure(static $ctx): void $onBeforeRender*/onBeforeRender(Closure $onBeforeRender)
Text::make('Thumbnail')->onBeforeRender(function(Text $ctx) {// ...})Text::make('Thumbnail')->onBeforeRender(function(Text $ctx) {// ...})
Получение значения из запроса
Метод onRequestValue() позволяет переопределить логику получения значения из Request.
/*** @param Closure(mixed $value, string $name, mixed $default, static $ctx): mixed $callback*/onRequestValue(Closure $callback)/*** @param Closure(mixed $value, string $name, mixed $default, static $ctx): mixed $callback*/onRequestValue(Closure $callback)
$value- значение полученное по умолчанию,$name- наименование параметра запроса,$default- значение поля по умолчанию, при отсутствии в запросе,$ctx- текущее поле,
Статический метод requestValueResolver() позволяет глобально переопределить логику получения значения из Request для всех полей.
/*** @param Closure(string|int|null $index, mixed $default, static $ctx): mixed $resolver*/requestValueResolver(Closure $resolver)/*** @param Closure(string|int|null $index, mixed $default, static $ctx): mixed $resolver*/requestValueResolver(Closure $resolver)
FormElement::requestValueResolver(function (string|int|null $index, mixed $default, static $ctx): mixed {return request()->input($ctx->getRequestNameDot($index), $default);})FormElement::requestValueResolver(function (string|int|null $index, mixed $default, static $ctx): mixed {return request()->input($ctx->getRequestNameDot($index), $default);})
До и после рендеринга
Методы beforeRender() и afterRender() позволяют вывести какую-то информацию перед и после поля соответственно.
beforeRender(Closure $closure)afterRender(Closure $closure)beforeRender(Closure $closure)afterRender(Closure $closure)
use MoonShine\UI\Fields\Field;Text::make('Title')->beforeRender(function(Field $field) {return $field->preview();})use MoonShine\UI\Fields\Field;Text::make('Title')->beforeRender(function(Field $field) {return $field->preview();})
Условные методы
Отображать компонент можно по условию, воспользовавшись методом canSee().
Text::make('Name')->canSee(function (Text $field) {return $field->toValue() !== 'hide';})Text::make('Name')->canSee(function (Text $field) {return $field->toValue() !== 'hide';})
use MoonShine\Laravel\Fields\Relationships\BelongsTo;// или для полей отношенийBelongsTo::make('Item', 'item', resource: ItemResource::class)->canSee(function (Comment $comment, BelongsTo $field) {// ваше условие})use MoonShine\Laravel\Fields\Relationships\BelongsTo;// или для полей отношенийBelongsTo::make('Item', 'item', resource: ItemResource::class)->canSee(function (Comment $comment, BelongsTo $field) {// ваше условие})
Метод when() реализует fluent interface и выполнит callback, когда первый аргумент, переданный методу, имеет значение true.
when($value = null,?callable $callback = null,?callable $default = null)when($value = null,?callable $callback = null,?callable $default = null)
use MoonShine\UI\Fields\Field;Text::make('Slug')->when(fn() => true,fn(Field $field) => $field->locked())use MoonShine\UI\Fields\Field;Text::make('Slug')->when(fn() => true,fn(Field $field) => $field->locked())
Метод unless() обратный методу when().
unless($value = null,?callable $callback = null,?callable $default = null)unless($value = null,?callable $callback = null,?callable $default = null)
Apply
У каждого поля реализован метод apply(), который трансформирует данные.
Чтобы переопределить стандартный apply у поля, можно воспользоваться методом onApply().
Подробнее о цикле жизни применения поля можно прочитать в разделе Основы > Процесс применения полей.
/*** @param Closure(mixed, mixed, FieldContract): mixed $onApply*/onApply(Closure $onApply)/*** @param Closure(mixed, mixed, FieldContract): mixed $onApply*/onApply(Closure $onApply)
// torchlight! {"summaryCollapsedIndicator": "namespaces"}// [tl! collapse:3]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, Field $field) {$path = 'thumbnail.jpg';if ($value) {$item->thumbnail = Storage::put($path, file_get_contents($value));}return $item;})// torchlight! {"summaryCollapsedIndicator": "namespaces"}// [tl! collapse:3]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, Field $field) {$path = 'thumbnail.jpg';if ($value) {$item->thumbnail = Storage::put($path, file_get_contents($value));}return $item;})
Пример onApply для фильтров:
// torchlight! {"summaryCollapsedIndicator": "namespaces"}// [tl! collapse:2]use MoonShine\UI\Fields\Text;use Illuminate\Contracts\Database\Eloquent\Builder;Text::make('Title')->onApply(function (Builder $query, mixed $value, Text $field) {$query->where('title', $value);})// torchlight! {"summaryCollapsedIndicator": "namespaces"}// [tl! collapse:2]use MoonShine\UI\Fields\Text;use Illuminate\Contracts\Database\Eloquent\Builder;Text::make('Title')->onApply(function (Builder $query, mixed $value, Text $field) {$query->where('title', $value);})
Для того чтобы выполнить какие-либо действия до "apply", можно воспользоваться методом onBeforeApply().
/*** @param Closure(mixed, mixed, FieldContract): static $onBeforeApply*/onBeforeApply(Closure $onBeforeApply)/*** @param Closure(mixed, mixed, FieldContract): static $onBeforeApply*/onBeforeApply(Closure $onBeforeApply)
Для того чтобы выполнить какие-либо действия после "apply", можно воспользоваться методом onAfterApply().
/*** @param Closure(mixed, mixed, FieldContract): static $onBeforeApply*/onAfterApply(Closure $onBeforeApply)/*** @param Closure(mixed, mixed, FieldContract): static $onBeforeApply*/onAfterApply(Closure $onBeforeApply)
Выполнять метод apply() можно по условию, воспользовавшись методом canApply().
canApply(Closure $canApply)canApply(Closure $canApply)
Text::make('Title')->canApply(fn() => false)Text::make('Title')->canApply(fn() => false)
Метод refreshAfterApply() инициирует ререндер поля после "применения".
Так же можно влиять на этот процесс через колбэк.
refreshAfterApply(?Closure $callback = null)refreshAfterApply(?Closure $callback = null)
Text::make('Title')->refreshAfterApply(fn(Text $ctx) => $ctx)Text::make('Title')->refreshAfterApply(fn(Text $ctx) => $ctx)
У полей File эта функция включена по умолчанию для обновления "preview".
Это поведение можно отключить с помощью метода disableRefreshAfterApply() или переопределить в соответствии со своей логикой.
Image::make('Avatar')->disableRefreshAfterApply()Image::make('Avatar')->disableRefreshAfterApply()
Image::make('Avatar')->refreshAfterApply(fn(Image $ctx) => $ctx)Image::make('Avatar')->refreshAfterApply(fn(Image $ctx) => $ctx)
Глобальное определение apply логики
Если вы хотите глобально для определенного поля изменить логику apply, то вы можете создать apply класс и привязать его к необходимому полю.
Для начала создайте apply класс:
php artisan moonshine:apply FileModelApplyphp artisan moonshine:apply FileModelApply
О всех поддерживаемых опциях можно узнать в разделе Команды.
// torchlight! {"summaryCollapsedIndicator": "namespaces"}// [tl! collapse:2]use MoonShine\Contracts\UI\ApplyContract;use MoonShine\Contracts\UI\FieldContract;/*** @implements ApplyContract<File>*/final class FileModelApply implements ApplyContract{/*** @param File $field*/public function apply(FieldContract $field): Closure{return function (mixed $item) use ($field): mixed {$requestValue = $field->getRequestValue();$newValue = // ...return data_set($item, $field->getColumn(), $newValue);};}}// torchlight! {"summaryCollapsedIndicator": "namespaces"}// [tl! collapse:2]use MoonShine\Contracts\UI\ApplyContract;use MoonShine\Contracts\UI\FieldContract;/*** @implements ApplyContract<File>*/final class FileModelApply implements ApplyContract{/*** @param File $field*/public function apply(FieldContract $field): Closure{return function (mixed $item) use ($field): mixed {$requestValue = $field->getRequestValue();$newValue = // ...return data_set($item, $field->getColumn(), $newValue);};}}
Далее зарегистрируйте его для поля:
// torchlight! {"summaryCollapsedIndicator": "namespaces"}// [tl! collapse:10]use Illuminate\Support\ServiceProvider;use MoonShine\Contracts\Core\DependencyInjection\CoreContract;use MoonShine\Laravel\DependencyInjection\MoonShine;use MoonShine\Laravel\DependencyInjection\MoonShineConfigurator;use MoonShine\Laravel\DependencyInjection\ConfiguratorContract;use MoonShine\Contracts\Core\DependencyInjection\AppliesRegisterContract;use MoonShine\UI\Applies\AppliesRegister;use App\MoonShine\Applies\FileModelApply;use MoonShine\UI\Fields\File;use MoonShine\Laravel\Resources\ModelResource;class MoonShineServiceProvider extends ServiceProvider{/*** @param MoonShine $core* @param MoonShineConfigurator $config* @param AppliesRegister $applies**/public function boot(CoreContract $core,ConfiguratorContract $config,AppliesRegisterContract $applies,): void{$applies// resource group, default ModelResource->for(ModelResource::class)// type fields or filters->fields()->add(File::class, FileModelApply::class);}}// torchlight! {"summaryCollapsedIndicator": "namespaces"}// [tl! collapse:10]use Illuminate\Support\ServiceProvider;use MoonShine\Contracts\Core\DependencyInjection\CoreContract;use MoonShine\Laravel\DependencyInjection\MoonShine;use MoonShine\Laravel\DependencyInjection\MoonShineConfigurator;use MoonShine\Laravel\DependencyInjection\ConfiguratorContract;use MoonShine\Contracts\Core\DependencyInjection\AppliesRegisterContract;use MoonShine\UI\Applies\AppliesRegister;use App\MoonShine\Applies\FileModelApply;use MoonShine\UI\Fields\File;use MoonShine\Laravel\Resources\ModelResource;class MoonShineServiceProvider extends ServiceProvider{/*** @param MoonShine $core* @param MoonShineConfigurator $config* @param AppliesRegister $applies**/public function boot(CoreContract $core,ConfiguratorContract $config,AppliesRegisterContract $applies,): void{$applies// resource group, default ModelResource->for(ModelResource::class)// type fields or filters->fields()->add(File::class, FileModelApply::class);}}
Заполнение
Поля можно заполнить значениями, используя метод fill().
Более подробно о процессе заполнения поля можно прочитать в разделе Основы > Изменить наполнение.
fill(mixed $value = null,?DataWrapperContract $casted = null,int $index = 0)fill(mixed $value = null,?DataWrapperContract $casted = null,int $index = 0)
Text::make('Title')->fill('Some title')Text::make('Title')->fill('Some title')
Изменение логики наполнения поля
Для того чтобы изменить логику заполнения поля, можно использовать метод changeFill().
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());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());
В данном примере к путям изображений будет добавлено https://cutcode.dev.
Действия после наполнения поля
Для применения логики к уже наполненному полю можно использовать метод afterFill().
Похожий по логике метод when срабатывает в момент создания экземпляра поля, когда оно еще не наполнено.
Метод afterFill() работает уже с наполненным полем.
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;})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;})
Методы onChange
С помощью методов onChangeMethod() и onChangeUrl() можно добавить логику при изменении значений полей.
Методы onChangeUrl() или onChangeMethod() присутствуют у всех полей, кроме полей отношений HasOne и HasMany.
onChangeUrl()
onChangeUrl(Closure $url,HttpMethod $method = HttpMethod::GET,array $events = [],?string $selector = null,?AsyncCallback $callback = null)onChangeUrl(Closure $url,HttpMethod $method = HttpMethod::GET,array $events = [],?string $selector = null,?AsyncCallback $callback = null)
$url- url запроса,$method- метод асинхронного запроса,$events- вызываемые AlpineJS события после успешного запроса,$selector- selector элемента у которого будет изменяться контент,$callback- js callback функция после получения ответа.
Switcher::make('Active')->onChangeUrl(fn() => '/endpoint')Switcher::make('Active')->onChangeUrl(fn() => '/endpoint')
Если требуется заменить область с html после успешного запроса, вы можете в ответе вернуть HTML контент или json с ключом html.
Switcher::make('Active')->onChangeUrl(fn() => '/endpoint', selector: '#my-selector')Switcher::make('Active')->onChangeUrl(fn() => '/endpoint', selector: '#my-selector')
onChangeMethod()
Метод onChangeMethod() позволяет асинхронно вызывать метод ресурса или страницы при изменении поля без необходимости создавать дополнительные контроллеры.
onChangeMethod(string $method,array|Closure $params = [],?string $message = null,?string $selector = null,array $events = [],?AsyncCallback $callback = null,?PageContract $page = null,?ResourceContract $resource = null)onChangeMethod(string $method,array|Closure $params = [],?string $message = null,?string $selector = null,array $events = [],?AsyncCallback $callback = null,?PageContract $page = null,?ResourceContract $resource = null)
$method- наименование метода,$params- параметры для запроса,$message- сообщения,$selector- selector элемента у которого будет изменяться контент,$events- вызываемые AlpineJS события после успешного запроса,$callback- js callback функция после получения ответа,$page- страница содержащая метод,$resource- ресурс содержащий метод.
Switcher::make('Active')->onChangeMethod('someMethod')Switcher::make('Active')->onChangeMethod('someMethod')
use MoonShine\Laravel\MoonShineRequest;public function someMethod(MoonShineRequest $request): void{// Logic}use MoonShine\Laravel\MoonShineRequest;public function someMethod(MoonShineRequest $request): void{// Logic}
Изменение render поля
Чтобы полностью изменить render поля, можно воспользоваться методом changeRender().
changeRender(Closure $callback)changeRender(Closure $callback)
В данном поле 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)))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)))
Методы для значений
Получение значение из исходного
Метод fromRaw() позволяет добавить замыкание для получения итоговое значение из исходного.
/*** @param Closure(mixed $raw, static): mixed $callback* @return $this*/fromRaw(Closure $callback)/*** @param Closure(mixed $raw, static): mixed $callback* @return $this*/fromRaw(Closure $callback)
// torchlight! {"summaryCollapsedIndicator": "namespaces"}// [tl! collapse:2]use App\Enums\StatusEnum;use MoonShine\UI\Fields\Enum;Enum::make('Status')->attach(StatusEnum::class)->fromRaw(fn(string $raw, Enum $ctx) => StatusEnum::tryFrom($raw))// torchlight! {"summaryCollapsedIndicator": "namespaces"}// [tl! collapse:2]use App\Enums\StatusEnum;use MoonShine\UI\Fields\Enum;Enum::make('Status')->attach(StatusEnum::class)->fromRaw(fn(string $raw, Enum $ctx) => StatusEnum::tryFrom($raw))
Получения необработанного значения
Метод modifyRawValue() позволяет добавить замыкание для получения необработанного значения.
/*** @param Closure(mixed $raw, mixed $data, static): mixed $callback* @return $this*/modifyRawValue(Closure $callback)/*** @param Closure(mixed $raw, mixed $data, static): mixed $callback* @return $this*/modifyRawValue(Closure $callback)
// torchlight! {"summaryCollapsedIndicator": "namespaces"}// [tl! collapse:2]use App\Enums\StatusEnum;use MoonShine\UI\Fields\Enum;Enum::make('Status')->attach(StatusEnum::class)->modifyRawValue(fn(StatusEnum $raw, Order $data, Enum $ctx) => $raw->value))// torchlight! {"summaryCollapsedIndicator": "namespaces"}// [tl! collapse:2]use App\Enums\StatusEnum;use MoonShine\UI\Fields\Enum;Enum::make('Status')->attach(StatusEnum::class)->modifyRawValue(fn(StatusEnum $raw, Order $data, Enum $ctx) => $raw->value))
Редактирование в режиме preview
Редактирование в режиме preview доступно для полей Text, Number, Checkbox, Select и Date.
Для редактирования полей в режиме preview, например в таблице или в любом другом IterableComponent, существуют следующие методы.
updateOnPreview
Метод updateOnPreview() позволяет редактировать поле в режиме предварительного просмотра.
После внесения изменений (по событию onChange), значение поля будет сохранено для конкретного элемента.
updateOnPreview(?Closure $url = null,?ResourceContract $resource = null,mixed $condition = null,array $events = [])updateOnPreview(?Closure $url = null,?ResourceContract $resource = null,mixed $condition = null,array $events = [])
$url- (опционально) url запроса,$resource- (опционально) ресурс содержащий updateOnPreview,$condition- (опционально) условие установки поля в режим updateOnPreview,$events- (опционально) вызываемые AlpineJS события после успешного запроса.
Параметры не являются обязательными, но должны быть заданы, если поле находится вне ресурса или же вы хотите указать полностью свой endpoint (тогда и ресурс не нужен).
Text::make('Name')->updateOnPreview()Text::make('Name')->updateOnPreview()
withUpdateRow
withUpdateRow() работает по аналогии с updateOnPreview(), но при этом может полностью обновить строку в таблице без перезагрузки страницы.
withUpdateRow(string $component)withUpdateRow(string $component)
$component- имя компонента, в котором присутствует данная строка.
Text::make('Name')->withUpdateRow('index-table-post-resource')Text::make('Name')->withUpdateRow('index-table-post-resource')
withUpdateRow() может использовать все параметры updateOnPreview(), например для изменения url запроса, для этого их необходимо вызвать вместе.
Text::make('Name')->updateOnPreview(url: '/my/url')->withUpdateRow()Text::make('Name')->updateOnPreview(url: '/my/url')->withUpdateRow()
updateInPopover
Метод updateInPopover() работает аналогично методу withUpdateRow(), но теперь все значения для редактирования появляются в отдельном окне.
updateInPopover(string $component)updateInPopover(string $component)
$component- имя компонента, в котором присутствует данная строка.
Text::make('Name')->updateInPopover('index-table-post-resource')Text::make('Name')->updateInPopover('index-table-post-resource')
Методы updateOnPreview(), withUpdateRow() и updateInPopover() формируют нужные endpoints и передают методу setUpdateOnPreviewUrl(), который работает с onChangeUrl().
Ассеты
Для добавления ассетов к полю можно использовать метод addAssets().
// torchlight! {"summaryCollapsedIndicator": "namespaces"}// [tl! collapse:2]use Illuminate\Support\Facades\Vite;use MoonShine\AssetManager\Css;Text::make('Name')->addAssets([new Css(Vite::asset('resources/css/text-field.css'))])// torchlight! {"summaryCollapsedIndicator": "namespaces"}// [tl! collapse:2]use Illuminate\Support\Facades\Vite;use MoonShine\AssetManager\Css;Text::make('Name')->addAssets([new Css(Vite::asset('resources/css/text-field.css'))])
Если вы реализуете собственное поле, то объявить набор ассетов в нем можно двумя способами.
- Через метод
assets():
// torchlight! {"summaryCollapsedIndicator": "namespaces"}// [tl! collapse:2]use MoonShine\AssetManager\Css;use MoonShine\AssetManager\Js;/*** @return list<AssetElementContract>*/protected function assets(): array{return [Js::make('/js/custom.js'),Css::make('/css/styles.css')];}// torchlight! {"summaryCollapsedIndicator": "namespaces"}// [tl! collapse:2]use MoonShine\AssetManager\Css;use MoonShine\AssetManager\Js;/*** @return list<AssetElementContract>*/protected function assets(): array{return [Js::make('/js/custom.js'),Css::make('/css/styles.css')];}
- Через метод
booted():
// torchlight! {"summaryCollapsedIndicator": "namespaces"}// [tl! collapse:2]use MoonShine\AssetManager\Css;use MoonShine\AssetManager\Js;protected function booted(): void{parent::booted();$this->getAssetManager()->add(Css::make('/css/app.css'))->append(Js::make('/js/app.js'));}// torchlight! {"summaryCollapsedIndicator": "namespaces"}// [tl! collapse:2]use MoonShine\AssetManager\Css;use MoonShine\AssetManager\Js;protected function booted(): void{parent::booted();$this->getAssetManager()->add(Css::make('/css/app.css'))->append(Js::make('/js/app.js'));}
Трейт Macroable
Всем полям доступен трейт Illuminate\Support\Traits\Macroable с методами mixin() и macro().
С помощью этого трейта вы можете расширять возможности полей, добавляя в них новый функционал без использования наследования.
use MoonShine\UI\Fields\Field;Field::macro('myMethod', fn() => /*реализация*/)Text::make()->myMethod()use MoonShine\UI\Fields\Field;Field::macro('myMethod', fn() => /*реализация*/)Text::make()->myMethod()
Field::mixin(new MyNewMethods())Field::mixin(new MyNewMethods())
Реактивность
Метод reactive() позволяет реактивно изменять поля.
reactive(?Closure $callback = null,bool $lazy = false,int $debounce = 0,int $throttle = 0,)reactive(?Closure $callback = null,bool $lazy = false,int $debounce = 0,int $throttle = 0,)
$callback- callback функция,$lazy- отложенный вызов функции,$debounce- время между вызовами функций (ms.),$throttle- интервал вызова функций (ms.).
// torchlight! {"summaryCollapsedIndicator": "namespaces"}// [tl! collapse:3]use MoonShine\UI\Collections\Fields;use MoonShine\UI\Components\FormBuilder;use MoonShine\UI\Fields\Text;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()])// torchlight! {"summaryCollapsedIndicator": "namespaces"}// [tl! collapse:3]use MoonShine\UI\Collections\Fields;use MoonShine\UI\Components\FormBuilder;use MoonShine\UI\Fields\Text;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-поля на основе заголовка. Slug будет генерироваться в процессе ввода текста.
Реактивное поле может менять состояние других полей, но не изменяет свое состояние!
Для изменения состояния поля инициирующего реактивность удобно воспользоваться параметрами callback-функции.
// torchlight! {"summaryCollapsedIndicator": "namespaces"}// [tl! collapse:3]use MoonShine\UI\Fields\Field;use MoonShine\UI\Fields\Select;use MoonShine\UI\Collections\Fields;Select::make('Category', 'category_id')->reactive(function(Fields $fields, ?string $value, Field $field, array $values): Fields {$field->setValue($value);return tap($fields, static fn ($fields) =>$fields->findByColumn('article_id')?->options(Article::where('category_id', $value)->get()->pluck('title', 'id')->toArray()););})// torchlight! {"summaryCollapsedIndicator": "namespaces"}// [tl! collapse:3]use MoonShine\UI\Fields\Field;use MoonShine\UI\Fields\Select;use MoonShine\UI\Collections\Fields;Select::make('Category', 'category_id')->reactive(function(Fields $fields, ?string $value, Field $field, array $values): Fields {$field->setValue($value);return tap($fields, static fn ($fields) =>$fields->findByColumn('article_id')?->options(Article::where('category_id', $value)->get()->pluck('title', 'id')->toArray()););})
Динамическое отображение
Поля можно скрывать или показывать динамически, в зависимости от значений других полей в реальном времени без перезагрузки страницы и запросов к серверу.
Для этого используются методы showWhen() и showWhenDate().
Метод showWhen
Метод showWhen() позволяет задать условие отображения поля в зависимости от значения другого поля.
showWhen(string $column,mixed $operator = null,mixed $value = null)showWhen(string $column,mixed $operator = null,mixed $value = null)
$column- имя поля, от которого зависит отображение,$operator- оператор сравнения (необязательный),$value- значение для сравнения.
Text::make('Name')->showWhen('category_id', 1)Text::make('Name')->showWhen('category_id', 1)
В этом примере поле "Name" будет отображаться только если значение поля "category_id" равно 1.
Если в функции showWhen передается только два параметра, то по умолчанию используется оператор '='.
Text::make('Name')->showWhen('category_id', 'in', [1, 2, 3])Text::make('Name')->showWhen('category_id', 'in', [1, 2, 3])
В этом примере поле "Name" будет отображаться только если значение поля "category_id" равно 1, 2 или 3.
Когда на поле присутствует условие showWhen, то при скрытии этого поля, на его DOM элемент устанавливается значение display:none и удаляется атрибут name, чтобы значение этого скрытого поля не попадало в итоговый запрос.
Если вы не хотите, чтобы атрибут name удалялся, необходимо в вашем ресурсе установить флаг $submitShowWhen в значение true.
class ArticleResource extends ModelResource{protected bool $submitShowWhen = true;// ...}class ArticleResource extends ModelResource{protected bool $submitShowWhen = true;// ...}
Метод showWhenDate
Метод showWhenDate() позволяет задать условие отображения поля в зависимости от значения поля типа date.
Логика для работы с датами была вынесена в отдельный метод из-за специфики конвертации и сравнения типа date и datetime на backend и frontent.
showWhenDate(string $column,mixed $operator = null,mixed $value = null)showWhenDate(string $column,mixed $operator = null,mixed $value = null)
$column- имя поля с датой, от которого зависит отображение,$operator- оператор сравнения (необязательный),$value- значение даты для сравнения.
Text::make('Content')->showWhenDate('created_at', '>', '2024-09-15 10:00')Text::make('Content')->showWhenDate('created_at', '>', '2024-09-15 10:00')
В этом примере поле "Content" будет отображаться только если значение поля "created_at" больше '2024-09-15 10:00'.
Если в функции showWhenDate передается только два параметра, то по умолчанию используется оператор '='.
Вы можете использовать любой формат даты, который может быть распознан функцией strtotime().
Вложенные поля
Методы showWhen() и showWhenDate() поддерживают работу с вложенными полями, например для работы с полем Json.
Для обращения к вложенным полям используется точечная нотация.
Text::make('Parts')->showWhen('attributes.1.size', '!=', 2)Text::make('Parts')->showWhen('attributes.1.size', '!=', 2)
В этом примере поле "Parts" будет отображаться только если значение вложенного поля "size" во втором элементе массива "attributes" не равно 2.
showWhen() работает и для вложенных полей типа Json:
Json::make('Attributes', 'attributes')->fields([Text::make('Size'),Text::make('Parts')->showWhen('category_id', 3),Json::make('Settings', 'settings')->fields([Text::make('Width')->showWhen('category_id', 3),Text::make('Height'),])]),Json::make('Attributes', 'attributes')->fields([Text::make('Size'),Text::make('Parts')->showWhen('category_id', 3),Json::make('Settings', 'settings')->fields([Text::make('Width')->showWhen('category_id', 3),Text::make('Height'),])]),
В данном примере весь столбец Parts внутри attributes и весь столбец Width внутри attributes.[n].settings будет отображаться только если значение поля category_id равно 3.
Множественные условия
Методы showWhen() и showWhenDate() могут быть вызваны несколько раз для одного поля, что позволяет задать несколько условий отображения.
BelongsTo::make('Category', 'category', resource: CategoryResource::class)->showWhenDate('created_at', '>', '2024-08-05 10:00')->showWhenDate('created_at', '<', '2024-08-05 19:00')BelongsTo::make('Category', 'category', resource: CategoryResource::class)->showWhenDate('created_at', '>', '2024-08-05 10:00')->showWhenDate('created_at', '<', '2024-08-05 19:00')
В этом примере поле "Category" будет отображаться только если значение поля "created_at" находится в диапазоне между '2024-08-05 10:00' и '2024-08-05 19:00'.
При использовании нескольких условий они объединяются логическим "И" (AND). Поле будет отображаться только если выполняются все заданные условия.
Поддерживаемые операторы
=!=><>=<=innot in
Оператор in проверяет, содержится ли значение в массиве.
Оператор not in проверяет, не содержится ли значение в массиве.
Кастомное поле
Вы можете создать собственное поле, со своим view и логикой и использовать его в административной панели MoonShine. Для этого воспользуйтесь командой:
php artisan moonshine:fieldphp artisan moonshine:field
О всех поддерживаемых опциях можно узнать в разделе Команды.