diff --git a/resources/views/table/actions/action.blade.php b/resources/views/actions/action.blade.php similarity index 100% rename from resources/views/table/actions/action.blade.php rename to resources/views/actions/action.blade.php diff --git a/resources/views/table/actions/form.blade.php b/resources/views/actions/form.blade.php similarity index 82% rename from resources/views/table/actions/form.blade.php rename to resources/views/actions/form.blade.php index 7e00a040f..3d3b9208a 100644 --- a/resources/views/table/actions/form.blade.php +++ b/resources/views/actions/form.blade.php @@ -1,6 +1,5 @@ -
+ @csrf - @method($method)
@foreach($fields as $field) {!! $field !!} diff --git a/resources/views/table/columns/actions.blade.php b/resources/views/columns/actions.blade.php similarity index 100% rename from resources/views/table/columns/actions.blade.php rename to resources/views/columns/actions.blade.php diff --git a/resources/views/table/cells/row-actions.blade.php b/resources/views/columns/cells/row-actions.blade.php similarity index 100% rename from resources/views/table/cells/row-actions.blade.php rename to resources/views/columns/cells/row-actions.blade.php diff --git a/resources/views/table/cells/row-select.blade.php b/resources/views/columns/cells/row-select.blade.php similarity index 100% rename from resources/views/table/cells/row-select.blade.php rename to resources/views/columns/cells/row-select.blade.php diff --git a/resources/views/table/cells/text.blade.php b/resources/views/columns/cells/text.blade.php similarity index 100% rename from resources/views/table/cells/text.blade.php rename to resources/views/columns/cells/text.blade.php diff --git a/resources/views/table/columns/column.blade.php b/resources/views/columns/column.blade.php similarity index 100% rename from resources/views/table/columns/column.blade.php rename to resources/views/columns/column.blade.php diff --git a/resources/views/table/columns/select-all.blade.php b/resources/views/columns/select-all.blade.php similarity index 100% rename from resources/views/table/columns/select-all.blade.php rename to resources/views/columns/select-all.blade.php diff --git a/resources/views/form/fields/boolean.blade.php b/resources/views/fields/boolean.blade.php similarity index 100% rename from resources/views/form/fields/boolean.blade.php rename to resources/views/fields/boolean.blade.php diff --git a/resources/views/form/fields/checkbox-option.blade.php b/resources/views/fields/checkbox-option.blade.php similarity index 100% rename from resources/views/form/fields/checkbox-option.blade.php rename to resources/views/fields/checkbox-option.blade.php diff --git a/resources/views/form/fields/checkbox.blade.php b/resources/views/fields/checkbox.blade.php similarity index 100% rename from resources/views/form/fields/checkbox.blade.php rename to resources/views/fields/checkbox.blade.php diff --git a/resources/views/form/fields/dropdown-option.blade.php b/resources/views/fields/dropdown-option.blade.php similarity index 100% rename from resources/views/form/fields/dropdown-option.blade.php rename to resources/views/fields/dropdown-option.blade.php diff --git a/resources/views/form/fields/dropdown.blade.php b/resources/views/fields/dropdown.blade.php similarity index 100% rename from resources/views/form/fields/dropdown.blade.php rename to resources/views/fields/dropdown.blade.php diff --git a/resources/views/form/fields/editor.blade.php b/resources/views/fields/editor.blade.php similarity index 74% rename from resources/views/form/fields/editor.blade.php rename to resources/views/fields/editor.blade.php index fd5696d2a..1502c2ddb 100644 --- a/resources/views/form/fields/editor.blade.php +++ b/resources/views/fields/editor.blade.php @@ -11,16 +11,16 @@ class="form-group--row form-group--row:vertical-start"
diff --git a/resources/views/form/fields/editor/align.blade.php b/resources/views/fields/editor/align.blade.php similarity index 100% rename from resources/views/form/fields/editor/align.blade.php rename to resources/views/fields/editor/align.blade.php diff --git a/resources/views/form/fields/editor/blocks.blade.php b/resources/views/fields/editor/blocks.blade.php similarity index 100% rename from resources/views/form/fields/editor/blocks.blade.php rename to resources/views/fields/editor/blocks.blade.php diff --git a/resources/views/form/fields/editor/format.blade.php b/resources/views/fields/editor/format.blade.php similarity index 100% rename from resources/views/form/fields/editor/format.blade.php rename to resources/views/fields/editor/format.blade.php diff --git a/resources/views/form/fields/editor/heading.blade.php b/resources/views/fields/editor/heading.blade.php similarity index 100% rename from resources/views/form/fields/editor/heading.blade.php rename to resources/views/fields/editor/heading.blade.php diff --git a/resources/views/form/fields/editor/history.blade.php b/resources/views/fields/editor/history.blade.php similarity index 100% rename from resources/views/form/fields/editor/history.blade.php rename to resources/views/fields/editor/history.blade.php diff --git a/resources/views/form/fields/editor/link.blade.php b/resources/views/fields/editor/link.blade.php similarity index 100% rename from resources/views/form/fields/editor/link.blade.php rename to resources/views/fields/editor/link.blade.php diff --git a/resources/views/form/fields/editor/list.blade.php b/resources/views/fields/editor/list.blade.php similarity index 100% rename from resources/views/form/fields/editor/list.blade.php rename to resources/views/fields/editor/list.blade.php diff --git a/resources/views/form/fields/editor/media.blade.php b/resources/views/fields/editor/media.blade.php similarity index 100% rename from resources/views/form/fields/editor/media.blade.php rename to resources/views/fields/editor/media.blade.php diff --git a/resources/views/form/fields/fieldset.blade.php b/resources/views/fields/fieldset.blade.php similarity index 100% rename from resources/views/form/fields/fieldset.blade.php rename to resources/views/fields/fieldset.blade.php diff --git a/resources/views/form/fields/file-option.blade.php b/resources/views/fields/file-option.blade.php similarity index 100% rename from resources/views/form/fields/file-option.blade.php rename to resources/views/fields/file-option.blade.php diff --git a/resources/views/form/fields/file.blade.php b/resources/views/fields/file.blade.php similarity index 100% rename from resources/views/form/fields/file.blade.php rename to resources/views/fields/file.blade.php diff --git a/resources/views/form/fields/hidden.blade.php b/resources/views/fields/hidden.blade.php similarity index 100% rename from resources/views/form/fields/hidden.blade.php rename to resources/views/fields/hidden.blade.php diff --git a/resources/views/form/fields/image.blade.php b/resources/views/fields/image.blade.php similarity index 100% rename from resources/views/form/fields/image.blade.php rename to resources/views/fields/image.blade.php diff --git a/resources/views/form/fields/input.blade.php b/resources/views/fields/input.blade.php similarity index 100% rename from resources/views/form/fields/input.blade.php rename to resources/views/fields/input.blade.php diff --git a/resources/views/form/fields/media.blade.php b/resources/views/fields/media.blade.php similarity index 100% rename from resources/views/form/fields/media.blade.php rename to resources/views/fields/media.blade.php diff --git a/resources/views/form/fields/optgroup.blade.php b/resources/views/fields/optgroup.blade.php similarity index 100% rename from resources/views/form/fields/optgroup.blade.php rename to resources/views/fields/optgroup.blade.php diff --git a/resources/views/form/fields/option.blade.php b/resources/views/fields/option.blade.php similarity index 100% rename from resources/views/form/fields/option.blade.php rename to resources/views/fields/option.blade.php diff --git a/resources/views/form/panel.blade.php b/resources/views/fields/panel.blade.php similarity index 100% rename from resources/views/form/panel.blade.php rename to resources/views/fields/panel.blade.php diff --git a/resources/views/form/fields/pending-file-option.blade.php b/resources/views/fields/pending-file-option.blade.php similarity index 100% rename from resources/views/form/fields/pending-file-option.blade.php rename to resources/views/fields/pending-file-option.blade.php diff --git a/resources/views/form/fields/range.blade.php b/resources/views/fields/range.blade.php similarity index 100% rename from resources/views/form/fields/range.blade.php rename to resources/views/fields/range.blade.php diff --git a/resources/views/form/fields/repeater-option.blade.php b/resources/views/fields/repeater-option.blade.php similarity index 100% rename from resources/views/form/fields/repeater-option.blade.php rename to resources/views/fields/repeater-option.blade.php diff --git a/resources/views/form/fields/repeater.blade.php b/resources/views/fields/repeater.blade.php similarity index 97% rename from resources/views/form/fields/repeater.blade.php rename to resources/views/fields/repeater.blade.php index c0baf6b37..9e52cc500 100644 --- a/resources/views/form/fields/repeater.blade.php +++ b/resources/views/fields/repeater.blade.php @@ -9,7 +9,7 @@ +
- @if($form->model->exists) + @if($model->exists)
@csrf diff --git a/resources/views/resources/index.blade.php b/resources/views/resources/index.blade.php index 4b1175abb..9a42d0d27 100644 --- a/resources/views/resources/index.blade.php +++ b/resources/views/resources/index.blade.php @@ -23,5 +23,5 @@
@endif - {!! $table !!} + @include('root::resources.table.table') @endsection diff --git a/resources/views/table/actions.blade.php b/resources/views/resources/table/actions.blade.php similarity index 100% rename from resources/views/table/actions.blade.php rename to resources/views/resources/table/actions.blade.php diff --git a/resources/views/table/filters/form.blade.php b/resources/views/resources/table/filters.blade.php similarity index 71% rename from resources/views/table/filters/form.blade.php rename to resources/views/resources/table/filters.blade.php index 767c953dd..0bbe93e8b 100644 --- a/resources/views/table/filters/form.blade.php +++ b/resources/views/resources/table/filters.blade.php @@ -1,8 +1,5 @@ - - @if($search) - {!! $search !!} - @endif - @if(! empty($fields)) + + @if(! empty($filters))
- @foreach($fields as $field) - {!! $field !!} + @foreach($filters as $filter) + {!! $filter !!} @endforeach
- +
diff --git a/resources/views/table/pagination.blade.php b/resources/views/resources/table/pagination.blade.php similarity index 100% rename from resources/views/table/pagination.blade.php rename to resources/views/resources/table/pagination.blade.php diff --git a/resources/views/table/table.blade.php b/resources/views/resources/table/table.blade.php similarity index 67% rename from resources/views/table/table.blade.php rename to resources/views/resources/table/table.blade.php index bcbd73998..51e74bb65 100644 --- a/resources/views/table/table.blade.php +++ b/resources/views/resources/table/table.blade.php @@ -1,7 +1,7 @@
-

