Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Group choices when expanded is true #19514

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

Closed
mysterty opened this issue Aug 2, 2016 · 25 comments
Closed

Group choices when expanded is true #19514

mysterty opened this issue Aug 2, 2016 · 25 comments

Comments

@mysterty
Copy link

mysterty commented Aug 2, 2016

Hello,

Actually choices are grouped when using the group_by capability only if expanded is false.
It should be also possible to group choices also when expanded is false:

<p>Group 1</p>
// list widget_choices for group 1
<p>Group 2</p>
// list widget_choices for group 1

http://symfony.com/doc/current/reference/forms/types/entity.html#group-by

@javiereguiluz
Copy link
Member

@mysterty just to make things more clear. Do you think this is a bug we should fix or better explain in the docs ... or are you asking to add this feature to the Form component? Thanks!

@mysterty
Copy link
Author

mysterty commented Aug 3, 2016

According to the doc, it's a bug, cause it isn't said it works only when expanded is false.
But as i know you and symfony, i think it's more a request to add this feature ;)

@ianfp
Copy link

ianfp commented Oct 26, 2016

If @mysterty means "Allow expanded choices to be grouped, too" then I'm +1. Feature request.

@TerjeBr
Copy link

TerjeBr commented Sep 24, 2018

Can be solved by having something like this in the templates:

{%- block choice_widget_expanded -%}
     <div {{ block('widget_container_attributes') }}>
        {% for name, choices in form.vars.choices %}
            {% if choices is iterable  %}

            <label class="choice_category">
                <strong>
                    {{ choice_translation_domain is same as(false) ? name : name|trans({}, choice_translation_domain) }}
                </strong>
            </label>
            <div>
            {% for key,choice in choices %}
                {{ form_widget(form[key]) }}
                {{ form_label(form[key]) }}
            {% endfor %}
            </div>

            {% else %}

            {{- form_widget(form[name]) -}}
            {{- form_label(form[name], null, {translation_domain: choice_translation_domain}) -}}

            {% endif %}
        {% endfor %}
{%- endblock choice_widget_expanded -%}

@Fanny42
Copy link

Fanny42 commented Oct 8, 2019

Hello,
I have the same problem, and it seems that with Symfony 4, the template above isn't working anymore. I tried to fix it, but I can't :/
Can someone confirm that this is not working in Symfony 4 ?
BTW, is it a feature for next version ?

@datqn7244
Copy link

Hi,
This is what I used. Hope it help

{%- block choice_widget_expanded -%}
	{% for name, choices in form.vars.choices %}
		{% if choices is iterable  %}
			<label class="choice_category">
				<strong>
					{{ choice_translation_domain is same as(false) ? name : name|trans({}, choice_translation_domain) }}
				</strong>
			</label>
			<div>
				{% for key,choice in choices %}
					<div class="form-control">
						<div class="custom-control custom-checkbox">
							{{- form_widget(form[key], {
                            'attr':{
                                'class':'custom-control-input',
                                'checked':true
                        }}) -}}
							{{- form_label(form[key], null, {
                            'label_attr':{
                                'class':'custom-control-label'
                            }
                        }) -}}
						</div>
					</div>
				{% endfor %}
			</div>
		{% else %}
        		<label class="choice_category">
				<strong>
					{{ choice_translation_domain is same as(false) ? form[name].vars.label : form[name].vars.label|trans({}, choice_translation_domain) }}
				</strong>
			</label>
			<div class="form-control">
				<div class="custom-control custom-checkbox">
					{{- form_widget(form[name], {
                            'attr':{
                                'class':'custom-control-input',
                                'checked':true
                        }}) -}}
					{{- form_label(form[name], null, {
                            'label_attr':{
                                'class':'custom-control-label'
                            }
                        }) -}}
				</div>
			</div>
		{% endif %}
	{% endfor %}
{%- endblock choice_widget_expanded -%}

@tbordinat
Copy link

tbordinat commented May 12, 2020

Hi,

With your answers, I did this version that works fine for me :

{%- block choice_widget_expanded -%}
    <div {{ block('widget_container_attributes') }}>
        {% if form.vars.choices|length != form.children|length %}
            {% for name, choices in form.vars.choices %}
                <div class="checkbox-group">
                    {% for key,choice in choices %}
                        {{- form_widget(form[key]) -}}
                        {{- form_label(form[key]) -}}
                    {% endfor %}
                </div>
            {% endfor %}
        {% else %}
            {%- for child in form %}
                {{- form_widget(child) -}}
                {{- form_label(child, null, {translation_domain: choice_translation_domain}) -}}
            {% endfor -%}
        {% endif %}
    </div>
{%- endblock choice_widget_expanded -%}

Then, you just have to define your .checkbox-group class, doing what you want.

In my case, I used this

        .checkbox-group:not(:last-child) {
            border-bottom: 1px solid #ccc;
            padding-bottom: 10px;
        }

