2012-04-17 21 views
19

tôi có ba đơn vị: Quốc gia, Nhà nước và thành phố với các mối quan hệ sau:symfony2 xích selectors

Image http://i39.tinypic.com/15gc85u.png

Khi tạo một thành phố, tôi muốn hai selectors, một cho quốc gia và một cho Nhà nước nơi thành phố thuộc về. Hai bộ chọn này cần phải được xích để thay đổi Quốc gia sẽ "lọc" các quốc gia được hiển thị trong bộ chọn khác.

Tôi đã tìm thấy một số tutorial cho biết cách thực hiện việc này bằng cách sử dụng Sự kiện biểu mẫu nhưng ví dụ của chúng không phải là trường hợp của tôi. Thành phố của tôi không liên quan trực tiếp đến thực thể Quốc gia (họ liên quan gián tiếp qua Tiểu bang), khi thiết lập trường quốc gia ở dạng Thành phố (bên trong lớp CityType), tôi buộc phải khai báo trường đó là 'property_path'=>false như bạn có thể xem trong mã bên dưới:

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'   => 'Test\TestBundle\Entity\State', 
       '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' => 'Test\TestBundle\Entity\City'); 
    } 
} 

Vấn đề là khi tôi cố gắng sửa thành phố hiện tại, quốc gia liên quan không được chọn theo mặc định trong biểu mẫu. Nếu tôi loại bỏ các dòng 'property_path'=>false tôi nhận được (không đáng ngạc nhiên) được thông báo lỗi:

Cả tài sản "Đất nước" hay phương pháp "getCountry()" hay phương pháp "isCountry()" tồn tại trong lớp "Test \ TestBundle \ Entity \ Thành phố "

Mọi ý tưởng?

Trả lời

23

OK, cuối cùng tôi đã tìm ra cách để làm điều đó đúng:

namespace Test\TestBundle\Form; 

use Symfony\Component\Form\AbstractType; 
use Symfony\Component\Form\FormBuilder; 

use Doctrine\ORM\EntityRepository; 
use Symfony\Component\Form\FormEvents; 
use Symfony\Component\Form\Event\DataEvent; 

use Test\TestBundle\Entity\Country; 
use Test\TestBundle\Entity\State; 
use Test\TestBundle\Entity\City; 


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'   => 'Test\TestBundle\Entity\State', 
       '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' => 'Test\TestBundle\Entity\City'); 
    } 
} 

Các 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); 
       } 
      }); 
     } 
    }); 
}); 

Tôi hy vọng điều này sẽ rất hữu ích cho người khác phải đối mặt với tình huống tương tự.

+0

nơi 'Routing.generate ('state_list'),' đến từ đâu? Bộ điều khiển xử lý tuyến đường 'state_list' ở đâu? – gondo

+0

Xin chào David, câu trả lời tuyệt vời ở đó, chỉ cần tự hỏi - bạn có biết cách sử dụng thành phố được tạo này khi bạn chọn, ví dụ, địa chỉ cho một người không? Vì vậy, chọn một quốc gia, làm mới trạng thái, chọn một tiểu bang, làm mới các thành phố? Tôi biết nó có liên quan gì đến Form Events, nhưng có vẻ như với tôi bạn cần phải lồng ghép chúng bằng cách nào đó? Tôi đã đăng câu hỏi về vấn đề này [ở đây] (http://stackoverflow.com/questions/20544442/add-event-listener-to-form-element-added-by-event-listener). Bất kỳ ý tưởng? – iLikeBreakfast

+0

cách thực hiện việc này trong phiên bản 'Symfony 2.3' –

0

Bạn sẽ cần một FieldType dành riêng cho hộp chọn có chuỗi. Và cũng là một bộ điều khiển xhr có thể trả về các tùy chọn con dựa trên tham số được truyền. Ofcourse property_path phải được đặt thành false.

+0

Bạn có thể chỉ cho tôi cách tạo FieldType chuyên dụng không? –

+0

http://symfony.com/doc/master/cookbook/form/create_custom_field_type.html – ken

+0

Tôi không biết làm thế nào một FieldType chuyên dụng sẽ giúp tôi ở đó.Vấn đề chính xác là thiết lập 'property_path = false' cho trường Country entity, cô lập anh ta từ thực thể thành phố và trạng thái liên quan của anh ta. –

7

Kể từ liên kết của bạn để phương pháp này là xuống tôi quyết định để bổ sung cho câu trả lời xuất sắc của bạn để ai cũng có thể sử dụng nó:

Để thực hiện lệnh javascript sau:

url: Routing.generate('state_list'), 

Bạn cần cài đặt FOSJsRoutingBundle có thể được tìm thấy trong here.

CHÚ Ý: trong phần đọc tôi của gói có hướng dẫn instalation nhưng có một cái gì đó mất tích. Nếu bạn sử dụng deps với điều này:

[FOSJsRoutingBundle] 
git=git://github.com/FriendsOfSymfony/FOSJsRoutingBundle.git 
target=/bundles/FOS/JsRoutingBundle 

Bạn phải chạy php bin/vendors update trước các bước tiếp theo.

Tôi vẫn đang cố gắng tìm ra tuyến đường nào cần thiết trong routing.yml để giải pháp này hoạt động. Ngay sau khi tôi phát hiện ra tôi sẽ chỉnh sửa câu trả lời này.