diff --git a/src/FormElement/MultiselectElement.php b/src/FormElement/MultiselectElement.php new file mode 100644 index 00000000..acbe3c06 --- /dev/null +++ b/src/FormElement/MultiselectElement.php @@ -0,0 +1,57 @@ +getName() . '[]'; + } + + public function setValue($value) + { + $this->value = empty($value) ? [] : (array) $value; + $this->valid = null; + + return $this; + } + + public function validate() + { + foreach ($this->getValue() as $value) { + $option = $this->getOption($value); + if (! $option || $option->getAttributes()->has('disabled')) { + $this->valid = false; + $this->addMessage(sprintf($this->translate("'%s' is not allowed here"), $value)); + + return $this; + } + } + + BaseFormElement::validate(); + + return $this; + } + + protected function isSelectedOption($optionValue) + { + return in_array($optionValue, $this->getValue(), ! is_int($optionValue)); + } + + protected function assemble() + { + $this->setAttribute('multiple', true); + + parent::assemble(); + } +} diff --git a/src/FormElement/SelectElement.php b/src/FormElement/SelectElement.php index be54941e..2edd0b56 100644 --- a/src/FormElement/SelectElement.php +++ b/src/FormElement/SelectElement.php @@ -3,9 +3,12 @@ namespace ipl\Html\FormElement; use ipl\Html\Html; +use ipl\I18n\Translation; class SelectElement extends BaseFormElement { + use Translation; + protected $tag = 'select'; /** @var SelectOption[] */ @@ -45,13 +48,13 @@ public function validate() ) ) { $this->valid = false; - $this->addMessage("'$value' is not allowed here"); - } elseif ($this->isRequired() && $value === null) { - $this->valid = false; - } else { - parent::validate(); + $this->addMessage(sprintf($this->translate("'%s' is not allowed here"), $value)); + + return $this; } + parent::validate(); + return $this; } @@ -67,9 +70,10 @@ public function disableOption($value) if ($option = $this->getOption($value)) { $option->getAttributes()->add('disabled', true); } - if ($this->getValue() == $value) { + + if ($this->isSelectedOption($value)) { $this->valid = false; - $this->addMessage("'$value' is not allowed here"); + $this->addMessage(sprintf($this->translate("'%s' is not allowed here"), $value)); } return $this; @@ -124,13 +128,7 @@ protected function makeOption($value, $label) } else { $option = new SelectOption($value, $label); $option->getAttributes()->registerAttributeCallback('selected', function () use ($option) { - $optionValue = $option->getValue(); - - return is_int($optionValue) - // The loose comparison is required because PHP casts - // numeric strings to integers if used as array keys - ? $this->getValue() == $optionValue - : $this->getValue() === $optionValue; + return $this->isSelectedOption($option->getValue()); }); $this->options[$value] = $option; @@ -138,6 +136,22 @@ protected function makeOption($value, $label) } } + /** + * Whether the given option is a selected option + * + * @param $optionValue + * + * @return bool + */ + protected function isSelectedOption($optionValue) + { + return is_int($optionValue) + // The loose comparison is required because PHP casts + // numeric strings to integers if used as array keys + ? $this->getValue() == $optionValue + : $this->getValue() === $optionValue; + } + protected function assemble() { $this->addHtml(...array_values($this->optionContent)); diff --git a/tests/FormElement/MultiselectElementTest.php b/tests/FormElement/MultiselectElementTest.php new file mode 100644 index 00000000..4500edaa --- /dev/null +++ b/tests/FormElement/MultiselectElementTest.php @@ -0,0 +1,383 @@ + [ + null => 'Please choose', + '1' => 'The one', + '4' => 'Four', + '5' => 'Hi five' + ], + ]); + + $html = << + + + + + +HTML; + + $this->assertHtml($html, $select); + } + + public function testRequiredAttribute() + { + $select = new MultiselectElement('elname', [ + 'required' => true, + 'options' => [ + null => 'Please choose', + '1' => 'The one', + '4' => 'Four', + '5' => 'Hi five' + ], + ]); + + $html = << + + + + + +HTML; + + $this->assertHtml($html, $select); + } + + public function testOptionValidity() + { + StaticTranslator::$instance = new NoopTranslator(); + $select = new MultiselectElement('elname', [ + 'value' => '3', + 'options' => [ + null => 'Please choose', + '1' => 'The one', + '4' => 'Four', + '5' => 'Hi five', + 'sub' => [ + 'Down' => 'Here' + ] + ], + ]); + $this->assertFalse($select->isValid()); + $select->setValue(3); + $this->assertFalse($select->isValid()); + $select->setValue(4); + $this->assertTrue($select->isValid()); + $select->setValue('4'); + $this->assertTrue($select->isValid()); + $select->setValue('Down'); + $this->assertTrue($select->isValid()); + $select->setValue('sub'); + $this->assertFalse($select->isValid()); + + $select->setValue(['1', 4, 'Down']); + $this->assertTrue($select->isValid()); + + $select->disableOption(4); + $select->setValue([ 4, '5']); + $this->assertFalse($select->isValid()); + } + + public function testSelectingMultipleOptions() + { + $select = new MultiselectElement('elname', [ + 'value' => '4', + 'options' => [ + null => 'Please choose', + '1' => 'The one', + '4' => 'Four', + '5' => 'Hi five', + 'sub' => [ + 'Down' => 'Here' + ] + ], + ]); + + $this->assertEquals($select->getValue(), [4]); + $select->setValue([1, '5', 'Down']); + $this->assertEquals($select->getValue(), [1, '5', 'Down']); + } + + public function testSelectingDisabledOptionIsNotPossible() + { + StaticTranslator::$instance = new NoopTranslator(); + $select = new MultiselectElement('elname', [ + 'value' => '4', + 'options' => [ + null => 'Please choose', + '1' => 'The one', + '4' => 'Four', + '5' => 'Hi five', + 'sub' => [ + 'Down' => 'Here' + ] + ], + ]); + + $this->assertTrue($select->isValid()); + $select->disableOption(4); + $this->assertFalse($select->isValid()); + } + + public function testSelectingMultipleDisabledOptionsIsNotPossible() + { + StaticTranslator::$instance = new NoopTranslator(); + $select = new MultiselectElement('elname', [ + 'value' => '4', + 'options' => [ + null => 'Please choose', + '1' => 'The one', + '4' => 'Four', + '5' => 'Hi five', + 'sub' => [ + 'Down' => 'Here' + ] + ], + ]); + + $this->assertTrue($select->isValid()); + $select->disableOptions([4, 5]); + $select->setValue(['4', 5, 'Down']); + $this->assertFalse($select->isValid()); + } + + public function testNestedOptions() + { + $select = new MultiselectElement('elname', [ + 'options' => [ + null => 'Please choose', + 'Some Options' => [ + '1' => 'The one', + '4' => 'Four', + ], + 'More options' => [ + '5' => 'Hi five', + ] + ], + ]); + + $html = << + + + + + + + + + +HTML; + + $this->assertHtml($html, $select); + } + + public function testDisabledNestedOptions() + { + $select = new MultiselectElement('elname', [ + 'options' => [ + null => 'Please choose', + 'Some options' => [ + '1' => 'The one', + '4' => 'Four' + ], + 'More options' => [ + '5' => 'Hi five' + ] + ], + ]); + + $select->disableOptions([4, '5']); + + $html = << + + + + + + + + + +HTML; + + $this->assertHtml($html, $select); + } + + public function testDeeplyDisabledNestedOptions() + { + $select = new MultiselectElement('elname', [ + 'options' => [ + null => 'Please choose', + 'Some options' => [ + '1' => 'The one', + '4' => [ + 'Deeper' => [ + '4x4' => 'Fourfour' + ], + ], + ], + 'More options' => [ + '5' => 'Hi five' + ] + ], + ]); + + $select->disableOptions([4, '5']); + + $html = << + + + + + + + + + + + + + +HTML; + $this->assertHtml($html, $select); + } + + public function testDefaultValueIsSelected() + { + $select = new MultiselectElement('elname', [ + 'value' => '1', + 'options' => [ + null => 'Please choose', + '1' => 'The one', + '4' => 'Four', + '5' => 'Hi five' + ] + ]); + + $html = << + + + + + +HTML; + + $this->assertHtml($html, $select); + } + + public function testMultiDefaultValuesAreSelected() + { + $select = new MultiselectElement('elname', [ + 'value' => ['1', '5'], + 'options' => [ + null => 'Please choose', + '1' => 'The one', + '4' => 'Four', + '5' => 'Hi five' + ] + ]); + + $html = << + + + + + +HTML; + + $this->assertHtml($html, $select); + } + + public function testSetValueSelectsAnOption() + { + $select = new MultiselectElement('elname', [ + 'options' => [ + null => 'Please choose', + '1' => 'The one', + '4' => 'Four', + '5' => 'Hi five' + ] + ]); + + $select->setValue('1'); + + $html = << + + + + + +HTML; + $this->assertHtml($html, $select); + + $select->setValue('5'); + + $html = << + + + + + +HTML; + + $this->assertHtml($html, $select); + + $select->setValue(null); + + $html = << + + + + + +HTML; + + $this->assertHtml($html, $select); + } + + public function testSetValueSelectsMultipleOptions() + { + $select = new MultiselectElement('elname', [ + 'options' => [ + null => 'Please choose', + '1' => 'The one', + '4' => 'Four', + '5' => 'Hi five' + ] + ]); + + $select->setValue(['1', 4]); + + $html = << + + + + + +HTML; + + $this->assertHtml($html, $select); + } +} diff --git a/tests/FormElement/SelectElementTest.php b/tests/FormElement/SelectElementTest.php index 2d5de9ba..538f52a8 100644 --- a/tests/FormElement/SelectElementTest.php +++ b/tests/FormElement/SelectElementTest.php @@ -4,6 +4,8 @@ use ipl\Html\FormElement\SelectElement; use ipl\Html\FormElement\SelectOption; +use ipl\I18n\NoopTranslator; +use ipl\I18n\StaticTranslator; use ipl\Tests\Html\TestCase; class SelectElementTest extends TestCase @@ -33,6 +35,7 @@ public function testFlatOptions() public function testOptionValidity() { + StaticTranslator::$instance = new NoopTranslator(); $select = new SelectElement('elname', [ 'label' => 'Customer', 'value' => '3', @@ -61,6 +64,7 @@ public function testOptionValidity() public function testSelectingDisabledOptionIsNotPossible() { + StaticTranslator::$instance = new NoopTranslator(); $select = new SelectElement('elname', [ 'label' => 'Customer', 'value' => '4',