Symfony: How to avoid custom form-types getting wrapped in a div automatically?
Asked Answered
D

6

10

UserType Form:

class UserType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('email', 'email', ['label' => 'EMail']);
        // various other fields....
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'validation_groups' => array('registration'),
            'data_class' => 'Vendor\Model\Entity\User',
        ));
    }

    public function getName()
    {
        return 'form_user';
    }
}

TutorType Form:

class TutorType extends Translate
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('user', new UserType(), ['label' => false]);

        $builder->add('school', 'entity', [
            'class' => 'Model:School',
            'property' => 'name',
            'label' => 'Label'
        ]);

        // Various other fields
        $builder->add('save', 'Submit');
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            //'validation_groups' => array('registration'),
            'data_class' => 'Vendor\Model\Entity\Tutor',
            'cascade_validation' => true,
        ));
    }

    public function getName()
    {
        return 'form_tutor';
    }
}

When rendering, the UserType is rendered inside a div, i cant find a way to overcome this.

The Form is rendered as

the result

<form name="form_tutor"
      method="post"
      action=""
      novalidate="novalidate"
      class="form-horizontal form-horizontal"
      id="form_tutor">
    <div id="form_tutor"
         novalidate="novalidate"
         class="form-horizontal">
        <div class="form-group">
            <div class="col-lg-10">
                <div id="form_tutor_user">
                    <div class="form-group">
                        <label class="col-lg-2 control-label aaaa required"
                             for="form_tutor_user_email">EMail</label>

                        <div class="col-lg-10">
                            <input type="email"
                                 id="form_tutor_user_email"
                                 name="form_tutor[user][email]"
                                 required="required"
                                 class="form-control" />
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <div class="form-group">
            <label class="col-lg-2 control-label aaaa required"
                 for="form_tutor_tutorType">Type</label>

            <div class="col-lg-10">
                <select id="form_tutor_tutorType"
                     name="form_tutor[tutorType]"
                     class="form-control">
                    </select>
            </div>
        </div>

        <div class="form-group">
            <div class="col-lg-offset-2 col-lg-10">
                <button type="submit"
                     id="form_tutor_save"
                     name="form_tutor[save]"
                     class="btn btn-default">Speichern</button>
            </div>
        </div><input type="hidden"
             id="form_tutor__token"
             name="form_tutor[_token]"
             class="form-control"
             value="s6i6zPxJs7KU5CiEe8i6Ahg_ca8rc2t5CnSk5yAsUhk" />
    </div>
</form>

The form_tutor_user is wrapped in a own form-group div. I tried to overwrite the form_tutor_user_widget but this is one level to deep. (And only a quick fix, it should be globally applied to all form type - Classes)

How can i change the theme so all custom types are not wrapped with the default form_row template?

Or how do i know in twig when a "subform" is rendered? so i can decide to print the <div class="form-group"> when the child-node is not a subform, or skip it, if this is the case.

TIA

Dong answered 25/2, 2014 at 12:52 Comment(3)
What bootstrap-bundle are you using there? Or is this your own custom boostrap form-theme ? The standard edition doesn't render divs with a form-group class.Kaufman
im using themeforest.net/item/…Dong
and github.com/braincrafted/bootstrap-bundle (but the theme file is slighty modified to match my html)Dong
P
5

By default, in the base form theme:

{% block form_row %}
{% spaceless %}
    <div>
        {{ form_label(form) }}
        {{ form_errors(form) }}
        {{ form_widget(form) }}
    </div>
{% endspaceless %}
{% endblock form_row %}

And, for custom compound forms:

{% block form_widget_compound %}
{% spaceless %}
    <div {{ block('widget_container_attributes') }}>
        {% if form.parent is empty %}
            {{ form_errors(form) }}
        {% endif %}
        {{ block('form_rows') }}
        {{ form_rest(form) }}
    </div>
{% endspaceless %}
{% endblock form_widget_compound %}

Unless you changed something here, the DIV you see should come from either one or the other bit of template.

However, in your specfic example, if form_tutor_user_row is defined, the first bit is never used, and if form_tutor_user_widget is defined, the last bit is never used.

Back to your question. Your question is : "How can i change the theme so all custom types are not wrapped with the default form_row template?"

Here is the problem the way I see it: you want that your TOP forms (the form in which all sub-forms are included) all have a common way of rendering, in sections. Each section will be included in a DIV with class="form-group". You may want to throw in some additional rendering operations but I will limit myself to this to keep things simple.

What you need to do then is to create a specfic form type and make all your TOP forms inherit from this new form type. For instance:

class TopType extends AbstractType
{
    public function getName()
    {
        return 'top_form';
    }
}

... and an inherited form:

class MyFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        ...
    }

    public function getName()
    {
        return 'my_form';
    }

    public function getParent()
    {
        return 'top_form';
    }
}

