2010-12-16 18 views
47

Estoy interesado en cómo implementar listas desplegables en cascada para direcciones en una vista Razor. Mi entidad de sitio tiene una propiedad Suburbio. El suburbio tiene CityId y City tiene ProvinceId. Me gustaría mostrar los menús desplegables para todos los suburbios, ciudades y provincias en la vista del sitio, donde, por ejemplo, el menú desplegable del barrio mostrará inicialmente "Primero seleccione una ciudad", y el menú desplegable Ciudad, "Primero seleccione una provincia". Al seleccionar una provincia, las ciudades de la provincia están ocupadas, etc.Desplegaderas en cascada en MVC 3 Razor view

¿Cómo puedo lograr esto? ¿Dónde empiezo?

+0

Mi Blog en cascada DropDownList en ASP.Net MVC (http://blogs.msdn.com/b/ rickandy/archive/2012/01/09/cascasding-dropdownlist-in-asp-net-mvc.aspx) hace esto exactamente. Véase también mi tutorial Cómo trabajar con DropDownList Box y jQuery (http://www.asp.net/mvc/tutorials/javascript/working-with-the-dropdownlist-box-and-jquery/using-the-dropdownlist-helper- with-aspnet-mvc) – RickAndMSFT

Respuesta

86

Vamos a ilustrarlo con un ejemplo. Como siempre empezar con un modelo:

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

siguiente de un controlador:

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

Y, finalmente, una vista:

@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> 

Como una mejora del código JavaScript podría acortarse escribiendo un jQuery complemento para evitar la duplicación de algunas partes.


ACTUALIZACIÓN:

Y hablando de un plugin que podría tener algo entre las líneas:

(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); 

Y luego simplemente cablear:

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

    $('#SelectedCityId').cascade({ 
     url: '@Url.Action("Suburbs")', 
     paramName: 'cityId', 
     childSelect: $('#SelectedSuburbId') 
    }); 
}); 
+1

Estaba a punto de escribir un comentario que decía "¿Por qué entrar en este esfuerzo? Use un plugin jquery" - entonces leí su última oración. :) +1 – RPM1984

+4

+ solo por la amplitud de su respuesta, gracias. Todavía no lo he usado en mi código. pero parece un ganador. – ProfK

+1

Acabo de escribir mi función javascript y luego me desplacé hacia abajo para ver una función:/+1 para ello. – Doomsknight

5

Gracias Darin por su ventaja a la solución. Me ayudó enormemente llegar al punto. Pero como se mencionó 'xxviktor', obtuve la referencia circular. error. Para deshacerme de él, lo hice de esta manera.

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

Y para lograr el auto en cascada, he extendido ligeramente la extensión jQuery de esta manera.

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

Y la JS real está utilizando este parámetro de la siguiente manera (en el interior solicitud JSON).

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

Espero que esto ayude a alguien con problemas similares.

0

Para implementar listas desplegables en cascada que admitan la validación y encuadernación integradas de MVC, tendrá que hacer algo un poco diferente de lo que se hace en las otras respuestas aquí.

Si su modelo tiene validación, esto lo admitirá.Un extracto de un modelo con la validación:

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

En su controlador es necesario agregar un método get, para que su vista será capaz de obtener los datos relevantes más tarde:

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

Ahora, a la opinión de que he mencionado anteriormente:

En su opinión que tiene dos menús desplegables similares a esta:

<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> 

El contenido de los menús desplegables está vinculado por el controlador y se completa automáticamente. Nota: en mi experiencia al eliminar este enlace y confiar en el script java para rellenar los menús desplegables, pierdes la validación. Además, la forma en que estamos vinculados aquí juega bien con la validación, por lo que no hay ninguna razón para cambiarla.

Ahora en nuestro plugin 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)); 

Puede copiar lo anterior jQuery que he creado, en <script>...</script> etiquetas en su opinión, o en un archivo de secuencia de comandos independiente si así lo desea (Nota I Actualizado esto para hacer se cruza el navegador, sin embargo, el escenario en el que estaba usando ya no es necesario, sin embargo debería funcionar).

En esas mismas etiquetas de script, (no en un archivo separado) se puede llamar el plugin utilizando el siguiente javascript:

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

recuerde añadir la parte $(document).ready, la página debe estar completamente cargada antes Intenta hacer que los drop downs entren en cascada.

0

<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>

desde el controlador que estoy recibiendo los valores

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)); 
    } 
Cuestiones relacionadas