{{ $title }}

- {!! $form !!} +

{{ $title }}

+ @include('root::resources.table.filters')
@@ -16,9 +16,9 @@ - @foreach($items as $item) + @foreach($data as $row) - @foreach($item['cells'] as $cell) + @foreach($row['cells'] as $cell) {!! $cell !!} @endforeach @@ -29,30 +29,30 @@
diff --git a/routes/api.php b/routes/api.php index 073ffe148..f24ac0c70 100644 --- a/routes/api.php +++ b/routes/api.php @@ -8,4 +8,4 @@ Route::apiResource('notifications', NotificationsController::class)->except(['store']); // Resource Fields -Route::any('/{resource}/form/fields/{field}', ResourceFieldController::class)->where('field', '.*'); +Route::any('/{resource}/fields/{field}', ResourceFieldController::class)->where('field', '.*'); diff --git a/src/Table/Actions/Action.php b/src/Actions/Action.php similarity index 66% rename from src/Table/Actions/Action.php rename to src/Actions/Action.php index 59d60602e..3d52b73aa 100644 --- a/src/Table/Actions/Action.php +++ b/src/Actions/Action.php @@ -1,25 +1,26 @@ table = $table; - } + protected ?string $apiUri = null; /** * Handle the action. @@ -85,7 +83,45 @@ public function getName(): string */ public function getModalKey(): string { - return sprintf('%s-action-%s', $this->table->getAttribute('id'), $this->getKey()); + return sprintf('action-%s', $this->getKey()); + } + + /** + * Set the API URI. + */ + public function setApiUri(string $apiUri): static + { + $this->apiUri = $apiUri; + + return $this; + } + + /** + * Get the API URI. + */ + public function getApiUri(): ?string + { + return $this->apiUri; + } + + /** + * Set the Eloquent query. + */ + public function setQuery(Builder $query): static + { + $this->query = $query; + + return $this; + } + + /** + * Handle the callback for the field resolution. + */ + protected function resolveField(Request $request, Field $field): void + { + $field->setForm($this); + $field->setApiUri(sprintf('/%s/fields/%s', $this->getApiUri(), $field->getUriKey())); + $field->setAttribute('form', $this->getKey()); } /** @@ -129,13 +165,11 @@ public function isConfirmable(): bool */ public function perform(Request $request): Response { - $query = $this->table->resolveFilteredQuery($request); - - $this->toForm($request, $query->getModel())->handle($request); + $this->validateFormRequest($request); $this->handle( $request, - $request->boolean('all') ? $query->get() : $query->findMany($request->input('models', [])) + $request->boolean('all') ? $this->query->get() : $this->query->findMany($request->input('models', [])) ); return Redirect::back()->with( @@ -144,14 +178,6 @@ public function perform(Request $request): Response ); } - /** - * Convert the object to a form using the request and the model. - */ - public function toForm(Request $request, Model $model): Form - { - return new ActionForm($model, ''); - } - /** * {@inheritdoc} */ @@ -165,9 +191,13 @@ public function toArray(): array 'destructive' => $this->isDestructive(), 'key' => $this->getKey(), 'name' => $this->getName(), - 'url' => null, + 'url' => $this->getApiUri(), 'modalKey' => $this->getModalKey(), - 'form' => $this->toForm($request, $this->table->getQuery()->getModel()), + 'fields' => $this->resolveFields($request) + ->each(function (Field $field): void { + $field->setModel($this->query->getModel()); + }) + ->all(), ]; }) ); @@ -180,8 +210,6 @@ public function toArray(): array */ public function toResponse($request): Response { - return match ($request->method()) { - default => $this->perform($request), - }; + return $this->perform($request); } } diff --git a/src/Actions/Actions.php b/src/Actions/Actions.php new file mode 100644 index 000000000..ba0ecac3f --- /dev/null +++ b/src/Actions/Actions.php @@ -0,0 +1,32 @@ +actions = new Collection($actions); + } + + /** + * Handle the dynamic method call. + */ + public function __call($method, $parameters): mixed + { + return $this->forwardCallTo($this->actions, $method, $parameters); + } +} diff --git a/src/Table/Cells/Cell.php b/src/Columns/Cells/Cell.php similarity index 89% rename from src/Table/Cells/Cell.php rename to src/Columns/Cells/Cell.php index b3ab797a1..047be3974 100644 --- a/src/Table/Cells/Cell.php +++ b/src/Columns/Cells/Cell.php @@ -1,9 +1,9 @@ label = $label; - $this->table = $table; $this->modelAttribute = $modelAttribute ??= Str::of($label)->lower()->snake()->value(); } - /** - * Create a new cell instance. - */ - abstract public function newCell(Model $model): Cell; - /** * Get the model attribute. */ @@ -141,6 +129,14 @@ public function format(Closure $callback): static return $this; } + /** + * Make a new cell instance. + */ + public function newCell(Model $model): Cell + { + return new Cell($this, $model); + } + /** * Convert the column to a cell. */ diff --git a/src/Table/Columns/Columns.php b/src/Columns/Columns.php similarity index 56% rename from src/Table/Columns/Columns.php rename to src/Columns/Columns.php index a28721a45..e32fb3c60 100644 --- a/src/Table/Columns/Columns.php +++ b/src/Columns/Columns.php @@ -1,9 +1,9 @@ table = $table; $this->columns = new Collection($columns); } /** - * Make a new column instance. - */ - public function column(string $column, string $label, string $key = null): mixed - { - $instance = new $column($this->table, $label, $key); - - $this->columns->push($instance); - - return $instance; - } - - /** - * Make a new ID column. + * Register the given columns. */ - public function id(string $label = 'ID', string $key = null): Text + public function register(array|Column $columns): static { - return $this->column(Text::class, $label, $key); - } + foreach (Arr::wrap($columns) as $column) { + $this->columns->push($column); + } - /** - * Make a new text column. - */ - public function text(string $label, string $key = null): Text - { - return $this->column(Text::class, $label, $key); + return $this; } /** diff --git a/src/Table/Columns/RowActions.php b/src/Columns/RowActions.php similarity index 71% rename from src/Table/Columns/RowActions.php rename to src/Columns/RowActions.php index f4071a6ae..cb2e0ca3a 100644 --- a/src/Table/Columns/RowActions.php +++ b/src/Columns/RowActions.php @@ -1,8 +1,8 @@ newCell($model)->value(function (Request $request, Model $model): string { - return $this->table->resolveRowUrl($request, $model); + return ''; }); } } diff --git a/src/Table/Columns/RowSelect.php b/src/Columns/RowSelect.php similarity index 64% rename from src/Table/Columns/RowSelect.php rename to src/Columns/RowSelect.php index 50f6e632b..40072a2ac 100644 --- a/src/Table/Columns/RowSelect.php +++ b/src/Columns/RowSelect.php @@ -1,8 +1,8 @@ setAttribute('multiple', true); $this->name($this->modelAttribute.'[]'); @@ -35,19 +35,25 @@ public function __construct(Form $form, string $label, string $modelAttribute = /** * {@inheritdoc} */ - public function getRelation(): EloquentRelation + public function setForm(Form $form): static { - $relation = parent::getRelation(); + if (! is_null($this->fields)) { + $this->fields->each(function (Field $field) use ($form): void { + $field->setForm($form); + }); + } - return $relation->withPivot($relation->newPivot()->getKeyName()); + return parent::setForm($form); } /** - * Create a new fields collection. + * {@inheritdoc} */ - public function newFieldsCollection(): Fields + public function getRelation(): EloquentRelation { - return new Fields($this->form); + $relation = parent::getRelation(); + + return $relation->withPivot($relation->newPivot()->getKeyName()); } /** @@ -72,10 +78,12 @@ public function withPivotFields(Closure $callback): static $this->withFields($callback); $this->pivotFieldsResolver = function (Model $related) use ($callback): Fields { - $fields = new Fields($this->form); + $fields = new Fields(); App::call(static function (Request $request) use ($callback, $fields): void { - call_user_func_array($callback, [$request, $fields]); + $fields->register( + Arr::wrap(call_user_func_array($callback, [$request])) + ); }); $fields->each(function (Field $field) use ($related): void { @@ -87,6 +95,7 @@ public function withPivotFields(Closure $callback): static ); $field->setModelAttribute($attribute) + ->setForm($this->form) ->name($attribute) ->id($attribute) ->value(function () use ($related, $key): mixed { diff --git a/src/Form/Fields/Boolean.php b/src/Fields/Boolean.php similarity index 75% rename from src/Form/Fields/Boolean.php rename to src/Fields/Boolean.php index 7ef5fc889..6d3f857ea 100644 --- a/src/Form/Fields/Boolean.php +++ b/src/Fields/Boolean.php @@ -1,8 +1,8 @@ type('checkbox'); } diff --git a/src/Form/Fields/Checkbox.php b/src/Fields/Checkbox.php similarity index 69% rename from src/Form/Fields/Checkbox.php rename to src/Fields/Checkbox.php index b1774daea..1f23e81ed 100644 --- a/src/Form/Fields/Checkbox.php +++ b/src/Fields/Checkbox.php @@ -1,15 +1,15 @@ type('color'); + } +} diff --git a/src/Form/Fields/Date.php b/src/Fields/Date.php similarity index 87% rename from src/Form/Fields/Date.php rename to src/Fields/Date.php index 67c0e8b64..d5f3a3963 100644 --- a/src/Form/Fields/Date.php +++ b/src/Fields/Date.php @@ -1,8 +1,7 @@ type('date')->step(1); } diff --git a/src/Form/Fields/Dropdown.php b/src/Fields/Dropdown.php similarity index 80% rename from src/Form/Fields/Dropdown.php rename to src/Fields/Dropdown.php index fcfda0123..4ec1f1651 100644 --- a/src/Form/Fields/Dropdown.php +++ b/src/Fields/Dropdown.php @@ -1,9 +1,8 @@ setAttribute('class', 'form-control combobox__control'); } diff --git a/src/Form/Fields/Editor.php b/src/Fields/Editor.php similarity index 76% rename from src/Form/Fields/Editor.php rename to src/Fields/Editor.php index 58558e888..145680157 100644 --- a/src/Form/Fields/Editor.php +++ b/src/Fields/Editor.php @@ -1,9 +1,9 @@ config = Config::get('root.editor', []); $this->height('350px'); } + /** + * {@inheritdoc} + */ + public function setForm(Form $form): static + { + if (! is_null($this->media)) { + $this->media->setForm($form); + } + + return parent::setForm($form); + } + /** * {@inheritdoc} */ @@ -80,13 +92,6 @@ public function getConfig(): array return $this->config; } - /** - * Create a new fields collection. - */ - protected function newFieldsCollection(): Fields - { - return new Fields($this->form); - } /** * Configure the media field. @@ -94,13 +99,13 @@ protected function newFieldsCollection(): Fields public function withMedia(Closure $callback = null): static { if (is_null($this->fields)) { - $this->fields = $this->newFieldsCollection(); + $this->fields = new Fields(); } if (is_null($this->media)) { $this->media = $this->newMediaField(); - $this->fields->push($this->media); + $this->fields->register($this->media); } if (! is_null($callback)) { @@ -123,15 +128,16 @@ public function getMedia(): ?Media */ protected function newMediaField(): Media { - return new class($this->form, $this->getModelAttribute()) extends Media + return new class($this->getModelAttribute()) extends Media { - public function __construct(Form $form, string $modelAttribute) + public function __construct(string $modelAttribute) { - parent::__construct($form, __('Media'), $modelAttribute.'-media', static function (): MorphToMany { + parent::__construct(__('Media'), $modelAttribute.'-media', static function (): MorphToMany { return new MorphToMany( Medium::proxy()->newQuery(), new class() extends Model { + // }, 'media', 'root_mediables', @@ -142,7 +148,7 @@ public function __construct(Form $form, string $modelAttribute) ); }); - $this->template = 'root::form.fields.editor.media'; + $this->template = 'root::fields.editor.media'; $this->multiple(); } diff --git a/src/Fields/Email.php b/src/Fields/Email.php new file mode 100644 index 000000000..2426aa31e --- /dev/null +++ b/src/Fields/Email.php @@ -0,0 +1,16 @@ +type('email'); + } +} diff --git a/src/Form/Fields/Field.php b/src/Fields/Field.php similarity index 94% rename from src/Form/Fields/Field.php rename to src/Fields/Field.php index ed89d03db..c12ece94e 100644 --- a/src/Form/Fields/Field.php +++ b/src/Fields/Field.php @@ -1,9 +1,9 @@ modelAttribute = $modelAttribute ?: Str::of($label)->lower()->snake()->value(); - $this->form = $form; $this->label($label); $this->name($this->modelAttribute); $this->id($this->modelAttribute); $this->setAttribute('class', 'form-control'); - $this->setAttribute('form', $form->getAttribute('id')); + } + + /** + * Set the form instance. + */ + public function setForm(Form $form): static + { + $this->form = $form; + + return $this; } /** @@ -106,7 +114,7 @@ public function __construct(Form $form, string $label, string $modelAttribute = */ public function getModel(): Model { - return $this->model ?: $this->form->model; + return $this->model ?: new class() extends Model {}; } /** @@ -386,7 +394,7 @@ public function updateRules(array|Closure $rules): static */ public function invalid(Request $request): bool { - return $this->form->errors($request)->has($this->getValidationKey()); + return $this->form?->errors($request)?->has($this->getValidationKey()) ?: false; } /** @@ -394,7 +402,7 @@ public function invalid(Request $request): bool */ public function error(Request $request): ?string { - return $this->form->errors($request)->first($this->getValidationKey()) ?: null; + return $this->form?->errors($request)?->first($this->getValidationKey()) ?: null; } /** diff --git a/src/Fields/Fields.php b/src/Fields/Fields.php new file mode 100644 index 000000000..56f10de71 --- /dev/null +++ b/src/Fields/Fields.php @@ -0,0 +1,68 @@ +fields = new Collection($fields); + } + + /** + * Register the given fields. + */ + public function register(array|Field $fields): static + { + foreach (Arr::wrap($fields) as $field) { + $this->fields->push($field); + } + + return $this; + } + + /** + * Persist the request value on the model. + */ + public function persist(Request $request): void + { + $this->fields->each(static function (Field $field) use ($request): void { + $field->persist( + $request, $field->getValueForHydrate($request) + ); + }); + } + + /** + * Map the fields to validate. + */ + public function mapToValidate(Request $request): array + { + return $this->fields->reduce(static function (array $rules, Field $field) use ($request): array { + return array_merge_recursive($rules, $field->toValidate($request)); + }, []); + } + + /** + * Handle the dynamic method call. + */ + public function __call($method, $parameters): mixed + { + return $this->forwardCallTo($this->fields, $method, $parameters); + } +} diff --git a/src/Form/Fields/Fieldset.php b/src/Fields/Fieldset.php similarity index 88% rename from src/Form/Fields/Fieldset.php rename to src/Fields/Fieldset.php index 34d458e9a..f5a33a253 100644 --- a/src/Form/Fields/Fieldset.php +++ b/src/Fields/Fieldset.php @@ -1,6 +1,6 @@ form); - } + protected string $template = 'root::fields.fieldset'; /** * Handle the callback for the field resolution. diff --git a/src/Form/Fields/File.php b/src/Fields/File.php similarity index 93% rename from src/Form/Fields/File.php rename to src/Fields/File.php index 37ae5190e..15ace9792 100644 --- a/src/Form/Fields/File.php +++ b/src/Fields/File.php @@ -1,10 +1,9 @@ type('file')->multiple(false); diff --git a/src/Form/Fields/HasMany.php b/src/Fields/HasMany.php similarity index 60% rename from src/Form/Fields/HasMany.php rename to src/Fields/HasMany.php index c495dbabd..86ee770a9 100644 --- a/src/Form/Fields/HasMany.php +++ b/src/Fields/HasMany.php @@ -1,9 +1,8 @@ setAttribute('multiple', true); } diff --git a/src/Form/Fields/HasOne.php b/src/Fields/HasOne.php similarity index 88% rename from src/Form/Fields/HasOne.php rename to src/Fields/HasOne.php index 0de50dc9e..1ed6db380 100644 --- a/src/Form/Fields/HasOne.php +++ b/src/Fields/HasOne.php @@ -1,6 +1,6 @@ type('hidden'); + } +} diff --git a/src/Form/Fields/Media.php b/src/Fields/Media.php similarity index 89% rename from src/Form/Fields/Media.php rename to src/Fields/Media.php index 411f4dc36..3200a886f 100644 --- a/src/Form/Fields/Media.php +++ b/src/Fields/Media.php @@ -1,11 +1,10 @@ form->getAttribute('id'), $this->getModelAttribute()); + return sprintf('media-field-%s', $this->getModelAttribute()); } /** @@ -143,9 +142,10 @@ public function toArray(): array public function toResponse($request): JsonResponse { return match ($request->method()) { + 'GET' => new JsonResponse($this->paginate($request)), 'POST' => $this->upload($request), 'DELETE' => new JsonResponse(['deleted' => $this->prune($request, $request->input('ids', []))]), - default => new JsonResponse($this->paginate($request)), + default => parent::toResponse($request), }; } } diff --git a/src/Form/Fields/Meta.php b/src/Fields/Meta.php similarity index 96% rename from src/Form/Fields/Meta.php rename to src/Fields/Meta.php index 1125682b9..ebdfdd41a 100644 --- a/src/Form/Fields/Meta.php +++ b/src/Fields/Meta.php @@ -1,9 +1,8 @@ metaData()->getRelated(); @@ -35,7 +34,7 @@ public function __construct(Form $form, string $label, string $modelAttribute = ->withDefault(['key' => $this->getModelAttribute()]); }; - parent::__construct($form, $label, $modelAttribute, $relation); + parent::__construct($label, $modelAttribute, $relation); $this->asText(); } diff --git a/src/Form/Fields/MorphMany.php b/src/Fields/MorphMany.php similarity index 88% rename from src/Form/Fields/MorphMany.php rename to src/Fields/MorphMany.php index 2e8443cfe..9a7885ae5 100644 --- a/src/Form/Fields/MorphMany.php +++ b/src/Fields/MorphMany.php @@ -1,6 +1,6 @@ type('number'); } diff --git a/src/Form/Fields/Options/CheckboxOption.php b/src/Fields/Options/CheckboxOption.php similarity index 88% rename from src/Form/Fields/Options/CheckboxOption.php rename to src/Fields/Options/CheckboxOption.php index 344e31cc5..9094a7d2d 100644 --- a/src/Form/Fields/Options/CheckboxOption.php +++ b/src/Fields/Options/CheckboxOption.php @@ -1,13 +1,13 @@ type('range')->step(1)->min(0)->max(100); + } +} diff --git a/src/Form/Fields/Relation.php b/src/Fields/Relation.php similarity index 93% rename from src/Form/Fields/Relation.php rename to src/Fields/Relation.php index c85782e24..ed3c8981f 100644 --- a/src/Form/Fields/Relation.php +++ b/src/Fields/Relation.php @@ -1,11 +1,10 @@ relation = $relation ?: $this->getModelAttribute(); } @@ -157,7 +156,7 @@ public function async(bool $value = true): static { $this->async = $value; - // $this->template = $value ? 'root::form.fields.dropdown' : 'root::form.fields.select'; + // $this->template = $value ? 'root::fields.dropdown' : 'root::fields.select'; return $this; } diff --git a/src/Form/Fields/Repeater.php b/src/Fields/Repeater.php similarity index 87% rename from src/Form/Fields/Repeater.php rename to src/Fields/Repeater.php index 586987961..80e823433 100644 --- a/src/Form/Fields/Repeater.php +++ b/src/Fields/Repeater.php @@ -1,13 +1,15 @@ fields)) { + $this->fields->each(function (Field $field) use ($form): void { + $field->setForm($form); + }); + } + + return parent::setForm($form); } /** * {@inheritdoc} */ - public function getOldValue(Request $request): mixed + public function getValueForHydrate(Request $request): mixed { - return array_values((array) parent::getOldValue($request)); + return array_values((array) parent::getValueForHydrate($request)); } /** - * Create a new fields collection. + * {@inheritdoc} */ - public function newFieldsCollection(): Fields + public function getOldValue(Request $request): mixed { - return new Fields($this->form); + return array_values((array) parent::getOldValue($request)); } /** @@ -102,12 +110,14 @@ protected function resolveField(Request $request, Field $field): void public function withFields(Closure $callback): static { $this->optionFieldsResolver = function (Model $tmpModel) use ($callback): Fields { - $fields = new Fields($this->form); - - $fields->hidden(__('Key'), '_key'); + $fields = new Fields([ + Hidden::make(__('Key'), '_key'), + ]); App::call(static function (Request $request) use ($callback, $fields): void { - call_user_func_array($callback, [$request, $fields]); + $fields->register( + Arr::wrap(call_user_func_array($callback, [$request])) + ); }); $fields->each(function (Field $field) use ($tmpModel): void { @@ -119,6 +129,7 @@ public function withFields(Closure $callback): static ); $field->setModelAttribute($attribute) + ->setForm($this->form) ->name($attribute) ->id($attribute) ->value(function () use ($tmpModel, $key): mixed { @@ -139,6 +150,7 @@ public function newTemporaryModel(array $attributes = []): Model { $model = new class() extends Model { + // }; return $model->forceFill(array_replace( diff --git a/src/Form/Fields/Select.php b/src/Fields/Select.php similarity index 95% rename from src/Form/Fields/Select.php rename to src/Fields/Select.php index c11a3fd2f..8815c9668 100644 --- a/src/Form/Fields/Select.php +++ b/src/Fields/Select.php @@ -1,9 +1,9 @@ type('text'); } diff --git a/src/Form/Fields/Textarea.php b/src/Fields/Textarea.php similarity index 81% rename from src/Form/Fields/Textarea.php rename to src/Fields/Textarea.php index 5060a9345..cd3a70f68 100644 --- a/src/Form/Fields/Textarea.php +++ b/src/Fields/Textarea.php @@ -1,6 +1,6 @@ table = $table; - } - /** * Apply the filter on the query. */ @@ -41,7 +27,7 @@ abstract public function apply(Request $request, Builder $query, mixed $value): /** * Convert the filter to a form field. */ - abstract public function toField(FilterForm $form): Field; + abstract public function toField(): Field; /** * Get the key. @@ -56,7 +42,7 @@ public function getKey(): string */ public function getRequestKey(): string { - return sprintf('%s:%s', $this->table->getAttribute('id'), $this->getKey()); + return $this->getKey(); } /** diff --git a/src/Table/Filters/Filters.php b/src/Filters/Filters.php similarity index 81% rename from src/Table/Filters/Filters.php rename to src/Filters/Filters.php index ef7f0ff53..b7f11d15d 100644 --- a/src/Table/Filters/Filters.php +++ b/src/Filters/Filters.php @@ -1,8 +1,7 @@ table = $table; $this->filters = new Collection($filters); } @@ -44,18 +37,6 @@ public function register(array|Filter $filters): static return $this; } - /** - * Make a new filter instance. - */ - public function filter(string $filter, ...$params): Filter - { - $instance = new $filter($this->table, ...$params); - - $this->register($instance); - - return $instance; - } - /** * Apply the filters on the query. */ diff --git a/src/Table/Filters/Search.php b/src/Filters/Search.php similarity index 80% rename from src/Table/Filters/Search.php rename to src/Filters/Search.php index 1aa6ef00f..6ae950695 100644 --- a/src/Table/Filters/Search.php +++ b/src/Filters/Search.php @@ -1,6 +1,6 @@ getName(), $this->getRequestKey()) + return SearchField::make($this->getName(), $this->getRequestKey()) ->value(function (Request $request, Model $model): ?string { return $model->getAttribute($this->getKey()); }) diff --git a/src/Filters/SearchField.php b/src/Filters/SearchField.php new file mode 100644 index 000000000..4ba1f554c --- /dev/null +++ b/src/Filters/SearchField.php @@ -0,0 +1,13 @@ +getName(), $this->getRequestKey()) + return Field::make($this->getName(), $this->getRequestKey()) ->options(App::call(function (Request $request): array { return $this->options($request); })) diff --git a/src/Table/Filters/TrashStatus.php b/src/Filters/TrashStatus.php similarity index 77% rename from src/Table/Filters/TrashStatus.php rename to src/Filters/TrashStatus.php index 960e4175c..a4b765300 100644 --- a/src/Table/Filters/TrashStatus.php +++ b/src/Filters/TrashStatus.php @@ -1,6 +1,6 @@ getValue($request) !== 'available'; + } + /** * Get the filter options. */ diff --git a/src/Form/Fields/Color.php b/src/Form/Fields/Color.php deleted file mode 100644 index 829086d4a..000000000 --- a/src/Form/Fields/Color.php +++ /dev/null @@ -1,18 +0,0 @@ -type('color'); - } -} diff --git a/src/Form/Fields/Email.php b/src/Form/Fields/Email.php deleted file mode 100644 index 602b84bb7..000000000 --- a/src/Form/Fields/Email.php +++ /dev/null @@ -1,18 +0,0 @@ -type('email'); - } -} diff --git a/src/Form/Fields/Fields.php b/src/Form/Fields/Fields.php deleted file mode 100644 index bf66dcecd..000000000 --- a/src/Form/Fields/Fields.php +++ /dev/null @@ -1,304 +0,0 @@ -form = $form; - $this->fields = new Collection($fields); - } - - /** - * Register the given fields. - */ - public function register(array|Field $fields): static - { - foreach (Arr::wrap($fields) as $field) { - $this->fields->push($field); - } - - return $this; - } - - /** - * Add a new field to the collection. - */ - public function field(string $field, string $label, string $modelAttribute = null, ...$params): Field - { - $instance = new $field($this->form, $label, $modelAttribute, ...$params); - - $this->register($instance); - - return $instance; - } - - /** - * Make a new text field. - */ - public function text(string $label, string $modelAttribute = null): Text - { - return $this->field(Text::class, $label, $modelAttribute); - } - - /** - * Make a new email field. - */ - public function email(string $label, string $modelAttribute = null): Email - { - return $this->field(Email::class, $label, $modelAttribute); - } - - /** - * Make a new textarea field. - */ - public function textarea(string $label, string $modelAttribute = null): Textarea - { - return $this->field(Textarea::class, $label, $modelAttribute); - } - - /** - * Make a new number field. - */ - public function number(string $label, string $modelAttribute = null): Number - { - return $this->field(Number::class, $label, $modelAttribute); - } - - /** - * Make a new range field. - */ - public function range(string $label, string $modelAttribute = null): Range - { - return $this->field(Range::class, $label, $modelAttribute); - } - - /** - * Make a new select field. - */ - public function select(string $label, string $modelAttribute = null): Select - { - return $this->field(Select::class, $label, $modelAttribute); - } - - /** - * Make a new boolean field. - */ - public function boolean(string $label, string $modelAttribute = null): Boolean - { - return $this->field(Boolean::class, $label, $modelAttribute); - } - - /** - * Make a new checkbox field. - */ - public function checkbox(string $label, string $modelAttribute = null): Checkbox - { - return $this->field(Checkbox::class, $label, $modelAttribute); - } - - /** - * Make a new tag field. - */ - public function tag(string $label, string $modelAttribute = null): Tag - { - return $this->field(Tag::class, $label, $modelAttribute); - } - - /** - * Make a new date field. - */ - public function date(string $label, string $modelAttribute = null): Date - { - return $this->field(Date::class, $label, $modelAttribute); - } - - /** - * Make a new radio field. - */ - public function radio(string $label, string $modelAttribute = null): Radio - { - return $this->field(Radio::class, $label, $modelAttribute); - } - - /** - * Make a new hidden field. - */ - public function hidden(string $label, string $modelAttribute = null): Hidden - { - return $this->field(Hidden::class, $label, $modelAttribute); - } - - /** - * Make a new editor field. - */ - public function editor(string $label, string $modelAttribute = null): Editor - { - return $this->field(Editor::class, $label, $modelAttribute); - } - - /** - * Make a new file field. - */ - public function file(string $label, string $modelAttribute = null, Closure|string $relation = null): File - { - return $this->field(File::class, $label, $modelAttribute, $relation); - } - - /** - * Make a new media field. - */ - public function media(string $label, string $modelAttribute = null, Closure|string $relation = null): Media - { - return $this->field(Media::class, $label, $modelAttribute, $relation); - } - - /** - * Make a new fieldset field. - */ - public function fieldset(string $label, string $modelAttribute = null): Fieldset - { - return $this->field(Fieldset::class, $label, $modelAttribute); - } - - /** - * Make a new repeater field. - */ - public function repeater(string $label, string $modelAttribute = null): Repeater - { - return $this->field(Repeater::class, $label, $modelAttribute); - } - - /** - * Make a new repeater field. - */ - public function dropdown(string $label, string $modelAttribute = null): Dropdown - { - return $this->field(Dropdown::class, $label, $modelAttribute); - } - - /** - * Make a new has one field. - */ - public function hasOne(string $label, string $modelAttribute = null, Closure|string $relation = null): HasOne - { - return $this->field(HasOne::class, $label, $modelAttribute, $relation); - } - - /** - * Make a new has many field. - */ - public function hasMany(string $label, string $modelAttribute = null, Closure|string $relation = null): HasMany - { - return $this->field(HasMany::class, $label, $modelAttribute, $relation); - } - - /** - * Make a new belongs to field. - */ - public function belongsTo(string $label, string $modelAttribute = null, Closure|string $relation = null): BelongsTo - { - return $this->field(BelongsTo::class, $label, $modelAttribute, $relation); - } - - /** - * Make a new belongs to many field. - */ - public function belongsToMany(string $label, string $modelAttribute = null, Closure|string $relation = null): BelongsToMany - { - return $this->field(BelongsToMany::class, $label, $modelAttribute, $relation); - } - - /** - * Make a new morph one field. - */ - public function morphOne(string $label, string $modelAttribute = null, Closure|string $relation = null): MorphOne - { - return $this->field(MorphOne::class, $label, $modelAttribute, $relation); - } - - /** - * Make a new morph many field. - */ - public function morphMany(string $label, string $modelAttribute = null, Closure|string $relation = null): MorphMany - { - return $this->field(MorphMany::class, $label, $modelAttribute, $relation); - } - - /** - * Make a new morph to field. - */ - public function morphTo(string $label, string $modelAttribute = null, Closure|string $relation = null): MorphTo - { - return $this->field(MorphTo::class, $label, $modelAttribute, $relation); - } - - /** - * Make a new morph to many field. - */ - public function morphToMany(string $label, string $modelAttribute = null, Closure|string $relation = null): MorphToMany - { - return $this->field(MorphToMany::class, $label, $modelAttribute, $relation); - } - - /** - * Make a new meta field. - */ - public function meta(string $label, string $modelAttribute = null, Closure|string $relation = null): Meta - { - return $this->field(Meta::class, $label, $modelAttribute, $relation); - } - - /** - * Persist the request value on the model. - */ - public function persist(Request $request): void - { - $this->fields->each(static function (Field $field) use ($request): void { - $field->persist( - $request, $field->getValueForHydrate($request) - ); - }); - } - - /** - * Map the fields to validate. - */ - public function mapToValidate(Request $request): array - { - return $this->fields->reduce(static function (array $rules, Field $field) use ($request): array { - return array_merge_recursive($rules, $field->toValidate($request)); - }, []); - } - - /** - * Handle the dynamic method call. - */ - public function __call($method, $parameters): mixed - { - return $this->forwardCallTo($this->fields, $method, $parameters); - } -} diff --git a/src/Form/Fields/Hidden.php b/src/Form/Fields/Hidden.php deleted file mode 100644 index 74885e0b3..000000000 --- a/src/Form/Fields/Hidden.php +++ /dev/null @@ -1,23 +0,0 @@ -type('hidden'); - } -} diff --git a/src/Form/Fields/Options/DropdownOption.php b/src/Form/Fields/Options/DropdownOption.php deleted file mode 100644 index acdf94e76..000000000 --- a/src/Form/Fields/Options/DropdownOption.php +++ /dev/null @@ -1,11 +0,0 @@ -type('range')->step(1)->min(0)->max(100); - } -} diff --git a/src/Form/Fields/Tag.php b/src/Form/Fields/Tag.php deleted file mode 100644 index ff3c6366f..000000000 --- a/src/Form/Fields/Tag.php +++ /dev/null @@ -1,11 +0,0 @@ -model = $model; - $this->apiUri = $apiUri; - - $this->action($action); - $this->method($model->exists ? 'PATCH' : 'POST'); - $this->id(Str::of(get_class($model))->classBasename()->lower()->append('-form')->value()); - $this->autocomplete('off'); - } - - /** - * Set the "enctype" HTML attribute. - */ - public function enctype(string $value): static - { - return $this->setAttribute('enctype', $value); - } - - /** - * Set the "autocomplete" HTML attribute. - */ - public function autocomplete(string $value): static - { - return $this->setAttribute('autocomplete', $value); - } - - /** - * Set the "method" HTML attribute. - */ - public function method(string $value): static - { - $this->method = $value; - - return $this->setAttribute('method', $value === 'GET' ? 'GET' : 'POST'); - } - - /** - * Set the "action" HTML attribute. - */ - public function action(string $value): static - { - $this->method = $value; - - return $this->setAttribute('action', $value); - } - - /** - * Handle the incoming form request. - */ - public function handle(Request $request): void - { - $this->validate($request); - - $this->resolveFields($request)->persist($request); - - $this->model->save(); - } - - /** - * Validate the incoming request. - */ - public function validate(Request $request): array - { - return $request->validateWithBag( - $this->errorBag, - $this->resolveFields($request)->mapToValidate($request) - ); - } - - /** - * Set the validation error bag. - */ - public function errorBag(string $value): static - { - $this->errorBag = $value; - - return $this; - } - - /** - * Get the errors for the form. - */ - public function errors(Request $request): MessageBag - { - if (is_null($this->errors)) { - $this->errors = $request->session()->get('errors', new ViewErrorBag())->getBag($this->errorBag); - } - - return $this->errors; - } - - /** - * Handle the callback for the field resolution. - */ - protected function resolveField(Request $request, Field $field): void - { - if (! is_null($this->apiUri)) { - $field->setApiUri(sprintf('%s/%s', $this->apiUri, $field->getUriKey())); - } - } - - /** - * Create a new fields collection. - */ - protected function newFieldsCollection(): Fields - { - return new Fields($this); - } - - /** - * Convert the form to an array. - */ - public function toArray(): array - { - return array_merge( - parent::toArray(), - App::call(function (Request $request): array { - return [ - 'errors' => $this->errors($request), - 'fields' => $this->resolveFields($request)->all(), - 'method' => $this->method, - ]; - }) - ); - } -} diff --git a/src/Http/Controllers/ResourceFieldController.php b/src/Http/Controllers/ResourceFieldController.php index 88e1f608f..afb5e99c9 100644 --- a/src/Http/Controllers/ResourceFieldController.php +++ b/src/Http/Controllers/ResourceFieldController.php @@ -18,7 +18,9 @@ public function __invoke(Request $request, Resource $resource): JsonResponse ? $resource->resolveRouteBinding($request, $request->query('model')) : $resource->getModelInstance(); - $field = $resource->toForm($request, $model)->findField( + $form = $resource->toForm($request, $model); + + $field = $resource->findField( $request, $request->path() ); diff --git a/src/Interfaces/AsForm.php b/src/Interfaces/AsForm.php deleted file mode 100644 index 94382942d..000000000 --- a/src/Interfaces/AsForm.php +++ /dev/null @@ -1,15 +0,0 @@ -query(); } + /** + * Resolve the filtered query for the given request. + */ + public function resolveFilteredQuery(Request $request): Builder + { + return $this->resolveFilters($request)->apply($request, $this->resolveQuery($request)); + } + /** * Resolve the route binding query. */ @@ -198,26 +217,68 @@ public function modelUrl(Model $model): string } /** - * Make a new form for the model. + * Handle the callback for the field resolution. */ - public function toTable(Request $request, Builder $query): Table + protected function resolveField(Request $request, Field $field): void { - return (new Table($query)) - ->rowUrl(function (Request $request, Model $model): string { - return $this->modelUrl($model); - }); + $field->setForm($this); + $field->setApiUri(sprintf('/root/api/%s/fields/%s', $this->getKey(), $field->getUriKey())); + $field->setAttribute('form', $this->getKey()); } /** - * Make a new form for the model. + * Handle the callback for the action resolution. */ - public function toForm(Request $request, Model $model): Form + protected function resolveAction(Request $request, Action $action): void { - return new Form( - $model, - $this->modelUrl($model), - sprintf('/root/api/%s/form/fields', $this->getKey()) - ); + $action->setQuery($this->resolveFilteredQuery($request)); + $action->setApiUri(sprintf('/root/api/%s/actions/%s', $this->getKey(), $action->getUriKey())); + } + + /** + * Get the per page. + */ + public function getPerPage(Request $request): ?int + { + return $request->input($this->getKey().':per_page'); + } + + /** + * Get the per page options. + */ + public function getPerPageOptions(): array + { + return Collection::make([$this->getModelInstance()->getPerPage()]) + ->merge([15, 25, 50, 100]) + ->filter() + ->unique() + ->values() + ->toArray(); + } + + /** + * Get the page name. + */ + public function getPageName(): string + { + return sprintf('%s:page', $this->getKey()); + } + + /** + * Perform the query and the pagination. + */ + public function paginate(Request $request): LengthAwarePaginator + { + return $this->resolveFilteredQuery($request) + ->latest() + ->paginate($this->getPerPage($request), ['*'], $this->getPageName()) + ->withQueryString() + ->through(function (Model $model) use ($request): array { + return [ + 'id' => $model->getKey(), + 'cells' => $this->resolveColumns($request)->mapToCells($model), + ]; + }); } /** @@ -243,8 +304,18 @@ public function toIndex(Request $request): array { return array_merge($this->toArray(), [ 'title' => $this->getName(), - 'table' => $this->toTable($request, $this->resolveQuery($request)), + 'columns' => $this->resolveColumns($request)->all(), + 'actions' => $this->resolveActions($request)->all(), + 'data' => $this->paginate($request), 'widgets' => $this->resolveWidgets($request)->all(), + 'perPageOptions' => $this->getPerPageOptions(), + 'filters' => $this->resolveFilters($request) + ->renderable() + ->map(static function (Filter $filter): Field { + return $filter->toField(); + }) + ->all(), + 'activeFilters' => $this->resolveFilters($request)->active($request)->count(), ]); } @@ -255,7 +326,14 @@ public function toCreate(Request $request): array { return array_merge($this->toArray(), [ 'title' => __('Create :model', ['model' => $this->getModelName()]), - 'form' => $this->toForm($request, $this->getModelInstance()), + 'model' => $model = $this->getModelInstance(), + 'action' => $this->getUrl(), + 'method' => 'POST', + 'fields' => $this->resolveFields($request) + ->each(function (Field $field) use ($model): void { + $field->setModel($model); + }) + ->all(), ]); } @@ -266,7 +344,14 @@ public function toEdit(Request $request, Model $model): array { return array_merge($this->toArray(), [ 'title' => '', - 'form' => $this->toForm($request, $model), + 'model' => $model, + 'action' => $this->modelUrl($model), + 'method' => 'PATCH', + 'fields' => $this->resolveFields($request) + ->each(static function (Field $field) use ($model): void { + $field->setModel($model); + }) + ->all(), ]); } } diff --git a/src/Table/Actions/ActionForm.php b/src/Table/Actions/ActionForm.php deleted file mode 100644 index 5375c5c38..000000000 --- a/src/Table/Actions/ActionForm.php +++ /dev/null @@ -1,22 +0,0 @@ -validate($request); - } -} diff --git a/src/Table/Actions/Actions.php b/src/Table/Actions/Actions.php deleted file mode 100644 index f90025b1f..000000000 --- a/src/Table/Actions/Actions.php +++ /dev/null @@ -1,62 +0,0 @@ -table = $table; - $this->actions = new Collection($actions); - } - - /** - * Make a new action instance. - */ - public function action(string $action, ...$params): Action - { - $instance = new $action($this->table, ...$params); - - $this->push($instance); - - return $instance; - } - - /** - * Register the action routes. - */ - public function registerRoutes(Router $router): void - { - $router->prefix('actions')->group(function (Router $router): void { - $this->actions->each->registerRoutes($router); - }); - } - - /** - * Handle the dynamic method call. - */ - public function __call($method, $parameters): mixed - { - return $this->forwardCallTo($this->actions, $method, $parameters); - } -} diff --git a/src/Table/Cells/Text.php b/src/Table/Cells/Text.php deleted file mode 100644 index 325437589..000000000 --- a/src/Table/Cells/Text.php +++ /dev/null @@ -1,11 +0,0 @@ -method('GET'); - $this->setAttribute('class', 'app-card__actions'); - } - - /** - * {@inheritdoc} - */ - public function handle(Request $request): void - { - // - } - - /** - * {@inheritdoc} - */ - public function toArray(): array - { - return array_merge( - parent::toArray(), - App::call(function (Request $request): array { - return [ - 'search' => $this->resolveFields($request)->first(function (Field $field): bool { - return $field instanceof SearchField; - }), - 'fields' => $this->resolveFields($request)->reject(function (Field $field): bool { - return $field instanceof SearchField; - })->all(), - ]; - } - )); - } -} diff --git a/src/Table/Filters/SearchField.php b/src/Table/Filters/SearchField.php deleted file mode 100644 index 528497ead..000000000 --- a/src/Table/Filters/SearchField.php +++ /dev/null @@ -1,13 +0,0 @@ -query = $query; - - $this->id( - Str::of(get_class($query->getModel()))->classBasename()->lower()->plural()->append('-table')->value() - ); - } - - /** - * Get the table title. - */ - public function getTitle(): string - { - return __(Str::of(get_class($this->query->getModel()))->classBasename()->headline()->plural()->value()); - } - - /** - * Get the table query. - */ - public function getQuery(): Builder - { - return $this->query; - } - - /** - * Resolve the filtered query. - */ - public function resolveFilteredQuery(Request $request): Builder - { - return $this->resolveFilters($request)->apply($request, $this->getQuery()); - } - - /** - * Get the per page. - */ - public function getPerPage(Request $request): ?int - { - return $request->input($this->getAttribute('id').':per_page'); - } - - /** - * Get the per page options. - */ - public function getPerPageOptions(): array - { - return Collection::make([$this->query->getModel()->getPerPage()]) - ->merge([15, 25, 50, 100]) - ->filter() - ->unique() - ->values() - ->toArray(); - } - - /** - * Get the page name. - */ - public function getPageName(): string - { - return sprintf('%s:page', $this->getAttribute('id')); - } - - /** - * Define the columns for the object. - */ - protected function columns(Request $request, Columns $columns): void - { - // - } - - /** - * Apply the given callback on the columns. - */ - public function withColumns(Closure $callback): static - { - $this->columnsResolver = $callback; - - return $this; - } - - /** - * Resolve the columns collection. - */ - public function resolveColumns(Request $request): Columns - { - if (is_null($this->columns)) { - $this->columns = new Columns($this); - - if ($this->resolveActions($request)->isNotEmpty()) { - $this->columns->push(new RowSelect($this, __('Select'), 'id')); - } - - $this->columns($request, $this->columns); - - if (! is_null($this->columnsResolver)) { - call_user_func_array($this->columnsResolver, [$request, $this->columns]); - } - - $this->columns->push(new RowActions($this, __('Actions'), 'id')); - - $this->columns->each(function (Column $column) use ($request): void { - $this->resolveColumn($request, $column); - }); - } - - return $this->columns; - } - - /** - * Handle the callback for the column resolution. - */ - protected function resolveColumn(Request $request, Column $column): void - { - // - } - - /** - * Define the actions for the object. - */ - protected function actions(Request $request, Actions $actions): void - { - // - } - - /** - * Set the actions resolver callback. - */ - public function withActions(Closure $callback): static - { - $this->actionsResolver = $callback; - - return $this; - } - - /** - * Resolve the actions collection. - */ - public function resolveActions(Request $request): Actions - { - if (is_null($this->actions)) { - $this->actions = new Actions($this); - - $this->actions($request, $this->actions); - - if (! is_null($this->actionsResolver)) { - call_user_func_array($this->actionsResolver, [$request, $this->actions]); - } - - $this->actions->each(function (Action $action) use ($request): void { - $this->resolveAction($request, $action); - }); - } - - return $this->actions; - } - - /** - * Handle the callback for the action resolution. - */ - protected function resolveAction(Request $request, Action $action): void - { - // - } - - /** - * Define the filters for the object. - */ - protected function filters(Request $request, Filters $filters): void - { - $filters->filter(Search::class); - // $this->filters->filter(Sort::class); - $filters->filter(TrashStatus::class); - } - - /** - * Apply the given callback on the filters. - */ - public function withFilters(Closure $callback): static - { - $this->filtersResolver = $callback; - - return $this; - } - - /** - * Resolve the filters collection. - */ - public function resolveFilters(Request $request): Filters - { - if (is_null($this->filters)) { - $this->filters = new Filters($this); - - $this->filters($request, $this->filters); - - if (! is_null($this->filtersResolver)) { - call_user_func_array($this->filtersResolver, [$request, $this->filters]); - } - - $this->filters->each(function (Filter $filter) use ($request): void { - $this->resolveFilter($request, $filter); - }); - } - - return $this->filters; - } - - /** - * Handle the callback for the filter resolution. - */ - protected function resolveFilter(Request $request, Filter $filter): void - { - // - } - - /** - * Set the row URL resolver callback. - */ - public function rowUrl(Closure $callback): static - { - $this->rowUrlResolver = $callback; - - return $this; - } - - /** - * Resolve the row URL. - */ - public function resolveRowUrl(Request $request, Model $model): string - { - if (is_null($this->rowUrlResolver)) { - return sprintf('%s/%s', $request->url(), $model->getKey()); - } - - return call_user_func_array($this->rowUrlResolver, [$request, $model]); - } - - /** - * Perform the query and the pagination. - */ - public function paginate(Request $request): LengthAwarePaginator - { - return $this->resolveFilteredQuery($request) - ->latest() - ->paginate($this->getPerPage($request), ['*'], $this->getPageName()) - ->withQueryString() - ->through(function (Model $model) use ($request): array { - return [ - 'id' => $model->getKey(), - 'cells' => $this->resolveColumns($request)->mapToCells($model), - ]; - }); - } - - /** - * Make a new form instance. - */ - public function toForm(Request $request, Model $model): FilterForm - { - $data = $this->resolveFilters($request)->mapToData($request); - - $model->forceFill($data); - - return (new FilterForm($model, $request->fullUrl())) - ->id(sprintf('%s-filters', $this->getAttribute('id'))) - ->withFields(function (Request $request, Fields $fields): void { - $this->resolveFilters($request) - ->renderable() - ->each(function (Filter $filter) use ($fields): void { - $fields->push($filter->toField($fields->form)); - }); - }) - ->setAttribute('data-active', $this->resolveFilters($request)->active($request)->count()); - } - - /** - * {@inheritdoc} - */ - public function toArray(): array - { - return array_merge( - parent::toArray(), - App::call(function (Request $request): array { - return [ - 'actions' => $this->resolveActions($request)->all(), - 'columns' => $this->resolveColumns($request)->all(), - 'filters' => $this->resolveFilters($request)->all(), - 'form' => $this->toForm($request, $this->query->getModel()), - 'items' => $this->paginate($request), - 'perPageOptions' => $this->getPerPageOptions(), - 'title' => $this->getTitle(), - ]; - }) - ); - } -} diff --git a/src/Traits/AsForm.php b/src/Traits/AsForm.php new file mode 100644 index 000000000..b52e72936 --- /dev/null +++ b/src/Traits/AsForm.php @@ -0,0 +1,58 @@ +validate($request); + + $this->resolveFields($request)->persist($request); + + $model->save(); + } + + /** + * Validate the request. + */ + public function validateFormRequest(Request $request): array + { + return $request->validateWithBag( + $this->errorBag, + $this->resolveFields($request)->mapToValidate($request) + ); + } + + /** + * Get the errors. + */ + public function errors(Request $request): MessageBag + { + if (is_null($this->errors)) { + $this->errors = $request->session()->get('errors', new ViewErrorBag())->getBag($this->errorBag); + } + + return $this->errors; + } +} diff --git a/src/Traits/ResolvesActions.php b/src/Traits/ResolvesActions.php new file mode 100644 index 000000000..cc42b1d9a --- /dev/null +++ b/src/Traits/ResolvesActions.php @@ -0,0 +1,47 @@ +actions)) { + $this->actions = new Actions($this->actions($request)); + + $this->actions->each(function (Action $action) use ($request): void { + $this->resolveAction($request, $action); + }); + } + + return $this->actions; + } + + /** + * Handle the callback for the action resolution. + */ + protected function resolveAction(Request $request, Action $action): void + { + // + } +} diff --git a/src/Traits/ResolvesColumns.php b/src/Traits/ResolvesColumns.php new file mode 100644 index 000000000..194421fbc --- /dev/null +++ b/src/Traits/ResolvesColumns.php @@ -0,0 +1,57 @@ +columns)) { + $this->columns = new Columns($this->columns($request)); + + if ($this->resolveActions($request)->isNotEmpty()) { + $this->columns->prepend(new RowSelect(__('Select'), 'id')); + } + + $this->columns->push(new RowActions(__('Actions'), 'id')); + + $this->columns->each(function (Column $column) use ($request): void { + $this->resolveColumn($request, $column); + }); + } + + return $this->columns; + } + + /** + * Handle the callback for the column resolution. + */ + protected function resolveColumn(Request $request, Column $column): void + { + // + } +} diff --git a/src/Traits/ResolvesFields.php b/src/Traits/ResolvesFields.php index cf3ff149f..7a42d5592 100644 --- a/src/Traits/ResolvesFields.php +++ b/src/Traits/ResolvesFields.php @@ -3,9 +3,10 @@ namespace Cone\Root\Traits; use Closure; -use Cone\Root\Form\Fields\Field; -use Cone\Root\Form\Fields\Fields; +use Cone\Root\Fields\Field; +use Cone\Root\Fields\Fields; use Illuminate\Http\Request; +use Illuminate\Support\Arr; trait ResolvesFields { @@ -19,17 +20,12 @@ trait ResolvesFields */ protected ?Closure $fieldsResolver = null; - /** - * Create a new fields collection. - */ - abstract protected function newFieldsCollection(): Fields; - /** * Define the fields for the object. */ - protected function fields(Request $request, Fields $fields): void + public function fields(Request $request): array { - // + return []; } /** @@ -48,12 +44,12 @@ public function withFields(Closure $callback): static public function resolveFields(Request $request): Fields { if (is_null($this->fields)) { - $this->fields = $this->newFieldsCollection(); - - $this->fields($request, $this->fields); + $this->fields = new Fields($this->fields($request)); if (! is_null($this->fieldsResolver)) { - call_user_func_array($this->fieldsResolver, [$request, $this->fields]); + $this->fields->register( + Arr::wrap(call_user_func_array($this->fieldsResolver, [$request])) + ); } $this->fields->each(function (Field $field) use ($request): void { diff --git a/src/Traits/ResolvesFilters.php b/src/Traits/ResolvesFilters.php new file mode 100644 index 000000000..918be6e06 --- /dev/null +++ b/src/Traits/ResolvesFilters.php @@ -0,0 +1,53 @@ +filters)) { + $this->filters = new Filters($this->filters($request)); + + $this->filters->each(function (Filter $filter) use ($request): void { + $this->resolveFilter($request, $filter); + }); + } + + return $this->filters; + } + + /** + * Handle the callback for the filter resolution. + */ + protected function resolveFilter(Request $request, Filter $filter): void + { + // + } +} diff --git a/src/Traits/ResolvesModelValue.php b/src/Traits/ResolvesModelValue.php index 4dfc4773f..e986391e5 100644 --- a/src/Traits/ResolvesModelValue.php +++ b/src/Traits/ResolvesModelValue.php @@ -21,7 +21,7 @@ trait ResolvesModelValue /** * Get the model. */ - abstract public function getModel(): Model; + abstract public function getModel(): ?Model; /** * Get the model attribute. diff --git a/stubs/Action.stub b/stubs/Action.stub index 6b1ec4196..91ff7e666 100644 --- a/stubs/Action.stub +++ b/stubs/Action.stub @@ -2,7 +2,7 @@ namespace {{ namespace }}; -use Cone\Root\Table\Actions\Action; +use Cone\Root\Actions\Action; use Illuminate\Database\Eloquent\Collection; use Illuminate\Http\Request; diff --git a/stubs/Field.stub b/stubs/Field.stub index f6727f98e..6e9e715c2 100644 --- a/stubs/Field.stub +++ b/stubs/Field.stub @@ -2,7 +2,7 @@ namespace {{ namespace }}; -use Cone\Root\Form\Fields\Field; +use Cone\Root\Fields\Field; class {{ class }} extends Field { diff --git a/stubs/Resource.stub b/stubs/Resource.stub index def29d755..e78736256 100644 --- a/stubs/Resource.stub +++ b/stubs/Resource.stub @@ -2,10 +2,10 @@ namespace {{ namespace }}; -use Cone\Root\Form\Fields\Fields; -use Cone\Root\Form\Form; +use Cone\Root\Fields\Fields; +use Cone\Root\Interfaces\Form; use Cone\Root\Resources\Resource; -use Cone\Root\Table\Columns\Columns; +use Cone\Root\Columns\Columns; use Cone\Root\Table\Table; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; @@ -19,34 +19,52 @@ class {{ class }} extends Resource protected string $model = {{ model }}; /** - * Define the widgets for the resource. + * Define the columns. */ - public function widgets(Request $request): array + public function columns(Request $request): array + { + return [ + // + ]; + } + + /** + * Define the fields. + */ + public function fields(Request $request): array + { + return [ + // + ]; + } + + /** + * Define the filters. + */ + public function filters(Request $request): array { - return array_merge(parent::widgets($request), [ + return [ // - ]); + ]; } /** - * Get the table instance for the resource. + * Define the actions. */ - public function toTable(Request $request, Builder $query): Table + public function actions(Request $request): array { - return parent::toTable($request, $query) - ->withColumns(static function (Request $request, Columns $columns): void { - $columns->id(); - }); + return [ + // + ]; } /** - * Get the form instance for the resource. + * Define the widgets for the resource. */ - public function toForm(Request $request, Model $model): Form + public function widgets(Request $request): array { - return parent::toForm($request, $model) - ->withFields(static function (Request $request, Fields $fields): void { - // - }); + return [ + // + ]; } } diff --git a/stubs/SelectFilter.stub b/stubs/SelectFilter.stub index d01553996..a2f3a1ee1 100644 --- a/stubs/SelectFilter.stub +++ b/stubs/SelectFilter.stub @@ -2,7 +2,7 @@ namespace {{ namespace }}; -use Cone\Root\Table\Filters\Select; +use Cone\Root\Filters\Select; use Illuminate\Database\Eloquent\Builder; use Illuminate\Http\Request; diff --git a/stubs/UserResource.stub b/stubs/UserResource.stub index 8214ce2fa..0060d291b 100644 --- a/stubs/UserResource.stub +++ b/stubs/UserResource.stub @@ -2,10 +2,10 @@ namespace App\Root\Resources; -use Cone\Root\Form\Fields\Fields; -use Cone\Root\Form\Form; +use Cone\Root\Fields\Fields; +use Cone\Root\Interfaces\Form; use Cone\Root\Resources\Resource; -use Cone\Root\Table\Columns\Columns; +use Cone\Root\Columns\Columns; use Cone\Root\Table\Table; use Illuminate\Database\Eloquent\Model; use Illuminate\Http\Request;