Symfony: How to dynamically add Lines to a Poll?

I created a Symfony Poll-Bundle which has the Entitys Campaign->Block->Line->Field->PollResult.
So i have a CollectionType CampaignType which consists of many blocks.
Block is also a CollectionType and consists of many Lines.
One Line consist of many Fields and every Field has one PollResult which holds the Answer of the user who filled out the campaign.
So the Campaign could be a Covid-19 Campaign with a block in it called Symptoms, which has Lines for the different Symptoms. Each Line has a dropdown with the Kind of Symptom (like Fever or Headache) the intensity, and two dates for the period when the Symptom appeared.

So i added 'allow_add' => true to my BlockType:

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder->add('lines', CollectionType::class, [
        'entry_type' => LineType::class,
        'entry_options' => ['label' => false],
        'allow_add' => true,
        'by_reference' => false,
    ]);
}

Then there are LineType, which add fields:

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder->add('fields', CollectionType::class, array(
        'entry_type' => FieldType::class,
        'entry_options' => array('label' => false),
    ));
}

and FieldType to add the pollResults:

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder->add('pollResults', CollectionType::class, array(
        'entry_type' => PollResultType::class,
        'entry_options' => array('label' => false),
    ));
}

and the pollResultType is a little more complex, because there are many types of input fields so i will put it at the end of this question!

On my html-page i have some for loops to display my form (i only show relevant stuff):

{{ form_start(form, {'attr': {'id': 'campaignEdit_form'} }) }}
{% for block in form.blocks %}
    {% for line in block.lines %}
        <ul class="singleLineUL lst-none" data-prototype="{{ form_widget(form.blocks[loop.parent.loop.index0].lines.vars.prototype)|e('html_attr') }}">
            <li>
                {% for field in line.fields %}
                    <div class=" {% if loop.first %} lineFieldFirst {% else %} lineField {% endif %} ">
                        {% for pollResult in field.pollResults %}
                            <div class="formLabelDiv">{{ field.vars.value.getTranslationName(app.request.getLocale()) }}</div>
                            <div class="formWidgetDiv">{{ form_widget(pollResult) }}</div>
                        {% endfor %}
                    </div>
                {% endfor %}
            </li>
        </ul>
    {% endfor %}
{% endfor %}

So in the ul.singleLineUL element i place the data-prototype which should hold the prototype for the four fields i described above (SymptomType, Intensity, and two date fields)
Here is the Javascript code which adds the ‘Add a Line’-Button and should add a Line when i press this Button:

<script>
    var $collectionHolder;
    //setup an "add a Line" Link
    var $addLineButton = $('<button type="button" class="add_line_link">Add a Line</button>');
    var $newLinkLi = $('<li></li>').append($addLineButton);

    jQuery(document).ready(function (){

        //Get the ul that holds the collection of fields
        $collectionHolder = $('ul.singleLineUL')

        // add the "add a line" anchor and li to the tags ul
        $collectionHolder.append($newLinkLi);

        // count the current form inputs we have (e.g. 2), use that as the new
        // index when inserting a new item (e.g. 2)
        $collectionHolder.attr('data-index', $collectionHolder.find(':input').length);

        $addLineButton.on('click', function(e) {
            // add a new tag form (see next code block)
            addFieldsForm($collectionHolder, $newLinkLi);
        });

    })

    function addFieldsForm($collectionHolder, $newLinkLi){
        //get the data-prototyp

        console.log($collectionHolder);

        var prototype = $collectionHolder.attr('data-prototype');
        console.log(prototype);

        //get the new index
        var index = $collectionHolder.attr('data-index');

        var newForm = prototype;
        console.log(newForm);

        newForm = newForm.replace(/__name__/g, index);
        console.log(newForm);

        $collectionHolder.attr('data-index', index+1);

        // Display the form in the page in an li, before the "Add a tag" link li
        var $newFormLi = $('<li></li>').append(newForm);
        $newLinkLi.before($newFormLi);
    }
</script>

But the only thing that happens is that a Label with the text ‘Fields’ gets printed and the prototype obviously only holds that label.

How can i make this Button to duplicate this whole Line and adds four new Fields?

I don’t think that this will help a lot but here is also the pollResultType Class:

class PollResultType extends AbstractType
{
    protected $requestStack;

    public function __construct(RequestStack $requestStack)
    {
        $this->requestStack = $requestStack;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $request = $this->requestStack->getCurrentRequest();
        $locale = $request->getLocale();

        $formFactory = $builder->getFormFactory();
        $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($formFactory, $locale) {
            /** @var PollResult $data */
            $data = $event->getData();
            $form = $event->getForm();

            $fieldOptions = [];
            $fieldOptions['auto_initialize'] = false;
            $fieldOptions['label'] = false;

            switch ($data->getField()->getType()){
                case 'choice':
                    /** @var Fieldvalue $fieldvalue */
                    foreach ($data->getField()->getFieldvalues() as $fieldvalue) {
                        $fieldOptions['choices'][$fieldvalue->getValue()] = $fieldvalue->getTranslationName($locale);
                    }
                    break;
                case 'radio':
                case 'gi_sex':
                    $fieldOptions['multiple'] = false;
                    $fieldOptions['expanded'] = true;
                    /** @var Fieldvalue $fieldvalue */
                    foreach ($data->getField()->getFieldvalues() as $fieldvalue) {
                        $fieldOptions['choices'][$fieldvalue->getValue()] = $fieldvalue->getTranslationName($locale);
                    }
                    break;
                case 'checkbox':
                case 'text':
                case 'gi_smallTown':
                    $fieldOptions['required'] = false;
                    break;
                case 'date':
                    $fieldOptions['required'] = false;
                    $fieldOptions['attr'] = ['class' => 'datepicker'];
                    break;
                case 'gi_country':
                    $fieldOptions['attr'] = ['id' => 'chooseCountry', 'data-toogle' => 'country', 'name'=> 'country', 'onchange' => 'changeDataCountry()'];
                    break;
                case 'gi_state':
                    $fieldOptions['attr'] = ['id' => 'chooseState', 'data-toggle' => 'state', 'name'=> 'state'];
                    break;
            }

            switch ($data->getField()->getType()){
                case 'radio':
                case 'gi_sex':
                case 'gi_country':
                case 'gi_state':
                    $form->add($formFactory->createNamed('value', 'choice', null, $fieldOptions));
                    break;
                case 'date':
                    $form->add($formFactory->createNamed('value', "text", null, $fieldOptions));
                    break;
                case 'gi_createdate':
                case 'gi_editdate':
                    $form->add($formFactory->createNamed('value', "hidden", null, $fieldOptions));
                    break;
                case 'gi_smallTown':
                    $form->add($formFactory->createNamed('value', "checkbox", null, $fieldOptions));
                    break;
                case 'gi_age':
                    $form->add($formFactory->createNamed('value', "number", null, $fieldOptions));
                    break;
                default:
                    $form->add($formFactory->createNamed('value', $data->getField()->getType(), null, $fieldOptions));
                    break;
            }
        });

        $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($formFactory) {
            $data = $event->getData();
            $fieldOptions = [];
            $fieldOptions['choices'][$data["value"]] = $data["value"];
            $fieldOptions['auto_initialize'] = false;

            $event->getForm()->add($formFactory->createNamed('value', "choice", null, $fieldOptions));
        });
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => PollResult::class,
        ));
    }
}

Source: Symfony Questions

Was this helpful?

0 / 0

Leave a Reply 0

Your email address will not be published. Required fields are marked *