根据父级下拉列表的选择填充子级下拉列表

3

我正在使用最新版本的jQuery和带有Razor视图引擎的ASP.NET MVC 3。

我尝试通过Google寻找一个合适的示例来加载子下拉列表,当选择父下拉项时。我想通过jQuery AJAX并使用JSON来实现这一点。我的了解是零。

我有一个类别类,其中包含一系列类别。这是一个父子关联关系。

如果我从父下拉列表中选择一个类别,则需要在所选父类别的子下拉列表中列出所有子类别。

这是我目前拥有的内容,但需要完善,不确定我是否朝着正确的方向:

$(document).ready(function () {
   $('#ddlParentCategories').change(function () {
      alert('changed');
   });
});

我从我的视图模型中加载了下拉列表,代码如下:

@Html.DropDownListFor(x => x.ParentCategoryId, new SelectList(Model.ParentCategories, "Id", "Name", Model.ParentCategoryId), "-- Select --", new { id = "ddlParentCategories" })

第一个选项有"--选择--"的文本(对于父和子下拉列表)。在初始页面加载时,子下拉列表中不必加载任何内容。当选择值时,子下拉列表必须被填充。当再次在父下拉列表中选择"--选择--"时,必须清除子下拉列表中的所有项目,除了"--选择--"。
如果可能,如果正在加载子类别,我该如何显示那个“圆形”加载图标?
更新
我已经更新了我的代码到Darin的代码,并且无法使其正常工作:
Category类:
public class Category
{
   public int Id { get; set; }
   public string Name { get; set; }
   public string Description { get; set; }
   public string MetaKeywords { get; set; }
   public string MetaDescription { get; set; }
   public bool IsActive { get; set; }
   public int? ParentCategoryId { get; set; }
   public virtual Category ParentCategory { get; set; }
   public virtual ICollection<Category> ChildCategories { get; set; }
}

EditProductViewModel 类:

public class EditProductViewModel
{
   public int Id { get; set; }
   public string Name { get; set; }
   public string ShortDescription { get; set; }
   public string LongDescription { get; set; }
   public bool IsActive { get; set; }
   public string PageTitle { get; set; }
   public bool OverridePageTitle { get; set; }
   public string MetaKeywords { get; set; }
   public string MetaDescription { get; set; }
   public int ParentCategoryId { get; set; }
   public IEnumerable<Category> ParentCategories { get; set; }
   public int ChildCategoryId { get; set; }
   public IEnumerable<Category> ChildCategories { get; set; }
}

ProductController类:

public ActionResult Create()
{
   EditProductViewModel viewModel = new EditProductViewModel
   {
      ParentCategories = categoryService.GetParentCategories()
         .Where(x => x.IsActive)
         .OrderBy(x => x.Name),
      ChildCategories = Enumerable.Empty<Category>(),
      IsActive = true
   };

   return View(viewModel);
}

public ActionResult AjaxBindingChildCategories(int parentCategoryId)
{
   IEnumerable<Category> childCategoryList = categoryService.GetChildCategoriesByParentCategoryId(parentCategoryId);

   return Json(childCategoryList, JsonRequestBehavior.AllowGet);
}

Create视图:

<tr>
   <td><label>Parent Category:</label> <span class="red">*</span></td>
   <td>@Html.DropDownListFor(x => x.ParentCategoryId,
         new SelectList(Model.ParentCategories, "Id", "Name", Model.ParentCategoryId),
         "-- Select --",
         new { data_url = Url.Action("AjaxBindingChildCategories"), id = "ParentCategories" }
      )
      @Html.ValidationMessageFor(x => x.ParentCategoryId)
   </td>
</tr>
<tr>
   <td><label>Child Category:</label> <span class="red">*</span></td>
   <td>@Html.DropDownListFor(x => x.ChildCategoryId,
         new SelectList(Model.ChildCategories, "Id", "Name", Model.ChildCategoryId),
         "-- Select --",
         new { id = "ChildCategories" }
      )
      @Html.ValidationMessageFor(x => x.ChildCategoryId)
   </td>
