diff --git a/CHANGELOG.md b/CHANGELOG.md index 37a8a465542..d3922972cf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,7 @@ - Removed `CraftCms\Cms\Element\Conditions\ElementCondition::$queryParams`. ([#19563](https://github.com/craftcms/cms/pull/19563)) ### Fields +- Added a “Default Row Values” setting to Table fields. ([#3621](https://github.com/craftcms/cms/issues/3621)) - `CraftCms\Cms\Field\Contracts\FieldInterface::modifyQuery()` now accepts an `Illuminate\Database\Query\Builder` object for its `$query` argument, and has a new `CraftCms\Cms\Element\Queries\Contracts\ElementQueryInterface $elementQuery` argument, and a `void` return type. ([#19562](https://github.com/craftcms/cms/pull/19562), [#19585](https://github.com/craftcms/cms/pull/19585)) - Moved legacy relation-field settings HTML and entry-title input HTML into the Yii adapter. ([#19591](https://github.com/craftcms/cms/pull/19591)) - Fixed multiple bugs with Table fields, including new columns moving or defaulting to Checkbox, settings rows not being draggable, and element edit forms failing when the field value was null. ([#19687](https://github.com/craftcms/cms/pull/19687)) diff --git a/resources/js/modules/forms/FormRenderer.test.ts b/resources/js/modules/forms/FormRenderer.test.ts index cc005c163a9..73973bde037 100644 --- a/resources/js/modules/forms/FormRenderer.test.ts +++ b/resources/js/modules/forms/FormRenderer.test.ts @@ -270,7 +270,10 @@ vi.mock('../editable-table', () => ({ string, {type: string; textExpanderTriggers?: TextExpanderTriggers} >, - settings: {minRows?: number | null} = {} + settings: { + defaultValues?: FormValues; + minRows?: number | null; + } = {} ) { const body = required( document.querySelector(`#${id} tbody`), @@ -282,7 +285,7 @@ vi.mock('../editable-table', () => ({ String(body.children.length), columns, baseName, - {} + settings.defaultValues ?? {} ).appendTo(body); } } @@ -3016,6 +3019,7 @@ describe('FormRenderer', () => { allowDelete: true, allowReorder: true, minRows: 2, + defaultValues: {name: '', enabled: false}, }, [{name: '', enabled: true}], ], @@ -3167,6 +3171,16 @@ describe('FormRenderer', () => { 'input[name="settings[rows][0][enabled]"][type="checkbox"]' )?.checked ).toBe(true); + expect( + container.querySelector( + 'textarea[name="settings[rows][1][name]"]' + )?.value + ).toBe(''); + expect( + container.querySelector( + 'input[name="settings[rows][1][enabled]"][type="checkbox"]' + )?.checked + ).toBe(false); await vi.waitFor(() => expect( container.querySelector( @@ -3206,7 +3220,7 @@ describe('FormRenderer', () => { )?.value ).toEqual([ {name: '', enabled: true}, - {name: '', enabled: false}, + {name: '', enabled: false}, ]) ); @@ -3242,7 +3256,7 @@ describe('FormRenderer', () => { settings: { rows: [ {name: '', enabled: true}, - {name: '', enabled: false}, + {name: '', enabled: false}, ], }, }); diff --git a/src/Field/Table.php b/src/Field/Table.php index 84aed426c5f..5dafef6cc58 100644 --- a/src/Field/Table.php +++ b/src/Field/Table.php @@ -121,6 +121,7 @@ public function formControl(FieldContext $context): Control return TableControl::make($context->path) ->columns($columns) + ->defaultValues($this->defaultRowValues) ->allowAdd(! $this->staticRows) ->allowDelete(! $this->staticRows) ->allowReorder(! $this->staticRows) @@ -175,6 +176,13 @@ public function settingsForm(FormContext $context = new FormContext): Form ->allowDelete() ->allowReorder() ->value($this->defaults ?? [])), + FormField::make(t('Default Row Values')) + ->instructions(t('Define the default values for new rows.')) + ->control(TableControl::make('defaultRowValues') + ->columns($defaultColumns) + ->minRows(1) + ->maxRows(1) + ->value([$this->defaultRowValues])), ])->dependsOn('settings.columns'), FormField::make(t('Static Rows')) ->instructions(t('Whether the table rows should be restricted to those defined by the “Default Values” setting.')) @@ -223,6 +231,9 @@ public function settingsForm(FormContext $context = new FormContext): Form /** @var list|null The default row values that new elements should have */ public ?array $defaults = [[]]; + /** @var TableRowData The default values for newly added rows */ + public array $defaultRowValues = []; + public function __construct($config = []) { // Config normalization @@ -261,15 +272,32 @@ public function __construct($config = []) } } + if (isset($config['defaultRowValues'])) { + $defaultRowValues = $config['defaultRowValues']; + + $config['defaultRowValues'] = match (true) { + ! is_array($defaultRowValues) => [], + count($defaultRowValues) === 1 + && is_array($defaultRowValues[0] ?? null) => $defaultRowValues[0], + default => $defaultRowValues, + }; + } + // handle some default cell values - if (! empty($config['columns']) && isset($config['defaults'])) { + if (! empty($config['columns'])) { foreach ($config['columns'] as $colId => $col) { // Convert default date cell values to ISO8601 strings if (in_array($col['type'], ['date', 'time'], true)) { - foreach ($config['defaults'] as &$row) { - if (isset($row[$colId])) { - $row[$colId] = DateTimeHelper::toIso8601($row[$colId]) ?: null; + if (isset($config['defaults'])) { + foreach ($config['defaults'] as &$row) { + if (isset($row[$colId])) { + $row[$colId] = DateTimeHelper::toIso8601($row[$colId]) ?: null; + } } + unset($row); + } + if (isset($config['defaultRowValues'][$colId])) { + $config['defaultRowValues'][$colId] = DateTimeHelper::toIso8601($config['defaultRowValues'][$colId]) ?: null; } } } @@ -843,6 +871,7 @@ private function inlineInputHtml(mixed $value, ?ElementInterface $element): stri 'name' => $this->handle, 'cols' => $columns, 'rows' => $value, + 'defaultValues' => $this->defaultRowValues, 'minRows' => $this->minRows, 'maxRows' => $this->maxRows, 'static' => false, diff --git a/tests/Unit/Field/TableTest.php b/tests/Unit/Field/TableTest.php index 35222f05039..c687b321c69 100644 --- a/tests/Unit/Field/TableTest.php +++ b/tests/Unit/Field/TableTest.php @@ -2,12 +2,43 @@ declare(strict_types=1); +use CraftCms\Cms\Field\FieldContext; use CraftCms\Cms\Field\Table; use CraftCms\Cms\Form\FormContext; use CraftCms\Cms\Form\FormHtmlRenderer; use CraftCms\Cms\Form\FormResolver; use Symfony\Component\DomCrawler\Crawler; +it('uses configured default row values for newly added rows', function () { + $field = new Table([ + 'columns' => [ + 'label' => ['heading' => 'Label', 'handle' => 'label', 'type' => 'singleline'], + 'enabled' => ['heading' => 'Enabled', 'handle' => 'enabled', 'type' => 'lightswitch'], + ], + 'defaultRowValues' => [[ + 'label' => 'New row', + 'enabled' => true, + ]], + ]); + + $settings = app(FormResolver::class)->resolve($field->settingsForm(), new FormContext); + $defaultRowValues = $settings->nodes[1]->children[1]; + + expect($defaultRowValues->props['label'])->toBe('Default Row Values') + ->and($settings->values['defaultRowValues'])->toBe([[ + 'label' => 'New row', + 'enabled' => true, + ]]) + ->and($defaultRowValues->control->props)->toMatchArray([ + 'minRows' => 1, + 'maxRows' => 1, + ]) + ->and($field->formControl(new FieldContext('details'))->props()['defaultValues'])->toBe([ + 'label' => 'New row', + 'enabled' => true, + ]); +}); + it('configures single-line defaults for new columns', function () { $field = new Table(['name' => 'Details', 'handle' => 'details']);