Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
22 changes: 18 additions & 4 deletions resources/js/modules/forms/FormRenderer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLTableSectionElement>(`#${id} tbody`),
Expand All @@ -282,7 +285,7 @@ vi.mock('../editable-table', () => ({
String(body.children.length),
columns,
baseName,
{}
settings.defaultValues ?? {}
).appendTo(body);
}
}
Expand Down Expand Up @@ -3016,6 +3019,7 @@ describe('FormRenderer', () => {
allowDelete: true,
allowReorder: true,
minRows: 2,
defaultValues: {name: '<New row>', enabled: false},
},
[{name: '<Row>', enabled: true}],
],
Expand Down Expand Up @@ -3167,6 +3171,16 @@ describe('FormRenderer', () => {
'input[name="settings[rows][0][enabled]"][type="checkbox"]'
)?.checked
).toBe(true);
expect(
container.querySelector<HTMLTextAreaElement>(
'textarea[name="settings[rows][1][name]"]'
)?.value
).toBe('<New row>');
expect(
container.querySelector<HTMLInputElement>(
'input[name="settings[rows][1][enabled]"][type="checkbox"]'
)?.checked
).toBe(false);
await vi.waitFor(() =>
expect(
container.querySelector<HTMLInputElement>(
Expand Down Expand Up @@ -3206,7 +3220,7 @@ describe('FormRenderer', () => {
)?.value
).toEqual([
{name: '<Row>', enabled: true},
{name: '', enabled: false},
{name: '<New row>', enabled: false},
])
);

Expand Down Expand Up @@ -3242,7 +3256,7 @@ describe('FormRenderer', () => {
settings: {
rows: [
{name: '<Changed row>', enabled: true},
{name: '', enabled: false},
{name: '<New row>', enabled: false},
],
},
});
Expand Down
37 changes: 33 additions & 4 deletions src/Field/Table.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.'))
Expand Down Expand Up @@ -223,6 +231,9 @@ public function settingsForm(FormContext $context = new FormContext): Form
/** @var list<TableRowData>|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
Expand Down Expand Up @@ -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;
}
}
}
Expand Down Expand Up @@ -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,
Expand Down
31 changes: 31 additions & 0 deletions tests/Unit/Field/TableTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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']);

Expand Down
Loading