【问题标题】:Cascading drop-downs in MVC 3 Razor viewMVC 3 Razor 视图中的级联下拉菜单
【发布时间】:2011-05-26 10:19:15
【问题描述】:

我对如何在 Razor 视图中实现地址的级联下拉列表感兴趣。我的站点实体有一个 SuburbId 属性。 Suburb 有 CityId,City 有 ProvinceId。我想在站点视图上显示所有郊区、城市和省的下拉列表,例如郊区下拉菜单最初将显示“首先选择一个城市”,而城市下拉菜单将显示“首先选择一个省”。在选择一个省时,该省的城市人口等等。

我怎样才能做到这一点?我从哪里开始?

【问题讨论】:

标签: asp.net-mvc asp.net-mvc-3 razor


【解决方案1】:

<script src="~/Scripts/jquery-1.10.2.min.js"></script>


<script type="text/javascript">
    $(document).ready(function () {
        //Dropdownlist Selectedchange event
        $("#country").change(function () {
            $("#State").empty();
            $.ajax({
                type: 'POST',
                url: '@Url.Action("State")', // we are calling json method

                dataType: 'json',

                data: { id: $("#country").val() },
                // here we are get value of selected country and passing same value
              

                success: function (states) {
                    // states contains the JSON formatted list
                    // of states passed from the controller

                    $.each(states, function (i, state) {
                        $("#State").append('<option value="' + state.Value + '">' +
                             state.Text + '</option>');
                        // here we are adding option for States

                    });
                },
            error: function (ex) {
                alert('Failed to retrieve states.' + ex);
            }
        });
        return false;
    })
    });
</script>
<div>
    @Html.DropDownList("country", ViewBag.country as List<SelectListItem>, "CountryName", new { style = "width: 200px;" })
</div>
<div>

</div>
<div>
    @Html.DropDownList("State", ViewBag.country as List<SelectListItem>)

</div>

我从控制器获取值

 public async Task<ActionResult> Country()
    {

        Country co = new Country();
        List<SelectListItem> li = new List<SelectListItem>();
        li.Add(new SelectListItem { Text = "Select", Value = "0" });
        li.Add(new SelectListItem { Text = "India", Value = "1" });
        li.Add(new SelectListItem { Text = "Nepal", Value = "2" });
        li.Add(new SelectListItem { Text = "USA", Value = "3" });
        li.Add(new SelectListItem { Text = "Kenya", Value = "4" }); ;
        ViewBag.country= li;
        return View();
    }
    public JsonResult  state(string id)
    {
        List<SelectListItem> states = new List<SelectListItem>();
        states.Add(new SelectListItem { Text = "--Select State--", Value = "0" });
        switch (id)
        {
            case "1":


                states.Add(new SelectListItem { Text = "MP", Value = "1" });
                states.Add(new SelectListItem { Text = "UP", Value = "2" });
                break;
            case "3":

                states.Add(new SelectListItem { Text = "USA1", Value = "3" });
                states.Add(new SelectListItem { Text = "USA2", Value = "4" });
                break;
        }

        return Json(new SelectList(states, "Value", "Text", JsonRequestBehavior.AllowGet));
    }

