diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig index 211eee5dd784f..f6e298f4ea47d 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig @@ -96,7 +96,8 @@ {{ block('choice_widget_options') }} {% else %} - + {% set attr = choice.attributes %} + {% endif %} {% endfor %} {% endspaceless %} @@ -421,3 +422,16 @@ {%- endfor -%} {% endspaceless %} {% endblock button_attributes %} + +{% block option_attributes %} +{% spaceless %} + {%- for attrname, attrvalue in attr -%} + {{- " " -}} + {% if attrvalue is sameas(true) -%} + {{- attrname }}="{{ attrname }}" + {%- elseif attrvalue is not sameas(false) -%} + {{- attrname }}="{{ attrvalue }}" + {%- endif -%} + {%- endfor -%} +{% endspaceless %} +{% endblock option_attributes %} diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget_options.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget_options.html.php index a7a9311d51326..65d8bddde62c3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget_options.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget_options.html.php @@ -6,6 +6,6 @@ block($form, 'choice_widget_options', array('choices' => $choice)) ?> - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/option_attributes.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/option_attributes.html.php new file mode 100644 index 0000000000000..e0ddedf0bf2e8 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/option_attributes.html.php @@ -0,0 +1,7 @@ + $v): ?> + +escape($k), $view->escape($k)) ?> + +escape($k), $view->escape($v)) ?> + + diff --git a/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php b/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php index b8966967e3965..2ba96d6a45f83 100644 --- a/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php +++ b/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php @@ -69,26 +69,27 @@ class ChoiceList implements ChoiceListInterface /** * Creates a new choice list. * - * @param array|\Traversable $choices The array of choices. Choices may also be given - * as hierarchy of unlimited depth. Hierarchies are - * created by creating nested arrays. The title of - * the sub-hierarchy can be stored in the array - * key pointing to the nested array. The topmost - * level of the hierarchy may also be a \Traversable. - * @param array $labels The array of labels. The structure of this array - * should match the structure of $choices. - * @param array $preferredChoices A flat array of choices that should be - * presented to the user with priority. + * @param array|\Traversable $choices The array of choices. Choices may also be given + * as hierarchy of unlimited depth. Hierarchies are + * created by creating nested arrays. The title of + * the sub-hierarchy can be stored in the array + * key pointing to the nested array. The topmost + * level of the hierarchy may also be a \Traversable. + * @param array $labels The array of labels. The structure of this array + * should match the structure of $choices. + * @param array $preferredChoices A flat array of choices that should be presented to the user with priority. + * @param array $attributes A multidimensionnal array of attributes. The structure of this + * array should match the structure of $choices. * * @throws UnexpectedTypeException If the choices are not an array or \Traversable. */ - public function __construct($choices, array $labels, array $preferredChoices = array()) + public function __construct($choices, array $labels, array $preferredChoices = array(), array $attributes = array()) { if (!is_array($choices) && !$choices instanceof \Traversable) { throw new UnexpectedTypeException($choices, 'array or \Traversable'); } - $this->initialize($choices, $labels, $preferredChoices); + $this->initialize($choices, $labels, $preferredChoices, $attributes); } /** @@ -99,8 +100,9 @@ public function __construct($choices, array $labels, array $preferredChoices = a * @param array|\Traversable $choices The choices to write into the list. * @param array $labels The labels belonging to the choices. * @param array $preferredChoices The choices to display with priority. + * @param array $attributes The choices' attributes. */ - protected function initialize($choices, array $labels, array $preferredChoices) + protected function initialize($choices, array $labels, array $preferredChoices, array $attributes = array()) { $this->choices = array(); $this->values = array(); @@ -112,7 +114,8 @@ protected function initialize($choices, array $labels, array $preferredChoices) $this->remainingViews, $choices, $labels, - $preferredChoices + $preferredChoices, + $attributes ); } @@ -258,11 +261,12 @@ public function getIndicesForValues(array $values) * @param array|\Traversable $choices The list of choices. * @param array $labels The labels corresponding to the choices. * @param array $preferredChoices The preferred choices. + * @param array $attributes The choices' attributes. * * @throws InvalidArgumentException If the structures of the choices and labels array do not match. * @throws InvalidConfigurationException If no valid value or index could be created for a choice. */ - protected function addChoices(array &$bucketForPreferred, array &$bucketForRemaining, $choices, array $labels, array $preferredChoices) + protected function addChoices(array &$bucketForPreferred, array &$bucketForRemaining, $choices, array $labels, array $preferredChoices, array $attributes = array()) { // Add choices to the nested buckets foreach ($choices as $group => $choice) { @@ -270,6 +274,8 @@ protected function addChoices(array &$bucketForPreferred, array &$bucketForRemai throw new InvalidArgumentException('The structures of the choices and labels array do not match.'); } + $attribute = isset($attributes[$group]) ? $attributes[$group] : array(); + if (is_array($choice)) { // Don't do the work if the array is empty if (count($choice) > 0) { @@ -279,7 +285,8 @@ protected function addChoices(array &$bucketForPreferred, array &$bucketForRemai $bucketForRemaining, $choice, $labels[$group], - $preferredChoices + $preferredChoices, + $attribute ); } } else { @@ -288,7 +295,8 @@ protected function addChoices(array &$bucketForPreferred, array &$bucketForRemai $bucketForRemaining, $choice, $labels[$group], - $preferredChoices + $preferredChoices, + $attribute ); } } @@ -305,10 +313,11 @@ protected function addChoices(array &$bucketForPreferred, array &$bucketForRemai * @param array $choices The list of choices in the group. * @param array $labels The labels corresponding to the choices in the group. * @param array $preferredChoices The preferred choices. + * @param array $attributes The choices' attributes. * * @throws InvalidConfigurationException If no valid value or index could be created for a choice. */ - protected function addChoiceGroup($group, array &$bucketForPreferred, array &$bucketForRemaining, array $choices, array $labels, array $preferredChoices) + protected function addChoiceGroup($group, array &$bucketForPreferred, array &$bucketForRemaining, array $choices, array $labels, array $preferredChoices, array $attributes = array()) { // If this is a choice group, create a new level in the choice // key hierarchy @@ -320,7 +329,8 @@ protected function addChoiceGroup($group, array &$bucketForPreferred, array &$bu $bucketForRemaining[$group], $choices, $labels, - $preferredChoices + $preferredChoices, + $attributes ); // Remove child levels if empty @@ -342,10 +352,11 @@ protected function addChoiceGroup($group, array &$bucketForPreferred, array &$bu * @param mixed $choice The choice to add. * @param string $label The label for the choice. * @param array $preferredChoices The preferred choices. + * @param array $attributes The choices' attributes. * * @throws InvalidConfigurationException If no valid value or index could be created. */ - protected function addChoice(array &$bucketForPreferred, array &$bucketForRemaining, $choice, $label, array $preferredChoices) + protected function addChoice(array &$bucketForPreferred, array &$bucketForRemaining, $choice, $label, array $preferredChoices, array $attributes = array()) { $index = $this->createIndex($choice); @@ -359,7 +370,7 @@ protected function addChoice(array &$bucketForPreferred, array &$bucketForRemain throw new InvalidConfigurationException(sprintf('The value created by the choice list is of type "%s", but should be a string.', gettype($value))); } - $view = new ChoiceView($choice, $value, $label); + $view = new ChoiceView($choice, $value, $label, $attributes); $this->choices[$index] = $this->fixChoice($choice); $this->values[$index] = $value; diff --git a/src/Symfony/Component/Form/Extension/Core/ChoiceList/ObjectChoiceList.php b/src/Symfony/Component/Form/Extension/Core/ChoiceList/ObjectChoiceList.php index 7adf3f2be479d..1dd4265cd6c63 100644 --- a/src/Symfony/Component/Form/Extension/Core/ChoiceList/ObjectChoiceList.php +++ b/src/Symfony/Component/Form/Extension/Core/ChoiceList/ObjectChoiceList.php @@ -64,35 +64,36 @@ class ObjectChoiceList extends ChoiceList /** * Creates a new object choice list. * - * @param array|\Traversable $choices The array of choices. Choices may also be given + * @param array|\Traversable $choices The array of choices. Choices may also be given * as hierarchy of unlimited depth by creating nested * arrays. The title of the sub-hierarchy can be * stored in the array key pointing to the nested * array. The topmost level of the hierarchy may also * be a \Traversable. - * @param string $labelPath A property path pointing to the property used + * @param string $labelPath A property path pointing to the property used * for the choice labels. The value is obtained - * by calling the getter on the object. If the + * by calling the getter on the object. If the * path is NULL, the object's __toString() method * is used instead. - * @param array $preferredChoices A flat array of choices that should be + * @param array $preferredChoices A flat array of choices that should be * presented to the user with priority. - * @param string $groupPath A property path pointing to the property used + * @param string $groupPath A property path pointing to the property used * to group the choices. Only allowed if * the choices are given as flat array. - * @param string $valuePath A property path pointing to the property used + * @param string $valuePath A property path pointing to the property used * for the choice values. If not given, integers * are generated instead. * @param PropertyAccessorInterface $propertyAccessor The reflection graph for reading property paths. + * @param array $attributes The choices' attributes. */ - public function __construct($choices, $labelPath = null, array $preferredChoices = array(), $groupPath = null, $valuePath = null, PropertyAccessorInterface $propertyAccessor = null) + public function __construct($choices, $labelPath = null, array $preferredChoices = array(), $groupPath = null, $valuePath = null, PropertyAccessorInterface $propertyAccessor = null, array $attributes = array()) { $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); $this->labelPath = null !== $labelPath ? new PropertyPath($labelPath) : null; $this->groupPath = null !== $groupPath ? new PropertyPath($groupPath) : null; $this->valuePath = null !== $valuePath ? new PropertyPath($valuePath) : null; - parent::__construct($choices, array(), $preferredChoices); + parent::__construct($choices, array(), $preferredChoices, $attributes); } /** @@ -103,11 +104,12 @@ public function __construct($choices, $labelPath = null, array $preferredChoices * @param array|\Traversable $choices The choices to write into the list. * @param array $labels Ignored. * @param array $preferredChoices The choices to display with priority. + * @param array $attributes The choices' attributes. * * @throws InvalidArgumentException When passing a hierarchy of choices and using * the "groupPath" option at the same time. */ - protected function initialize($choices, array $labels, array $preferredChoices) + protected function initialize($choices, array $labels, array $preferredChoices, array $attributes = array()) { if (null !== $this->groupPath) { $groupedChoices = array(); @@ -145,7 +147,7 @@ protected function initialize($choices, array $labels, array $preferredChoices) $this->extractLabels($choices, $labels); - parent::initialize($choices, $labels, $preferredChoices); + parent::initialize($choices, $labels, $preferredChoices, $attributes); } /** diff --git a/src/Symfony/Component/Form/Extension/Core/ChoiceList/SimpleChoiceList.php b/src/Symfony/Component/Form/Extension/Core/ChoiceList/SimpleChoiceList.php index abc1bc43fed15..c48ef85f34a5e 100644 --- a/src/Symfony/Component/Form/Extension/Core/ChoiceList/SimpleChoiceList.php +++ b/src/Symfony/Component/Form/Extension/Core/ChoiceList/SimpleChoiceList.php @@ -34,18 +34,19 @@ class SimpleChoiceList extends ChoiceList /** * Creates a new simple choice list. * - * @param array $choices The array of choices with the choices as keys and - * the labels as values. Choices may also be given - * as hierarchy of unlimited depth by creating nested - * arrays. The title of the sub-hierarchy is stored - * in the array key pointing to the nested array. + * @param array $choices The array of choices with the choices as keys and + * the labels as values. Choices may also be given + * as hierarchy of unlimited depth by creating nested + * arrays. The title of the sub-hierarchy is stored + * in the array key pointing to the nested array. * @param array $preferredChoices A flat array of choices that should be * presented to the user with priority. + * @param array $attributes The choices' attributes having the same structure than $choices. */ - public function __construct(array $choices, array $preferredChoices = array()) + public function __construct(array $choices, array $preferredChoices = array(), array $attributes = array()) { // Flip preferred choices to speed up lookup - parent::__construct($choices, $choices, array_flip($preferredChoices)); + parent::__construct($choices, $choices, array_flip($preferredChoices), $attributes); } /** @@ -85,11 +86,14 @@ public function getValuesForChoices(array $choices) * @param array|\Traversable $choices The list of choices. * @param array $labels Ignored. * @param array $preferredChoices The preferred choices. + * @param array $attributes The choices' attributes. */ - protected function addChoices(array &$bucketForPreferred, array &$bucketForRemaining, $choices, array $labels, array $preferredChoices) + protected function addChoices(array &$bucketForPreferred, array &$bucketForRemaining, $choices, array $labels, array $preferredChoices, array $attributes = array()) { // Add choices to the nested buckets foreach ($choices as $choice => $label) { + $attribute = isset($attributes[$choice]) ? $attributes[$choice] : array(); + if (is_array($label)) { // Don't do the work if the array is empty if (count($label) > 0) { @@ -99,7 +103,8 @@ protected function addChoices(array &$bucketForPreferred, array &$bucketForRemai $bucketForRemaining, $label, $label, - $preferredChoices + $preferredChoices, + $attribute ); } } else { @@ -108,7 +113,8 @@ protected function addChoices(array &$bucketForPreferred, array &$bucketForRemai $bucketForRemaining, $choice, $label, - $preferredChoices + $preferredChoices, + $attribute ); } } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php index 712f5b6de8fe1..4e113ff843001 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php @@ -12,6 +12,8 @@ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Exception\InvalidConfigurationException; +use Symfony\Component\Form\Exception\RuntimeException; use Symfony\Component\Form\Extension\Core\View\ChoiceView; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormInterface; @@ -45,6 +47,12 @@ public function buildForm(FormBuilderInterface $builder, array $options) throw new LogicException('Either the option "choices" or "choice_list" must be set.'); } + if (!empty($options['choices_attr']) && empty($options['choices'])) { + throw new InvalidConfigurationException('The option "choices_attr" cannot be specified when "choice_list" is set. + Pass the choices attributes when creating the choice list instead.' + ); + } + if ($options['expanded']) { // Initialize all choices before doing the index check below. // This helps in cases where index checks are optimized for non @@ -169,7 +177,33 @@ public function setDefaultOptions(OptionsResolverInterface $resolver) $hash = hash('sha256', serialize(array($choices, $options['preferred_choices']))); if (!isset($choiceListCache[$hash])) { - $choiceListCache[$hash] = new SimpleChoiceList($choices, $options['preferred_choices']); + $attributes = $options['choices_attr']; + + // Execute the callable with the choice as parameter + if (is_callable($attributes)) { + $callable = $attributes; + $attributes = array(); + + foreach ($choices as $group => $choice) { + if (is_array($choice)) { + foreach ($choice as $key => $value) { + if (!is_array($result = call_user_func($callable, $value))) { + throw new RuntimeException('The callable for "choices_attr" must return an array.'); + } + + $attributes[$group][$key] = $result; + } + } else { + if (!is_array($result = call_user_func($callable, $choice))) { + throw new RuntimeException('The callable for "choices_attr" must return an array.'); + } + + $attributes[$group] = $result; + } + } + } + + $choiceListCache[$hash] = new SimpleChoiceList($choices, $options['preferred_choices'], $attributes); } return $choiceListCache[$hash]; @@ -221,6 +255,7 @@ public function setDefaultOptions(OptionsResolverInterface $resolver) // is manually set to an object. // See https://github.com/symfony/symfony/pull/5582 'data_class' => null, + 'choices_attr' => array(), )); $resolver->setNormalizers(array( @@ -229,6 +264,7 @@ public function setDefaultOptions(OptionsResolverInterface $resolver) $resolver->setAllowedTypes(array( 'choice_list' => array('null', 'Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface'), + 'choices_attr' => array('array', 'callable'), )); } @@ -257,6 +293,7 @@ private function addSubForms(FormBuilderInterface $builder, array $choiceViews, $choiceOpts = array( 'value' => $choiceView->value, 'label' => $choiceView->label, + 'attr' => $choiceView->attributes, 'translation_domain' => $options['translation_domain'], ); diff --git a/src/Symfony/Component/Form/Extension/Core/View/ChoiceView.php b/src/Symfony/Component/Form/Extension/Core/View/ChoiceView.php index 97cdd214c28f8..ccef2cc9349a4 100644 --- a/src/Symfony/Component/Form/Extension/Core/View/ChoiceView.php +++ b/src/Symfony/Component/Form/Extension/Core/View/ChoiceView.php @@ -39,17 +39,26 @@ class ChoiceView */ public $label; + /** + * The choice's attributes. + * + * @var array + */ + public $attributes; + /** * Creates a new ChoiceView. * - * @param mixed $data The original choice. - * @param string $value The view representation of the choice. - * @param string $label The label displayed to humans. + * @param mixed $data The original choice. + * @param string $value The view representation of the choice. + * @param string $label The label displayed to humans. + * @param array $attributes The choice's attributes. */ - public function __construct($data, $value, $label) + public function __construct($data, $value, $label, array $attributes = array()) { $this->data = $data; $this->value = $value; $this->label = $label; + $this->attributes = $attributes; } } diff --git a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php index 532dc41be75f5..1878e131a96bd 100644 --- a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Form\Tests; +use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList; use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormView; use Symfony\Component\Form\Extension\Csrf\CsrfExtension; @@ -1969,4 +1970,102 @@ public function testButtonAttributeHiddenIfFalse() // no foo $this->assertSame('', $html); } + + public function testWidgetChoiceAttributes() + { + $form = $this->factory->createNamed('choice', 'choice', null, array( + 'choices' => array('a' => 'A', 'b' => 'B', 'c' => 'C'), + 'choices_attr' => array( + 'a' => array('disabled' => true, 'data-foo' => 'bar'), + 'c' => array('disabled' => true, 'data-bar' => 'baz'), + ), + )); + + $html = $this->renderWidget($form->createView()); + + $this->assertMatchesXpath($html, + '/select + [@name="choice"] + [ + ./option[@disabled][@data-foo="bar"][@value="a"] + /following-sibling::option[not(@disabled)][@value="b"] + /following-sibling::option[@disabled][@data-bar="baz"][@value="c"] + ] + ' + ); + } + + public function testWidgetChoiceListAttributes() + { + $form = $this->factory->createNamed('choice', 'choice', null, array( + 'choice_list' => new SimpleChoiceList(array('a' => 'A', 'b' => 'B', 'c' => 'C'), array(), array( + 'a' => array('disabled' => true, 'data-foo' => 'bar'), + 'c' => array('disabled' => true, 'data-bar' => 'baz'), + )) + )); + + $html = $this->renderWidget($form->createView()); + + $this->assertMatchesXpath($html, + '/select + [@name="choice"] + [ + ./option[@disabled][@data-foo="bar"][@value="a"] + /following-sibling::option[not(@disabled)][@value="b"] + /following-sibling::option[@disabled][@data-bar="baz"][@value="c"] + ] + ' + ); + } + + public function testWidgetRadioAttributesFromChoice() + { + $form = $this->factory->createNamed('choice', 'choice', null, array( + 'choices' => array('a' => 'A', 'b' => 'B', 'c' => 'C'), + 'choices_attr' => array( + 'a' => array('disabled' => true, 'data-foo' => 'bar'), + 'c' => array('disabled' => true, 'data-bar' => 'baz'), + ), + 'expanded' => true, + )); + + $html = $this->renderWidget($form->createView()); + + $this->assertMatchesXpath($html, + '/div + [@id="choice"] + [ + ./input[@type="radio"][@disabled][@data-foo="bar"][@value="a"] + /following-sibling::input[@type="radio"][not(@disabled)][@value="b"] + /following-sibling::input[@type="radio"][@disabled][@data-bar="baz"][@value="c"] + ] + ' + ); + } + + public function testWidgetCheckboxAttributesFromChoice() + { + $form = $this->factory->createNamed('choice', 'choice', null, array( + 'choices' => array('a' => 'A', 'b' => 'B', 'c' => 'C'), + 'choices_attr' => array( + 'a' => array('disabled' => true, 'data-foo' => 'bar'), + 'c' => array('disabled' => true, 'data-bar' => 'baz'), + ), + 'expanded' => true, + 'multiple' => true, + )); + + $html = $this->renderWidget($form->createView()); + + $this->assertMatchesXpath($html, + '/div + [@id="choice"] + [ + ./input[@type="checkbox"][@disabled][@data-foo="bar"][@value="a"] + /following-sibling::input[@type="checkbox"][not(@disabled)][@value="b"] + /following-sibling::input[@type="checkbox"][@disabled][@data-bar="baz"][@value="c"] + ] + ' + ); + } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/ChoiceList/ChoiceListTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/ChoiceList/ChoiceListTest.php index 59f002d8ccb00..b7cb5fbb8db8e 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/ChoiceList/ChoiceListTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/ChoiceList/ChoiceListTest.php @@ -48,6 +48,25 @@ public function testInitArray() $this->assertEquals(array(0 => new ChoiceView($this->obj1, '0', 'A'), 2 => new ChoiceView($this->obj3, '2', 'C'), 3 => new ChoiceView($this->obj4, '3', 'D')), $this->list->getRemainingViews()); } + public function testInitAttributes() + { + $list = new ChoiceList( + array($this->obj1, $this->obj2, $this->obj3, $this->obj4), + array('A', 'B', 'C', 'D'), + array(), + array(array('foo' => 'bar1'), array('foo' => 'bar2'), array('foo' => 'bar3')) + ); + + $this->assertEquals(array( + 0 => new ChoiceView($this->obj1, '0', 'A', array('foo' => 'bar1')), + 1 => new ChoiceView($this->obj2, '1', 'B', array('foo' => 'bar2')), + 2 => new ChoiceView($this->obj3, '2', 'C', array('foo' => 'bar3')), + 3 => new ChoiceView($this->obj4, '3', 'D') + ), + $list->getRemainingViews() + ); + } + /** * Necessary for interoperability with MongoDB cursors or ORM relations as * choices parameter. A choice itself that is an object implementing \Traversable @@ -94,6 +113,38 @@ public function testInitNestedArray() ), $this->list->getRemainingViews()); } + public function testInitNestedAttributes() + { + $list = new ChoiceList( + array( + 'Group 1' => array($this->obj1, $this->obj2), + 'Group 2' => array($this->obj3, $this->obj4), + ), + array( + 'Group 1' => array('A', 'B'), + 'Group 2' => array('C', 'D'), + ), + array(), + array( + 'Group 1' => array(array('foo' => 'bar1'), array('foo' => 'bar2')), + 'Group 2' => array(array(), array('foo' => 'bar4')) + ) + ); + + $this->assertEquals(array( + 'Group 1' => array( + new ChoiceView($this->obj1, '0', 'A', array('foo' => 'bar1')), + new ChoiceView($this->obj2, '1', 'B', array('foo' => 'bar2')) + ), + 'Group 2' => array( + 2 => new ChoiceView($this->obj3, '2', 'C'), + 3 => new ChoiceView($this->obj4, '3', 'D', array('foo' => 'bar4')) + ) + ), + $list->getRemainingViews() + ); + } + /** * @expectedException \InvalidArgumentException */ diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/ChoiceList/ObjectChoiceListTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/ChoiceList/ObjectChoiceListTest.php index 655101ca1a784..b201cb4ec293e 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/ChoiceList/ObjectChoiceListTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/ChoiceList/ObjectChoiceListTest.php @@ -63,6 +63,24 @@ public function testInitArray() $this->assertEquals(array(0 => new ChoiceView($this->obj1, '0', 'A'), 2 => new ChoiceView($this->obj3, '2', 'C'), 3 => new ChoiceView($this->obj4, '3', 'D')), $this->list->getRemainingViews()); } + public function testInitAttributes() + { + $list = new ObjectChoiceList( + array($this->obj1, $this->obj2, $this->obj3, $this->obj4), + 'name', array(), null, null, null, + array(array('foo' => 'bar1'), array('foo' => 'bar2'), array('foo' => 'bar3'), array('foo' => 'bar4')) + ); + + $this->assertEquals(array( + 0 => new ChoiceView($this->obj1, '0', 'A', array('foo' => 'bar1')), + 1 => new ChoiceView($this->obj2, '1', 'B', array('foo' => 'bar2')), + 2 => new ChoiceView($this->obj3, '2', 'C', array('foo' => 'bar3')), + 3 => new ChoiceView($this->obj4, '3', 'D', array('foo' => 'bar4')) + ), + $list->getRemainingViews() + ); + } + public function testInitNestedArray() { $this->assertSame(array($this->obj1, $this->obj2, $this->obj3, $this->obj4), $this->list->getChoices()); @@ -77,6 +95,34 @@ public function testInitNestedArray() ), $this->list->getRemainingViews()); } + public function testInitNestedAttributes() + { + $list = new ObjectChoiceList( + array( + 'Group 1' => array($this->obj1, $this->obj2), + 'Group 2' => array($this->obj3, $this->obj4), + ), + 'name', array(), null, null, null, + array( + 'Group 1' => array(array('foo' => 'bar1'), array('foo' => 'bar2')), + 'Group 2' => array(array(), array('foo' => 'bar4')) + ) + ); + + $this->assertEquals(array( + 'Group 1' => array( + new ChoiceView($this->obj1, '0', 'A', array('foo' => 'bar1')), + new ChoiceView($this->obj2, '1', 'B', array('foo' => 'bar2')) + ), + 'Group 2' => array( + 2 => new ChoiceView($this->obj3, '2', 'C'), + 3 => new ChoiceView($this->obj4, '3', 'D', array('foo' => 'bar4')) + ) + ), + $list->getRemainingViews() + ); + } + public function testInitArrayWithGroupPath() { $this->obj1 = (object) array('name' => 'A', 'category' => 'Group 1'); diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/ChoiceList/SimpleChoiceListTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/ChoiceList/SimpleChoiceListTest.php index 838a8e0864e41..72b59963b19d8 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/ChoiceList/SimpleChoiceListTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/ChoiceList/SimpleChoiceListTest.php @@ -27,6 +27,23 @@ public function testInitArray() $this->assertEquals(array(0 => new ChoiceView('a', 'a', 'A'), 2 => new ChoiceView('c', 'c', 'C')), $this->list->getRemainingViews()); } + public function testInitAttributes() + { + $list = new SimpleChoiceList( + array('a' => 'A', 'b' => 'B', 'c' => 'C'), + array(), + array('a' => array('foo' => 'bar1'), 'c' => array('foo' => 'bar3')) + ); + + $this->assertEquals(array( + new ChoiceView('a', 'a', 'A', array('foo' => 'bar1')), + new ChoiceView('b', 'b', 'B'), + new ChoiceView('c', 'c', 'C', array('foo' => 'bar3')) + ), + $list->getRemainingViews() + ); + } + public function testInitNestedArray() { $this->assertSame(array(0 => 'a', 1 => 'b', 2 => 'c', 3 => 'd'), $this->list->getChoices()); @@ -41,6 +58,34 @@ public function testInitNestedArray() ), $this->list->getRemainingViews()); } + public function testInitNestedAttributes() + { + $list = new SimpleChoiceList( + array( + 'Group 1' => array('a' => 'A', 'b' => 'B'), + 'Group 2' => array('c' => 'C', 'd' => 'D'), + ), + array(), + array( + 'Group 1' => array('a' => array('foo' => 'bar1'), 'b' => array('foo' => 'bar2')), + 'Group 2' => array('c' => array(), 'd' => array('foo' => 'bar4')) + ) + ); + + $this->assertEquals(array( + 'Group 1' => array( + new ChoiceView('a', 'a', 'A', array('foo' => 'bar1')), + new ChoiceView('b', 'b', 'B', array('foo' => 'bar2')) + ), + 'Group 2' => array( + 2 => new ChoiceView('c', 'c', 'C'), + 3 => new ChoiceView('d', 'd', 'D', array('foo' => 'bar4')) + ) + ), + $list->getRemainingViews() + ); + } + /** * @dataProvider dirtyValuesProvider */ diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php index b251da031c61a..9d95b1ea0eff6 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php @@ -86,6 +86,30 @@ public function testChoiceListOptionExpectsChoiceListInterface() )); } + /** + * @expectedException \Symfony\Component\Form\Exception\InvalidConfigurationException + */ + public function testChoiceListSpecifiedAndAttributes() + { + $this->factory->create('choice', null, array( + 'choice_list' => new ObjectChoiceList(array()), + 'choices_attr' => array(1), + )); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\RuntimeException + */ + public function testChoiceAttributesCallableDoesNotReturnArray() + { + $this->factory->create('choice', null, array( + 'choices' => array('a' => 'A', 'b' => 'B', 'c' => 'C'), + 'choices_attr' => function () { + return false; + } + )); + } + public function testChoiceListAndChoicesCanBeEmpty() { $this->factory->create('choice'); @@ -1274,4 +1298,125 @@ public function testInitializeWithDefaultObjectChoice() // Trigger data initialization $form->getViewData(); } + + public function testPassAttributesToView() + { + $attributes = array('foo' => 'bar', 'bar' => 'baz'); + + $form = $this->factory->create('choice', null, array( + 'choices' => array('a' => 'A', 'b' => 'B', 'c' => 'C'), + 'choices_attr' => array('a' => $attributes, 'c' => $attributes), + )); + + $view = $form->createView(); + + $this->assertEquals(array( + new ChoiceView('a', 'a', 'A', $attributes), + new ChoiceView('b', 'b', 'B'), + new ChoiceView('c', 'c', 'C', $attributes), + ), $view->vars['choices']); + } + + public function testPassAttributesToViewWithCallable() + { + $form = $this->factory->create('choice', null, array( + 'choices' => array('a' => 'A', 'b' => 'B', 'c' => 'C'), + 'choices_attr' => function ($choice) { + return array('choice' => $choice); + } + )); + + $view = $form->createView(); + + $this->assertEquals(array( + new ChoiceView('a', 'a', 'A', array('choice' => 'A')), + new ChoiceView('b', 'b', 'B', array('choice' => 'B')), + new ChoiceView('c', 'c', 'C', array('choice' => 'C')), + ), $view->vars['choices']); + } + + public function testPassAttributesToViewNestedWithCallable() + { + $form = $this->factory->create('choice', null, array( + 'choices' => array( + 'Group 1' => array('a' => 'A', 'b' => 'B'), + 'Group 2' => array('c' => 'C', 'd' => 'D'), + ), + 'choices_attr' => function ($choice) { + return array('choice' => $choice); + } + )); + + $view = $form->createView(); + + $this->assertEquals(array( + 'Group 1' => array( + 0 => new ChoiceView('a', 'a', 'A', array('choice' => 'A')), + 1 => new ChoiceView('b', 'b', 'B', array('choice' => 'B')), + ), + 'Group 2' => array( + 2 => new ChoiceView('c', 'c', 'C', array('choice' => 'C')), + 3 => new ChoiceView('d', 'd', 'D', array('choice' => 'D')), + ), + ), $view->vars['choices']); + } + + public function testPassAttributesToViewFromChoiceList() + { + $attributes = array('foo' => 'bar', 'bar' => 'baz'); + $obj1 = (object) array('value' => 'a', 'label' => 'A'); + $obj2 = (object) array('value' => 'b', 'label' => 'B'); + $obj3 = (object) array('value' => 'c', 'label' => 'C'); + + $form = $this->factory->create('choice', null, array( + 'choice_list' => new ObjectChoiceList(array($obj1, $obj2, $obj3), 'label', array(), null, 'value', null, array($attributes, array(), $attributes)) + )); + + $view = $form->createView(); + + $this->assertEquals(array( + new ChoiceView($obj1, 'a', 'A', $attributes), + new ChoiceView($obj2, 'b', 'B'), + new ChoiceView($obj3, 'c', 'C', $attributes), + ), $view->vars['choices']); + } + + public function testExpandedAttributesArePassedToChildrenRadios() + { + $attributes = array('foo' => 'bar', 'bar' => 'baz'); + + $form = $this->factory->create('choice', null, array( + 'choices' => array('a' => 'A', 'b' => 'B', 'c' => 'C'), + 'choices_attr' => array('a' => $attributes, 'c' => $attributes), + 'expanded' => true, + )); + + $optionsA = $form->get(0)->getConfig()->getOptions(); + $optionsB = $form->get(1)->getConfig()->getOptions(); + $optionsC = $form->get(2)->getConfig()->getOptions(); + + $this->assertEquals($attributes, $optionsA['attr']); + $this->assertEmpty($optionsB['attr']); + $this->assertEquals($attributes, $optionsC['attr']); + } + + public function testExpandedAttributesArePassedToChildrenCheckboxes() + { + $attributes = array('foo' => 'bar', 'bar' => 'baz'); + + $form = $this->factory->create('choice', null, array( + 'choices' => array('a' => 'A', 'b' => 'B', 'c' => 'C'), + 'choices_attr' => array('a' => $attributes, 'c' => $attributes), + 'expanded' => true, + 'multiple' => true, + )); + + $optionsA = $form->get(0)->getConfig()->getOptions(); + $optionsB = $form->get(1)->getConfig()->getOptions(); + $optionsC = $form->get(2)->getConfig()->getOptions(); + + $this->assertEquals($attributes, $optionsA['attr']); + $this->assertEmpty($optionsB['attr']); + $this->assertEquals($attributes, $optionsC['attr']); + } }