使用多级选择级联制作Symfony2表单的最佳方法

时间:2012-09-14 10:38:54

标签: forms symfony doctrine-orm symfony-2.1

我有3个实体(国家,地区,城市)

namespace ****\****Bundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

class Country
{
  private $id;

  private $name;

  /**
   * @var integer $regions
   * 
   * @ORM\OneToMany(targetEntity="Region", mappedBy="Country")
   */
  protected $regions;
  //...
}

class Region
{
  private $id;

  private $name;

  /**
   * @var integer $country
   *
   * @Assert\Type(type="****\***Bundle\Entity\Country")
   * @ORM\ManyToOne(targetEntity="Country", inversedBy="regions")
   * @ORM\JoinColumn(name="country_id", referencedColumnName="id", nullable=false)
   */
  private $country;

  /**
   * @ORM\OneToMany(targetEntity="City", mappedBy="Region")
   */
  protected $cities;
}

class City
{
    private $id;

    private $name;

    /**
     * @var integer $region
     *
     * @Assert\Type(type="****\****Bundle\Entity\Region")
     * @ORM\ManyToOne(targetEntity="Region", inversedBy="cities")
     * @ORM\JoinColumn(name="region_id", referencedColumnName="id", nullable=false)
     */
    private $region;

    /**
     * @ORM\OneToMany(targetEntity="Company", mappedBy="City")
     */
    protected $companys;
//...
}

这是我的城市表格类:

namespace ****\****Bundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class CityType extends AbstractType
{

  public function buildForm(FormBuilderInterface $builder, array $options)
  {
    $builder
            ->add('name')
            ->add('region');
  }

  public function setDefaultOptions(OptionsResolverInterface $resolver)
  {
    $resolver->setDefaults(array(
        'data_class' => '****\****Bundle\Entity\City',
    ));
  }

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

这会创建一个基本的HTML5表单,其中包含名称的textBox和一个所有区域都可用的SelectBox。

我的问题是添加第一个SelectBox的最佳方法是什么,它允许我选择国家以过滤第二个SelectBox并减少Region的选择数量?

EventListener? 事件调度程序组件?

4 个答案:

答案 0 :(得分:2)

否,EventListener和Event调度程序用于在服务器上发生的事件,而不是客户端上发生的事件。你需要使用Javascript。当其中一个选择框发生变化时,这应该触发一个javascript函数并进行一次AJAX调用并用该调用的结果填充另一个选择框,或者使用一些javascript代码来选择在第二个框中显示哪些选项。

查看here了解一些想法

答案 1 :(得分:1)

正如Carlos Granados所说,你基本上有两种选择:

