2

I am trying to follow the Symfony 2.7 docs to create Embed a Collection of Forms using a custom Collection Prototype.

Problem is, that I am not able to create a custom collection prototype as described in the docs.

As in the example there are two simple classes: A Task class that manages the description of the task and additionally any number of tags, represented by its own Tag class

class Task {
    protected $description;
    protected $tags;

    public function __construct() {
        $this->tags = new array();
    }

    // Getter & Setter for description + additional addTag & removeTag methods
    // ...

    // Tags getter
    public function getTags() {
        return $this->tags;
    }
}

class Tag {
    protected $name;
    // ... setName(...), getName()...
}

These are the custom form types:

class TaskType extends AbstractType {
    public function buildForm(FormBuilderInterface $builder, array $options) {
        $builder->add('description');
        $builder->add('tags', 'collection', array('type' => new TagType()));
    }

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

    // ...
}


class TagType extends AbstractType {
    public function buildForm(FormBuilderInterface $builder, array $options) {
        $builder->add('name');
    }

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

    // ...
}

Twig file to render the form

{{ form_start(form) }}
    {# render the task's only field: description #}
    {{ form_row(form.description) }}

    {# render tags - use table instead of ul as in example #}
    <div class="table-responsive">
        <table class="table">
            <thead>
                <th>{{ 'task.tag.headline'|trans }}</th>    
            </thead>
            <tbody class="tags-container" data-prototype="{{ form_widget(form.tags.vars.prototype)|e('html_attr') }}">   
                {{ form_row(form.rules) }}          
            </tbody>
        </table>
    </div>
{{ form_end(form) }}

This works fine and renders the tags list inside the table. However, this code uses the default prototype, that (of course) does not create table rows for the different tags.

I tried to add the code to use a custom prototype as described in the docs. How ever the docs does not say anything about where to add this code or how to use it:

Twig code WITH custom prototype code

{{ form_start(form) }}
    {# Custom Prototype Code from docs #}
    {% form_theme form _self %}

    {% block _tags_entry_widget %}
        <tr>
            <td>{{ form_widget(form.name) }}</td>
        </tr>
    {% endblock %} 


    {# render the task's only field: description #}
    {{ form_row(form.description) }}

    {# render tags - use table instead of ul as in example #}
    <div class="table-responsive">
        <table class="table">
            <thead>
                <th>{{ 'task.tag.headline'|trans }}</th>    
            </thead>
            <tbody class="tags-container" data-prototype="{{ form_widget(form.tags.vars.prototype)|e('html_attr') }}">   
                {{ form_row(form.rules) }}          
            </tbody>
        </table>
    </div>
{{ form_end(form) }}

Using the custom prototype code like this results in the error:

Method "name" for object "Symfony\Component\Form\FormView" does not exist in "MyAppBundle:Task:task.html.twig"

This sounds reasonable, since name belongs to the Tag class and not to the Task class.

Problem 1: How to use/access the Tag form inside the template?**

I removed <td>{{ form_widget(form.name) }}</td> from the prototype template and replaced it with <td>Test</td> to see if the template is used. The result: The template is NOT used and has no effect.

Problem 2: What is the correct way to set/activate the prototype template?

I found other threads dealing with prototype question/problems. The answers propose different solutions using macros, external twig files, etc. Since the Symfony docs seems to offer a solution within the same file without using hacks like macros, I would like to know implement this solution.

1 Answer 1

3

This is working for me - I am sorry - it is Symfony 3, but you should be able to translate.

In the buildForm method of my PersonType class, I have a CollectionType of emails

             ->add( 'emails', CollectionType::class, [
                'label' => 'common.email',
                'entry_type' => AppEmailType::class,
                'by_reference' => true,
                'required' => false,
                'label' => false,
                'empty_data' => null,
                'allow_add' => true,
                'allow_delete' => true,
                'delete_empty' => true,
                'mapped' => false,
                'prototype_name' => '__email__'
                ] )

The template to render the PersonType includes the emails form like so

{% include 'common/emails.html.twig' with {'form': form.emails } %}

common/emails.html.twig

This template is the container for the collection

   <div class="emails">
        <span class="sub-form-legend">{{'common.email'|trans}}</span>
        {{form_row(form)}}
        {% if form.vars.allow_add %}
        <div class="add-one-more-row">{{ 'common.add_one_more'|trans}}</div>
        {% endif %}
    </div>

In fields.html.twig, I have an entry_row template specific to the form, which uses a common email template defined in the same file.

{% block _user_person_emails_entry_row %}
    {{ block('_emails_entry_row') }}
{% endblock %}

{% block _emails_entry_row %}
    {% spaceless %}
        <div class="form-row email">
            <span class="type-select">{{ form_widget(form.type) }}</span>
            <span class="input">{{ form_widget(form.email) }}</span>
            <span class="comment">{{ form_widget(form.comment) }}</span>
            <span class="remove-form-row" title="{{'common.remove'|trans}}" id="email-__email__"><i class="fa fa-remove"></i></span>
        </div>
    {% endspaceless %}
{% endblock %}

To find out the name, trace the names of your forms and prefix them with an underscore. Use {{dump(form)}} to get the unique name.

The HTML will be placed in the data-prototype attribute.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.