Skip to content

Commit

Permalink
feature #48343 [Form] [ChoiceType] Add a placeholder_attr option (e…
Browse files Browse the repository at this point in the history
…lementaire)

This PR was merged into the 6.3 branch.

Discussion
----------

[Form] [ChoiceType] Add a `placeholder_attr` option

| Q             | A
| ------------- | ---
| Branch?       | 6.3
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Tickets       | #38958
| Similar Pull Request | #47570
| License       | MIT
| Doc PR        | symfony/symfony-docs#17469

Allow to custom the attributes of the placeholder (`data-*`, `title`, `disabled`, etc.).

Commits
-------

2d4396a [Form][ChoiceType] Add placeholder_attr field option
  • Loading branch information
fabpot committed Apr 8, 2023
2 parents 4610857 + 2d4396a commit 3067b9b
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
{%- endif -%}
<select {{ block('widget_attributes') }}{% if multiple %} multiple="multiple"{% endif %}>
{%- if placeholder is not none -%}
<option value=""{% if required and value is empty %} selected="selected"{% endif %}>{{ placeholder != '' ? (translation_domain is same as(false) ? placeholder : placeholder|trans({}, translation_domain)) }}</option>
<option value=""{% if placeholder_attr|default({}) %}{% with { attr: placeholder_attr } %}{{ block('attributes') }}{% endwith %}{% endif %}{% if required and value is empty %} selected="selected"{% endif %}>{{ placeholder != '' ? (translation_domain is same as(false) ? placeholder : placeholder|trans({}, translation_domain)) }}</option>
{%- endif -%}
{%- if preferred_choices|length > 0 -%}
{% set options = preferred_choices %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@
{%- endif -%}
<select {{ block('widget_attributes') }}{% if multiple %} multiple="multiple" data-customforms="disabled"{% endif %}>
{% if placeholder is not none -%}
<option value=""{% if required and value is empty %} selected="selected"{% endif %}>{{ translation_domain is same as(false) ? placeholder : placeholder|trans({}, translation_domain) }}</option>
<option value=""{% if placeholder_attr|default({}) %}{% with { attr: placeholder_attr } %}{{ block('attributes') }}{% endwith %}{% endif %}{% if required and value is empty %} selected="selected"{% endif %}>{{ translation_domain is same as(false) ? placeholder : placeholder|trans({}, translation_domain) }}</option>
{%- endif %}
{%- if preferred_choices|length > 0 -%}
{% set options = preferred_choices %}
Expand Down
5 changes: 5 additions & 0 deletions src/Symfony/Component/Form/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
CHANGELOG
=========

6.3
---

* Add a `placeholder_attr` option to `ChoiceType`

6.2
---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public function buildForm(FormBuilderInterface $builder, array $options)
// Check if the choices already contain the empty value
// Only add the placeholder option if this is not the case
if (null !== $options['placeholder'] && 0 === \count($choiceList->getChoicesForValues(['']))) {
$placeholderView = new ChoiceView(null, '', $options['placeholder']);
$placeholderView = new ChoiceView(null, '', $options['placeholder'], $options['placeholder_attr']);

// "placeholder" is a reserved name
$this->addSubForm($builder, 'placeholder', $placeholderView, $options);
Expand Down Expand Up @@ -237,6 +237,7 @@ public function buildView(FormView $view, FormInterface $form, array $options)
'choices' => $choiceListView->choices,
'separator' => '-------------------',
'placeholder' => null,
'placeholder_attr' => [],
'choice_translation_domain' => $choiceTranslationDomain,
'choice_translation_parameters' => $options['choice_translation_parameters'],
]);
Expand All @@ -257,6 +258,7 @@ public function buildView(FormView $view, FormInterface $form, array $options)
// Only add the empty value option if this is not the case
if (null !== $options['placeholder'] && !$view->vars['placeholder_in_choices']) {
$view->vars['placeholder'] = $options['placeholder'];
$view->vars['placeholder_attr'] = $options['placeholder_attr'];
}