As you can see, there is no need to make PHP inheritance for form theming inheritance to work.

Template-theming-wise (can I even say that?), if no specific form theming is set for my_form, Symfony will understand that the default form theme to use here is the form theme of top_form, that you can define as such:

{% block top_form_widget %}
{% spaceless %}
    {% for child in form %}
        <div class="form-group">
            {{ form_widget(child) }}
        </div>
    {% endfor %}
    {{ form_rest(form) }}
{% endspaceless %}
{% endblock top_form_widget %}

I should add that this is a problem I already encountered and solved. Tell me how that works for you.

Edit:

To sum it up, what you have to do is:

  • Create the TopType form type,
  • Add the top_form_widget block in your form theme,
  • For all your root forms (i.e. top-level forms, forms that have no parent), add a getParent() method that will return the name of your TopType form ("top_form")
Puberty answered 3/3, 2014 at 16:18 Comment(5)
i will give this a try, but it seems i will end up with a lot of form widget blocks in the theme file. so long the best solution. thanks mate.Dong
But I count only 1 form theme block and 1 class for the whole solution? The 2 first theme blocks in my post come from the SF2 default form theme, they are only here for the sake of explanation.Puberty
maybe i missunderstand, but my_form and top_form have to be unique string, dont they? i have >20 forms/subforms in this application.Dong
In SF2, you must have a single form name ("top_form", "my_form", i.e. strings returned by getName()) by form type, different for each form type. Post edited for clearer directions.Puberty
yep this is what i meant.. but nevermind, this is still the best solution so far. thanks zephyr!Dong
D
3

In theory, if you override the form_widget_compound block in a global form theme this way, it should work as you want:

// app/Resources/views/form.html.twig

{% block form_widget_compound %}
    {% if form.parent is empty %}
        <div {{ block('widget_container_attributes') }}>
        {{ form_errors(form) }}
    {% endif %}
    {{ block('form_rows') }}
    {{ form_rest(form) }}
    {% if form.parent is empty %}
        </div>
    {% endif %}
{% endblock %}

And register your form theme:

// app/config/config.yml
twig:
    form:
        resources:
            - "::form.html.twig"
Dumbhead answered 27/2, 2014 at 23:43 Comment(1)
first, thanks for your answer, unfortunately this does not work. The compound is nested too. (compound code in compound code)Dong
C
3

I usually solve this problem by rendering the nested form's fields manually:

{{ form_row(form.tutor.school) }}
{{ form_row(form.tutor.user.email) }}

Probably that's not the most elegant solution, but it works for me and I haven't looked for an elegant one yet.

Circumscription answered 28/2, 2014 at 14:46 Comment(3)
thanks for your answer. this is my current workaround, but im in the process of needing many forms, which will change a lot (agil project) to change the view every time i change the form is not what i want.Dong
If you find a better way, I'd be happy to know it. It just has never been my top priority.Circumscription
this solution is simple, effective, and will be easy to understand even when I come back to the code months later - perfect!Cardew
C
1

Bootstrap 3.3.4 I ended up doing this.

They key part of this:

 <div class="{% if form.parent.parent is empty %}form-group{% endif %}{% if (not compound or force_error|default(false)) and not valid %} has-error{% endif %}">

Full template.

{% block form_row -%}
    <div class="{% if form.parent.parent is empty %}form-group{% endif %}{% if (not compound or force_error|default(false)) and not valid %} has-error{% endif %}">
        {% if form.parent.parent is null and label is not empty %}
            {{- form_label(form) -}}
        {% elseif label is empty %}
            {{- form_label(form) -}}
        {% endif %}
        {% if compound is empty %}<div class="{{ block('form_group_class') }}">{% endif %}
            {{- form_widget(form) -}}
            {{- form_errors(form) -}}
        {% if compound is empty %}</div>{% endif %}
    </div>
{%- endblock form_row %}
Chelseychelsie answered 23/10, 2019 at 18:41 Comment(1)
in the meantime i came to the same solution - but forgot to comment it. thanks for your answer.Dong
D
0

Maybe it is not an elegant solution, however works for me, because I was also trying to find the solution.

As an example:

{% for custom_field in form.custom_fields %}
<div class="edit_custom">
{{ form_row(custom_field.name) }}
{{ form_row(custom_field.value) }}
</div>
{% endfor %}

<script>
$('.edit_custom').find('input').unwrap();
</script>
Distraint answered 1/12, 2015 at 4:53 Comment(0)
C
-1

Try using form_themes.

First, in you parent template define the form theme:

{% form_theme form with ['BundleName:ControlerName:somesubform_form.html.twig'] %}

btw replace BundleName, ControllerName and somesubform with the proper names.

then render it with:

{{ form_row(form.somesubform) }}
Chichihaerh answered 3/3, 2014 at 16:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.