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',