if ($options['multiple'] && !$options['expanded']) {
Expand Down Expand Up @@ -344,6 +346,7 @@ public function configureOptions(OptionsResolver $resolver)
'group_by' => null,
'empty_data' => $emptyData,
'placeholder' => $placeholderDefault,
'placeholder_attr' => [],
'error_bubbling' => false,
'compound' => $compound,
// The view data is always a string or an array of strings,
Expand All @@ -367,6 +370,7 @@ public function configureOptions(OptionsResolver $resolver)
$resolver->setAllowedTypes('choice_value', ['null', 'callable', 'string', PropertyPath::class, ChoiceValue::class]);
$resolver->setAllowedTypes('choice_attr', ['null', 'array', 'callable', 'string', PropertyPath::class, ChoiceAttr::class]);
$resolver->setAllowedTypes('choice_translation_parameters', ['null', 'array', 'callable', ChoiceTranslationParameters::class]);
$resolver->setAllowedTypes('placeholder_attr', ['array']);
$resolver->setAllowedTypes('preferred_choices', ['array', \Traversable::class, 'callable', 'string', PropertyPath::class, PreferredChoice::class]);
$resolver->setAllowedTypes('group_by', ['null', 'callable', 'string', PropertyPath::class, GroupBy::class]);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,14 @@ public function testChoiceLoaderOptionExpectsChoiceLoaderInterface()
]);
}

public function testPlaceholderAttrOptionExpectsArray()
{
$this->expectException(InvalidOptionsException::class);
$this->factory->create(static::TESTED_TYPE, null, [
'placeholder_attr' => new \stdClass(),
]);
}