</tr>

<script type="text/javascript">

   $(document).ready(function () {
      $('#ParentCategories').change(function () {
         var url = $(this).data('url');
         var data = { parentCategoryId: $(this).val() };

         $.getJSON(url, data, function (childCategories) {
            var childCategoriesDdl = $('#ChildCategories');
            childCategoriesDdl.empty();

            $.each(childCategories, function (index, childCategory) {
               childCategoriesDdl.append($('<option/>', {
                  value: childCategory, text: childCategory
               }));
            });
         });
      });
   });


</script>

它进入了我的AjaxBindingChildCategories动作,并带回记录,但它不想显示我的子类别下拉列表。我查看了Fire Bug并得到的错误是:

GET AjaxBindingChildCategories?parentCategoryId=1

500 Internal Server Error

你所询问的是“级联下拉菜单”。这个术语可以帮助你在谷歌上搜索相关信息。 - StriplingWarrior
好的,谢谢。让我去进一步搜索 :) - Brendan Vogt
3个回答

4

这是一个级联下拉列表的示例。一如既往地,首先定义一个视图模型:

public class MyViewModel
{
    [DisplayName("Country")]
    [Required]
    public string CountryCode { get; set; }
    public IEnumerable<SelectListItem> Countries { get; set; }

    public string City { get; set; }
    public IEnumerable<SelectListItem> Cities { get; set; }
}

然后是控制器:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        var model = new MyViewModel
        {
            // TODO: Fetch countries from somewhere
            Countries = new[]
            {
                new SelectListItem { Value = "FR", Text = "France" },
                new SelectListItem { Value = "US", Text = "USA" },
            },

            // initially we set the cities ddl to empty
            Cities = Enumerable.Empty<SelectListItem>()
        };
        return View(model);
    }

    public ActionResult Cities(string countryCode)
    {
        // TODO: based on the selected country return the cities:
        var cities = new[]
        {
            "Paris", "Marseille", "Lyon"
        };
        return Json(cities, JsonRequestBehavior.AllowGet);
    }
}

一个视图:
@model MyViewModel

@using (Html.BeginForm())
{
    <div>
        @Html.LabelFor(x => x.CountryCode)
        @Html.DropDownListFor(
            x => x.CountryCode, 
            Model.Countries, 
            "-- Select country --", 
            new { data_url = Url.Action("cities") }
        )
        @Html.ValidationMessageFor(x => x.CountryCode)
    </div>

    <div>
        @Html.LabelFor(x => x.City)
        @Html.DropDownListFor(
            x => x.City, 
            Model.Cities, 
            "-- Select city --"
        )
        @Html.ValidationMessageFor(x => x.City)
    </div>

    <p><input type="submit" value="OK" /></p>
}

最后是单独文件中的不显眼的JavaScript:

$(function () {
    $('#CountryCode').change(function () {
        var url = $(this).data('url'); 
        var data = { countryCode: $(this).val() };
        $.getJSON(url, data, function (cities) {
            var citiesDdl = $('#City');
            citiesDdl.empty();
            $.each(cities, function (index, city) {
                citiesDdl.append($('<option/>', {
                    value: city,
                    text: city
                }));
            });
        });
    });
});

谢谢Darin。我想在我的编辑页面上使用相同的代码(目前是用于创建页面)。我该如何使用它,以便在加载时选择国家和城市? - Brendan Vogt
我实际上有城市ID和名称。如何更改上面的代码以适应这一点? - Brendan Vogt
@Brendan Vogt,你可以在你的视图模型中添加一个Cities属性,类型为IEnumerable<SelectListItem>,并让你的编辑操作将相应国家的城市填充到其中。然后在编辑视图中使用以下代码生成城市下拉列表框:@Html.DropDownListFor(x => x.City, Model.Cities, "-- 选择城市 --")。我已经更新了我的答案,将此属性包含在视图模型中。 - Darin Dimitrov
我正在逐行检查你的代码。我刚刚在url和data变量上执行了一个alert,但它们返回为空值,这样正确吗? - Brendan Vogt
@Brendan Vogt,URL是从国家下拉列表中作为HTML5 data-url="/home/cities"属性获取的(请注意传递给DropDownListFor ddl的第四个参数)。至于cities变量,它是由jQuery传递给AJAX请求的匿名成功回调函数的参数。在这种情况下,它是一个简单的城市数组,因为Cities控制器操作返回JSON对象。 - Darin Dimitrov
显示剩余5条评论

