Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
456 views
in Technique[技术] by (71.8m points)

symfony - symfony2 chained selectors

I have three entities: Country, State and City with the following relationships:

image

When creating a City, I want two selectors, one for the Country and one for the State where the city belongs. These two selectors need to be chained so changing the Country will "filter" the States shown in the other selector.

I found a tutorial showing how to do this using Form Events but their example it's not quite my case. My entity City it's not directly related to the Country entity (they are indirectly related through State) so, when setting the country field in the City form (inside the class CityType), I'm forced to declare that field as 'property_path'=>false as you can see in the code below:

class CityType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder->add('country', 'entity', array(
            'class'=>'TestBundle:Country', 
            'property'=>'name', 
            'property_path'=>false //Country is not directly related to City
        ));
        $builder->add('name');

        $factory = $builder->getFormFactory();

        $refreshStates = function ($form, $country) use ($factory) 
        {
            $form->add($factory->createNamed('entity', 'state', null, array(
                'class'         => 'TestTestBundleEntityState',
                'property'      => 'name',
                'query_builder' => function (EntityRepository $repository) use ($country)
                                   {
                                       $qb = $repository->createQueryBuilder('state')
                                                        ->innerJoin('state.country', 'country');

                                        if($country instanceof Country) {
                                            $qb->where('state.country = :country')
                                               ->setParameter('country', $country);
                                        } elseif(is_numeric($country)) {
                                            $qb->where('country.id = :country')
                                               ->setParameter('country', $country);
                                        } else {
                                            $qb->where('country.name = :country')
                                               ->setParameter('country', "Venezuela");;
                                        }        
                                        return $qb;
                                    }
            )));
        };

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

            if($data == null)
                return;               

            if($data instanceof City){
                if($data->getId()) { //An existing City
                    $refreshStates($form, $data->getState()->getCountry());
                }else{               //A new City
                    $refreshStates($form, null);
                }
            }
        });

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

            if(array_key_exists('country', $data)) {
                $refreshStates($form, $data['country']);
            }
        });
    }

    public function getName()
    {
        return 'city';
    }

    public function getDefaultOptions(array $options)
    {
        return array('data_class' => 'TestTestBundleEntityCity');
    }
}

The problem is that when I try to edit an existing City, the related Country is not selected by default in the form. If I remove the line 'property_path'=>false I get (not surprisingly) the error message:

Neither property "country" nor method "getCountry()" nor method "isCountry()" exists in class "TestTestBundleEntityCity"

Any ideas?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

OK, I finally figured out how to do it properly:

namespace TestTestBundleForm;

use SymfonyComponentFormAbstractType;
use SymfonyComponentFormFormBuilder;

use DoctrineORMEntityRepository;
use SymfonyComponentFormFormEvents;
use SymfonyComponentFormEventDataEvent;

use TestTestBundleEntityCountry;
use TestTestBundleEntityState;
use TestTestBundleEntityCity;


class CityType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder->add('name');

        $factory = $builder->getFormFactory();

        $refreshStates = function ($form, $country) use ($factory) {
            $form->add($factory->createNamed('entity','state', null, array(
                'class'         => 'TestTestBundleEntityState',
                'property'      => 'name',
                'empty_value'   => '-- Select a state --',
                'query_builder' => function (EntityRepository $repository) use ($country) {
                    $qb = $repository->createQueryBuilder('state')
                        ->innerJoin('state.country', 'country');

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

                    return $qb;
               })
           ));
        };

        $setCountry = function ($form, $country) use ($factory) {
            $form->add($factory->createNamed('entity', 'country', null, array(
                'class'         => 'TestBundle:Country', 
                'property'      => 'name', 
                'property_path' => false,
                'empty_value'   => '-- Select a country --',
                'data'          => $country,
            )));
        };

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

            if ($data == null) {
                return;
            }

            if ($data instanceof City) {
                $country = ($data->getId()) ? $data->getState()->getCountry() : null ;
                $refreshStates($form, $country);
                $setCountry($form, $country);
            }
        });

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

            if(array_key_exists('country', $data)) {
                $refreshStates($form, $data['country']);
            }
        });
    }

    public function getName()
    {
        return 'city';
    }

    public function getDefaultOptions(array $options)
    {
        return array('data_class' => 'TestTestBundleEntityCity');
    }
}

The jQuery AJAX selector

$(document).ready(function () {
    $('#city_country').change(function(){
        $('#city_state option:gt(0)').remove();
        if($(this).val()){
            $.ajax({
                type: "GET",
                data: "country_id=" + $(this).val(),
                url: Routing.generate('state_list'),
                success: function(data){
                    $('#city_state').append(data);
                }
            });
        }
    });
});

I hope this will be helpful to somebody else facing the same situation.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...