Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Form\Element\Select multiple is always required #6009

Merged
merged 5 commits into from

4 participants

@tommyseus

The Select Element is always required. The Validation fails (required) if the multiple select is unselected. It is not possible to send an empty list.

I added the option for an hidden element. If nothing is selected the empty hidden field is send (required).

Select Element has the new Option 'use_hidden_element' and 'unselected_value'.
The FormSelect Helper generates an hidden element before the select element.

Reference to other Issues.
#4750, #5842, doctrine/DoctrineModule#215

library/Zend/Form/Element/Select.php
@@ -270,4 +346,13 @@ protected function getOptionValue($key, $optionSpec)
{
return is_array($optionSpec) ? $optionSpec['value'] : $key;
}
+
+ /**
+ * @return boolean

@return bool for consistency

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@Ocramius Ocramius commented on the diff
library/Zend/Form/Element/Select.php
@@ -235,6 +292,24 @@ public function getInputSpecification()
'required' => true,
);
+ if ($this->useHiddenElement() && $this->isMultiple()) {
+ $unselectedValue = $this->getUnselectedValue();
@Ocramius Collaborator
Ocramius added a note

What happens if $unselectedValue corresponds with a value in the dropdown?

@weierophinney Owner

@Ocramius It would either be marked as selected, or that value would be considered a default value.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@tommyseus

If nothing is selected, the hidden value returns a string.
$select = 'dummy'
The filter removes the dummy value and $form->getData()['select'] returns an empty array.

If at least one value is selected, the select element sends an array.
$select = array('selected-option')

There is no conflict, because the results are different types.

I have added a testcase to the dataPovider for your example.

@weierophinney weierophinney added this to the 2.3.1 milestone
@weierophinney weierophinney self-assigned this
@weierophinney weierophinney merged commit 2849ed4 into from
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
98 library/Zend/Form/Element/Select.php
@@ -51,6 +51,16 @@ class Select extends Element implements InputProviderInterface
protected $valueOptions = array();
/**
+ * @var bool
+ */
+ protected $useHiddenElement = false;
+
+ /**
+ * @var string
+ */
+ protected $unselectedValue = '';
+
+ /**
* @return array
*/
public function getValueOptions()
@@ -128,6 +138,14 @@ public function setOptions($options)
$this->setDisableInArrayValidator($this->options['disable_inarray_validator']);
}
+ if (isset($options['use_hidden_element'])) {
+ $this->setUseHiddenElement($options['use_hidden_element']);
+ }
+
+ if (isset($options['unselected_value'])) {
+ $this->setUnselectedValue($options['unselected_value']);
+ }
+
return $this;
}
@@ -206,10 +224,7 @@ protected function getValidator()
'strict' => false
));
- $multiple = (isset($this->attributes['multiple']))
- ? $this->attributes['multiple'] : null;
-
- if (true === $multiple || 'multiple' === $multiple) {
+ if ($this->isMultiple()) {
$validator = new ExplodeValidator(array(
'validator' => $validator,
'valueDelimiter' => null, // skip explode if only one value
@@ -222,9 +237,51 @@ protected function getValidator()
}
/**
- * Provide default input rules for this element
+ * Do we render hidden element?
+ *
+ * @param bool $useHiddenElement
+ * @return Select
+ */
+ public function setUseHiddenElement($useHiddenElement)
+ {
+ $this->useHiddenElement = (bool) $useHiddenElement;
+ return $this;
+ }
+
+ /**
+ * Do we render hidden element?
+ *
+ * @return bool
+ */
+ public function useHiddenElement()
+ {
+ return $this->useHiddenElement;
+ }
+
+ /**
+ * Set the value if the select is not selected
*
- * Attaches the captcha as a validator.
+ * @param string $unselectedValue
+ * @return Select
+ */
+ public function setUnselectedValue($unselectedValue)
+ {
+ $this->unselectedValue = (string) $unselectedValue;
+ return $this;
+ }
+
+ /**
+ * Get the value when the select is not selected
+ *
+ * @return string
+ */
+ public function getUnselectedValue()
+ {
+ return $this->unselectedValue;
+ }
+
+ /**
+ * Provide default input rules for this element
*
* @return array
*/
@@ -235,6 +292,24 @@ public function getInputSpecification()
'required' => true,
);
+ if ($this->useHiddenElement() && $this->isMultiple()) {
+ $unselectedValue = $this->getUnselectedValue();
@Ocramius Collaborator
Ocramius added a note

What happens if $unselectedValue corresponds with a value in the dropdown?

@weierophinney Owner

@Ocramius It would either be marked as selected, or that value would be considered a default value.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ $spec['allow_empty'] = true;
+ $spec['continue_if_empty'] = true;
+ $spec['filters'] = array(array(
+ 'name' => 'Callback',
+ 'options' => array(
+ 'callback' => function ($value) use ($unselectedValue) {
+ if ($value === $unselectedValue) {
+ $value = array();
+ }
+ return $value;
+ }
+ )
+ ));
+ }
+
if ($validator = $this->getValidator()) {
$spec['validators'] = array(
$validator,
@@ -270,4 +345,15 @@ protected function getOptionValue($key, $optionSpec)
{
return is_array($optionSpec) ? $optionSpec['value'] : $key;
}
+
+ /**
+ * Element has the multiple attribute
+ *
+ * @return bool
+ */
+ public function isMultiple()
+ {
+ return isset($this->attributes['multiple'])
+ && ($this->attributes['multiple'] === true || $this->attributes['multiple'] === 'multiple');
+ }
}
View
45 library/Zend/Form/View/Helper/FormSelect.php
@@ -9,6 +9,7 @@
namespace Zend\Form\View\Helper;
+use Zend\Form\Element\Hidden;
use Zend\Form\ElementInterface;
use Zend\Form\Element\Select as SelectElement;
use Zend\Form\Exception;
@@ -67,6 +68,11 @@ class FormSelect extends AbstractHelper
);
/**
+ * @var FromHidden|null
+ */
+ protected $formHiddenHelper;
+
+ /**
* Invoke helper as functor
*
* Proxies to {@link render()}.
@@ -123,11 +129,22 @@ public function render(ElementInterface $element)
}
$this->validTagAttributes = $this->validSelectAttributes;
- return sprintf(
+ $rendered = sprintf(
'<select %s>%s</select>',
$this->createAttributesString($attributes),
$this->renderOptions($options, $value)
);
+
+ // Render hidden element
+ $useHiddenElement = method_exists($element, 'useHiddenElement')
+ && method_exists($element, 'getUnselectedValue')
+ && $element->useHiddenElement();
+
+ if ($useHiddenElement) {
+ $rendered = $this->renderHiddenElement($element) . $rendered;
+ }
+
+ return $rendered;
}
/**
@@ -278,4 +295,30 @@ protected function validateMultiValue($value, array $attributes)
return $value;
}
+
+ protected function renderHiddenElement(ElementInterface $element)
+ {
+ $hiddenElement = new Hidden($element->getName());
+ $hiddenElement->setValue($element->getUnselectedValue());
+
+ return $this->getFormHiddenHelper()->__invoke($hiddenElement);
+ }
+
+ /**
+ * @return FormHidden
+ */
+ protected function getFormHiddenHelper()
+ {
+ if (!$this->formHiddenHelper) {
+ if (method_exists($this->view, 'plugin')) {
+ $this->formHiddenHelper = $this->view->plugin('formhidden');
+ }
+
+ if (!$this->formHiddenHelper instanceof FormHidden) {
+ $this->formHiddenHelper = new FormHidden();
+ }
+ }
+
+ return $this->formHiddenHelper;
+ }
}
View
34 tests/ZendTest/Form/Element/SelectTest.php
@@ -235,4 +235,38 @@ public function testUnsetUndefinedValueOption()
$this->assertArrayNotHasKey('Option Undefined', $valueOptions);
}
+ public function testSetOptionsToSelectMultiple()
+ {
+ $element = new SelectElement(null, array(
+ 'label' => 'Importance',
+ 'use_hidden_element' => true,
+ 'unselected_value' => 'empty',
+ 'value_options' => array(
+ 'foo' => 'Foo',
+ 'bar' => 'Bar'
+ ),
+ ));
+ $element->setAttributes(array('multiple' => 'multiple'));
+
+ $this->assertTrue($element->isMultiple());
+ $this->assertTrue($element->useHiddenElement());
+ $this->assertEquals('empty', $element->getUnselectedValue());
+ }
+
+ public function testProvidesInputSpecificationForMultipleSelectWithUseHiddenElement()
+ {
+ $element = new SelectElement();
+ $element
+ ->setUseHiddenElement(true)
+ ->setAttributes(array(
+ 'multiple' => true,
+ ));
+
+ $inputSpec = $element->getInputSpecification();
+
+ $this->assertArrayHasKey('allow_empty', $inputSpec);
+ $this->assertTrue($inputSpec['allow_empty']);
+ $this->assertArrayHasKey('continue_if_empty', $inputSpec);
+ $this->assertTrue($inputSpec['continue_if_empty']);
+ }
}
View
89 tests/ZendTest/Form/FormTest.php
@@ -1906,6 +1906,95 @@ public function testFormElementValidatorsMergeIntoAppliedInputFilter()
$this->assertTrue($this->form->isValid());
}
+ /**
+ * @param bool $expectedIsValid
+ * @param array $expectedFormData
+ * @param array $data
+ * @param string $unselectedValue
+ * @param bool $useHiddenElement
+ * @dataProvider formWithSelectMultipleAndEmptyUnselectedValueDataProvider
+ */
+ public function testFormWithSelectMultipleAndEmptyUnselectedValue(
+ $expectedIsValid,
+ array $expectedFormData,
+ array $data,
+ $unselectedValue,
+ $useHiddenElement
+ )
+ {
+ $this->form->add(array(
+ 'name' => 'multipleSelect',
+ 'type' => 'Zend\Form\Element\Select',
+ 'attributes' => array('multiple' => 'multiple'),
+ 'options' => array(
+ 'label' => 'Importance',
+ 'use_hidden_element' => $useHiddenElement,
+ 'unselected_value' => $unselectedValue,
+ 'value_options' => array(
+ 'foo' => 'Foo',
+ 'bar' => 'Bar'
+ ),
+ ),
+ ));
+
+ $actualIsValid = $this->form->setData($data)->isValid();
+ $this->assertEquals($expectedIsValid, $actualIsValid);
+
+ $formData = $this->form->getData();
+ $this->assertEquals($expectedFormData, $formData);
+ }
+
+ /**
+ * @return array
+ */
+ public function formWithSelectMultipleAndEmptyUnselectedValueDataProvider()
+ {
+ return array(
+ array(
+ true,
+ array('multipleSelect' => array('foo')),
+ array('multipleSelect' => array('foo')),
+ '',
+ true
+ ),
+ array(
+ true,
+ array('multipleSelect' => array()),
+ array('multipleSelect' => ''),
+ '',
+ true
+ ),
+ array(
+ true,
+ array('multipleSelect' => array()),
+ array('multipleSelect' => 'empty'),
+ 'empty',
+ true
+ ),
+ array(
+ false,
+ array('multipleSelect' => ''),
+ array('multipleSelect' => ''),
+ 'empty',
+ true
+ ),
+ array(
+ false,
+ array('multipleSelect' => ''),
+ array('multipleSelect' => ''),
+ '',
+ false
+ ),
+ array(
+ true,
+ array('multipleSelect' => array()),
+ array('multipleSelect' => 'foo'),
+ 'foo',
+ true
+ ),
+ );
+ }
+
public function testCanSetUseInputFilterDefaultsViaArray()
{
$spec = array(
View
10 tests/ZendTest/Form/View/Helper/FormSelectTest.php
@@ -384,6 +384,16 @@ public function testCanMarkOptionsAsSelectedWhenEmptyOptionOrZeroValueSelected()
$this->assertContains('<option value="0" selected="selected">label0</option>', $markup);
}
+ public function testHiddenElementWhenAttributeMultipleIsSet()
+ {
+ $element = new SelectElement('foo');
+ $element->setUseHiddenElement(true);
+ $element->setUnselectedValue('empty');
+
+ $markup = $this->helper->render($element);
+ $this->assertContains('<input type="hidden" name="foo" value="empty"><select', $markup);
+ }
+
public function testRenderInputNotSelectElementRaisesException()
{
$element = new Element\Text('foo');
Something went wrong with that request. Please try again.