diff --git a/src/Forms/Controls/RadioList.php b/src/Forms/Controls/RadioList.php index 3be94c26..ef6b8586 100644 --- a/src/Forms/Controls/RadioList.php +++ b/src/Forms/Controls/RadioList.php @@ -27,6 +27,7 @@ class RadioList extends ChoiceControl protected Html $separator; protected Html $container; protected Html $itemLabel; + private string|Stringable|false $emptyItem = false; public function __construct(string|Stringable|null $label = null, ?array $items = null) @@ -40,6 +41,25 @@ public function __construct(string|Stringable|null $label = null, ?array $items } + /** + * Sets the first N/A item. + */ + public function setEmptyItem(string|Stringable|false $label): static + { + $this->emptyItem = $label; + return $this; + } + + + /** + * Returns the first N/A item. + */ + public function getEmptyItem(): string|Stringable|false + { + return $this->emptyItem; + } + + public function getControl(): Html { $input = parent::getControl(); @@ -51,12 +71,25 @@ public function getControl(): Html } } + $selected = $this->value; + if ($this->emptyItem !== false) { + if ($this->isRequired()) { + throw new Nette\InvalidStateException('Required radiolist cannot have empty item.'); + } + $emptyKey = ''; + while (isset($items[$emptyKey])) { + $emptyKey .= "\x1"; + } + $items = [$emptyKey => $this->translate($this->emptyItem)] + $items; + $selected ??= $emptyKey; + } + return $this->container->setHtml( Nette\Forms\Helpers::createInputList( $this->translate($items), array_merge($input->attrs, [ 'id:' => $ids, - 'checked?' => $this->value, + 'checked?' => $selected, 'disabled:' => $this->disabled, 'data-nette-rules:' => [key($items) => $input->attrs['data-nette-rules']], ]), diff --git a/tests/Forms/Controls.RadioList.loadData.phpt b/tests/Forms/Controls.RadioList.loadData.phpt index 14b24cf0..ba507229 100644 --- a/tests/Forms/Controls.RadioList.loadData.phpt +++ b/tests/Forms/Controls.RadioList.loadData.phpt @@ -141,6 +141,17 @@ testException('setValue() and invalid argument', function () use ($series) { }, Nette\InvalidArgumentException::class, "Value 'unknown' is out of allowed set ['red-dwarf', 'the-simpsons', 0, ''] in field 'radio'."); +test('empty item selected', function () { + $_POST = ['radio' => '']; + + $form = new Form; + $input = $form->addRadioList('radio', null, ['a' => 'First'])->setEmptyItem('n/a'); + + Assert::true($form->isValid()); + Assert::null($input->getValue()); +}); + + test('object as value', function () { $form = new Form; $input = $form->addRadioList('radio', null, ['2013-07-05 00:00:00' => 1]) diff --git a/tests/Forms/Controls.RadioList.render.phpt b/tests/Forms/Controls.RadioList.render.phpt index 024bdece..0bfc5980 100644 --- a/tests/Forms/Controls.RadioList.render.phpt +++ b/tests/Forms/Controls.RadioList.render.phpt @@ -88,6 +88,23 @@ test('Html', function () { }); +test('empty item', function () { + $form = new Form; + $input = $form->addRadioList('list', 'Label', [ + '' => 'First', + ])->setEmptyItem('n/a'); + + Assert::same('
', (string) $input->getControl()); + + $input->setRequired(); + Assert::exception( + fn() => $input->getControl(), + Nette\InvalidStateException::class, + 'Required radiolist cannot have empty item.', + ); +}); + + test('validation rules', function () { $form = new Form; $input = $form->addRadioList('list', 'Label', [