How to use select box related on another select bo

2019-03-08 10:54发布

How to use related select boxes in Symfony ?

Let's say, I have a select list containing compagnies and another containing employees of the selected company. How do I define those in Symfony? I have already created all the Javascript related code but when submitting my form and having errors on some fields, the all "sub" select fields are reset to null.

Any ideas?
Thanks,

EDIT : As the question seems to be misunderstood, I'll add some precisions :

Description :

  1. I have an entity Company containing a list of Employees using a @OneToMany relation.
  2. When I select a company in a select/dropdown list, the second dropdown list containing the employees is updated via jQuery. That part is done, works perfectly
  3. When submitting the form without errors, the entity form solution works fine.
  4. When submitting the form containing errors, the second dropdown list is containing all possible values. They are not filtered on the selected company.

Tried solutions :

  • My first idea was to use the form entity type, thinking the component could be binded somehow on another field. ie. update list of employees based on the value of the selected company.

Not working, there is no way out of the box to do this. Even non out of the box solutions...

  • Then I thought about manually passing the selected company as parameter to the query builder of the second dropdown list.

But when the form is created, the values are empty. The values are only set on bindRequest.

  • Thought about using the choice type. Delegating all filter functionnality to the UI via Javascript. Meaning when the page loads, an empty list appears and is populated by Javascript based on the selected company.

This actually works, but I think there is no other word than really really ugly programming here.

PS :

Question has been asked here, on the Symfony2 mailing-list, on Twitter and the officiel Symfony 2 forum. I have of course searched each of those several times before posting my questions.

3条回答
叛逆
2楼-- · 2019-03-08 11:28

Concerning what you already tried, I think you should retry your first/second ideas:

My first idea was to use the form entity type, thinking the component could be binded somehow on another field. ie. update list of employees based on the value of the selected company.

You can populate the employees select box using the entity type. All you have to do is to define the good options:

class FooType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder
            ->add('employee', 'entity', array(
                'class' => 'Entity\Employee',
                'query_builder' => function ($repository) use($options) {
                    return $repository
                        ->createQueryBuilder('e')
                        ->where('e.company = :company')
                        ->setParameter('company', $options['companyId'])
                    ;
                },
            ))
        ;
    }

    public function getDefaultOptions(array $options)
    {
        return array('data_class' => 'Entity\Foo', 'companyId' => null);
    }
}

Then I thought about manually passing the selected company as parameter to the query builder of the second dropdown list.

The example here filters the employees list based on the companyId form's option. You can modify this behavior by filtering directly on the company present in the form's data.

public function buildForm(FormBuilder $builder, array $options)
{
    $companyId = $builder->getData()->getCompanyId();
    $builder
        ->add('employee', 'entity', array(
            'class' => 'Entity\Employee',
            'query_builder' => function ($repository) use ($companyId) {
                return $repository
                    ->createQueryBuilder('e')
                    ->where('e.company = :company')
                    ->setParameter('company', $companyId)
                ;
            },
        ))
    ;
}

You still have to implement the getEmployee() and setEmployee() methods in your Entity\Foo class.

But when the form is created, the values are empty. The values are only set on bindRequest.

No. Values are set when you create you form using form factory (third argument), OR when you call $form->setData($foo);. Data is modified when you bind new inputs to the form.

There could be a problem with this approach: it's possible that the employee id bound to the form is not available in the form choice list, because you changed the company (and thus the employees).

查看更多
Explosion°爆炸
3楼-- · 2019-03-08 11:40

You can create action, that will be return json array with employees of company. When the list of companies changed you must request this action by ajax and create select list of employees.

Form controller action:

/**
 * @Route("/job", name="_demo_job")
 * @Template()
 */
public function jobAction()
{
    $form = $this->createForm(new JobType());

    $request = $this->getRequest();
    if ($request->getMethod() == 'POST') {
        $form->bindRequest($request);
        $data = $form->getData();
        // some operations with form data
    }

    return array(
        'form' => $form->createView(),
    );
}

Conroller, that return employees by company in json:

/**
 * Finds all employees by company
 *
 * @Route("/{id}/show", name="employees_by_category")
 */
public function listByCompanyAction($id)
{
    $request = $this->getRequest();

    if ($request->isXmlHttpRequest() && $request->getMethod() == 'POST') {
        $em = $this->getDoctrine()->getEntityManager();

        $company = $em->getRepository('AcmeDemoBundle:Company')->find($id);

        // create array for json response
        $empoloyees = array();
        foreach ($company->getEmployees() as $employee) {
            $empoloyees[] = array($employee->getId(), $employee->getName());
        }

        $response = new Response(json_encode($empoloyees));
        $response->headers->set('Content-Type', 'application/json');

        return $response;
    }
    return new Response();
}

Form template:

