4

I want to create a Survey that consists of multiple Questions (different implementation classes). I would love to represent the Survey creation as well as all the Questions as FormType to make it easy to have Validation and all the good stuff from the Symfony Form Component. It is very easy to nest forms like described here.

But now comes the tricky part:

Every Question has its own FormType and a survey should be created as well as answered (filled out by the survey taker) on one page. So all questions on one page. More or less this is like Google Forms, being able to add new Questions on one page quickly as well make it easy for the user to see all questions at once.

My 2 Question would be:

  1. How can I add a FormType whose nested Types are known at runtime (admin can select which Question Type he wants to add)?
  2. How can I validate and store all the Forms on one page when the survey taker fills out a survey?

I would love to hear some ideas from you.

Thanks, Lukas

1 Answer 1

4
+50
  1. Use the power of Listeners. You can use almost the same flow that CollectionType uses with ResizeListener.

    public function preSetData(FormEvent $event)
    {
        $form = $event->getForm();
        $data = $event->getData();
        ...
        foreach ($data as $name => $value) {
            $form->add($name, $this->getTypeByClass($value), array_replace(array(
                'property_path' => '['.$name.']',
            ), $this->options));
        }
    }
    
    ...
    
    public function preSubmit(FormEvent $event)
    {
        $form = $event->getForm();
        $data = $event->getData();
        ...
        if ($this->allowAdd) {
            foreach ($data as $name => $value) {
                if (!$form->has($name)) {
                    // put special value into sub-form to indicate type of the question
                    $type = $value['type'];
                    unset($value['type']);
    
                $form->add($name, $type, array_replace(array(
                    'property_path' => '['.$name.']',
                ), $this->options));
            }
        }
    }
    

    }

    Try to implement the very similar flow with allowDelete, allowAdd features.

  2. There should be another classes like SurveyData.{items, survey, ...} with n-1 relation to Survey, SurveyItem.{answer, ...} with n-1 association to QuestionAnswer. On the base of the your structure there should be written validators.

    Cascade validation can be triggered with Valid constraint.

    http://symfony.com/doc/current/reference/constraints/Valid.html

UPDATE

  1. What to do to form mutable part.

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        if ($options['allow_add'] && $options['prototyped']) {
            // @var ['prototype_name' => '__name__', 'type' => 'question_type']
            foreach ($options['prototypes'] as $prototype) {
                $prototype = $builder->create($prototype['prototype_name'], $options['type'], $options['options']);
                $prototype->add('type', 'hidden', ['data' => $options['type'], 'mapped' => false]);
                $prototypes[$options['type']] =  $prototype->getForm();
            }
            $builder->setAttribute('prototypes', $prototypes);
        }
        ...
    }
    
    public function buildView(FormView $view, FormInterface $form, array $options)
    {
        $view->vars = array_replace($view->vars, array(
            'allow_add'    => $options['allow_add'],
            'allow_delete' => $options['allow_delete'],
        ));
    
        if ($form->getConfig()->hasAttribute('prototypes')) {
            $view->vars['prototypes'] = $form->getConfig()->getAttribute('prototypes')->createView($view);
        }
    }
    

    Now are able to use prototypes in the form block in twig.

    {% for key, prototype in prototypes %}
         {% set data_prototypes[key] = form_row(prototype) %}
    {% endfor %}
    {% set attr = attr|merge({'data-prototypes' : data_prototypes|json_encode })
    

    Now you don't need ajax requests in JS - just use prototypes.

    (var collection = $('your_collection')).append(collection.data('prototypes')[question_type].replace(/__name__/g, counter+1));
    

    You added the element to collection, now admin can fill it and submit the form. Rest of the work (mapping data to class) will be done by Symfony.

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

9 Comments

Thanks so far for the idea. How could I add elements to the form when the form is already rendered? So basically add a Form element via Ajax..
If you mean the getting of the form html code it is better to use prototype. CollectionType suggests the prototype - it renders needed type in the CollectionType::buildForm with placeholder __name__ and put the code into data-prototype attribute of the collection. You just need to extend this behavior and render all possible form types into prototype.
Don't get it completely.. Could you form a very simple example for that? So I have the form but what part would you do in javascript and which part in symphony? So where to form which mutable Form part? Cause problem is a already mutable form is rendered but it should be possible to add a new form to the form more or less.. Thanks :)
That looks great! So basically I am generating the form for each question as a prototype and pass it with the form and then with javascript I control the adding of a question to the list. Correct? Or do I still get something wrong? Why do you store the attr variable in twig?
Correct. This is a bit extended CollectionType flow. Questions that are already added and filled will be rendered by backend with filled data. Prototypes can be used to add new questions to this form by js - don't forget to replace __name__ with the next collection index. I stores prototype code to the attribute as Collection do it. You are free to add code directly to js. symfony.com/doc/current/reference/forms/types/…
|

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.