public function testChoiceListAndChoicesCanBeEmpty()
{
$this->assertInstanceOf(FormInterface::class, $this->factory->create(static::TESTED_TYPE, null, []));
Expand Down Expand Up @@ -189,15 +197,19 @@ public function testExpandedChoiceListWithBooleanAndNullValuesAndFalseAsPreSetDa

public function testPlaceholderPresentOnNonRequiredExpandedSingleChoice()
{
$placeholderAttr = ['attr' => 'value'];

$form = $this->factory->create(static::TESTED_TYPE, null, [
'multiple' => false,
'expanded' => true,
'required' => false,
'choices' => $this->choices,
'placeholder_attr' => $placeholderAttr,
]);

$this->assertArrayHasKey('placeholder', $form);
$this->assertCount(\count($this->choices) + 1, $form, 'Each choice should become a new field');
$this->assertSame($placeholderAttr, $form->createView()->children['placeholder']->vars['attr']);
}

public function testPlaceholderNotPresentIfRequired()
Expand Down Expand Up @@ -1667,80 +1679,84 @@ public function testPlaceholderIsEmptyStringByDefaultIfNotRequired()
/**
* @dataProvider getOptionsWithPlaceholder
*/
public function testPassPlaceholderToView($multiple, $expanded, $required, $placeholder, $viewValue)
public function testPassPlaceholderToView($multiple, $expanded, $required, $placeholder, $placeholderViewValue, $placeholderAttr, $placeholderAttrViewValue)
{
$view = $this->factory->create(static::TESTED_TYPE, null, [
'multiple' => $multiple,
'expanded' => $expanded,
'required' => $required,
'placeholder' => $placeholder,
'placeholder_attr' => $placeholderAttr,
'choices' => $this->choices,
])
->createView();

$this->assertSame($viewValue, $view->vars['placeholder']);
$this->assertSame($placeholderViewValue, $view->vars['placeholder']);
$this->assertSame($placeholderAttrViewValue, $view->vars['placeholder_attr']);
$this->assertFalse($view->vars['placeholder_in_choices']);
}

/**
* @dataProvider getOptionsWithPlaceholder
*/
public function testDontPassPlaceholderIfContainedInChoices($multiple, $expanded, $required, $placeholder, $viewValue)
public function testDontPassPlaceholderIfContainedInChoices($multiple, $expanded, $required, $placeholder, $placeholderViewValue, $placeholderAttr, $placeholderAttrViewValue)
{
$view = $this->factory->create(static::TESTED_TYPE, null, [
'multiple' => $multiple,
'expanded' => $expanded,
'required' => $required,
'placeholder' => $placeholder,
'placeholder_attr' => $placeholderAttr,
'choices' => ['Empty' => '', 'A' => 'a'],
])
->createView();

$this->assertNull($view->vars['placeholder']);
$this->assertSame([], $view->vars['placeholder_attr']);
$this->assertTrue($view->vars['placeholder_in_choices']);
}

public static function getOptionsWithPlaceholder()
{
return [
// single non-expanded
[false, false, false, 'foobar', 'foobar'],
[false, false, false, '', ''],
[false, false, false, null, null],
[false, false, false, false, null],
[false, false, true, 'foobar', 'foobar'],
[false, false, true, '', ''],
[false, false, true, null, null],
[false, false, true, false, null],
[false, false, false, 'foobar', 'foobar', ['attr' => 'value'], ['attr' => 'value']],
[false, false, false, '', '', ['attr' => 'value'], ['attr' => 'value']],
[false, false, false, null, null, ['attr' => 'value'], []],
[false, false, false, false, null, ['attr' => 'value'], []],
[false, false, true, 'foobar', 'foobar', ['attr' => 'value'], ['attr' => 'value']],
[false, false, true, '', '', ['attr' => 'value'], ['attr' => 'value']],
[false, false, true, null, null, ['attr' => 'value'], []],
[false, false, true, false, null, ['attr' => 'value'], []],
// single expanded
[false, true, false, 'foobar', 'foobar'],
[false, true, false, 'foobar', 'foobar', ['attr' => 'value'], ['attr' => 'value']],
// radios should never have an empty label
[false, true, false, '', 'None'],
[false, true, false, null, null],
[false, true, false, false, null],
[false, true, false, '', 'None', ['attr' => 'value'], ['attr' => 'value']],
[false, true, false, null, null, ['attr' => 'value'], []],
[false, true, false, false, null, ['attr' => 'value'], []],
// required radios should never have a placeholder
[false, true, true, 'foobar', null],
[false, true, true, '', null],
[false, true, true, null, null],
[false, true, true, false, null],
[false, true, true, 'foobar', null, ['attr' => 'value'], []],
[false, true, true, '', null, ['attr' => 'value'], []],
[false, true, true, null, null, ['attr' => 'value'], []],
[false, true, true, false, null, ['attr' => 'value'], []],
// multiple non-expanded
[true, false, false, 'foobar', null],
[true, false, false, '', null],
[true, false, false, null, null],
[true, false, false, false, null],
[true, false, true, 'foobar', null],
[true, false, true, '', null],
[true, false, true, null, null],
[true, false, true, false, null],
[true, false, false, 'foobar', null, ['attr' => 'value'], []],
[true, false, false, '', null, ['attr' => 'value'], []],
[true, false, false, null, null, ['attr' => 'value'], []],
[true, false, false, false, null, ['attr' => 'value'], []],
[true, false, true, 'foobar', null, ['attr' => 'value'], []],
[true, false, true, '', null, ['attr' => 'value'], []],
[true, false, true, null, null, ['attr' => 'value'], []],
[true, false, true, false, null, ['attr' => 'value'], []],
// multiple expanded
[true, true, false, 'foobar', null],
[true, true, false, '', null],
[true, true, false, null, null],
[true, true, false, false, null],
[true, true, true, 'foobar', null],
[true, true, true, '', null],
[true, true, true, null, null],
[true, true, true, false, null],
[true, true, false, 'foobar', null, ['attr' => 'value'], []],
[true, true, false, '', null, ['attr' => 'value'], []],
[true, true, false, null, null, ['attr' => 'value'], []],
[true, true, false, false, null, ['attr' => 'value'], []],
[true, true, true, 'foobar', null, ['attr' => 'value'], []],
[true, true, true, '', null, ['attr' => 'value'], []],
[true, true, true, null, null, ['attr' => 'value'], []],
[true, true, true, false, null, ['attr' => 'value'], []],
];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"group_by",
"multiple",
"placeholder",
"placeholder_attr",
"preferred_choices"
],
"overridden": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ Symfony\Component\Form\Extension\Core\Type\ChoiceType (Block prefix: "choice")
group_by data
multiple disabled
placeholder form_attr
preferred_choices getter
help
placeholder_attr getter
preferred_choices help
help_attr
help_html
help_translation_parameters
Expand Down

0 comments on commit 3067b9b

Please sign in to comment.