<script type="text/javascript">
    $(document).ready(function() {
        $('#form_company').change(function() {
            var companyId = $(this).val();
            $.post('{{ route }}/' + companyId + '/show', function(data) {
                // last selected employee
                var selectedVal = $('option:selected', '#form_employee').attr('value');

                $('#form_employee option').remove();
                for (i in data) {
                    // create option with employee
                    var option = $('<option></option>').
                        attr('value', data[i][0]).
                        text(data[i][1]);
                    // set selected employee
                    if (data[i][0] == selectedVal) {
                        option.attr('selected', 'selected');
                    }
                    // append to employee to employees select
                    $('#form_employee').append(option);
                }
            }, 'json');
        })

        // request employees by company
        $('#form_company').change();
    })

</script>
<form action="{{ path('_demo_job') }}" method="post" {{ form_enctype(form) }}>
    {{ form_widget(form) }}

    <input type="submit" />
</form>

For more detail example you should describe your problem in more detail.

查看更多
再贱就再见
4楼-- · 2019-03-08 11:51

I had the same problem. You must use form events. My code example with country, region, city relations.

namespace Orfos\UserBundle\Form\Type;

///import form events namespace
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\Event\DataEvent;

class RegistrationFormType extends BaseType
{

    private $request;

    public function __construct($class, $request, $doctrine)
    {
        parent::__construct($class);
        $this->request = $request;
        $this->doctrine = $doctrine;
    }

    public function buildForm(FormBuilder $builder, array $options)
    {
        parent::buildForm($builder, $options);
        //other fields

        $locale = $this->request->getLocale();

        $builder->add('country', 'entity', array(
            'class' => 'Orfos\CoreBundle\Entity\Country',
            'property' => $locale . 'name',
            'label' => 'register.country.label',
            'query_builder' => function(EntityRepository $er) {
                return $er->createQueryBuilder('c')
                                ->select('c', 't')
                                ->join('c.translations', 't');
            },
        ));

        $factory = $builder->getFormFactory();
        $refreshRegion = function ($form, $country) use ($factory, $locale) {
                    $form->add($factory->createNamed('entity', 'region', null, array(
                                'class' => 'Orfos\CoreBundle\Entity\Region',
                                'property' => $locale . 'name',
                                'label' => 'register.region.label',
                                'query_builder' => function (EntityRepository $repository) use ($country) {
                                    $qb = $repository->createQueryBuilder('region')
                                            ->select('region', 'translation')
                                            ->innerJoin('region.country', 'country')
                                            ->join('region.translations', 'translation');

                                    if ($country instanceof Country) {
                                        $qb = $qb->where('region.country = :country')
                                                ->setParameter('country', $country);
                                    } elseif (is_numeric($country)) {
                                        $qb = $qb->where('country.id = :country_id')
                                                ->setParameter('country_id', $country);
                                    } else {
                                        $qb = $qb->where('country.id = 1');
                                    }

                                    return $qb;
                                }
                            )));
                };
        $factory = $builder->getFormFactory();
        $refreshCity = function($form, $region) use ($factory, $locale) {
                    $form->add($factory->createNamed('entity', 'city', null, array(
                                'class' => 'Orfos\CoreBundle\Entity\City',
                                'property' => $locale . 'name',
                                'label' => 'register.city.label',
                                'query_builder' => function (EntityRepository $repository) use ($region) {
                                    $qb = $repository->createQueryBuilder('city')
                                            ->select('city', 'translation')
                                            ->innerJoin('city.region', 'region')
                                            ->innerJoin('city.translations', 'translation');

                                    if ($region instanceof Region) {
                                        $qb = $qb->where('city.region = :region')
                                                ->setParameter('region', $region);
                                    } elseif (is_numeric($region)) {
                                        $qb = $qb->where('region.id = :region_id')
                                                ->setParameter('region_id', $region);
                                    } else {
                                        $qb = $qb->where('region.id = 1');
                                    }

                                    return $qb;
                                }
                            )));
                };

        $builder->addEventListener(FormEvents::PRE_SET_DATA, function (DataEvent $event) use ($refreshRegion, $refreshCity) {
                    $form = $event->getForm();
                    $data = $event->getData();

                    if ($data == null){
                        $refreshRegion($form, null);
                        $refreshCity($form, null);
                    }

                    if ($data instanceof Country) {
                        $refreshRegion($form, $data->getCountry()->getRegions());
                        $refreshCity($form, $data->getRegion()->getCities());
                    }
                });

        $builder->addEventListener(FormEvents::PRE_BIND, function (DataEvent $event) use ($refreshRegion, $refreshCity) {
                    $form = $event->getForm();
                    $data = $event->getData();

                    if (array_key_exists('country', $data)) {
                        $refreshRegion($form, $data['country']);
                    }
                    if (array_key_exists('region', $data)) {
                        $refreshCity($form, $data['region']);
                    }                    
                });
    }

}
查看更多
登录 后发表回答