diff --git a/config/fields/structure.php b/config/fields/structure.php index 622507ee39..4e09e9b2e6 100644 --- a/config/fields/structure.php +++ b/config/fields/structure.php @@ -141,7 +141,7 @@ } $column['type'] ??= $field['type']; - $column['label'] ??= $field['label'] ?? $name; + $column['label'] ??= $field['label'] ?? Str::ucfirst($name); $column['label'] = I18n::translate($column['label'], $column['label']); $columns[$name] = $column; diff --git a/src/Form/Field.php b/src/Form/Field.php index 35725ef834..ebaf3150b0 100644 --- a/src/Form/Field.php +++ b/src/Form/Field.php @@ -3,12 +3,9 @@ namespace Kirby\Form; use Closure; -use Kirby\Cms\HasSiblings; use Kirby\Exception\InvalidArgumentException; use Kirby\Toolkit\A; use Kirby\Toolkit\Component; -use Kirby\Toolkit\I18n; -use Kirby\Toolkit\Str; /** * Form Field object that takes a Vue component style @@ -23,22 +20,15 @@ */ class Field extends Component { - /** - * @use \Kirby\Cms\HasSiblings<\Kirby\Form\Fields> - */ - use HasSiblings; - use Mixin\Api; + use Mixin\Common; + use Mixin\Endpoints; use Mixin\Model; + use Mixin\Siblings; use Mixin\Translatable; use Mixin\Validation; use Mixin\When; use Mixin\Value; - /** - * Parent collection with all fields of the current form - */ - public Fields $siblings; - /** * Registry for all component mixins */ @@ -81,8 +71,22 @@ public function __construct( parent::__construct($type, $attrs); - // set the siblings collection - $this->siblings = $siblings ?? new Fields([$this]); + $this->setSiblings($attrs['siblings'] ?? null); + } + + /** + * Returns field api routes + */ + public function api(): array + { + if ( + isset($this->options['api']) === true && + $this->options['api'] instanceof Closure + ) { + return $this->options['api']->call($this); + } + + return []; } /** @@ -95,38 +99,38 @@ public static function defaults(): array /** * Optional text that will be shown after the input */ - 'after' => function ($after = null) { - return I18n::translate($after, $after); + 'after' => function (array|string|null $after = null) { + return $this->i18n($after); }, /** * Sets the focus on this field when the form loads. Only the first field with this label gets */ - 'autofocus' => function (bool|null $autofocus = null): bool { - return $autofocus ?? false; + 'autofocus' => function (bool $autofocus = false): bool { + return $autofocus; }, /** * Optional text that will be shown before the input */ - 'before' => function ($before = null) { - return I18n::translate($before, $before); + 'before' => function (array|string|null $before = null) { + return $this->i18n($before); }, /** * Default value for the field, which will be used when a page/file/user is created */ - 'default' => function ($default = null) { + 'default' => function (mixed $default = null) { return $default; }, /** * If `true`, the field is no longer editable and will not be saved */ - 'disabled' => function (bool|null $disabled = null): bool { - return $disabled ?? false; + 'disabled' => function (bool $disabled = false): bool { + return $disabled; }, /** * Optional help text below the field */ - 'help' => function ($help = null) { - return I18n::translate($help, $help); + 'help' => function (array|string|null $help = null) { + return $this->i18n($help); }, /** * Optional icon that will be shown at the end of the field @@ -137,20 +141,20 @@ public static function defaults(): array /** * The field label can be set as string or associative array with translations */ - 'label' => function ($label = null) { - return I18n::translate($label, $label); + 'label' => function (array|string|null $label = null) { + return $this->i18n($label); }, /** * Optional placeholder value that will be shown when the field is empty */ - 'placeholder' => function ($placeholder = null) { - return I18n::translate($placeholder, $placeholder); + 'placeholder' => function (array|string|null $placeholder = null) { + return $this->i18n($placeholder); }, /** * If `true`, the field has to be filled in correctly to be saved. */ - 'required' => function (bool|null $required = null): bool { - return $required ?? false; + 'required' => function (bool $required = false): bool { + return $required; }, /** * If `false`, the field will be disabled in non-default languages and cannot be translated. This is only relevant in multi-language setups. @@ -161,62 +165,18 @@ public static function defaults(): array /** * Conditions when the field will be shown (since 3.1.0) */ - 'when' => function ($when = null) { + 'when' => function (array|null $when = null) { return $when; }, /** * The width of the field in the field grid. Available widths: `1/1`, `1/2`, `1/3`, `1/4`, `2/3`, `3/4` */ - 'width' => function (string $width = '1/1') { + 'width' => function (string|null $width = null) { return $width; }, 'value' => function ($value = null) { return $value; } - ], - 'computed' => [ - 'after' => function () { - /** @var \Kirby\Form\Field $this */ - if ($this->after !== null) { - return $this->model()->toString($this->after); - } - }, - 'before' => function () { - /** @var \Kirby\Form\Field $this */ - if ($this->before !== null) { - return $this->model()->toString($this->before); - } - }, - 'default' => function () { - /** @var \Kirby\Form\Field $this */ - if ($this->default === null) { - return; - } - - if (is_string($this->default) === false) { - return $this->default; - } - - return $this->model()->toString($this->default); - }, - 'help' => function () { - /** @var \Kirby\Form\Field $this */ - if ($this->help) { - $help = $this->model()->toSafeString($this->help); - $help = $this->kirby()->kirbytext($help); - return $help; - } - }, - 'label' => function () { - /** @var \Kirby\Form\Field $this */ - return $this->model()->toString($this->label ?? Str::ucfirst($this->name)); - }, - 'placeholder' => function () { - /** @var \Kirby\Form\Field $this */ - if ($this->placeholder !== null) { - return $this->model()->toString($this->placeholder); - } - } ] ]; } @@ -281,7 +241,7 @@ public function fill(mixed $value): static $this->applyProp('value', $this->options['props']['value'] ?? $value); // reevaluate the computed props - $this->applyComputed($this->options['computed']); + $this->applyComputed($this->options['computed'] ?? []); // reset the errors cache $this->errors = null; @@ -289,22 +249,6 @@ public function fill(mixed $value): static return $this; } - /** - * @deprecated 5.0.0 Use `::siblings() instead - */ - public function formFields(): Fields - { - return $this->siblings; - } - - /** - * Checks if the field is disabled - */ - public function isDisabled(): bool - { - return $this->disabled === true; - } - /** * Checks if the field is hidden */ @@ -313,14 +257,6 @@ public function isHidden(): bool return ($this->options['hidden'] ?? false) === true; } - /** - * Checks if the field is required - */ - public function isRequired(): bool - { - return $this->required ?? false; - } - /** * Checks if the field is saveable */ @@ -329,46 +265,6 @@ public function isSaveable(): bool return ($this->options['save'] ?? true) !== false; } - /** - * Returns field api routes - */ - public function routes(): array - { - if ( - isset($this->options['api']) === true && - $this->options['api'] instanceof Closure - ) { - return $this->options['api']->call($this); - } - - return []; - } - - /** - * Checks if the field is saveable - * @deprecated 5.0.0 Use `::isSaveable()` instead - */ - public function save(): bool - { - return $this->isSaveable(); - } - - /** - * Parent collection with all fields of the current form - */ - public function siblings(): Fields - { - return $this->siblings; - } - - /** - * Returns all sibling fields for the HasSiblings trait - */ - protected function siblingsCollection(): Fields - { - return $this->siblings; - } - /** * Converts the field to a plain array */ diff --git a/src/Form/Field/BlocksField.php b/src/Form/Field/BlocksField.php index dbadae0042..be597ae368 100644 --- a/src/Form/Field/BlocksField.php +++ b/src/Form/Field/BlocksField.php @@ -46,6 +46,71 @@ public function __construct(array $params = []) $this->setPretty($params['pretty'] ?? false); } + public function api(): array + { + $field = $this; + + return [ + [ + 'pattern' => 'uuid', + 'action' => fn (): array => ['uuid' => Str::uuid()] + ], + [ + 'pattern' => 'paste', + 'method' => 'POST', + 'action' => function () use ($field): array { + $request = App::instance()->request(); + $value = BlocksCollection::parse($request->get('html')); + $blocks = BlocksCollection::factory($value); + + return $field->pasteBlocks($blocks->toArray()); + } + ], + [ + 'pattern' => 'fieldsets/(:any)', + 'method' => 'GET', + 'action' => function ( + string $fieldsetType + ) use ($field): array { + $fields = $field->fields($fieldsetType); + $defaults = $field->form($fields, [])->data(true); + $content = $field->form($fields, $defaults)->values(); + + return Block::factory([ + 'content' => $content, + 'type' => $fieldsetType + ])->toArray(); + } + ], + [ + 'pattern' => 'fieldsets/(:any)/fields/(:any)/(:all?)', + 'method' => 'ALL', + 'action' => function ( + string $fieldsetType, + string $fieldName, + string|null $path = null + ) use ($field) { + $fields = $field->fields($fieldsetType); + $field = $field->form($fields)->field($fieldName); + + $fieldApi = $this->clone([ + 'routes' => $field->api(), + 'data' => [ + ...$this->data(), + 'field' => $field + ] + ]); + + return $fieldApi->call( + $path, + $this->requestMethod(), + $this->requestData() + ); + } + ], + ]; + } + public function blocksToValues( array $blocks, string $to = 'values' @@ -174,71 +239,6 @@ public function props(): array ] + parent::props(); } - public function routes(): array - { - $field = $this; - - return [ - [ - 'pattern' => 'uuid', - 'action' => fn (): array => ['uuid' => Str::uuid()] - ], - [ - 'pattern' => 'paste', - 'method' => 'POST', - 'action' => function () use ($field): array { - $request = App::instance()->request(); - $value = BlocksCollection::parse($request->get('html')); - $blocks = BlocksCollection::factory($value); - - return $field->pasteBlocks($blocks->toArray()); - } - ], - [ - 'pattern' => 'fieldsets/(:any)', - 'method' => 'GET', - 'action' => function ( - string $fieldsetType - ) use ($field): array { - $fields = $field->fields($fieldsetType); - $defaults = $field->form($fields, [])->data(true); - $content = $field->form($fields, $defaults)->values(); - - return Block::factory([ - 'content' => $content, - 'type' => $fieldsetType - ])->toArray(); - } - ], - [ - 'pattern' => 'fieldsets/(:any)/fields/(:any)/(:all?)', - 'method' => 'ALL', - 'action' => function ( - string $fieldsetType, - string $fieldName, - string|null $path = null - ) use ($field) { - $fields = $field->fields($fieldsetType); - $field = $field->form($fields)->field($fieldName); - - $fieldApi = $this->clone([ - 'routes' => $field->api(), - 'data' => [ - ...$this->data(), - 'field' => $field - ] - ]); - - return $fieldApi->call( - $path, - $this->requestMethod(), - $this->requestData() - ); - } - ], - ]; - } - protected function setDefault(mixed $default = null): void { // set id for blocks if not exists diff --git a/src/Form/Field/LayoutField.php b/src/Form/Field/LayoutField.php index 39e89739ad..7f0ee41655 100644 --- a/src/Form/Field/LayoutField.php +++ b/src/Form/Field/LayoutField.php @@ -30,6 +30,74 @@ public function __construct(array $params) parent::__construct($params); } + public function api(): array + { + $field = $this; + $routes = parent::api(); + + $routes[] = [ + 'pattern' => 'layout', + 'method' => 'POST', + 'action' => function () use ($field): array { + $request = App::instance()->request(); + + $input = $request->get('attrs') ?? []; + $defaults = $field->attrsForm($input)->data(true); + $attrs = $field->attrsForm($defaults)->values(); + $columns = $request->get('columns') ?? ['1/1']; + + return Layout::factory([ + 'attrs' => $attrs, + 'columns' => array_map(fn ($width) => [ + 'blocks' => [], + 'id' => Str::uuid(), + 'width' => $width, + ], $columns) + ])->toArray(); + }, + ]; + + $routes[] = [ + 'pattern' => 'layout/paste', + 'method' => 'POST', + 'action' => function () use ($field): array { + $request = App::instance()->request(); + $value = Layouts::parse($request->get('json')); + $layouts = Layouts::factory($value); + + return $field->pasteLayouts($layouts->toArray()); + } + ]; + + $routes[] = [ + 'pattern' => 'fields/(:any)/(:all?)', + 'method' => 'ALL', + 'action' => function ( + string $fieldName, + string|null $path = null + ) use ($field): array { + $form = $field->attrsForm(); + $field = $form->field($fieldName); + + $fieldApi = $this->clone([ + 'routes' => $field->api(), + 'data' => [ + ...$this->data(), + 'field' => $field + ] + ]); + + return $fieldApi->call( + $path, + $this->requestMethod(), + $this->requestData() + ); + } + ]; + + return $routes; + } + public function fill(mixed $value = null): void { $value = Data::decode($value, type: 'json', fail: false); @@ -133,74 +201,6 @@ public function props(): array ]; } - public function routes(): array - { - $field = $this; - $routes = parent::routes(); - - $routes[] = [ - 'pattern' => 'layout', - 'method' => 'POST', - 'action' => function () use ($field): array { - $request = App::instance()->request(); - - $input = $request->get('attrs') ?? []; - $defaults = $field->attrsForm($input)->data(true); - $attrs = $field->attrsForm($defaults)->values(); - $columns = $request->get('columns') ?? ['1/1']; - - return Layout::factory([ - 'attrs' => $attrs, - 'columns' => array_map(fn ($width) => [ - 'blocks' => [], - 'id' => Str::uuid(), - 'width' => $width, - ], $columns) - ])->toArray(); - }, - ]; - - $routes[] = [ - 'pattern' => 'layout/paste', - 'method' => 'POST', - 'action' => function () use ($field): array { - $request = App::instance()->request(); - $value = Layouts::parse($request->get('json')); - $layouts = Layouts::factory($value); - - return $field->pasteLayouts($layouts->toArray()); - } - ]; - - $routes[] = [ - 'pattern' => 'fields/(:any)/(:all?)', - 'method' => 'ALL', - 'action' => function ( - string $fieldName, - string|null $path = null - ) use ($field): array { - $form = $field->attrsForm(); - $field = $form->field($fieldName); - - $fieldApi = $this->clone([ - 'routes' => $field->api(), - 'data' => [ - ...$this->data(), - 'field' => $field - ] - ]); - - return $fieldApi->call( - $path, - $this->requestMethod(), - $this->requestData() - ); - } - ]; - - return $routes; - } - public function selector(): array|null { return $this->selector; diff --git a/src/Form/FieldClass.php b/src/Form/FieldClass.php index 896ebc42e7..abf530a43c 100644 --- a/src/Form/FieldClass.php +++ b/src/Form/FieldClass.php @@ -2,10 +2,6 @@ namespace Kirby\Form; -use Kirby\Cms\HasSiblings; -use Kirby\Toolkit\I18n; -use Kirby\Toolkit\Str; - /** * Abstract field class to be used instead * of functional field components for more @@ -19,32 +15,15 @@ */ abstract class FieldClass { - /** - * @use \Kirby\Cms\HasSiblings<\Kirby\Form\Fields> - */ - use HasSiblings; - use Mixin\Api; + use Mixin\Common; + use Mixin\Endpoints; use Mixin\Model; + use Mixin\Siblings; use Mixin\Translatable; use Mixin\Validation; use Mixin\Value; use Mixin\When; - protected string|null $after; - protected bool $autofocus; - protected string|null $before; - protected mixed $default; - protected bool $disabled; - protected string|null $help; - protected string|null $icon; - protected string|null $label; - protected string|null $name; - protected string|null $placeholder; - protected bool $required; - public Fields $siblings; - protected mixed $value = null; - protected string|null $width; - public function __construct( protected array $params = [] ) { @@ -80,45 +59,6 @@ public function __call(string $param, array $args): mixed return $this->params[$param] ?? null; } - public function after(): string|null - { - return $this->stringTemplate($this->after); - } - - public function autofocus(): bool - { - return $this->autofocus; - } - - public function before(): string|null - { - return $this->stringTemplate($this->before); - } - - /** - * Returns optional dialog routes for the field - */ - public function dialogs(): array - { - return []; - } - - /** - * If `true`, the field is no longer editable and will not be saved - */ - public function disabled(): bool - { - return $this->disabled; - } - - /** - * Returns optional drawer routes for the field - */ - public function drawers(): array - { - return []; - } - /** * Sets a new value for the field */ @@ -128,84 +68,6 @@ public function fill(mixed $value = null): void $this->errors = null; } - /** - * @deprecated 5.0.0 Use `::siblings() instead - */ - public function formFields(): Fields - { - return $this->siblings; - } - - /** - * Optional help text below the field - */ - public function help(): string|null - { - if (empty($this->help) === false) { - $help = $this->stringTemplate($this->help); - $help = $this->kirby()->kirbytext($help); - return $help; - } - - return null; - } - - protected function i18n(string|array|null $param = null): string|null - { - return empty($param) === false ? I18n::translate($param, $param) : null; - } - - /** - * Optional icon that will be shown at the end of the field - */ - public function icon(): string|null - { - return $this->icon; - } - - public function id(): string - { - return $this->name(); - } - - public function isDisabled(): bool - { - return $this->disabled; - } - - public function isHidden(): bool - { - return false; - } - - public function isRequired(): bool - { - return $this->required; - } - - public function isSaveable(): bool - { - return true; - } - - /** - * The field label can be set as string or associative array with translations - */ - public function label(): string - { - return $this->stringTemplate( - $this->label ?? Str::ucfirst($this->name()) - ); - } - - /** - * Returns the field name - */ - public function name(): string - { - return $this->name ?? $this->type(); - } - /** * Returns all original params for the field */ @@ -214,14 +76,6 @@ public function params(): array return $this->params; } - /** - * Optional placeholder value that will be shown when the field is empty - */ - public function placeholder(): string|null - { - return $this->stringTemplate($this->placeholder); - } - /** * Define the props that will be sent to * the Vue component @@ -249,23 +103,6 @@ public function props(): array ]; } - /** - * If `true`, the field has to be filled in correctly to be saved. - */ - public function required(): bool - { - return $this->required; - } - - /** - * Checks if the field is saveable - * @deprecated 5.0.0 Use `::isSaveable()` instead - */ - public function save(): bool - { - return $this->isSaveable(); - } - protected function setAfter(array|string|null $after = null): void { $this->after = $this->i18n($after); @@ -321,39 +158,11 @@ protected function setRequired(bool $required = false): void $this->required = $required; } - protected function setSiblings(Fields|null $siblings = null): void - { - $this->siblings = $siblings ?? new Fields([$this]); - } - - /** - * Setter for the field width - */ protected function setWidth(string|null $width = null): void { $this->width = $width; } - /** - * Returns all sibling fields for the HasSiblings trait - */ - protected function siblingsCollection(): Fields - { - return $this->siblings; - } - - /** - * Parses a string template in the given value - */ - protected function stringTemplate(string|null $string = null): string|null - { - if ($string !== null) { - return $this->model->toString($string); - } - - return null; - } - /** * Converts the field to a plain array */ @@ -373,13 +182,4 @@ public function type(): string { return lcfirst(basename(str_replace(['\\', 'Field'], ['/', ''], static::class))); } - - /** - * Returns the width of the field in - * the Panel grid - */ - public function width(): string - { - return $this->width ?? '1/1'; - } } diff --git a/src/Form/Mixin/Api.php b/src/Form/Mixin/Api.php deleted file mode 100644 index d6482497c2..0000000000 --- a/src/Form/Mixin/Api.php +++ /dev/null @@ -1,19 +0,0 @@ -routes(); - } - - /** - * Routes for the field API - */ - public function routes(): array - { - return []; - } -} diff --git a/src/Form/Mixin/Common.php b/src/Form/Mixin/Common.php new file mode 100644 index 0000000000..cffc53eae4 --- /dev/null +++ b/src/Form/Mixin/Common.php @@ -0,0 +1,174 @@ +stringTemplate($this->after); + } + + /** + * Sets the focus on this field when the form loads. Only the first field with this label gets focused + */ + public function autofocus(): bool + { + return $this->autofocus; + } + + /** + * Optional text that will be shown before the input + */ + public function before(): string|null + { + return $this->stringTemplate($this->before); + } + + /** + * @deprecated 5.0.0 Use `::isDisabled` instead + */ + public function disabled(): bool + { + return $this->isDisabled(); + } + + /** + * Optional help text below the field + */ + public function help(): string|null + { + if (empty($this->help) === false) { + return $this->kirby()->kirbytext( + $this->stringTemplate($this->help, safe: true) + ); + } + + return null; + } + + /** + * Translate field parameters + */ + protected function i18n(string|array|null $param = null): string|null + { + return empty($param) === false ? I18n::translate($param, $param) : null; + } + + /** + * Optional icon that will be shown at the end of the field + */ + public function icon(): string|null + { + return $this->icon; + } + + /** + * The field id is used in fields collections. The name is used as id by default. + */ + public function id(): string + { + return $this->name(); + } + + /** + * If `true`, the field is no longer editable and will not be saved + */ + public function isDisabled(): bool + { + return $this->disabled; + } + + /** + * Checks if the field is hidden + */ + public function isHidden(): bool + { + return false; + } + + /** + * If `true`, the field has to be filled in correctly to be saved. + */ + public function isRequired(): bool + { + return $this->required; + } + + /** + * Checks if the field is saveable + */ + public function isSaveable(): bool + { + return true; + } + + /** + * The field label can be set as string or associative array with translations + */ + public function label(): string|null + { + return $this->stringTemplate( + $this->label ?? Str::ucfirst($this->name()) + ); + } + + /** + * Returns the field name + */ + public function name(): string + { + return $this->name ?? $this->type(); + } + + /** + * Optional placeholder value that will be shown when the field is empty + */ + public function placeholder(): string|null + { + return $this->stringTemplate($this->placeholder); + } + + /** + * @deprecated 5.0.0 Use `::isRequired` instead + */ + public function required(): bool + { + return $this->isRequired(); + } + + /** + * Checks if the field is saveable + * @deprecated 5.0.0 Use `::isSaveable()` instead + */ + public function save(): bool + { + return $this->isSaveable(); + } + + /** + * The width of the field in the field grid. Available widths: `1/1`, `1/2`, `1/3`, `1/4`, `2/3`, `3/4` + */ + public function width(): string + { + return $this->width ?? '1/1'; + } +} diff --git a/src/Form/Mixin/Endpoints.php b/src/Form/Mixin/Endpoints.php new file mode 100644 index 0000000000..15f7c8617f --- /dev/null +++ b/src/Form/Mixin/Endpoints.php @@ -0,0 +1,38 @@ +api(); + } +} diff --git a/src/Form/Mixin/Model.php b/src/Form/Mixin/Model.php index 69b4c1957e..018b1fee66 100644 --- a/src/Form/Mixin/Model.php +++ b/src/Form/Mixin/Model.php @@ -32,4 +32,18 @@ protected function setModel(ModelWithContent|null $model = null): void { $this->model = $model ?? App::instance()->site(); } + + /** + * Parses a string template in the given value + */ + protected function stringTemplate( + string|null $string = null, + bool $safe = false + ): string|null { + if ($string !== null) { + return $safe === true ? $this->model->toSafeString($string) : $this->model->toString($string); + } + + return null; + } } diff --git a/src/Form/Mixin/Siblings.php b/src/Form/Mixin/Siblings.php new file mode 100644 index 0000000000..bc70fb9879 --- /dev/null +++ b/src/Form/Mixin/Siblings.php @@ -0,0 +1,47 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier + * @license https://opensource.org/licenses/MIT + */ +trait Siblings +{ + /** + * @use \Kirby\Cms\HasSiblings<\Kirby\Form\Fields> + */ + use HasSiblings; + + /** + * Parent collection with all fields of the current form + */ + public Fields $siblings; + + /** + * @deprecated 5.0.0 Use `::siblings() instead + */ + public function formFields(): Fields + { + return $this->siblings; + } + + protected function setSiblings(Fields|null $siblings = null): void + { + $this->siblings = $siblings ?? new Fields([$this]); + } + + /** + * Returns all sibling fields for the HasSiblings trait + */ + protected function siblingsCollection(): Fields + { + return $this->siblings; + } +} diff --git a/src/Form/Mixin/Value.php b/src/Form/Mixin/Value.php index df03fb1e2a..0c8983f281 100644 --- a/src/Form/Mixin/Value.php +++ b/src/Form/Mixin/Value.php @@ -11,6 +11,9 @@ */ trait Value { + protected mixed $default; + protected mixed $value = null; + /** * @deprecated 5.0.0 Use `::toStoredValue()` instead */ diff --git a/src/Toolkit/Component.php b/src/Toolkit/Component.php index 54f9bb6ab0..fa22bf61cc 100644 --- a/src/Toolkit/Component.php +++ b/src/Toolkit/Component.php @@ -286,7 +286,18 @@ public function toArray(): array return $closure->call($this); } - $array = [...$this->attrs, ...$this->props, ...$this->computed]; + $props = []; + + // lazy-load all properties + foreach ($this->props as $key => $value) { + $props[$key] = $this->$key(); + } + + $array = [ + ...$this->attrs, + ...$props, + ...$this->computed + ]; ksort($array); diff --git a/tests/Form/FieldClassTest.php b/tests/Form/FieldClassTest.php index f819850d40..726bf649bf 100644 --- a/tests/Form/FieldClassTest.php +++ b/tests/Form/FieldClassTest.php @@ -7,7 +7,7 @@ class FieldWithApiRoutes extends FieldClass { - public function routes(): array + public function api(): array { return FieldClassTest::apiRoutes(); } diff --git a/tests/Form/Fields/BlocksFieldTest.php b/tests/Form/Fields/BlocksFieldTest.php index 9e7275079b..05db672a2b 100644 --- a/tests/Form/Fields/BlocksFieldTest.php +++ b/tests/Form/Fields/BlocksFieldTest.php @@ -10,6 +10,83 @@ class BlocksFieldTest extends TestCase { + public function testApi() + { + $field = $this->field('blocks'); + + $routes = $field->api(); + + $this->assertIsArray($routes); + $this->assertCount(4, $routes); + } + + public function testApiUUID() + { + $field = $this->field('blocks'); + $route = $field->api()[0]; + + $response = $route['action'](); + + $this->assertIsArray($response); + $this->assertArrayHasKey('uuid', $response); + } + + public function testApiPaste() + { + $this->app = $this->app->clone([ + 'request' => [ + 'query' => [ + 'html' => '

Test

' + ] + ] + ]); + + $field = $this->field('blocks'); + $route = $field->api()[1]; + + $response = $route['action'](); + + $this->assertCount(1, $response); + $this->assertSame(['text' => '

Test

'], $response[0]['content']); + $this->assertFalse($response[0]['isHidden']); + $this->assertSame('text', $response[0]['type']); + } + + public function testApiPasteFieldsets() + { + $this->app = $this->app->clone([ + 'request' => [ + 'query' => [ + 'html' => '

Hello World

Test

Sincerely
' + ] + ] + ]); + + $field = $this->field('blocks', ['fieldsets' => ['heading']]); + $route = $field->api()[1]; + + $response = $route['action'](); + + $this->assertCount(2, $response); + $this->assertSame(['level' => 'h1', 'text' => 'Hello World'], $response[0]['content']); + $this->assertSame('heading', $response[0]['type']); + $this->assertSame(['level' => 'h6', 'text' => 'Sincerely'], $response[1]['content']); + $this->assertSame('heading', $response[1]['type']); + } + + public function testApiFieldset() + { + $field = $this->field('blocks'); + $route = $field->api()[2]; + + $response = $route['action']('text'); + + $this->assertSame(['text' => ''], $response['content']); + $this->assertArrayHasKey('id', $response); + $this->assertFalse($response['isHidden']); + $this->assertSame('text', $response['type']); + } + public function testDefaultProps() { $field = $this->field('blocks', []); @@ -203,83 +280,6 @@ public function testRequiredValid() $this->assertTrue($field->isValid()); } - public function testRoutes() - { - $field = $this->field('blocks'); - - $routes = $field->routes(); - - $this->assertIsArray($routes); - $this->assertCount(4, $routes); - } - - public function testRouteUUID() - { - $field = $this->field('blocks'); - $route = $field->routes()[0]; - - $response = $route['action'](); - - $this->assertIsArray($response); - $this->assertArrayHasKey('uuid', $response); - } - - public function testRoutePaste() - { - $this->app = $this->app->clone([ - 'request' => [ - 'query' => [ - 'html' => '

Test

' - ] - ] - ]); - - $field = $this->field('blocks'); - $route = $field->routes()[1]; - - $response = $route['action'](); - - $this->assertCount(1, $response); - $this->assertSame(['text' => '

Test

'], $response[0]['content']); - $this->assertFalse($response[0]['isHidden']); - $this->assertSame('text', $response[0]['type']); - } - - public function testRoutePasteFieldsets() - { - $this->app = $this->app->clone([ - 'request' => [ - 'query' => [ - 'html' => '

Hello World

Test

Sincerely
' - ] - ] - ]); - - $field = $this->field('blocks', ['fieldsets' => ['heading']]); - $route = $field->routes()[1]; - - $response = $route['action'](); - - $this->assertCount(2, $response); - $this->assertSame(['level' => 'h1', 'text' => 'Hello World'], $response[0]['content']); - $this->assertSame('heading', $response[0]['type']); - $this->assertSame(['level' => 'h6', 'text' => 'Sincerely'], $response[1]['content']); - $this->assertSame('heading', $response[1]['type']); - } - - public function testRouteFieldset() - { - $field = $this->field('blocks'); - $route = $field->routes()[2]; - - $response = $route['action']('text'); - - $this->assertSame(['text' => ''], $response['content']); - $this->assertArrayHasKey('id', $response); - $this->assertFalse($response['isHidden']); - $this->assertSame('text', $response['type']); - } - public function testToStoredValue() { $value = [ diff --git a/tests/Form/Fields/LayoutFieldTest.php b/tests/Form/Fields/LayoutFieldTest.php index 9e0f1199ff..5f9d308cba 100644 --- a/tests/Form/Fields/LayoutFieldTest.php +++ b/tests/Form/Fields/LayoutFieldTest.php @@ -7,6 +7,16 @@ class LayoutFieldTest extends TestCase { + public function testApi() + { + $field = $this->field('layout'); + + $routes = $field->api(); + + $this->assertIsArray($routes); + $this->assertCount(7, $routes); + } + public function testDefaultProps() { $field = $this->field('layout', []); @@ -119,16 +129,6 @@ public function testProps() $this->assertSame([['1/1']], $props['layouts']); } - public function testRoutes() - { - $field = $this->field('layout'); - - $routes = $field->routes(); - - $this->assertIsArray($routes); - $this->assertCount(7, $routes); - } - public function testToStoredValue() { $value = [