  1. 创建一个单独的Symfony操作,该操作将一个国家/地区作为参数,并以XML或JSON格式返回关联区域的列表。 (您可以使用Symfony\Component\HttpFoundation\JsonResponse发送JSON响应,但是没有相应的XmlResponse类。每当用户更改当前选定的项目时,使用jQuery(或任何其他JS库甚至普通Javascript)向服务器发送请求。在回调中(当您在Javascript中检索响应时),您可以更新区域选择框。您可能会发现jQuery Ajax有趣的文档。

  2. 您可以在HTML代码中存储所有国家/地区及其相关区域的列表(您可以使用JSON或生成本机Javascript数组),每当用户更改国家/地区选择框的值时,您只需更换区域中的区域列表选择框。

  3. 第二种方法对表单的初始加载负载较重,因为必须加载所有国家及其相关区域(从数据库或文本文件或存储它们的任何位置)并以易于格式呈现由JS阅读。

    然而,第一种方法必须在每次用户选择其他国家/地区时发送请求。此外,您还必须实施其他操作。

答案 2 :(得分:0)

我自己在表格上这样做。 我更改了一个字段(一个产品),并且更新了可以测量数量的单位。 我正在使用带参数的宏来更容易地调整它。

宏:

{% macro javascript_filter_unit(event, selector) %}
<script>
    $(function(){
        $('#usersection')
                .on('{{ event }}', '{{ selector }}', function(e){
                    e.preventDefault();
                    if (!$(this).val()) return;
                    $.ajax({
                        $parent: $(this).closest('.child_collection'),
                        url: $(this).attr('data-url'),
                        type: "get",
                        dataType: "json",
                        data: {'id' : $(this).val(), 'repo': $(this).attr('data-repo'), parameter: $(this).attr('data-parameter')},
                        success: function (result) {
                            if (result['success'])
                            {
                                var units = result['units'];
                                this.$parent.find('.unit').eq(0).html(units);   
                            }
                        }
                    });
                })
    });

</script>

{% endmacro %}

ajax返回一个数组:array('success'=&gt; $ value,'units'=&gt; $ html)。您使用$ html代码并将其替换为您要更改的选择。 当然,需要修改ajax调用的javascript代码以匹配您的字段。

您可以像平常一样调用宏:

{% import ':Model/Macros:_macros.html.twig' as macros %}
{{ macros.javascript_filter_unit('change', '.unitTrigger') }}

所以我有两个参数:事件,通常是一个选择的变化。和一个选择器,其更改触发ajax调用。

我希望有所帮助。

答案 3 :(得分:0)

正如Carlos Granados所说,你必须使用客户端编程:Javascript。 我也遇到了和你一样的问题,并提出了一个对我来说非常有效的解决方案,这里是the snippet on codepen,你可以从(Cascade Ajax Selects)获得灵感

    //-------------------------------SELECT CASCADING-------------------------//
    var currentCities=[];
// This is a demo API key that can only be used for a short period of time, and will be unavailable soon. You should rather request your API key (free)  from http://battuta.medunes.net/   
var BATTUTA_KEY="00000000000000000000000000000000"
    // Populate country select box from battuta API
    url="http://battuta.medunes.net/api/country/all/?key="+BATTUTA_KEY+"&callback=?";
    $.getJSON(url,function(countries)
    {
        console.log(countries);
      $('#country').material_select();
        //loop through countries..
        $.each(countries,function(key,country)
        {
            $("<option></option>")
                            .attr("value",country.code)
                            .append(country.name)
                            .appendTo($("#country"));

        }); 
        // trigger "change" to fire the #state section update process
        $("#country").material_select('update');
        $("#country").trigger("change");


    });

    $("#country").on("change",function()
    {

        countryCode=$("#country").val();

        // Populate country select box from battuta API
        url="http://battuta.medunes.net/api/region/"
        +countryCode
        +"/all/?key="+BATTUTA_KEY+"&callback=?";

        $.getJSON(url,function(regions)
        {
            $("#region option").remove();
            //loop through regions..
            $.each(regions,function(key,region)
            {
                $("<option></option>")
                                .attr("value",region.region)
                                .append(region.region)
                                .appendTo($("#region"));
            });
            // trigger "change" to fire the #state section update process
            $("#region").material_select('update');
            $("#region").trigger("change");

        }); 

    });
    $("#region").on("change",function()
    {

        // Populate country select box from battuta API
        countryCode=$("#country").val();
        region=$("#region").val();
        url="http://battuta.medunes.net/api/city/"
        +countryCode
        +"/search/?region="
        +region
        +"&key="
        +BATTUTA_KEY
        +"&callback=?";

        $.getJSON(url,function(cities)
        {
            currentCities=cities;
            var i=0;
            $("#city option").remove();

            //loop through regions..
            $.each(cities,function(key,city)
            {
                $("<option></option>")
                                .attr("value",i++)
                                .append(city.city)
                        .appendTo($("#city"));
            });
            // trigger "change" to fire the #state section update process
            $("#city").material_select('update');
            $("#city").trigger("change");

        }); 

    }); 
    $("#city").on("change",function()
    {
      currentIndex=$("#city").val();
      currentCity=currentCities[currentIndex];
      city=currentCity.city;
      region=currentCity.region;
      country=currentCity.country;
      lat=currentCity.latitude;
      lng=currentCity.longitude;
      $("#location").html('<i class="fa fa-map-marker"></i> <strong> '+city+"/"+region+"</strong>("+lat+","+lng+")");
    });
   //-------------------------------END OF SELECT CASCADING-------------------------//