@ThomasLandauer
Copy link
Contributor

#5489 (comment) works for me on Symfony 4.4. However, the semantically correct way to do this, is to use <fieldset> and <legend> (instead of <div> of <label>).

@xabbuh
Copy link
Member

xabbuh commented Jun 3, 2020

Anyone wants to work on a PR for this?

@tbordinat
Copy link

I can have a try, which solution do you think we have to implement?

@ThomasLandauer
Copy link
Contributor

I guess this has to be implemented into all form themes (form_div_layout.html.twig, foundation_5_layout.html.twig, etc.), right?

I could commit it for bootstrap_4_layout.html.twig. I used #5489 (comment) (=same as #19514 (comment)) as a starting point; since #19514 (comment) and #19514 (comment) didn't really work out for me.
So I could only do the first step, then somebody else needs to take care of the rest, especially checking if translation works and write the tests.

Which branch am I supposed to commit to? master?

@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?

@ThomasLandauer
Copy link
Contributor

@carsonbot Yes :-)

@carsonbot carsonbot removed the Stalled label Feb 18, 2021
@xabbuh
Copy link
Member

xabbuh commented Feb 18, 2021

Someone who is interested in this feature will need to create a pull request against the 5.x branch. :)

@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

Could I get an answer? If I do not hear anything I will assume this issue is resolved or abandoned. Please get back to me <3

@carsonbot
Copy link

Hey,

I didn't hear anything so I'm going to close it. Feel free to comment if this is still relevant, I can always reopen!

@tonybob-tl
Copy link

Since this capability still hasn't been added to Symfony 5, I've enhanced tbordinat's answer to include the placeholder if anyone needs it included in the choice list.

{% block choice_widget_expanded -%}
	<div {{ block('widget_container_attributes') }}> 
        {% if form.vars.choices|length != form.children|length %}
            {% if form.children['placeholder'] is defined %}
                {{- form_widget(form.children['placeholder'], {
                        parent_label_class: label_attr.class|default(''),
                        translation_domain: choice_translation_domain,
                        valid: valid,
                    }) -}}
            {% endif %}
            {% for name, choices in form.vars.choices %}
                <div class='choice_category'>
                    <strong>{{name}}</strong>
                </div>
                <div class="my-radio-group">
                    {% for key,choice in choices %}
                        {{- form_widget(form[key]) -}}
                        {{- form_label(form[key]) -}}
                    {% endfor %}
                </div>
            {% endfor %}
        {% else %}
            {%- for child in form %}
                {{- form_widget(child, {
                        parent_label_class: label_attr.class|default(''),
                        translation_domain: choice_translation_domain,
                        valid: valid,
                    }) -}}
            {% endfor -%}
        {% endif %}
    </div>
{%- endblock choice_widget_expanded %}

@kevinpapst
Copy link

Six years later, SF 6 and still not solved 😁
I wonder if this is just not needed or if everyone is implementing it on project side.
Anyway, thanks @tlarsonlgdor and everyone else, who posted solutions before 👍

@Boutannoura
Copy link

Boutannoura commented Nov 25, 2023

Hi @kevinpapst For SF6

{% block choice_widget_expanded -%}

<div {{ block('widget_container_attributes') }}> 
        {% if form.children['placeholder'] is defined %}
            {{- form_widget(form.children['placeholder'], {
                    parent_label_class: label_attr.class|default(''),
                    translation_domain: choice_translation_domain,
                    valid: valid,
                }) -}}
        {% endif %}
        {% for name, choices in form.vars.choices %}
            <div class='choice_category'>
                <strong>{{name}}</strong>
            </div>
            <div class="my-radio-group">
                {% for key,choice in choices %}
                    {{- form_widget(form[key]) -}}
                    {{- form_label(form[key]) -}}
                {% endfor %}
            </div>
        {% endfor %}
</div>

{%- endblock choice_widget_expanded %}

@janklan
Copy link

janklan commented Mar 27, 2024

I wonder if this is just not needed or if everyone is implementing it on project side.

For me, It's the first time I ever needed it, and I've been working with Symfony since 2012. I guess "it's just not needed" is half of the reason, the other half is "it's not as simple as just throwing <optgroup> into the stew". This means you have to handle rendering of the HTML code of the choices somehow else (with all of the attributes etc), it's not just another <option>, as handled in the OEM code:

{%- block choice_widget_options -%}
    {% for group_label, choice in options %}
        {%- if choice is iterable -%}
            <optgroup label="{{ choice_translation_domain is same as(false) ? group_label : group_label|trans({}, choice_translation_domain) }}">
                {% set options = choice %}
                {{- block('choice_widget_options') -}}
            </optgroup>
        {%- else -%}
            <option value="{{ choice.value }}"{% if choice.attr %}{% with { attr: choice.attr } %}{{ block('attributes') }}{% endwith %}{% endif %}{% if not render_preferred_choices|default(false) and choice is selectedchoice(value) %} selected="selected"{% endif %}>{{ choice_translation_domain is same as(false) ? choice.label : choice.label|trans(choice.labelTranslationParameters, choice_translation_domain) }}</option>
        {%- endif -%}
    {% endfor %}
{%- endblock choice_widget_options -%}

