Skip to content

Improve handling of choice fields with no choices in form templates #17579

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
backbone87 opened this issue Jan 27, 2016 · 23 comments
Open

Improve handling of choice fields with no choices in form templates #17579

backbone87 opened this issue Jan 27, 2016 · 23 comments

Comments

@backbone87
Copy link
Contributor

Simple use-case: categories and products
When editing the product an expanded (multiple) choice field of categories is displayed.
However when you create a product before you have created any category, there is simply the label/legend of the choice and some empty containers.

There are multiple solutions i came up with:

  • Add a (configurable) "no choices available" message (my preferred solution)
  • Just output nothing for form_row and form_widget; form_label behaves normal
@javiereguiluz
Copy link
Member

For this kind of issues, a screenshot of the error comes in very handy. Could you please provide one? Thanks!

@backbone87
Copy link
Contributor Author

There is no error?
Its just a choice field with no choices available (which imo is a completely valid use case). The problem is, that the default form tpls (all of their flavors), still output containers, which either needs extra styling or results in empty spaces. And another problem is that a input label is displayed without any inputs to fill. Therefore a message should be displayed to the user "No choices available").

@HeahDude
Copy link
Contributor

@backbone87 you can just use the option 'empty_data' => no choices available or 'empty_data' => $defaultChoicesArray
see http://symfony.com/doc/current/reference/forms/types/choice.html#empty-data

@backbone87
Copy link
Contributor Author

@HeahDude i am talking about extended choices

@HeahDude
Copy link
Contributor

@backbone87 if you mean expanded you need to pass an array 'empty_data' => ['no choices avlaible'], otherwise could you be more precise ?

@backbone87
Copy link
Contributor Author

Choice with options (all fine)

$form->add('categories', 'choice', array(
  'choices_as_values'   => true,
  'choices'         => array('A' => 'a'),
  'multiple'            => true,
  'expanded'            => true,
  'required'            => false,
));

HTML generated with bootstrap 3 layout

<div class="form-group">
  <label class="h4 control-label">Kategorien</label>
  <div id="categories">
    <div class="checkbox">
      <label class="h4">
        <input type="checkbox" id="categories_0" name="categories[]" value="a"> A
      </label>
    </div>
  </div>
</div>

Choice with no options

$form->add('categories', 'choice', array(
  'choices_as_values'   => true,
  'choices'         => array(),
  'multiple'            => true,
  'expanded'            => true,
  'required'            => false,
));

HTML generated with bootstrap 3 layout

<div class="form-group">
  <label class="h4 control-label">Kategorien</label>
  <div id="categories"></div>
</div>

There are multiple things to consider:

  • label without input element
  • empty div
  • no message for the user that there are (currently) no choice available

empty_data does nothing presentation wise. what you mean is probably placeholder (formerly empty_value), which doesnt apply to multiple = true. but even with expanded = true and multiple = false providing a placeholder could, depending on the use case, confuse the user (it would be better to display a message, that no options are available to chose from)

edit: ignore the h4 classes, they come from my form_row call (label_attr)

@HeahDude
Copy link
Contributor

@backbone87 I've found a workaround

<?php

namespace AppBundle\Form\Type;

use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;

class ExtendedChoiceType extends ChoiceType
{
    public function buildView(FormView $view, FormInterface $form, array $options)
    {
        parent::buildView($view, $form, $options);

        if (empty($view->vars['choices'])) {
            $view->vars['empty_choices'] = $options['empty_choices'];
        }
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefault('empty_choices', '<em>No choices available</em>');

        parent::configureOptions($resolver);
    }
}

and then override the twig block with :

