c# - llenar - mejor manera de cargar 2 menús desplegables en mvc
llenar dropdownlist mvc c# (5)
¿Qué hay de usar Knockout?
Knockout es una biblioteca de JavaScript que le ayuda a crear interfaces de usuario de pantalla y editor ricas y con capacidad de respuesta con un modelo de datos subyacente limpio
Tienes que usar ajax para tus ciudades. Pero con knockout no necesitas escribir
var html = ''<option value="0">Select City</option>''; $(responce).each(function () { html += ''<option value="''+this.Value+''">''+this.Text+''</option>''}); $("#ddlCity").html(html);
en tu javascript.Knockout lo hace simple.
Simplemente puede escribir:
function CityModel() {
var self = this; // that means this CityModel
self.cities = ko.observableArray([]);
self.getCities = function () {
$.ajax({
url: "/Home/GetCities",
data: { id: $(obj).val() },
contentType: "application/json",
success: self.cities
});
}
}
ko.applyBindings(new CityModel());
eso es todo. Pero debes unir tus datos en elementos html. En lugar de usar: @Html.DropDownListFor(m => m.CityId, Cities, "Select City", new { id="ddlCity"})
Puedes usar:
<select data-bind="options:cities,optionsValue:"Id",optionsText:"CityName",optionsCaption:"Select City""></select>
o puedes mezclar la navaja y el knockout:
@Html.DropDownListFor(m => m.CityId, Cities, "Select City", new { id="ddlCity",data_bind:"options:cities,optionsValue:/"Id/",optionsText:/"CityName/""})
Una cosa más que tiene que llamar a GetCities
cuando el State
cambia, puede:
@Html.DropDownList("State", States, "Select State", new {data_bind:"event:/"change/":/"$root.GetCities/""})
No te asustes con / "/" cosas porque "
es un personaje de escape y tenemos que decir a la máquina de afeitar que quiero usar" usando / antes.
Puede encontrar más información sobre knockout: Knockout
Y mezclar con la maquinilla de afeitar: Razor y Knockout
Pd: sí, usar knockout es suspendernos de Razor y Mvc. Tienes que escribir otro ViewModel. Pero así las situaciones ko son útiles. Mezcla de afeitar y knockout es otra opción para usted.
Así es como estoy cargando en el estado de carga de la página y en el menú desplegable de la ciudad:
Mi método de controlador :
Este es el primer método que llama cuando se carga la página.
public ActionResult Index()
{
var states = GetStates();
var cities = Enumerable.Empty<SelectListItem>();
ViewBag.States = states;
ViewBag.Cities = cities;
}
private IEnumerable<SelectListItem> GetStates()
{
using (var db = new DataEntities())
{
return db.States.Select(d => new SelectListItem { Text = d.StateName, Value =d.Id.ToString() });
}
}
[HttpGet]
public ActionResult GetCities(int id)
{
using (var db = new DataEntities())
{
var data = db.Cities.Where(d=>d.StateId==id).Select(d => new { Text = d.CityName, Value = d.Id }).ToList();
return Json(data, JsonRequestBehavior.AllowGet);
}
}
Mi vista :
IEnumerable<SelectListItem> States = ViewBag.States;
IEnumerable<SelectListItem> Cities = ViewBag.Cities;
@Html.DropDownList("State", States, "Select State", new { onchange="loadCities(this)"})
@Html.DropDownListFor(m => m.CityId, Cities, "Select City", new { id="ddlCity"})
function loadCities(obj) {
$.ajax({
url: "/Home/GetCities",
data: { id: $(obj).val() },
contentType:"application/json",
success:function(responce){
var html = ''<option value="0">Select City</option>'';
$(responce).each(function () {
html += ''<option value="''+this.Value+''">''+this.Text+''</option>''
});
$("#ddlCity").html(html);
}
});
}
¿Hay alguna forma mejor que cargar el estado y el menú desplegable de la ciudad?
public class HomeController : Controller
{
public ActionResult Index(int id=0)
{
Person model = null;
var states = GetStates().ToList();
var cities = Enumerable.Empty<SelectListItem>();
if (id > 0)
{
using (var db = new DataEntities())
{
model = db.People.Include("City").FirstOrDefault(d => d.Id == id);
if (model == null)
model = new Person();
else
{
states.First(d => d.Value == model.City.StateId.ToString()).Selected = true;
cities = db.Cities.Where(d => d.StateId == model.City.StateId).ToList().Select(d => new SelectListItem { Text = d.CityName,Value=d.Id.ToString(),Selected=d.Id==model.CityId });
}
}
}
else
{
model = new Person();
}
ViewBag.States = states;
ViewBag.Cities = cities;
ViewBag.Persons = GetPersons();
return View(model);
}
[HttpGet]
public ActionResult GetCities(int id)
{
using (var db = new DataEntities())
{
var data = db.Cities.Where(d=>d.StateId==id).Select(d => new { Text = d.CityName, Value = d.Id }).ToList();
return Json(data, JsonRequestBehavior.AllowGet);
}
}
public ActionResult SavePersonDetail([Bind(Exclude = "Id")] Person model)
{
// var employeeDal= new Emploee();
//employee.firstname=model.
if (ModelState.IsValid)
{
var Id = model.Id;
int.TryParse(Request["Id"], out Id);
using (var db = new DataEntities())
{
if (Id > 0)
{
var person = db.People.FirstOrDefault(d => d.Id == Id);
if (person != null)
{
model.Id = Id;
db.People.ApplyCurrentValues(model);
}
}
else
{
db.People.AddObject(model);
}
db.SaveChanges();
}
}
if (!Request.IsAjaxRequest())
{
ViewBag.States = GetStates();
ViewBag.Persons = GetPersons();
ViewBag.Cities = Enumerable.Empty<SelectListItem>();
return View("Index");
}
else
{
return PartialView("_personDetail",GetPersons());
}
}
public ActionResult Delete(int id)
{
using (var db = new DataEntities())
{
var model = db.People.FirstOrDefault(d => d.Id == id);
if (model != null)
{
db.People.DeleteObject(model);
db.SaveChanges();
}
}
if (Request.IsAjaxRequest())
{
return Content(id.ToString());
}
else
{
ViewBag.States = GetStates();
ViewBag.Persons = GetPersons();
ViewBag.Cities = Enumerable.Empty<SelectListItem>();
return View("Index");
}
}
private IEnumerable<SelectListItem> GetStates()
{
using (var db = new DataEntities())
{
return db.States.ToList().Select(d => new SelectListItem { Text = d.StateName, Value =d.Id.ToString() });
}
}
private IEnumerable<Person> GetPersons()
{
using (var db = new DataEntities())
{
return db.People.Include("City").Include("City.State").ToList();
}
}
public ActionResult HomeAjax()
{
ViewBag.States = GetStates();
ViewBag.Cities = Enumerable.Empty<SelectListItem>();
using (var db = new DataEntities())
{
var data = db.States.Include("Cities").Select(d => new { Id = d.Id, Name = d.StateName, Cities = d.Cities.Select(x => new { Id=x.Id,Name=x.CityName}) }).ToList();
ViewBag.CityStateJson = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(data);
}
ViewBag.Persons = GetPersons();
return View();
}
}
@model IEnumerable<Person>
<div>
<table>
<tr>
<th>
First Name
</th>
<th>
Last Name
</th>
<th>
Email
</th>
<th>
City
</th>
<th>
State
</th>
<th>
Edit
</th>
</tr>
@if (Model.Count() == 0)
{
<tr>
<td colspan="6">
<h3>No data available</h3>
</td>
</tr>
}
else {
foreach (var item in Model) {
<tr data-id="@item.Id">
<td data-id="fn">@item.FirstName</td>
<td data-id="ln">@item.LastName</td>
<td data-id="email">@item.Email</td>
<td data-id="cn">@item.CityName<input type="hidden" value="@item.CityId" /></td>
<td>@item.StateName</td>
<td>
@if (ViewBag.Title == "Home Ajax" || Request.IsAjaxRequest())
{
<a href="javascript:void(0);" onclick="Edit(this,@item.Id);">Update</a>
<span>@Ajax.ActionLink("Delete", "Delete", new { id = item.Id }, new AjaxOptions {OnSuccess="deleteSuccess",OnBegin="showLoader",OnComplete="hideLoader" })</span>
}
else {
<span>@Html.ActionLink("Update", "Index", new { id = item.Id })</span>
<span>@Html.ActionLink("Delete", "Delete", new { id = item.Id })</span>
}
</td>
</tr>
}
}
</table>
</div>
@model Person
@{
ViewBag.Title = "Home Ajax";
IEnumerable<Person> persons = ViewBag.Persons;
IEnumerable<SelectListItem> States = ViewBag.States;
IEnumerable<SelectListItem> Cities = ViewBag.Cities;
IEnumerable<State> fullStates=ViewBag.CityStates;
}
@section featured {
<section class="featured">
<div class="content-wrapper">
<hgroup class="title">
<h1>@ViewBag.Title.</h1>
</hgroup>
</div>
</section>
}
@section styles{
<style type="text/css">
td,th {
border:1px solid;
padding:5px 10px;
}
select {
padding:5px 2px;
width:310px;
font-size:16px;
}
</style>
}
@section scripts{
@Scripts.Render("~/bundles/jqueryval")
<script type="text/javascript">
var jsonArray = @Html.Raw(ViewBag.CityStateJson)
function clearValues() {
$("input[type=''text''],select").val('''');
$("input[type=''hidden''][name=''Id'']").val(0);
}
function loadCities(obj) {
for (var i = 0; i < jsonArray.length; i++) {
if (jsonArray[i].Id == parseInt($(obj).val())) {
fillCity(jsonArray[i].Cities);
break;
}
}
}
function Edit(obj, Id) {
// alert("hi")
$("input[type=''hidden''][name=''Id'']").val(Id);
var tr = $(obj).closest("tr");
$("#txtfirstName").val($("td[data-id=''fn'']", tr).text().trim());
$("#txtlastName").val($("td[data-id=''ln'']", tr).text().trim());
$("#txtemail").val($("td[data-id=''email'']", tr).text().trim());
var city = $("td[data-id=''cn''] input[type=''hidden'']", tr).val();
var state;
for (var i = 0; i < jsonArray.length; i++) {
for (var j = 0; j < jsonArray[i].Cities.length; j++) {
if (jsonArray[i].Cities[j].Id == parseInt(city)) {
state = jsonArray[i].Id;
break;
}
}
if (state) {
fillCity(jsonArray[i].Cities);
break;
}
}
$("#ddlState").val(state);
$("#ddlCity").val(city);
}
function fillCity(obj) {
var html = ''<option value="0">Select City</option>'';
$(obj).each(function () {
html += ''<option value="'' + this.Id + ''">'' + this.Name + ''</option>''
});
$("#ddlCity").html(html);
}
function deleteSuccess(responce) {
alert("record deleted successfully");
$("tr[data-id=''" + responce + "'']").remove();
}
function insertSuccess() {
alert("Record saved successfully");
clearValues();
}
function showLoader() {
$("#overlay").show();
}
function hideLoader() {
$("#overlay").hide();
}
</script>
}
<h3>Add Personal Detail</h3>
@using (Ajax.BeginForm("SavePersonDetail", "Home", new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "personList" ,OnSuccess="insertSuccess",OnBegin="showLoader",OnComplete="hideLoader"}))
{
@Html.HiddenFor(m => m.Id);
<ol class="round">
<li>
@Html.LabelFor(m => m.FirstName)
@Html.TextBoxFor(m => m.FirstName, new { id = "txtfirstName" })
@Html.ValidationMessageFor(m => m.FirstName)
</li>
<li>
@Html.LabelFor(m => m.LastName)
@Html.TextBoxFor(m => m.LastName, new { id = "txtlastName" })
@Html.ValidationMessageFor(m => m.LastName)
</li>
<li>
@Html.LabelFor(m => m.Email)
@Html.TextBoxFor(m => m.Email, new { id = "txtemail" })
@Html.ValidationMessageFor(m => m.Email)
</li>
<li>
@Html.Label("State")
@Html.DropDownList("State", States, "Select State", new { onchange = "loadCities(this)", id = "ddlState" })
</li>
<li>
@Html.LabelFor(m => m.CityId)
@Html.DropDownListFor(m => m.CityId, Cities, "Select City", new { id = "ddlCity" })
@Html.ValidationMessageFor(m => m.CityId)
</li>
</ol>
<input type="submit" value="Save" />
<input type="button" value="Cancel" onclick="clearValues();"/>
}
<h2>
Person List
</h2>
<div style="position:fixed;text-align:center;top:0;bottom:0;left:0;right:0;z-index:10;background-color:black;opacity:0.6;display:none;" id="overlay">
<img style="position:relative;top:370px" src="~/Images/ajax-loader.gif" />
</div>
<div id="personList">
@Html.Partial("_personDetail", persons)
</div>
Así es como lo haría sin actualizar la página, suponiendo que la lista de ciudades no sea demasiado larga. Supongo que puede crear un método GetStatesAndCities
para devolver un diccionario.
public ActionResult Index()
{
Dictionary<string, List<String>> statesAndCities = GetStatesAndCities();
ViewBag.StatesAndCities = Json(statesAndCities);
}
Luego en la vista:
var states = JSON.parse(@ViewBag.StatesAndCities);
function loadCities(obj) {
var cities = states[$(obj).val()];
var html = ''<option value="0">Select City</option>'';
$(cities).each(function () {
html += ''<option value="''+this.Value+''">''+this.Text+''</option>''
});
$("#ddlCity").html(html);
}
De esta forma, cuando se cambia el estado, el campo de las ciudades se actualiza inmediatamente sin necesidad de devolución de llamada.
Este es un enfoque correcto, pero puedes simplificar tu javascript:
function loadCities(obj) {
$.getJSON("/Home/GetCities", function (data) {
var html = ''<option value="0">Select City</option>'';
$(data).each(function () {
html += ''<option value="''+this.Value+''">''+this.Text+''</option>''
});
$("#ddlCity").html(html);
});
}
Otra simplificación posible: agregue el elemento predeterminado (Seleccionar ciudad) del lado del servidor, por lo que su javascript será más pequeño.
Te acercas a usar ajax está bien, aunque recomendaría algunas mejores prácticas, incluyendo el uso de un modelo de vista con propiedades para StateID
, CityID
StateList
y CityList
, y utilizando discreto JavaScript en lugar de contaminar tu marcado con el comportamiento y generar el primero ("selecciona por favor" ) opción con un valor null
lugar de 0
para que pueda usarse con el atributo [Required]
HTML
@Html.DropDownList(m => m.StateID, States, "Select State") // remove the onchange
@Html.DropDownListFor(m => m.CityID, Cities, "Select City") // why change the default ID?
GUIÓN
var url = ''@Url.Action("GetCities", "Home")''; // use the helper (dont hard code)
var cities = $(''#CityID''); // cache the element
$(''#StateID'').change(function() {
$.getJSON(url, { id: $(this).val() }, function(response) {
// clear and add default (null) option
cities.empty().append($(''<option></option>'').val('''').text(''Please select''));
$.each(response, function(index, item) {
cities.append($(''<option></option>'').val(item.Value).text(item.Text));
});
});
});
Si estaba procesando varios elementos (digamos que le pedía al usuario que seleccionara las últimas 10 ciudades que visitó), puede almacenar en caché el resultado de la primera llamada para evitar llamadas repetidas, donde las selecciones pueden incluir ciudades del mismo estado.
var cache = {};
$(''#StateID'').change(function() {
var selectedState = $(this).val();
if (cache[selectedState]) {
// render the options from the cache
} else {
$.getJSON(url, { id: selectedState }, function(response) {
// add to cache
cache[selectedState] = response;
.....
});
}
});
Finalmente, en respuesta a sus comentarios sobre cómo hacerlo sin ajax, puede pasar todas las ciudades a la vista y asignarlas a una matriz de javascript. Solo recomendaría esto si tiene algunos países, cada uno con algunas ciudades. Es una cuestión de equilibrar el ligero tiempo de carga inicial extra frente a la ligera demora en hacer la llamada ajax.
En el controlador
model.CityList = db.Cities.Select(d => new { City = d.CountryID, Text = d.CityName, Value = d.Id }).ToList();
En la vista (script)
// assign all cities to javascript array
var allCities= JSON.parse(''@Html.Raw(Json.Encode(Model.CityList))'');
$(''#StateID'').change(function() {
var selectedState = $(this).val();
var cities = $.grep(allCities, function(item, index) {
return item.CountryID == selectedState;
});
// build options based on value of cities
});
descargo de responsabilidad: esta no es una respuesta de código, hay muchas otras respuestas.
Creo que es la mejor manera de mantenerte feliz de separar las páginas de la interfaz de usuario de los datos => convertirlos en llamadas API:
- / GetCities
- / GetStates
Ahora puede simplemente dejar la selección vacía en Razor renderizando la página. Y use un complemento de Jquery / Bootstrap para crear un cuadro de selección de AJAX.
De esta forma, cuando el usuario deja de escribir su búsqueda, esta cadena de búsqueda se puede enviar con la llamada AJAX (por ejemplo: /GetStates?search=test
) y luego se puede enviar un pequeño conjunto de resultados al sitio web.
Esto da:
- Mejor separación en el código del servicio
- Mejor experiencia de usuario.
- Cargas de página más pequeñas (ya que ya no envía todas las opciones al usuario cuando solicita la página, solo cuando abre el cuadro de selección).