Another complication is that the code above is tapping into the choices variable, which contains the array of either choices or your groups and nested choices - the ultimate problem being the type of the value in that array: ChoiceView. You can't pass that to your standard {{ form_*() }} functions because they require a FormView, meaning you have to re-implement all the styling, etc, if you want to render a ChoiceView directly. Not good if you ask me.

Long story short, in my "solution," I walk the array of form groups and their choices, and then I find the FormView from form.vars.data. It works, it's incredibly hacky, and here it is for your inspiration or amusement:

{%- block choice_widget_expanded -%}
    {%- set attr = attr|merge({ class: attr.class|default('mt-2') }) -%}
    <div {{ block('widget_container_attributes') }}>
        {%- set options = choices -%}
        {{- block('choice_widget_expanded_options') -}}
    </div>
{%- endblock choice_widget_expanded -%}

{%- block choice_widget_expanded_options -%}
    {%- for group_label, option in options -%}
        {%- if option is iterable -%}
            <fieldset>
                <legend>{{ choice_translation_domain is same as(false) ? group_label : group_label|trans({}, choice_translation_domain) }}</legend>
                {%- set options = option -%}
                {{- block('choice_widget_expanded_options') -}}
            </fieldset>
        {%- else -%}

            <div>
                {%- set child = form.children|filter(child => child.vars.value == option.value)|first -%}
                {{- form_widget(child) -}}
                {{- form_label(child, null, {
                    translation_domain: choice_translation_domain,
                    label_attr: label_attr|merge({class: 'label-standard -ml-2 pl-2 pr-2'}),
                    required: false,
                }) -}}
            </div>
        {%- endif -%}
    {% endfor %}
{% endblock %}

I'm curious to know other people's alternatives.

@Alexandre-Fernandez
Copy link

I personally used @janklan's solution, I don't see anything wrong with it, it could probably be copy-pasted in the default form theme of the next symfony release.
Is there a reason why this issue is closed @javiereguiluz ? The documentation doesn't state that "group_by" doesn't work with "expanded".

@janklan
Copy link

janklan commented Apr 2, 2024

it could probably be copy-pasted in the default form theme of the next Symfony release.

That's a pretty brave statement not even I would make :)

@janklan
Copy link

janklan commented Apr 4, 2024

@Alexandre-Fernandez hold your horses with "copy&paste in the default form theme", (1) I've already found the first thing I broke, and (2) I suspect there is more elegant way to use the form rendering mechanism with all the magical block names and prefixes.

The thing that broke was a missing placeholder choice. The reason it broke is because the choice never appears in the array of grouped options, and the template is no longer rendering all form.child elements.

Here is an updated version:

{%- block choice_widget_expanded_options -%}
    {#- See https://github.com/symfony/symfony/issues/19514#issuecomment-2022062889 -#}

    {%- if form.children.placeholder is defined -%}
        {%- with {child: form.children.placeholder} -%}
            {{- block('choice_widget_expanded_option') -}}
        {%- endwith -%}
    {%- endif -%}

    {%- for group_label, option in options -%}
        {%- if option is iterable -%}
            <fieldset>
                <legend>{{ choice_translation_domain is same as(false) ? group_label : group_label|trans({}, choice_translation_domain) }}</legend>
                {%- set options = option -%}
                {{- block('choice_widget_expanded_options') -}}
            </fieldset>
        {%- else -%}
            {%- with {child: form.children|filter(child => child.vars.value == option.value)|first} -%}
                {{- block('choice_widget_expanded_option') -}}
            {%- endwith -%}
        {%- endif -%}
    {%- endfor -%}
{%- endblock -%}

{%- block choice_widget_expanded_option -%}
    <div>
        {%- set label_attr = child.vars.label_attr -%}
        {{- form_widget(child) -}}
        {{- form_label(child, null, {
            translation_domain: choice_translation_domain,
            label_attr: label_attr|merge({class: 'label-standard -ml-2 pl-2 pr-2'}),
            required: false,
        }) -}}
    </div>
{%- endblock -%}

@Alexandre-Fernandez
Copy link

@Alexandre-Fernandez hold your horses with "copy&paste in the default form theme", (1) I've already found the first thing I broke, and (2) I suspect there is more elegant way to use the form rendering mechanism with all the magical block names and prefixes...```

This is why I said "probably", my point still remains however, we could just add something similar to what you have done with minor changes and call it a day. This issue is 8 years old, and the documentation implies that this should work (no mention that it doesn't when expanded is true), I don't see why the issue is closed.
Actually I'll probably create a PR with your updated code if you don't do it yourself.

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