Validating dynamically loaded choices in Symfony 2
Asked Answered
B

5

19

I have a choice field type named *sub_choice* in my form whose choices will be dynamically loaded through AJAX depending on the selected value of the parent choice field, named *parent_choice*. Loading the choices works perfectly but I'm encountering a problem when validating the value of the sub_choice upon submission. It gives a "This value is not valid" validation error since the submitted value is not in the choices of the sub_choice field when it was built. So is there a way I can properly validate the submitted value of the sub_choice field? Below is the code for building my form. I'm using Symfony 2.1.

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder->add('parent_choice', 'entity', array(
                    'label' => 'Parent Choice',
                    'class' => 'Acme\TestBundle\Entity\ParentChoice'
    ));

    $builder->add('sub_choice', 'choice', array(
                    'label' => 'Sub Choice',
                    'choices' => array(),
                    'virtual' => true
    ));
}
Bloated answered 18/10, 2012 at 3:5 Comment(3)
did you have any luck with this? im stuck on something similar.Medick
A more recent similar question links to this one, and one of the answers looks pretty good, to do with using a PRE_BIND event to sort out the valid list of options: #18207976Humiliation
here is version with any value accept #28245527Leading
C
25

To do the trick you need to overwrite the sub_choice field before submitting the form:

public function buildForm(FormBuilderInterface $builder, array $options)
{
    ...

    $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
        $parentChoice = $event->getData();
        $subChoices = $this->getValidChoicesFor($parentChoice);

        $event->getForm()->add('sub_choice', 'choice', [
            'label'   => 'Sub Choice',
            'choices' => $subChoices,
        ]);
    });
}
Cahn answered 16/4, 2014 at 9:34 Comment(1)
I think this answer most completely and directly gives what the question wants: options set dependent on another selected value, without having to messily provide all options to pass validation and then maybe re-setting options later. This strikes me as the "proper Symfony 2" solution.Humiliation
L
3

this accept any value

 $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
    $data = $event->getData();
    if(is_array($data['tags']))$data=array_flip($data['tags']);
    else $data = array();
    $event->getForm()->add('tags', 'tag', [
        'label'   => 'Sub Choice',
        'choices' => $data,
        'mapped'=>false,
        'required'=>false,
        'multiple'=>true,
    ]);
});
Leading answered 31/1, 2015 at 7:57 Comment(1)
That's not a solution that "properly validate the submitted value" (as asked by OP). It just works around validation. What was the point to add this answer months after Eugene Leonovich's answer which already better addressed OP question?Scathing
A
0

Adding an alternate approach for future readers since I had to do a lot of investigation to get my form working. Here is the breakdown:

  1. Adding a "New" option to a dropdown via jquery
  2. If "New" is selected display new form field "Custom Option"
  3. Submit Form
  4. Validate data
  5. Save to database

jquery code for twig:

$(function(){
    $(document).ready(function() {
        $("[name*='[custom_option]']").parent().parent().hide(); // hide on load

        $("[name*='[options]']").append('<option value="new">New</option>'); // add "New" option
        $("[name*='[options]']").trigger("chosen:updated");
    });

    $("[name*='[options]']").change(function() {
        var companyGroup = $("[name*='[options]']").val();

        if (companyGroup == 'new') { // when new option is selected display text box to enter custom option
            $("[name*='[custom_option]']").parent().parent().show();
        } else {
            $("[name*='[custom_option]']").parent().parent().hide();
        }
    });
});

// Here's my Symfony 2.6 form code:
    ->add('options', 'entity', [
    'class'         => 'Acme\TestBundle\Entity\Options',
    'property'      => 'display',
    'empty_value'   => 'Select an Option',
    'mapped'        => true,
    'property_path' => 'options.optionGroup',
    'required' => true,
])
->add('custom_option', 'text', [
    'required' => false,
    'mapped'   => false,
])

To handle the form data we need to use the PRE_SUBMIT form event.

    $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
    $data = $event->getData();
    $form = $event->getForm();
    if (isset($data['options']) && $data['options'] === 'new') {
        $customOption = $data['custom_option'];

        // todo: handle this better on your own
        if (empty($customOption)) {
            $form->addError(new FormError('Please provide a custom option'));
            return;
        }

        // Check for a duplicate option
        $matches = $this->doctrine->getRepository('Acme\TestBundle\Entity\Options')->matchByName([$customOption]);
        if (count($matches) > 0) {
            $form->addError(new FormError('Duplicate option found'));
            return;
        }

        // More validation can be added here

        // Creates new option in DB
        $newOption = $this->optionsService->createOption($customOption); // return object after persist and flush in service
        $data['options'] = $newOption->getOptionId();
        $event->setData($data);
    }
});

Let me know if ya'll have any questions or concerns. I know this might not be the best solution but it works. Thanks!

Aldredge answered 18/12, 2018 at 18:11 Comment(0)
S
-3

you cannot not build the sub_choice validation because during you config its validator you don't know which values are valid (values depend on value of parent_choice).

What you can do is to resolve parent_choice into entity before you make new YourFormType() in your controller. Then you can get all the possible values for sub_choice and provide them over the form constructor - new YourFormType($subChoice).

In YourFormType you have to add __construct method like this one:

/**
 * @var array
 */
protected $subChoice = array();

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

and use provided values in form add:

$builder->add('sub_choice', 'choice', array(
                'label' => 'Sub Choice',
                'choices' => $this->subChoice,
                'virtual' => true
));
Sundial answered 14/8, 2013 at 10:52 Comment(0)
T
-6

Suppose for sub choices you have id's right ? Create and empty array with a certain number of values and give it as a choice

$indexedArray = []; for ($i=0; $i<999; $i++){ $indexedArray[$i]= ''; }

then 'choices' => $indexedArray, :)

Thermoluminescence answered 18/4, 2013 at 14:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.