Skip to content

Commit d31d481

Browse files
committed
feat: Validation
Issue #14
1 parent f9090d4 commit d31d481

File tree

2 files changed

+100
-17
lines changed

2 files changed

+100
-17
lines changed

README.md

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,21 @@ Layouts::make('Content')
3636
->addLayout('Banner section', 'banner', [
3737
Text::make('Title'),
3838
Image::make('Banner image', 'thumbnail'),
39-
]),
39+
], validation: ['title' => 'required']),
4040
```
4141
#### Adding layouts
4242

4343
Layouts can be added using the following method on your Layouts fields:
4444

4545
```php
46-
addLayout(string $title, string $name, iterable $fields, ?int $limit = null)
46+
addLayout(string $title, string $name, iterable $fields, ?int $limit = null, iterable $headingAdditionalFields = null, array $validation = [])
4747
```
4848
1. The `$title` parameter allows you to specify the name of a group of fields that will be displayed in the form.
4949
2. The `$name` parameter is used to store the chosen layout in the field's value.
5050
3. The `$fields` parameter accepts an array of fields that will be used to populate a group of fields in the form.
5151
4. `$limit` allows you to set the max number of groups in the field.
52+
5. `$headingAdditionalFields` components in header
53+
6. `$validation` validation rules.
5254

5355
#### Adding cast
5456

@@ -91,3 +93,35 @@ Layouts::make('Content')
9193
])
9294
->searchable()
9395
```
96+
97+
98+
#### Validation
99+
100+
```php
101+
use MoonShine\UI\Fields\Email;Layouts::make('Content')
102+
->addLayout('Info section', 'info', [
103+
Email::make('Email')
104+
], validation: ['email' => ['required', 'email']])
105+
```
106+
107+
```php
108+
use MoonShine\UI\Fields\Email;Layouts::make('Content')
109+
->addLayout('Info section', 'info', [
110+
Email::make('Email')
111+
]),
112+
->addLayout('Additionally section', 'additionally', [
113+
Text::make('Title')
114+
])
115+
->validation(['info' => ['email' => 'required'], 'additionally' => ['title' => 'required']])
116+
```
117+
118+
```php
119+
use MoonShine\UI\Fields\Email;Layouts::make('Content')
120+
->addLayout('Info section', 'info', [
121+
Email::make('Email')
122+
], validation: ['email' => ['email']]),
123+
->addLayout('Additionally section', 'additionally', [
124+
Text::make('Title')
125+
])
126+
->validation(['info' => ['email' => ['required']], 'additionally' => ['title' => 'required']])
127+
```

src/Fields/Layouts.php

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
use Closure;
88
use Illuminate\Contracts\View\View;
99
use Illuminate\Support\Collection;
10+
use Illuminate\Support\Facades\Validator;
11+
use Illuminate\Validation\ValidationException;
1012
use MoonShine\AssetManager\Js;
1113
use MoonShine\Contracts\Core\HasComponentsContract;
1214
use MoonShine\Contracts\Core\PageContract;
@@ -50,6 +52,8 @@ final class Layouts extends Field
5052

5153
private ?PageContract $page = null;
5254

55+
private array $rules = [];
56+
5357
protected function assets(): array
5458
{
5559
return [
@@ -63,15 +67,20 @@ public function addLayout(
6367
iterable $fields,
6468
?int $limit = null,
6569
?iterable $headingAdditionalFields = null,
70+
array $validation = [],
6671
): self {
6772
$this->layouts[] = new Layout(
6873
$title,
6974
$name,
7075
$fields,
7176
$limit,
72-
$headingAdditionalFields
77+
$headingAdditionalFields,
7378
);
7479

80+
if($validation !== []) {
81+
$this->rules[$name] = $validation;
82+
}
83+
7584
return $this;
7685
}
7786

@@ -101,7 +110,7 @@ public function getLayoutButtons(): array
101110
->map(
102111
fn (LayoutContract $layout) => Link::make('#', $layout->title())
103112
->icon('plus')
104-
->customAttributes(['@click.prevent' => "add(`{$layout->name()}`);closeDropdown()"])
113+
->customAttributes(['@click.prevent' => "add(`{$layout->name()}`);closeDropdown()"]),
105114
)
106115
->toArray();
107116
}
@@ -119,7 +128,7 @@ public function getFilledLayouts(): LayoutCollection
119128
$this->getData()->getOriginal(),
120129
$this->getColumn(),
121130
$values,
122-
[]
131+
[],
123132
);
124133
}
125134

@@ -133,17 +142,17 @@ public function getFilledLayouts(): LayoutCollection
133142

134143
$layout = clone $layout->when(
135144
$this->disableSort,
136-
fn (Layout $l): Layout => $l->disableSort()
145+
fn (Layout $l): Layout => $l->disableSort(),
137146
)
138147
->when(
139148
$this->isPreviewMode(),
140-
fn (Layout $l): Layout => $l->forcePreview()
149+
fn (Layout $l): Layout => $l->forcePreview(),
141150
)
142151
->setKey($data->getKey());
143152

