vikesh
vikesh

Reputation: 61

Nested form Collection in symfony 2

I have a Form which includes a collection type field, inside the collection type field there is one more collection type field. I have to add nested form fields in the twig but I am unable to populate the form fields and wasn't able to find a example where it shows, how nested fields can be populated with JQuery.

First form class :

class SurveyType extends AbstractType
   {
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
       $builder->add(
                'questiongroups',
                CollectionType::class,
                [
                    'entry_type' => QuestionGroupType::class,
                    'allow_add' => true,
                    'allow_delete' => true,
                    'entry_options' => [
                         'submitOption' => $options['submitOption']
                    ]
                ]
            );
    }

Second form class which have another collection type field :

class QuestionGroupType extends AbstractType
   {
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
       $builder->add(
                'questions',
                CollectionType::class,
                [
                    'entry_type' => QuestionType::class,
                    'allow_add' => true,
                    'allow_delete' => true,
                    'entry_options' => [
                         'submitOption' => $options['submitOption']
                    ],
                    'prototype_name' => '__que__'
                ]
            );
    }

On twig I its like this:


                           <ul id="questiongroups-field-list" data-prototype-question="{{ form_widget(form.questiongroups.vars.prototype.children['questions'].vars.prototype)|e }}"
                        data-prototype="{{ form_widget(form.questiongroups.vars.prototype)|e}}"
                        data-widget-tags="{{'<li></li>'|e}}">

My Jquery for Populating the fields:

$(document).on('click', '.add-another-collection-widget', function(e){
                var list = $($(this).attr('data-list'));
                var counter = list.data('widget-counter') | list.children().length;

                var newWidget = list.attr('data-prototype');
                newWidget = newWidget.replace(/__name__/g, counter);
                console.log(newWidget);
                counter++;

                list.data('widget-counter', counter);

                var newElem = jQuery(list.attr('data-widget-tags')).html(newWidget);
                newElem.appendTo(list);
                addTagFormDeleteLink(newElem);
            });

            function addTagFormDeleteLink($tagFormLi) {
                var $addQuestionButton = $('<button class="button" type="button">Add Question</button>');
                var $removeFormButton = $('<button class="alert button" type="button">Delete Group</button>');
                $tagFormLi.append($addQuestionButton);
                $tagFormLi.append($removeFormButton);
                $removeFormButton.on('click', function(e) {
                    // remove the li for the tag form
                    $tagFormLi.remove();
                });

                $addQuestionButton.on('click', function(){
                    var list = $('#questiongroups-field-list');
                    var counter = list.data('widget-counter') | list.children().length;
                    var newWidget = list.attr('data-prototype-question');
                    newWidget = newWidget.replace(/__name__/g, counter);
                    counter++;

                    list.data('widget-counter', counter);
                    $tagFormLi.append(newWidget);
                });
            }

Upvotes: 1

Views: 803

Answers (2)

Inmarelibero
Inmarelibero

Reputation: 74

forms are a pain in cases like these

I normally resolve, and I propose it for you in this context, in another way:

  • every piece of html displaying a single item of a collection is loaded via AJAX (what I call "subform")
  • so, when I need to add an item of a collection in a form, I do:
/**
 * @Route("edit/break-evens/_form", name="admin_ride_edit_ride_break_evens_subform", options={"expose"=true}, methods={"GET"})
 */
public function _formAction()
{
    $entity = new Ride();
    $entity->addBreakEven(new BreakEven());

    $form = $this->createForm(
        RideType::class,
        $entity
    );

    $form = $form->get('breakEvens')[0];

    return $this->render('AdminBundle:Ride:_edit.html.twig', [
        'form' => $form->createView(),
    ]);
}

where:

  • Ride is 1:n with BreakEven, as to say that breakEvens is a Collection in a RideType form

so, when I want to add an item:

  • I call the action above
  • I create a "full" form, adding an item (new BreakEven()) of the interested collection
  • I create the view only for the first object of the collection
  • then change the number of the form field with js when loaded

of course if you have collections of collections the thing get more difficult, but the you can consider the concept

Upvotes: 0

vikesh
vikesh

Reputation: 61

After carefully going through the prototype variables. This is how I have modified the code of Single form collection into nested form collection: Prototye Field Changes:

<ul id="questiongroups-field-list" data-prototype-question = "{{ form_widget(form.questiongroups.vars.prototype.children['questions'].vars.prototype)|e }}"
                    data-prototype="{{ form_widget(form.questiongroups.vars.prototype)|e}}"
                    data-widget-tags="{{'<li></li>'|e}}" data-tag-list = "{{ '<ul class="question-list"></ul>' |e}}">

Below is the Jquery Code:

$(document).on('click', '.add-questiongroup[data-target]', function(event) {
            var collectionHolder = $($(this).attr('data-target'));

            if (!collectionHolder.attr('data-counter')) {
                collectionHolder.attr('data-counter', collectionHolder.children().length);
            }

            var prototype = collectionHolder.attr('data-prototype');
            var form = prototype.replace(/__name__/g, collectionHolder.attr('data-counter'));

            newWidget = $(collectionHolder.attr('data-widget-tags')).html(form);
            newWidget.append(collectionHolder.attr('data-tag-list'));

            collectionHolder.attr('data-counter', Number(collectionHolder.attr('data-counter')) + 1);
            collectionHolder.append(newWidget);

            var questionPrototype = collectionHolder.attr('data-prototype-question');
            var counter = collectionHolder.attr('data-counter');

            newWidget.attr('data-counter', counter);
            addTagFormDeleteLink(newWidget);
            $('.option-min, .option-max').parent().hide();
            event && event.preventDefault();
        });

        function addTagFormDeleteLink(newWidget) {
            $removeFormButton = $('<button type="button" class="alert button">Delete Group</button>');
            $addQuestionButton = $('<button type="button" class="button">Add Question</button><br>');
            $removeQuestion = $('<button type="button" class="alert button deleteQuebtn">Delete Question</button>');
            newWidget.append($addQuestionButton);
            newWidget.append($removeFormButton);

            $removeFormButton.on('click', function(e) {
                newWidget.remove();
            });

            $addQuestionButton.on('click', function(e){
                $holder = $('#questiongroups-field-list');
                $question = $holder.attr('data-prototype-question');
                $questionHolder = newWidget.find('ul');

                var counter = $questionHolder.children().length;
                var form = $question.replace(/__name__/g, $holder.attr('data-counter')-1).replace(/__que__/g, counter++);
                var newQuestion = $($holder.attr('data-widget-tags')).html(form);

                $questionHolder.append(newQuestion);

            });
        }

Upvotes: 1

Related Questions