MVC 3 Razor视图中的级联下拉菜单
我感兴趣的是如何在Razor视图中实现地址的级联下拉列表。 我的网站实体具有SuburbId属性。 郊区有CityId,City有ProvinceId。 我想在站点视图中显示所有郊区,城市和省的下拉菜单,例如,郊区下拉菜单最初将显示“首选城市”,城市下拉菜单“首选省份”。 在select一个省份时,省内的城市人口众多
我怎样才能做到这一点? 我从哪说起呢?
我们来举个例子来说明。 一如往常从模型开始:
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>
作为一个改进,JavaScript代码可以通过编写一个jQuery插件来缩短,以避免重复某些部分。
更新:
谈到一个插件,你可以有以下几点:
(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') }); });
感谢Darin为您带来解决scheme。 这大大帮助我达到了这一点。 但是正如'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(); }
希望这可以帮助有类似问题的人。
请注意,此解决scheme不能直接与EF 4.0一起使用。 它会导致“序列化…时检测到循环引用”错误。 这里有可能的解决schemehttp://blogs.telerik.com/atanaskorchev/posts/10-01-25/resolving_circular_references_when_binding_the_mvc_grid.aspx ,我用了第二个。
为了实现支持MVC内置validation和绑定的级联下拉列表,您需要做一些与其他答案中所做的不同的事情。
如果你的模型有validation,这将支持它。 摘录一个模型validation:
[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脚本来填充下拉菜单,使您失去validation。 此外,我们在这里绑定的方式在validation方面很好,所以没有理由去改变它。
现在到我们的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));
你可以将你创build的jQuery复制到你的视图中的<script>...</script>
标记中,或者如果你愿意,也可以在一个单独的脚本文件中(注意我更新了它,使它跨浏览器,我正在使用的是不再需要,但它应该工作)。
在那些相同的脚本标记中,(不在单独的文件中),可以使用以下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
部分,页面必须完全加载,然后才能尝试使下拉级联。
<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>