Как использовать поле выбора, связанное с другим полем выбора? - PullRequest
23 голосов
/ 29 января 2012

Как использовать связанные блоки выбора в Symfony?

Допустим, у меня есть список выбора, содержащий компании и другой, содержащий сотрудников выбранной компании.Как мне определить их в Symfony?Я уже создал весь код, связанный с Javascript, но при отправке формы и появлении ошибок в некоторых полях все поля «sub» select обнуляются.

Любые идеи?
Спасибо,

РЕДАКТИРОВАТЬ: Поскольку вопрос, как представляется, неправильно понято, я добавлю некоторые точности:

Описание:

  1. У меня есть сущность Company, содержащая список сотрудников, использующий отношение @OneToMany.
  2. Когда я выбираю компанию в раскрывающемся списке, второй раскрывающийся список, содержащий сотрудников, обновляется с помощью jQuery. Эта часть выполнена, работает отлично
  3. При отправке формы без ошибок, решение формы сущности работает отлично.
  4. При отправке формы, содержащей ошибки, второй раскрывающийся список содержит все возможные значения.Они не фильтруются по выбранной компании.

Пробные решения:

  • Моей первой идеей было использование типа form entity , полагая, что компонент может быть каким-либо образом связан с другим полем.то есть.обновить список сотрудников на основе стоимости выбранной компании.

Не работает, нет выхода из коробки, чтобы сделать это.Даже нестандартные решения ...

  • Затем я подумал о ручной передаче выбранной компании в качестве параметра в построитель запросоввторой выпадающий список.

Но при создании формы значения пусты.Значения установлены только на bindRequest .

  • Мысль об использовании типа выбора.Делегирование всей функциональности фильтра в пользовательский интерфейс через Javascript.То есть при загрузке страницы появляется пустой список, который заполняется Javascript на основе выбранной компании.

Это на самом деле работает, но я думаю, что нет другого слова, кроме как на самом деле действительно ужасное программирование здесь.

PS:

Вопрос был задан здесь, в списке рассылки Symfony2, в Twitter и на официальном форуме Symfony 2.Я конечно искал каждый из них несколько раз, прежде чем отправлять свои вопросы.

Ответы [ 3 ]

13 голосов
/ 02 февраля 2012

Что касается того, что вы уже попробовали, я думаю, вам следует повторить ваши первые / вторые идеи:

Моей первой идеей было использовать тип сущности формы, думая, что компонент может быть привязан как-то на другом поле. то есть. обновить список сотрудников на основе стоимости выбранной компании.

Вы можете заполнить поле выбора сотрудников, используя тип entity. Все, что вам нужно сделать, это определить хорошие варианты:

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

Тогда я подумал о том, чтобы вручную передать выбранную компанию как Параметр для построителя запросов из второго выпадающего списка.

В приведенном здесь примере фильтруется список сотрудников на основе параметра формы companyId. Вы можете изменить это поведение, отфильтровав данные о компании, представленной в данных формы.

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

Вы все еще должны реализовать методы getEmployee() и setEmployee() в своем классе Entity\Foo.

Но когда форма создана, значения пусты. Значения установлены только на bindRequest.

Нет. Значения устанавливаются при создании формы с использованием фабрики форм (третий аргумент), ИЛИ когда вы звоните $form->setData($foo);. Данные изменяются, когда вы bind вводите новые данные в форму.

При таком подходе может возникнуть проблема: возможно, идентификатор сотрудника, связанный с формой, недоступен в списке выбора формы, поскольку вы изменили компанию (и, следовательно, сотрудников).

5 голосов
/ 08 февраля 2012

У меня была такая же проблема.Вы должны использовать события формы.Пример моего кода с указанием страны, региона, города.

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

}
1 голос
/ 01 февраля 2012

Вы можете создать действие, которое будет возвращать массив json сотрудникам компании.Когда список компаний изменился, вы должны запросить это действие по ajax и создать список выбора сотрудников.

Действие контроллера формы:

/**
 * @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(),
    );
}

Контроллер, который возвращает сотрудников по компании в 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();
}

Шаблон формы:

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

Для более подробного примера вы должны описать свою проблему более подробно.

...