{# when using form_div_layout.html.twig #}
{%- block choice_widget_expanded -%}
    <div {{ block('widget_container_attributes') }}>
    {%- for child in form %}
        {{- form_widget(child) -}}
        {{- form_label(child, null, {translation_domain: choice_translation_domain}) -}}
    {% else %}
        {{ empty_choices|raw }}  {# this is it #}
    {% endfor -%}
    </div>
{%- endblock choice_widget_expanded -%}

{# or when using bootstrap_3.html.twig #}
{% block choice_widget_expanded -%}
    {% if '-inline' in label_attr.class|default('') -%}
        <div class="control-group">
            {%- for child in form %}
                {{- form_widget(child, {
                    parent_label_class: label_attr.class|default(''),
                    translation_domain: choice_translation_domain,
                }) -}}
            {% else %}
                {{ empty_choices|raw }}
            {% endfor -%}
        </div>
    {%- else -%}
        <div {{ block('widget_container_attributes') }}>
            {%- for child in form %}
                {{- form_widget(child, {
                    parent_label_class: label_attr.class|default(''),
                    translation_domain: choice_translation_domain,
                }) -}}
            {% else %}
                {{ empty_choices|raw }}
            {% endfor -%}
        </div>
    {%- endif %}
{%- endblock choice_widget_expanded %}

now you can use empty_choices option to customise the value when building forms (string can contain html thanks to raw filter)

@backbone87
Copy link
Contributor Author

That is something similar i am using currently, but i thought it would be a good feature to be included in symfony itself

@HeahDude
Copy link
Contributor

Actually it might look like a bug when choice is expanded since an empty select tag can be obvious but an empty div may not and seems wrong from a developer or user perspective.

On another hand it can't be solved globally without providing a way to customise the empty value IMO which means adding an option to an already over-optionnable ChoiceType and as a feature it could only be merged in 3.1.

However the implementation I suggested above is about 10 lines of code to add in the core and is definitely more dev friendly so maybe it's worth it indeed.

@webmozart
Copy link
Contributor

Thanks for reporting this issue! I think this can be solved quite easily in userland by overriding the _widget block of the field in question:

{% use 'form_div_layout.html.twig' %}

{% block body %}
    {% form_theme form _self %}
    {# ... #}
{% endblock %}

{% block _my_field_id_widget %}
    {%- if form.children is empty -%}
        {# your HTML here #}
    {%- else -%}
        {# default rendering #}
        {{- form_widget(form) -}}
    {%- endif -%}
{% endblock %}

Can you try that?

@backbone87
Copy link
Contributor Author

I use a similar workaround, that works. but the point was to have something like this included in the component itself.

@webmozart
Copy link
Contributor

@backbone87 I see. You could create a PR that renders the placeholder in an expanded choice field if it is empty. This could be a good solution.

@backbone87
Copy link
Contributor Author

@HeahDude is already working on one: #17609

@HeahDude
Copy link
Contributor

#17609 is just an example of implementation.

To defend it, I would say it tries to solve this globally (expanded or not, multiple or not) with a dedicated view var, configurable through the option empty_choices that can be easily transposed to the CollectionType (empty_collection ?) which I think has the same issue when there is no items.

I find it handy to define a default message directly in the form type options.

So @backbone87 feel free to open another PR to explore some other ideas, I've opened mine because I just think it sometimes simplifies the discussion about an implementation by allowing inline comments and showing an explicit diff.

Cheers

@backbone87
Copy link
Contributor Author

@HeahDude i agree with your approach, but ultimately the symfony deciders have to agree. maybe i would rename the option to empty_message to keep a common DX, if it would be used in collection type, too (maybe in a later PR).

@HeahDude
Copy link
Contributor

It sounds like a good idea to use the same option name through different types which could also use by default the same empty_row (?) block in the templates.

It may be a way to be able to use it with compound custom form types when no fields show up, I'm thinking about dynamic forms.

It might even be an approach to fix #17580 in the process since an empty compound form could actually be rendered if it has no children and the empty_form option is different from false or null (??).

The option false by default would not render this block to keep BC, so we could have something like:

  {# for compound #}
  {%- block form_rows -%}
      {% for child in form %}
          {{- form_row(child) -}}
+     {% else %}
+         {% if empty_form|default(false) %}{{ block('empty_row') }}{% endif %}
      {% endfor %}
  {%- endblock form_rows -%}

+ {%- block empty_row -%}
+     {{ empty_form|trans({}, translation_domain) }}
+ {%- endblock empty_row -%}

choice_* blocks would still need a treatment similar to what I've done in #17609.

Then one would just have to override the empty_row block:

{%- block empty_row -%}
    <em>{{ empty_form|default('The field ' ~ full_name ~ ' is empty') }}</em>
{%- endblock empty_row -%}

and set the option:

$resolver->setDefault('empty_form', 'No fields yet but...');

What do you think ?

@backbone87
Copy link
Contributor Author

sounds fine to me in general.

@HeahDude
Copy link
Contributor

@webmozart Forgive me as I just love to debate and I don't fully agree with you on this one.

placeholder is a ChoiceType option only and can be displayed whether there is some choices or not, has a very specific normalization and commonly says something like "Please choose" which makes no sense when there is nothing to choose.

Having another option would answer globally to the case any compound form type, including ChoiceType, has no children, so nothing to render except its label which looks wrong to me and is in imho the main concern of this issue.

@webmozart
Copy link
Contributor

@HeahDude I realized the mistake in my comment already and deleted it. Thanks for pointing it out.

@bandanh
Copy link

bandanh commented Feb 1, 2018

The problem seems to be still there but the workaround is not working anymore.
'choices_as_values' => true, is giving me an error The option "choices_as_values" does not exist.

@xabbuh
Copy link
Member

xabbuh commented Feb 1, 2018

Just omit this option. It was only needed for the migration path from Symfony 2.6 to 3.0.

@carsonbot
Copy link

Thank you for this suggestion.
There has not been a lot of activity here for a while. Would you still like to see this feature?

@carsonbot
Copy link

Friendly ping? Should this still be open? I will close if I don't hear anything.

@carsonbot carsonbot removed the Stalled label Jan 17, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants