Chris
Chris

Reputation: 223

Validating dynamically loaded choices in Symfony 2

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
    ));
}

Upvotes: 19

Views: 10378

Answers (5)

alakin_11
alakin_11

Reputation: 719

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!

Upvotes: 0

Developer
Developer

Reputation: 2895

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,
    ]);
});

Upvotes: 3

Eugene Leonovich
Eugene Leonovich

Reputation: 924

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,
        ]);
    });
}

Upvotes: 25

Lazy Ants
Lazy Ants

Reputation: 27

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
));

Upvotes: -3

Ionut Tudorel
Ionut Tudorel

Reputation: 5

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, :)

Upvotes: -6

Related Questions