- Description
- Create Field
- Field Display
- Attributes
- Modifying Field Value
- Editing in Preview Mode
- Assets
- Macroable Trait
- Reactivity
- Dynamic Display
- Custom Field
Description
All fields inherit the base class Field
, which provides basic methods for working with fields.
Create Field
To create an instance of a field, the static method make()
is used.
Text::make(Closure|string|null $label = null, ?string $column = null, ?Closure $formatted = null)
-
$label
- the label or title of the field. -
$column
- the relationship between the database column and thename
attribute of the input field (e.g.:description
><input name="description">
). If this field is a relationship, the name of the relationship is used (e.g.: countries). -
$formatted
- a closure for formatting the field's value in preview mode (for BelongsTo and BelongsToMany, formats values for selection).
HTML tags can be added in
$label
, they will not be escaped.
If
$column
is not specified, the database field will be automatically determined based on$label
(only for English).
Example of a closure $formatted
for formatting a value.
Text::make( 'Name', 'first_name', fn($item) => $item->first_name . ' ' . $item->last_name)
Fields that do not support
formatted
:Json
,File
,Range
,RangeSlider
,DateRange
,Select
,Enum
,HasOne
,HasMany
.
Field Display
Label
If you need to change the Label
after creating an instance of the field, you can use the setLabel()
method.
setLabel(Closure|string $label)
Slug::make('Slug') ->setLabel( fn(Field $field) => $field->getData()?->exists ? 'Slug (do not change)' : 'Slug' )
To translate Label
, you must pass a translation key as the name and add the translatable()
method.
translatable(string $key = '')
Text::make('ui.Title')->translatable()
or
Text::make('Title')->translatable('ui')
or
Text::make(fn() => __('Title'))
insideLabel()
To wrap a field in a <label>
tag, you can use the insideLabel()
method.
Text::make('Name') ->insideLabel(),
beforeLabel()
To display the Label after the input field, you can use the beforeLabel()
method.
Text::make('Name') ->beforeLabel(),
Hint
A hint with a description can be created using the hint()
method.
hint(string $hint)
Number::make('Rating') ->hint('From 0 to 5') ->min(0) ->max(5) ->stars()
Link
You can add a link to the field (e.g., with instructions) using link()
.
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)
Badge
To display the field in preview mode as a badge, you need to use the badge()
method.
badge(string|Color|Closure|null $color = null)
Available colors:
primary secondary success warning error info
purple pink blue green yellow red gray
Text::make('Title') ->badge(Color::PRIMARY)
or
Text::make('Title') ->badge(fn($status, Field $field) => 'green')
Horizontal Display
The horizontal()
method allows displaying the label and the field horizontally.
horizontal()
Text::make('Title') ->horizontal(),
Wrapper
Fields when displayed in forms use a special wrapper for headers, hints, links, etc. Sometimes there may arise a situation when it is necessary to display a field without additional elements. The withoutWrapper()
method allows disabling the creation of a wrapper.
withoutWrapper(mixed $condition = null)
Text::make('Title') ->withoutWrapper()
Sorting
To allow sorting the field in tables (on the main page), you must add the sortable()
method.
sortable(Closure|string|null $callback = null)
Text::make('Title')->sortable()
The sortable()
method can accept a database field name or a closure as a parameter.
BelongsTo::make('Author')->sortable('author_id'), Text::make('Title')->sortable(function (Builder $query, string $column, string $direction) { $query->orderBy($column, $direction);})
View Modes
You can read more about view modes in the section Basics > Change View Mode.
Default Mode
To ensure that the field always works in "Default" mode (render as "input" field) regardless of context, use the defaultMode()
method.
Text::make('Title')->defaultMode()
Preview Mode
To ensure that the field always works in "Preview" mode regardless of context, use the previewMode()
method.
Text::make('Title')->previewMode()
RawMode
To ensure that the field always works in "RawMode" (render as the original state), use the rawMode()
method.
Text::make('Title')->rawMode()
Attributes
Basic HTML attributes such as required
, disabled
, and readonly
should be specified for the field through their respective methods.
Required
required(Closure|bool|null $condition = null)
Text::make('Title')->required()
Disabled
disabled(Closure|bool|null $condition = null)
Text::make('Title')->disabled()
Readonly
readonly(Closure|bool|null $condition = null)
Text::make('Title')->readonly()
Other Attributes
To specify any other attributes, the customAttributes()
method is used.
Fields are components, read more about attributes in the section Component Attributes
customAttributes(array $attributes, bool $override = false)
-
$attributes
- an array of attributes -
$override
- to add attributes to the field, usemerge
. If the attribute you want to add to the field already exists, it will not be added. Setting$override = true
allows changing this behavior and overwriting the already added attribute.
Password::make('Title') ->customAttributes(['autocomplete' => 'off'])
Attributes for Wrapper Field
The customWrapperAttributes()
method allows adding attributes for the wrapper field.
customWrapperAttributes(array $attributes)
Password::make('Title') ->customWrapperAttributes(['class' => 'mt-8'])
Modifying the "name" Attribute
wrapName
To add a wrapper for the value of the name
attribute, the wrapName
method is used.
Text::make('Name')->wrapName('options')
As a result, the name attribute will look like <input name="options[name]>
. This is especially useful for setting up filters.
virtualName
Sometimes it is necessary to store two values in one input field. For example, under a display condition one of the fields may become invisible but still exist in the DOM and be sent with the request.
File::make('image') // this is displayed in showWhen under one conditionFile::make('image') // this is displayed in showWhen under another condition
To change the name attribute of these fields, the virtualName
method is used.
File::make('image')->virtualColumn('image_1')File::make('image')->virtualColumn('image_2')
Then, for example in the onApply method, we can handle these fields as we see fit.
Modifying Field Value
Default Value
To specify a default value, the default()
method is used.
default(mixed $default)
Text::make('Name') ->default('Default value')
or
Enum::make('Status') ->attach(ColorEnum::class) ->default(ColorEnum::from('B')->value)
Nullable
If you need to keep NULL as the default value for the field, then the nullable()
method should be used.
nullable(Closure|bool|null $condition = null)
Password::make('Title') ->nullable()
Changing Display
When you need to change the view using the fluent interface, you can use the customView()
method.
customView(string $view, array $data = [])
Text::make('Title')->customView('fields.my-custom-input')
The changePreview()
method allows overriding the view for preview (everywhere except the form).
Text::make('Thumbnail') ->changePreview(function (?string $value, Text $field) { return Thumbnails::make($value); })
Hook Before Render
If you need to access a field just before rendering, you can use the onBeforeRender()
method.
/** * @param Closure(static $ctx): void $onBeforeRender */public function onBeforeRender(Closure $onBeforeRender): static
Text::make('Thumbnail')->onBeforeRender(function(Text $ctx) { //})
Getting Value from Request
The requestValueResolver()
method allows overriding the logic for obtaining the value from the Request.
/*** @param Closure(string|int|null $index, mixed $default, static $ctx): mixed $resolver*/requestValueResolver(Closure $resolver)
Relationship fields do not support the
requestValueResolver
method.
Before and After Rendering
The beforeRender()
and afterRender()
methods allow displaying some information before and after the field, respectively.
beforeRender(Closure $closure)afterRender(Closure $closure)
Text::make('Title') ->beforeRender(function(Field $field) { return $field->preview(); })
Conditional Methods
Components can be displayed conditionally using the canSee()
method.
Text::make('Name') ->canSee(function (Text $field) { return $field->toValue() !== 'hide'; })
or for relationship fields:
BelongsTo::make('Item', 'item', resource: ItemResource::class) ->canSee(function (Comment $comment, BelongsTo $field) { // your condition }),
The when()
method implements a fluent interface and will execute the callback when the first argument passed to the method is true.
when($value = null, ?callable $callback = null, ?callable $default = null)
Text::make('Slug') ->when(fn() => true, fn(Field $field) => $field->locked())
The unless()
method is the opposite of the when()
method.
unless($value = null, ?callable $callback = null, ?callable $default = null)
Apply
Each field has an apply()
method that transforms the data. To override the default apply
of a field, you can use the onApply()
method. Read more about the lifecycle of field application in the section Basics > Field Application Process.
/** * @param Closure(mixed, mixed, FieldContract): mixed $onApply */onApply(Closure $onApply)
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; })
To perform actions before "apply", you can use the onBeforeApply()
method.
/** * @param Closure(mixed, mixed, FieldContract): static $onBeforeApply */function onBeforeApply(Closure $onBeforeApply)
To perform actions after "apply", you can use the onAfterApply()
method.
/** * @param Closure(mixed, mixed, FieldContract): static $onBeforeApply */function onAfterApply(Closure $onBeforeApply)
Global Definition of Apply Logic
If you want to globally change the apply
logic for a certain field, you can create an apply
class and bind it to the necessary field.
First, create the apply
class:
php artisan moonshine:apply FileModelApply
/** * @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); }; }}
Then register it for the field:
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
Fields can be filled with values using the fill()
method. You can read more details about the filling process in the section Basics > Change Fill.
fill(mixed $value = null, ?DataWrapperContract $casted = null, int $index = 0)
Text::make('Title') ->fill('Some title')
Changing the Field Filling Logic
To change the filling logic of a field, you can use the changeFill()
method.
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() );
In this example, https://cutcode.dev
will be added to the image paths.
Actions After Filling the Field
To apply logic to an already filled field, you can use the afterFill()
method.
A similar logic method when triggers when creating a field instance, when it is not yet filled.
The afterFill
method works with an already filled field.
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 Methods
Using the onChangeMethod()
and onChangeUrl()
methods, you can add logic when field values change.
The methods
onChangeUrl()
oronChangeMethod()
are present on all fields except for HasOne and HasMany relationship fields.
onChangeUrl()
onChangeUrl( Closure $url, HttpMethod $method = HttpMethod::GET, array $events = [], ?string $selector = null, ?AsyncCallback $callback = null,)
-
$url
- the request url, -
$method
- the method for the asynchronous request, -
$events
- triggers AlpineJS events after a successful request, -
$selector
- the selector of the element whose content will change, -
$callback
- a js callback function after receiving a response.
Switcher::make('Active') ->onChangeUrl(fn() => '/endpoint')
If you need to replace an area with HTML after a successful request, you can return HTML content or JSON with the key html in the response.
Switcher::make('Active') ->onChangeUrl(fn() => '/endpoint', selector: '#my-selector')
onChangeMethod()
The onChangeMethod()
method allows asynchronously calling a resource or page method when the field changes without the need to create additional controllers.
onChangeMethod( string $method, array|Closure $params = [], ?string $message = null, ?string $selector = null, array $events = [], ?AsyncCallback $callback = null, ?PageContract $page = null, ?ResourceContract $resource = null,)
-
$method
- the name of the method, -
$params
- parameters for the request, -
$message
- messages, -
$selector
- the selector of the element whose content will change, -
$events
- triggers AlpineJS events after a successful request, -
$callback
- a js callback function after receiving a response, -
$page
- the page containing the method, -
$resource
- the resource containing the method.
Switcher::make('Active') ->onChangeMethod('someMethod')
public function someMethod(MoonShineRequest $request): void{ // Logic}
Changing Field Render
To completely change the render of a field, you can use the changeRender()
method.
changeRender(Closure $callback)
In this example, the Select field transforms into text:
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)) )
Methods for Values
Obtaining Value from Raw
The fromRaw()
method allows adding a closure to obtain the final value from the raw one.
/** * @param Closure(mixed $raw, static): mixed $callback * @return $this */ fromRaw(Closure $callback)
use App\Enums\StatusEnum;use MoonShine\UI\Fields\Enum; Enum::make('Status') ->attach(StatusEnum::class) ->fromRaw(fn(string $raw, Enum $ctx) => StatusEnum::tryFrom($raw))
Obtaining Raw Value
The modifyRawValue()
method allows adding a closure to obtain a raw value.
/** * @param Closure(mixed $raw, static): mixed $callback * @return $this */modifyRawValue(Closure $callback)
use App\Enums\StatusEnum;use MoonShine\UI\Fields\Enum; Enum::make('Status') ->attach(StatusEnum::class) ->modifyRawValue(fn(StatusEnum $raw, Enum $ctx) => $raw->value))
Editing in Preview Mode
Editing in preview mode is available for Text
, Number
, Checkbox
, Select
, Date
fields.
For editing fields in preview mode, such as in a table or any other IterableComponent
, there are the following methods.
updateOnPreview
The updateOnPreview()
method allows editing a field in preview mode. After making changes (onChange event), the value of the field will be saved for the specific item.
public function updateOnPreview( ?Closure $url = null, ?ResourceContract $resource = null, mixed $condition = null, array $events = [],)
-
$url
- (optional) request url, -
$resource
- (optional) resource containing updateOnPreview, -
$condition
- (optional) condition for setting the field to updateOnPreview mode, -
$events
- (optional) triggers AlpineJS events after a successful request.
Parameters are not mandatory but should be provided if the field is outside a resource or if you want to specify a completely custom endpoint (then the resource is not needed)
Text::make('Name')->updateOnPreview()
withUpdateRow
withUpdateRow()
works similarly to updateOnPreview()
, but can completely update the row in the table without reloading the page.
public function withUpdateRow(string $component)
-
$component
- the name of the component that contains this row.
Text::make('Name')->withUpdateRow('index-table-post-resource')
withUpdateRow()
can use all parameters from updateOnPreview()
, for example, to change the request url; they need to be called together.
Text::make('Name')->updateOnPreview(url: '/my/url')->withUpdateRow()
updateInPopover
The updateInPopover()
method works similarly to the withUpdateRow()
method, but now all values for editing appear in a separate window.
public function updateInPopover(string $component)
-
$component
- the name of the component that contains this row.
Text::make('Name')->updateInPopover('index-table-post-resource')
The methods updateOnPreview
, withUpdateRow
, and updateInPopover
create the necessary endpoints and pass them to the setUpdateOnPreviewUrl()
method, which works with onChangeUrl()
Assets
To add assets to the field, you can use the addAssets()
method.
Text::make('Name') ->addAssets([ new Css(Vite::asset('resources/css/text-field.css')) ]),
If you are implementing your custom field, you can declare the asset set in it in two ways.
- Through the
assets()
method:
/** * @return list<AssetElementContract> */protected function assets(): array{ return [ Js::make('/js/custom.js'), Css::make('/css/styles.css') ];}
- Through the
booted()
method:
protected function booted(): void{ parent::booted(); $this->getAssetManager() ->add(Css::make('/css/app.css')) ->append(Js::make('/js/app.js'));}
Macroable Trait
All fields have access to the Illuminate\Support\Traits\Macroable
trait with the mixin
and macro
methods. You can use this trait to extend the functionality of fields by adding new features without the need for inheritance.
Field::macro('myMethod', fn() => /*implementation*/) Text::make()->myMethod()
or
Field::mixin(new MyNewMethods())
Reactivity
The reactive()
method allows reactively changing fields.
reactive( ?Closure $callback = null, bool $lazy = false, int $debounce = 0, int $throttle = 0,)
-
$callback
- callback function, -
$lazy
- delayed function call, -
$debounce
- time between function calls (ms.), -
$throttle
- function call interval (ms.).
Fields that support reactivity: Text, Number, Checkbox, Select, Date, and their descendants.
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() ])
In this example, the slug field is created based on the title. The slug will be generated during the input process.
A reactive field can change the state of other fields but does not change its own state!
To change the state of the field initiating reactivity, it is convenient to use the parameters of the callback
function.
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() ); ); })
Dynamic Display
To change the display of fields depending on the values of other fields in real-time, without reloading the page and making server requests, the showWhen
and showWhenDate
methods are used.
showWhen Method
The showWhen
method allows setting a display condition for a field based on the value of another field.
public function showWhen( string $column, mixed $operator = null, mixed $value = null): static
-
$column
- the name of the field on which the display depends, -
$operator
- comparison operator (optional), -
$value
- value for comparison.
Text::make('Name') ->showWhen('category_id', 1)
In this example, the field "Name" will only be displayed if the value of the field "category_id" is equal to 1.
If only two parameters are passed to the showWhen
function, the '='
operator is used by default.
Text::make('Name') ->showWhen('category_id', 'in', [1, 2, 3])
In this example, the field "Name" will only be displayed if the value of the field "category_id" is equal to 1, 2, or 3.
showWhenDate Method
The showWhenDate
method allows setting a display condition for a field based on the value of a date-type field. The logic for working with dates has been separated into a specific method due to the specifics of converting and comparing date and datetime types on both the backend and frontend.
public function showWhenDate( string $column, mixed $operator = null, mixed $value = null): static
-
$column
- the name of the date field on which the display depends, -
$operator
- comparison operator (optional), -
$value
- date value for comparison.
Text::make('Content') ->showWhenDate('created_at', '>', '2024-09-15 10:00')
In this example, the field "Content" will only be displayed if the value of the field "created_at" is greater than '2024-09-15 10:00'.
If only two parameters are passed to the showWhenDate
function, the '='
operator is used by default.
You can use any date format that can be recognized by the strtotime()
function.
Nested Fields
The showWhen
and showWhenDate
methods support working with nested fields, such as working with the Json
field. Point notation is used to access nested fields.
Text::make('Parts') ->showWhen('attributes.1.size', '!=', 2)
In this example, the field "Parts" will only be displayed if the value of the nested field "size" in the second element of the array "attributes" is not equal to 2.
showWhen also works with nested Json
fields:
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'), ])]),
In this example, the entire column Parts
inside attributes
and the entire column Width
inside attributes.[n].settings
will only be displayed if the value of the field category_id
is equal to 3.
Multiple Conditions
The showWhen
and showWhenDate
methods can be called multiple times for the same field, allowing the specification of several display conditions.
BelongsTo::make('Category', 'category', resource: CategoryResource::class) ->showWhenDate('created_at', '>', '2024-08-05 10:00') ->showWhenDate('created_at', '<', '2024-08-05 19:00')
In this example, the field "Category" will only be displayed if the value of the field "created_at" is within the range between '2024-08-05 10:00' and '2024-08-05 19:00'.
When using multiple conditions, they are combined logically with "AND". The field will be displayed only if all specified conditions are fulfilled.
Supported Operators
-
=
-
!=
-
>
-
<
-
>=
-
<=
-
in
-
not in
The in
operator checks if the value is in the array.
The not in
operator checks if the value is not in the array.
Custom Field
You can create a custom field with your view and logic and use it in the MoonShine administration panel. To do this, use the command:
php artisan moonshine:field