Des menus déroulants en cascade dans la vue MVC 3 Razor

Je suis intéressé par la façon de mettre en place des listes déroulantes en cascade pour les adresses dans une vue de razor. L’entité Mon site a une propriété SuburbId. La banlieue a une ville et la ville a la province. Je souhaite afficher les listes déroulantes de toutes les banlieues, villes et provinces sur la vue du site, où par exemple la liste déroulante des banlieues affichera initialement “Sélectionner d’abord une ville” et la liste déroulante Ville, “Sélectionner d’abord une province”. En sélectionnant une province, les villes de la province sont peuplées, etc.

Comment puis-je atteindre cet objective? Où est-ce que je commence?

Illustrons avec un exemple. Comme toujours, commencez par un modèle:

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

Suivant un contrôleur:

 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).ToSsortingng(), 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); } } 

Et enfin une vue:

 @model SomeNs.Models.MyViewModel @{ ViewBag.Title = "Home Page"; }   
Province: @Html.DropDownListFor(x => x.SelectedProvinceId, new SelectList(Model.Provinces, "Id", "Name"))
City: @Html.DropDownListFor(x => x.SelectedCityId, Enumerable.Empty())
Suburb: @Html.DropDownListFor(x => x.SelectedSuburbId, Enumerable.Empty())

En guise d’amélioration, le code javascript pourrait être raccourci en écrivant un plugin jquery pour éviter de dupliquer certaines parties.


METTRE À JOUR:

Et en parlant d’un plugin, vous pourriez avoir quelque chose parmi les lignes:

 (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( $('') .attr('value', item.Id) .text(item.Name) ); }); }); }); }); }; })(jQuery); 

Et puis tout simplement le câbler:

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

Merci Darin pour votre consortingbution à la solution. Cela m’a beaucoup aidé pour arriver au point. Mais comme le mentionnait ‘xxviktor’, j’ai eu la réf. Erreur. Pour m’en débarrasser, j’ai fait comme ça.

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

Et pour réaliser la mise en cascade automatique, j’ai légèrement étendu l’extension jQuery de cette façon.

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

Et le JS actuel utilise ce paramètre comme ci-dessous (dans la requête JSON).

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

J’espère que cela aide quelqu’un avec un problème similaire.

Sachez que cette solution ne fonctionne pas directement avec EF 4.0. Cela provoque une erreur “Une référence circulaire a été détectée lors de la sérialisation …”. Voici des solutions possibles http://blogs.telerik.com/atanaskorchev/posts/10-01-25/resolving_circular_references_when_binding_the_mvc_grid.aspx , j’ai utilisé la deuxième.

Pour implémenter des listes déroulantes en cascade prenant en charge la validation et la liaison intégrées de MVC, vous devrez faire quelque chose de différent de ce qui est fait dans les autres réponses.

Si votre modèle a la validation, cela le supportera. Un extrait d’un modèle avec validation:

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

Dans votre contrôleur, vous devez append une méthode get pour que votre vue puisse obtenir les données pertinentes ultérieurement:

 [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); } 

Maintenant, à la vue que j’ai mentionné plus tôt:

À votre avis, vous avez deux listes déroulantes similaires à celles-ci:

 
@Html.LabelFor(model => model.stateId, "State")
@Html.DropDownList("stateId", Ssortingng.Empty) @Html.ValidationMessageFor(model => model.stateId)
@Html.LabelFor(model => model.cityId, "City")
@**@ @Html.DropDownList("cityId", Ssortingng.Empty) @Html.ValidationMessageFor(model => model.cityId)

Le contenu des listes déroulantes est lié au contrôleur et est automatiquement renseigné. Remarque: selon mon expérience de la suppression de cette liaison et de l’utilisation d’un script Java pour remplir les menus déroulants, vous perdez la validation. De plus, la façon dont nous lions ici est agréable avec la validation, il n’ya donc aucune raison de la changer.

Maintenant sur notre plugin jQuery:

 (function ($) { $.fn.cascade = function (secondaryDropDown, actionUrl, ssortingngValueToCompare) { 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)); 

Vous pouvez copier le fichier jQuery ci-dessus que j'ai créé dans des balises dans votre vue ou dans un fichier de script distinct si vous le souhaitez. ce que j'utilisais n'est plus nécessaire, il devrait fonctionner cependant).

Dans ces mêmes balises de script (pas dans un fichier séparé), vous pouvez appeler le plug-in en utilisant le javascript suivant:

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

N'oubliez pas d'append la partie $(document).ready , la page doit être entièrement chargée avant d'essayer de créer une cascade de menus déroulants.

   
 
@Html.DropDownList("country", ViewBag.country as List, "CountryName", new { style = "width: 200px;" })
@Html.DropDownList("State", ViewBag.country as List)