144153
$fields = $this->fillClonedRecursively(
145154
$layout->fields(),
146-
$data->getValues()
155+
$data->getValues(),
147156
);
148157

149158
$layout
@@ -152,14 +161,14 @@ public function getFilledLayouts(): LayoutCollection
152161
->prepend(
153162
Hidden::make('_layout')
154163
->customAttributes(['class' => '_layout-value'])
155-
->setValue($data->getName())
164+
->setValue($data->getName()),
156165
)
157166
->prepareAttributes()
158167
->prepareReindexNames($this);
159168

160169
$fields = $this->fillClonedRecursively(
161170
$layout->getHeadingAdditionalFields(),
162-
$data->getValues()
171+
$data->getValues(),
163172
);
164173

165174
$layout
@@ -176,13 +185,13 @@ private function fillClonedRecursively(ComponentsContract|Collection $collection
176185
return $collection->map(function (mixed $item) use ($data) {
177186
if ($item instanceof HasComponentsContract) {
178187
$item = (clone $item)->setComponents(
179-
$this->fillClonedRecursively($item->getComponents(), $data)->toArray()
188+
$this->fillClonedRecursively($item->getComponents(), $data)->toArray(),
180189
);
181190
}
182191

183192
if ($item instanceof HasFieldsContract) {
184193
$item = (clone $item)->fields(
185-
$this->fillClonedRecursively($item->getFields(), $data)->toArray()
194+
$this->fillClonedRecursively($item->getFields(), $data)->toArray(),
186195
);
187196
}
188197

@@ -289,6 +298,13 @@ protected function resolvePreview(): View|string
289298
->render();
290299
}
291300

301+
public function validation(array $rules): self
302+
{
303+
$this->rules = array_merge_recursive($this->rules, $rules);
304+
305+
return $this;
306+
}
307+
292308
protected function resolveOnApply(): ?Closure
293309
{
294310
return function ($item) {
@@ -308,20 +324,20 @@ protected function resolveOnApply(): ?Closure
308324
function (Field $field) use ($value, $index, &$applyValues): void {
309325
$field->appendRequestKeyPrefix(
310326
"{$this->getColumn()}.$index",
311-
$this->getRequestKeyPrefix()
327+
$this->getRequestKeyPrefix(),
312328
);
313329

314330
$apply = $field->apply(
315331
fn ($data): mixed => data_set($data, $field->getColumn(), $value[$field->getColumn()] ?? ''),
316-
$value
332+
$value,
317333
);
318334

319335
data_set(
320336
$applyValues,
321337
$field->getColumn(),
322-
data_get($apply, $field->getColumn())
338+
data_get($apply, $field->getColumn()),
323339
);
324-
}
340+
},
325341
);
326342

327343
return [
@@ -342,6 +358,39 @@ function (Field $field) use ($value, $index, &$applyValues): void {
342358
*/
343359
protected function resolveBeforeApply(mixed $data): mixed
344360
{
361+
if($this->rules !== []) {
362+
$value = $this->getRequestValue();
363+
364+
if(!is_array($value)) {
365+
$value = [];
366+
}
367+
368+
$value = Collection::make($value)->mapToGroups(function ($v) {
369+
return [$v['_layout'] => $v];
370+
});
371+
372+
$rules = [];
373+
$attributes = [];
374+
375+
foreach ($this->rules as $layoutName => $rule) {
376+
$layout = $this->getLayouts()->findByName($layoutName);
377+
378+
if(\is_null($layout)) {
379+
continue;
380+
}
381+
382+
foreach ($rule as $fieldName => $args) {
383+
$field = $layout->fields()->onlyFields()->findByColumn($fieldName);
384+
$column = $field?->getLabel() ?? $fieldName;
385+
386+
$rules["$layoutName.*.$fieldName"] = $args;
387+
$attributes["$layoutName.*.$fieldName"] = "{$layout->title()}(:position) {$column}";
388+
}
389+
}
390+
391+
Validator::validate($value->toArray(), $rules, attributes: $attributes);
392+
}
393+
345394
return $this->resolveCallback($data, function (Field $field, mixed $value): void {
346395
$field->beforeApply($value);
347396
});
@@ -386,7 +435,7 @@ protected function resolveCallback(mixed $data, Closure $callback, bool $fill =
386435
->each(function (Field $field) use ($data, $index, $value, $callback, $fill): void {
387436
$field->appendRequestKeyPrefix(
388437
"{$this->getColumn()}.$index",
389-
$this->getRequestKeyPrefix()
438+
$this->getRequestKeyPrefix(),
390439
);
391440

392441
$field->when($fill, fn (Field $f): Field => $f->resolveFill($data));

0 commit comments

Comments
 (0)