38

I want to include a collection type inside another collection type. It should look like this: enter image description here

Using just one collection works fine, but I need to edit the prototype of the outer form, so it renders the prototype of the inner form for each line.

Any ideas how could I do that? Also what would be the best way to save

EDIT: Now I am trying to render the prototype of the nested form:

  <ul class="characteristics-container" data-prototype="{{ form_widget(form.characteristics.vars.prototype)|e }}" data-prototype-options="{{ form_widget(form.characteristics.options.vars.prototype|e ) }}">
                    {# iterate over each existing tag and render its only field: name #}
                    {% for characteristic in form.characteristics %}
                        <li>{{ form_row(characteristic.name) }}</li>

                        <div class="characteristics-options">
                            {% for opt in form.characteristics.options %}

                            {% endfor %}                     
                        </div>


                    {% endfor %}
                </ul>

It gives error in form_widget(form.characteristics.options.vars.prototype|e

Method "options" for object "Symfony\Component\Form\FormView" does not exist in 

I tried characteristics[0], and it says the key doesnt exist

Here are my form classes:

PromotionType (the base form)

$builder              
            ->add('characteristics','collection', array(
                'label'         => 'Caracteristicas',
                 'type'         => new PromotionCharacteristicType(),
                 'allow_add'    => true,
                 'allow_delete' => true,
                 'by_reference' => false
            ))

PromotionCharacteristicType

 $builder
            ->add('name',NULL, array('label'  => 'Nome'))
            ->add('options', 'collection', array(
                'type' => new PromotionCharacteristicOptionType(),
                'allow_add' => true,
                'allow_delete' => true,      
                'prototype' => true,
                'by_reference' => false,
            ))                       
        ;

PromotionCharacteristicOptionType

 $builder
            ->add('name',NULL, array('label'  => 'Nome')) 
        ;

The first level prototype, works fine.

brpaz
  • 3,453
  • 9
  • 44
  • 70
  • Hi all. I am trying to develop similar forms, to create (new) and edit nested entities. I developed a toy Symfony 3.1 bundle for entities A 1:m B 1:m C. It seems to work. I appreciate if you check and suggest how to improve/rewrite it. See at https://github.com/mario6097/SimpleBundle. Thanks. – mario Jan 30 '17 at 18:22

2 Answers2

32

forms and prototype

You need to keep two prototypes from different collections. Symfony offers to store them in a data-prototype attribute of div tag, which wrap the collection. In your situation it`s very inefficient. So you can just render it by hand in empty div somethere

Example you have characters form

class CharacterType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('opts', 'collection', array(
            'type' => new OptionType(),
            'allow_add' => true,
            'allow_delete' => true,
            'prototype' => true,
            'prototype_name' => '__opt_prot__'
        ));
        $builder->add('char_desc', 'text');
    }

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

Then create form that has characters collection

$form = $this->createFormBuilder()
    ->add('chars', 'collection', array(
        'type' => new CharacterType(),
        'allow_add' => true,
        'allow_delete' => true,
        'prototype_name' => '__char_prot__'
    ))
    ->getForm();

    # example data
    $form->setData(
        array(
            'chars' => array(
                array('options' => array(), 'char_desc' => 1),
                array('options' => array(), 'char_desc' => 2),
            ),
        )
    );

and get prototypes

<div
    id="prots"
    data-prototype-opt="{{ form_widget(form.chars.vars.prototype.children['opts'].vars.prototype) | e }}"
    data-prototype-char="{{ form_widget(form.chars.vars.prototype) | e }}"
>
</div>

And then render collection like in this example or override collection_widget block

{% for char in form.chars %}
    {{ form_row(char.char_desc) }}
    <label for="">opts</label>
    {% for opt in char.opts %}
        {{ form_row(opt.text) }}
    {% endfor %}
{% endfor %}

how to save it

Use nosql database if you can. Or use EAV model for relation databases. But if you do not need to search through the options, or sort them, you can store a serialized array in the database and use the doctrine type array

Alexey B.
  • 11,570
  • 2
  • 45
  • 71
  • I have a problem with data-prototype2: Key "0" in object (with ArrayAccess) of type "Symfony\Component\Form\FormView" does not exist. – brpaz May 13 '13 at 10:33
  • Sorry, set up new version of symfony and see the error. Update my answer. – Alexey B. May 14 '13 at 06:48
  • 1
    Thanks. Only in your answer I found how to reach the prototype in nested forms: `data-prototype-opt="{{ form_widget(form.chars.vars.prototype.children['opts'].vars.prototype) | e }}"`. – ziiweb Jan 25 '15 at 10:05
  • Some strange behaviour: nested form prototype is available only before form rendering! Maybe some bug in my code, maybe I will save some else time for researching :) – po_taka Aug 06 '15 at 08:16
  • @brpaz : can u please tell me how your javascript, html and formtypes finally are created? I am struggling with the same, as you can see here: – Lex Hartman Nov 11 '16 at 11:10
  • http://stackoverflow.com/questions/40514638/embedded-collection-in-embedded-collection-add-button-symfony-2-8 and also i tried creating a fiddle for my problem: http://jsfiddle.net/lexhartman/s5Lrtjtf/ (use the 'click me' button to update the form and to see the add button for the child forms. The problem is that when a child form is inserted in the 1st parentform and a 2nd parent form is added you can no longer add childforms to the 1st parent form... and also a lot of other wrong coded functions... So i would like to see how you ended up solving this problem. Thanx in advance! – Lex Hartman Nov 11 '16 at 11:16
  • @Lex, did you fix your fiddle? – mario Nov 28 '16 at 09:35
  • 1
    The order of prototypes is critical: the most nested one should go first, and then - its parents (in reverse order). Otherwise child prototype will go to the parent, and you'll get an empty string in the template. – Vasiliy Toporov May 25 '17 at 14:59
1

Adding to Alexey B.'s answer, I don't usually seem to need to use prototype.children['opts'] in order to be able to access the prototype for the collection. I just use prototype.<collection_name> as follows:

<div
    id="prots"
    data-prototype-opt="{{ form_widget(form.chars.vars.prototype.opts.vars.prototype) | e }}"
    data-prototype-char="{{ form_widget(form.chars.vars.prototype) | e }}"
>
</div>
Community
  • 1
  • 1
kerdany
  • 21
  • 2
  • This is because Twig uses dot notation to access associative array index, object get method of the same name, or object property. Allowing `prototype.opts` to access `prototype['opts']` or `prototype->getOpts()` or `prototype->opts`. – Will B. Apr 12 '20 at 01:55