1

jQuery脚本将如下所示:

<script type="text/javascript">
function getCities(abbr) {
    $.ajax({
        url: "@Url.Action("Cities", "Locations")",
        data: {abbreviation: abbr},
        dataType: "json",
        type: "POST",
        error: function() {
            alert("An error occurred.");
        },
        success: function(data) {
            var items = "";
            $.each(data, function(i, item) {
                items += "<option value=\"" + item.Value + "\">" + item.Text + "</option>";
            });

            $("#City").html(items);
        }
    });
}

$(document).ready(function(){
    $("#State").change(function() {
        var abbr = $("#State").val();

        getCities(abbr);
    });
});
</script>

一个检索数据的代码库可能看起来像这样(当然要将其连接到实时数据):
public class LocationRepository : ILocationRepository
{
    public IQueryable<State> GetStates()
    {
        return new List<State>
        {
            new State { Abbreviation = "NE", Name = "Nebraska" },
            new State { Abbreviation = "NC", Name = "North Carolina" }
        }.AsQueryable();
    }

    public IQueryable<City> GetCities(string abbreviation)
    {
        var cities = new List<City>();

        if (abbreviation == "NE")
        {
            cities.AddRange(new List<City> {
                new City { Id = 1, Name = "Omaha" },
                new City { Id = 2, Name = "Lincoln" }
            });
        }
        else if (abbreviation == "NC")
        {
            cities.AddRange(new List<City> {
                new City { Id = 3, Name = "Charlotte" },
                new City { Id = 4, Name = "Raleigh" }
            });
        }

        return cities.AsQueryable();
    }
}

public interface ILocationRepository
{
    IQueryable<State> GetStates();
    IQueryable<City> GetCities(string abbreviation);
}

控制器可能看起来像这样:

public class LocationsController : Controller
{
    private ILocationRepository locationRepository = new LocationRepository();

    [HttpPost]
    public ActionResult States()
    {
        var states = locationRepository.GetStates();

        return Json(new SelectList(state, "Id", "Name"));
    } 

     [HttpPost]
    public ActionResult Cities(string abbreviation)
    {
        var cities = locationRepository.GetCities(abbreviation);

        return Json(new SelectList(cities, "Abbreviation", "Name")); 
    }
}

谢谢,但我可以看一下你的HTML代码来展示这两个下拉菜单吗?从我的帖子中可以看出我如何填充父级别类别。你会如何构建子级别类别,同样是从视图模型中? - Brendan Vogt
请看一下我更新的帖子。不确定我在这里做错了什么? - Brendan Vogt

1

我假设您从服务器端填充父级下拉菜单,并且此父级下拉菜单中的第一个选项是"--- 选择 ---"

您可以尝试这个

$(document).ready(function () {
   var $parent = $('#ddlParentCategories');

   var $child = $('#ddlChildCategories');
   $child.find("option:gt(0)").remove();

   if(!$parent.children().eq(0).is(":selected")){

     $parent.change(function () {
        $.ajax({
          url: "urlToFetchTheChild",
          data: { categoryId: this.value },
          success: function(data){  
            //The data you send should be a well formed array of json object containing code/value pair of child categories

            for(var i = 0;i<data.length;i++){
               $child.append("<option value='"+ data[i].code +"'>"+ data[i].value +"</option>"); 
            }

          }
        });
     });
   }


});

请查看我的更新帖子。不确定我在这里做错了什么? - Brendan Vogt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接