【讨论】:

    【解决方案2】:

    感谢Darin 为您提供解决方案。它极大地帮助了我达到目的。但正如“xxviktor”所提到的,我确实得到了循环参考。错误。为了摆脱它,我这样做了。

        public string GetCounties(int countryID)
        {
            List<County> objCounties = new List<County>();
            var objResp = _mastRepo.GetCounties(countryID, ref objCounties);
            var objRetC = from c in objCounties
                          select new SelectListItem
                          {
                              Text = c.Name,
                              Value = c.ID.ToString()
                          };
            return new JavaScriptSerializer().Serialize(objRetC);
        }
    

    为了实现自动级联,我用这种方式稍微扩展了 jQuery 扩展。

            $('#ddlCountry').cascade({
                url: '@Url.Action("GetCounties")',
                paramName: 'countryID',
                childSelect: $('#ddlState'),
                childCascade: true
            });
    

    实际的 JS 使用这个参数如下(在 JSON 请求中)。

                    // trigger child change
                    if (opts.childCascade) {
                        opts.childSelect.change();
                    }
    

    希望这对有类似问题的人有所帮助。

    【讨论】:

      【解决方案3】:

      要实现支持 MVC 内置验证和绑定的级联下拉列表,您需要做一些不同于此处其他答案的操作。

      如果您的模型有验证,这将支持它。带有验证的模型的摘录:

      [Required]
      [DisplayFormat(ConvertEmptyStringToNull = false)]    
      public Guid cityId { get; set; }
      

      在你的控制器中你需要添加一个get方法,这样你的视图以后就可以获取相关数据了:

      [AcceptVerbs(HttpVerbs.Get)]
      public JsonResult GetData(Guid id)
      {
          var cityList = (from s in db.City where s.stateId == id select new { cityId = s.cityId, name = s.name }); 
          //simply grabbing all of the cities that are in the selected state 
      
          return Json(cityList.ToList(), JsonRequestBehavior.AllowGet);  
      }
      

      现在,我之前提到的视图:

      在您看来,您有两个类似的下拉菜单:

      <div class="editor-label">
          @Html.LabelFor(model => model.stateId, "State")
      </div>
      <div class="editor-field">
          @Html.DropDownList("stateId", String.Empty)
          @Html.ValidationMessageFor(model => model.stateId)
      </div>
      
      <div class="editor-label">
          @Html.LabelFor(model => model.cityId, "City")
      </div>
      <div class="editor-field">
          @*<select id="cityId"></select>*@
          @Html.DropDownList("cityId", String.Empty)
          @Html.ValidationMessageFor(model => model.cityId)
      </div>
      

      下拉列表中的内容由控制器绑定,并自动填充。注意:根据我的经验,删除此绑定并依靠 java 脚本填充下拉列表会使您失去验证。此外,我们在这里绑定的方式很适合验证,所以没有理由改变它。

      现在进入我们的 jQuery 插件:

      (function ($) {
      $.fn.cascade = function (secondaryDropDown, actionUrl, stringValueToCompare) {
          primaryDropDown = this; //This doesn't necessarily need to be global
          globalOptions = new Array(); //This doesn't necessarily need to be global
          for (var i = 0; i < secondaryDropDown.options.length; i++) {
              globalOptions.push(secondaryDropDown.options[i]);
          }
      
          $(primaryDropDown).change(function () {
              if ($(primaryDropDown).val() != "") {
                  $(secondaryDropDown).prop('disabled', false); //Enable the second dropdown if we have an acceptable value
                  $.ajax({
                      url: actionUrl,
                      type: 'GET',
                      cache: false,
                      data: { id: $(primaryDropDown).val() },
                      success: function (result) {
                          $(secondaryDropDown).empty() //Empty the dropdown so we can re-populate it
                          var dynamicData = new Array();
                          for (count = 0; count < result.length; count++) {
                              dynamicData.push(result[count][stringValueToCompare]);
                          }
      
                          //allow the empty option so the second dropdown will not look odd when empty
                          dynamicData.push(globalOptions[0].value);
                          for (var i = 0; i < dynamicData.length; i++) {
                              for (var j = 0; j < globalOptions.length; j++) {
                                  if (dynamicData[i] == globalOptions[j].value) {
                                      $(secondaryDropDown).append(globalOptions[j]);
                                      break;
                                  }
                              }
      
                          }
                      },
                      dataType: 'json',
                      error: function () { console.log("Error retrieving cascading dropdown data from " + actionUrl); }
                  });
              }
              else {
                  $(secondaryDropDown).prop('disabled', true);
              }
              secondaryDropDown.selectedindex = 0; //this prevents a previous selection from sticking
          });
          $(primaryDropDown).change();
      };
      } (jQuery));
      

      您可以将我创建的上述 jQuery 复制到您视图中的 &lt;script&gt;...&lt;/script&gt; 标记中,或者如果您愿意,也可以复制到单独的脚本文件中(注意我更新了它以使其跨浏览器,但是我使用的场景不再需要,但它应该可以工作)。

      在这些相同的脚本标签中,(不是在单独的文件中)您可以使用以下 javascript 调用插件:

      $(document).ready(function () {
          var primaryDropDown = document.getElementById('stateId');
          var secondaryDropdown = document.getElementById('cityId');
          var actionUrl = '@Url.Action("GetData")'
          $(primaryDropDown).cascade(secondaryDropdown, actionUrl);
      });
      

      记得添加$(document).ready 部分,页面必须完全加载后才能尝试使下拉菜单级联。

      【讨论】:

        【解决方案4】:

        请注意,此解决方案不能直接用于 EF 4.0。它会导致“序列化时检测到循环引用...”错误。以下是可能的解决方案http://blogs.telerik.com/atanaskorchev/posts/10-01-25/resolving_circular_references_when_binding_the_mvc_grid.aspx,我用过第二个。

        【讨论】:

          【解决方案5】:

          让我们用一个例子来说明。一如既往地从模型开始:

          public class MyViewModel
          {
              public string SelectedProvinceId { get; set; }
              public string SelectedCityId { get; set; }
              public string SelectedSuburbId { get; set; }
              public IEnumerable<Province> Provinces { get; set; }
          }
          
          public class Province
          {
              public string Id { get; set; }
              public string Name { get; set; }
          }
          

          接下来是控制器:

          public class HomeController : Controller
          {
              public ActionResult Index()
              {
                  var model = new MyViewModel
                  {
                      // TODO: Fetch those from your repository
                      Provinces = Enumerable.Range(1, 10).Select(x => new Province
                      {
                          Id = (x + 1).ToString(),
                          Name = "Province " + x
                      })
                  };
                  return View(model);
              }
          
              public ActionResult Suburbs(int cityId)
              {
                  // TODO: Fetch the suburbs from your repository based on the cityId
                  var suburbs = Enumerable.Range(1, 5).Select(x => new
                  {
                      Id = x,
                      Name = "suburb " + x
                  });
                  return Json(suburbs, JsonRequestBehavior.AllowGet);
              }
          
              public ActionResult Cities(int provinceId)
              {
                  // TODO: Fetch the cities from your repository based on the provinceId
                  var cities = Enumerable.Range(1, 5).Select(x => new
                  {
                      Id = x,
                      Name = "city " + x
                  });
                  return Json(cities, JsonRequestBehavior.AllowGet);
              }
          }
          

          最后是一个视图:

          @model SomeNs.Models.MyViewModel
          
          @{
              ViewBag.Title = "Home Page";
          }
          
          <script type="text/javascript" src="/scripts/jquery-1.4.4.js"></script>
          <script type="text/javascript">
              $(function () {
                  $('#SelectedProvinceId').change(function () {
                      var selectedProvinceId = $(this).val();
                      $.getJSON('@Url.Action("Cities")', { provinceId: selectedProvinceId }, function (cities) {
                          var citiesSelect = $('#SelectedCityId');
                          citiesSelect.empty();
                          $.each(cities, function (index, city) {
                              citiesSelect.append(
                                  $('<option/>')
                                      .attr('value', city.Id)
                                      .text(city.Name)
                              );
                          });
                      });
                  });
          
                  $('#SelectedCityId').change(function () {
                      var selectedCityId = $(this).val();
                      $.getJSON('@Url.Action("Suburbs")', { cityId: selectedCityId }, function (suburbs) {
                          var suburbsSelect = $('#SelectedSuburbId');
                          suburbsSelect.empty();
                          $.each(suburbs, function (index, suburb) {
                              suburbsSelect.append(
                                  $('<option/>')
                                      .attr('value', suburb.Id)
                                      .text(suburb.Name)
                              );
                          });
                      });
                  });
              });
          </script>
          
          <div>
              Province: 
              @Html.DropDownListFor(x => x.SelectedProvinceId, new SelectList(Model.Provinces, "Id", "Name"))
          </div>
          <div>
              City: 
              @Html.DropDownListFor(x => x.SelectedCityId, Enumerable.Empty<SelectListItem>())
          </div>
          <div>
              Suburb: 
              @Html.DropDownListFor(x => x.SelectedSuburbId, Enumerable.Empty<SelectListItem>())
          </div>
          

          作为一项改进,可以通过编写 jquery 插件来缩短 javascript 代码以避免重复某些部分。


          更新:

          谈到一个插件,你可能会有一些东西:

          (function ($) {
              $.fn.cascade = function (options) {
                  var defaults = { };
                  var opts = $.extend(defaults, options);
          
                  return this.each(function () {
                      $(this).change(function () {
                          var selectedValue = $(this).val();
                          var params = { };
                          params[opts.paramName] = selectedValue;
                          $.getJSON(opts.url, params, function (items) {
                              opts.childSelect.empty();
                              $.each(items, function (index, item) {
                                  opts.childSelect.append(
                                      $('<option/>')
                                          .attr('value', item.Id)
                                          .text(item.Name)
                                  );
                              });
                          });
                      });
                  });
              };
          })(jQuery);
          

          然后简单地连接起来:

          $(function () {
              $('#SelectedProvinceId').cascade({
                  url: '@Url.Action("Cities")',
                  paramName: 'provinceId',
                  childSelect: $('#SelectedCityId')
              });
          
              $('#SelectedCityId').cascade({
                  url: '@Url.Action("Suburbs")',
                  paramName: 'cityId',
                  childSelect: $('#SelectedSuburbId')
              });
          });
          

          【讨论】:

          • 我正要写一条评论说“为什么要做这项工作?使用 jquery 插件” - 然后我读了你的最后一句话。 :) +1
          • + 只是为了您的回答的全面性,谢谢。我还没有在我的代码中使用它。但它看起来像个赢家。
          • 我刚刚写出了我的 javascript 函数,然后向下滚动查看一个函数:/ +1。
          • 我仍然想知道如何以更有效的方式做到这一点。似乎应该有一个更好的 MVC 方式来修复这个的初始绑定。
          • @Darin Dimitrov,嘿只是想停下来告诉你我(以及我确信的所有开发人员)非常感谢你愿意在 SO 上提供帮助。我已经做了几年开发人员了,虽然对 SO 很陌生,但我认为你是一个非常棒的人,可以抽出时间来帮助解决重要的(有时是与工作相关的)问题。
          猜你喜欢
          • 2018-04-25
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-06-23
          • 1970-01-01
          • 2011-03-03
          • 1970-01-01
          相关资源
          最近更新 更多