Working With ViewModels In Kendo UI ASP.net MVC - asp.net-mvc

I have Three CaseCade Comboboxes, which work just fine for me
The problem is begins when I want to SAVE the id that Are Selected ,
I know how to get the Selected Id Which will be like this, and i have no problem with that
var countries = $("#countries").data("kendoDropDownList")
countries.value()
i don't know how to Set this values in my view model that i defined Above in My view, i don't know a way to pass view model to POST function.
This Is My View
#using System.Web.Optimization
#using Kendo.Mvc.UI
#model Mfr.Admin.Models.Address.CreateViewModel
#{
Layout = "~/Views/Shared/_Main.cshtml";
}
<
<div class="demo-section k-content">
<h4>Countries:</h4>
#(Html.Kendo().DropDownListFor(m=>m.CountryId)
.HtmlAttributes(new {style = "width:100%"})
.OptionLabel("Select Country...")
.DataTextField("Title")
.DataValueField("Id")
.DataSource(source =>
{
source.Read(read =>
{
read.Action("GetCascadeCountries", "Address");
});
})
)
<h4 style="margin-top: 2em;">states:</h4>
#(Html.Kendo().DropDownListFor(m=>m.StateId)
.HtmlAttributes(new {style = "width:100%"})
.OptionLabel("Select state...")
.DataTextField("stateTitle")
.DataValueField("stateId")
.DataSource(source =>
{
source.Read(read =>
{
read.Action("GetCascadeStates", "Address")
.Data("filterStates");
})
.ServerFiltering(true);
})
.Enable(false)
.AutoBind(false)
.CascadeFrom("CountryId")
)
<script>
function filterStates() {
return {
countries: $("#CountryId").val()
};
}
</script>
<h4 style="margin-top: 2em;">cities:</h4>
#(Html.Kendo().DropDownListFor(m=>m.CityId)
.HtmlAttributes(new {style = "width:100%"})
.OptionLabel("Select city...")
.DataTextField("cityTitle")
.DataValueField("cityId")
.DataSource(source =>
{
source.Read(read =>
{
read.Action("GetCascadeCities", "Address")
.Data("filterCities");
})
.ServerFiltering(true);
})
.Enable(false)
.AutoBind(false)
.CascadeFrom("StateId")
)
<script>
function filterCities() {
return {
cities: $("#StateId").val()
};
}
</script>
<button class="k-button k-primary" id="get" style="margin-top: 2em; float: right;">Save</button>
</div>
<script>
$(document).ready(function () {
$("#get").click(function () {
// I want To call My post function Here, and pass viewmodel with initialized values to that
// I Suppose It will be something like this
//but I dont know how to set values to view model
//$.ajax({
// url: "#Html.Raw(Url.Action("Create", "Address"))",
// type: "POST",
// dataType: "json"
//)};
});
});
</script>
<style>
.k-readonly {
color: gray;
}
This is My save Action
address here is not initialized
[HttpPost]
public ActionResult Create(CreateViewModel address)
{
if (address == null)
throw new ArgumentNullException(nameof(address));
var addressModel = new Address()
{
Description = address.Description,
CityId = address.CityId,
StateId = address.StateId,
CountryId = address.CountryId,
UserApplicationId = User.Identity.GetUserId<int>()
};
_addressRepository.Add(addressModel);
_addressRepository.Complete();
return Json("");
}
this is view model
public class CreateViewModel
{
public int Id { get; set; }
public int UserApplicationId { get; set; }
public int CountryId { get; set; }
public int StateId { get; set; }
public int CityId { get; set; }
public string Description { get; set; }
}

You just need to send json back to controller
var countries = $("#countries").data("kendoDropDownList")
var countryId = countries.value();
var id = ..... //and so on
var data = {'Id': id, 'CountryId': countryId, /*ad so on*/ }
...
data: JSON.stringify(data)
...
Collect all data you need, put it in json object, stringify and if json properties correspond correctly to model properties, it will be automatically binded to controllers parameter.

Related

#Html.CheckBoxFor Set Value With Value From Model

How do I set the value of this checkbox with the value of SelectedWebSite1URL property?
#model WLWeb.Models.MyModel
...
<label>#Html.CheckBoxFor(m => m.MyModel.SelectedWebSite1, new { #id = "chk1", #class = "chkWebSite", value = "HowDoIsetThis?" })#Html.DisplayFor(m => m.MyModel.SelectedWebSite1Name)</label>
model:
public class MyModel
{
...
public bool SelectedWebSite1 { get; set; }
public string SelectedWebSite1Name { get; set; }
public string SelectedWebSite1URL { get; set; }
}
Note: the reason I need this is to get the value (website url) with jquery:
$(function () {
$('#btnGoSite').click(function () {
$('.chkWebSite:checked').each(function () {
alert(this.value);
});
});
});
Just Remove MyModel
<label>#Html.CheckBoxFor(m => m.SelectedWebSite1, new { #id = "chk1", #class = "chkWebSite", value = "yourvaluehere?" })
#Html.DisplayFor(m => m.SelectedWebSite1Name)</label>
I think you are misunderstanding a checkbox. A checkbox is for boolean values (true or false) not strings.
It works for public bool SelectedWebSite1 { get; set; } but not for public string SelectedWebSite1URL { get; set; }
If you want to access SelectedWebSite1URL, render its value in hidden input and use something like (assumes the hidden input is immediately after the checkbox)
$(function () {
$('#btnGoSite').click(function () {
$('.chkWebSite:checked').each(function () {
alert($(this).next('input[type="hidden"]').val());
});
});
});
Try this
<label>
#Html.CheckBoxFor(x => x.SelectedWebSite1,new { #id = "chk1", #class = "chkWebSite"})
#Html.DisplayFor(m => m.MyModel.SelectedWebSite1Name)
</label>
No need to set value in html object attributes as check box will be automatically check/uncheck based on value of SelectedWebSite1 is true/false.

I want to fill city dropdown automatically from database according to state dropdown in ASP.NET MVC and Ajax

I want to get city list from database and store selected city's id into database. I have used Ajax to call function of member class. But it is not working, please help me to sort this out.
Here is my Model:
[Required]
[Display(Name = "State")]
public int stateid { get; set; }
public string stateName { get; set; }
public List<SelectListItem> stateList = new List<SelectListItem>();
[Required]
[Display(Name = "City")]
public int Cityid { get; set; }
public string CityName { get; set; }
public List<SelectListItem> CityList = new List<SelectListItem>();
clubDataContext cd = new clubDataContext();
public void insertmember(M_Reg m)
{
M_Registarion m1 = new M_Registarion();
m1.M_StatteId = m.stateid;
m1.M_CityId = 1; //temporary storing 1
cd.M_Registarions.InsertOnSubmit(m1);
cd.SubmitChanges();
}
Here is my controller:
[HttpGet]
public ActionResult Registration()
{
var model = new M_Reg();
using (var db = new clubDataContext())
{
model.stateList = content2.Select(c2 => new SelectListItem
{
Text = c2.S_Name,
Value = c2.S_ID.ToString()
}).ToList();
}
return View(model);
}
[HttpGet]
public SelectList getCity(int stateId, int selectCityId)
{
var db = new clubDataContext();
var model = new M_Reg();
var content = from p in db.CityInfos where p.S_ID == stateId
select new { p.C_ID, p.C_Name };
model.CityList = content.Select(c => new SelectListItem
{
Text = c.C_Name,
Value = c.C_ID.ToString()
}).ToList();
return new SelectList(model.CityList, "Value", "Text", selectCityId);
}
View:
Razor code:
<div class="editor-label">
#Html.LabelFor(m=> m.stateid)
</div>
<div class="editor-field">
#Html.DropDownListFor(m => m.stateid,Model.stateList)
#Html.ValidationMessageFor(m => m.stateid)
</div>
<div class="editor-label">
#Html.LabelFor(m=> m.Cityid)
</div>
<div class="editor-field">
#Html.DropDownListFor(m => m.Cityid, Model.CityList)
#Html.ValidationMessageFor(m => m.Cityid, Model.c)
</div>
Ajax code:
$("#stateid").change(function () {
$.ajax({
type: "POST",
url: '#Url.Action("Member", "getCity")',
data: { stateId: $("#stateid > option:selected").attr("value") },
success: function (data) {
var items = [];
items.push("<option>--Choose Your City--</option>");
$.each(data, function () {
items.push("<option value=" + this.Value + ">" + this.Text + "</option>");
});
$("#Cityid").html(items.join(' '));
}
})
});
Try Like This
Here Controller :
public JsonResult functionname(){
List<string> City = new List<string>();
var DBcity = yourDBentity.TableName.Where(x=>x.columnname==condition).select(x=>x.city);
foreach(var i in DBcity){
City.Add(i);
}
return Json(City, JsonRequestBehavior.AllowGet);
}
Jquery:
$(document).ready(function (result) {
$.post('/yourcontorller/functionname', {parameter : parameter }, function (result) {
$.each(result, function (key, value) {
$('#yourDropdownId').append($("<option></option>").html(value).val(value));
});
},"json");
});
Finally, this code works...
Model Class:
clubDataContext _db = new clubDataContext();
[Required]
[Display(Name="City")]
public virtual string icityid { get; set; }
public List<SelectListItem> cityList = new List<SelectListItem>();
[Required]
[Display(Name = "State")]
public virtual string istateid { get; set; }
public SelectList getState()
{
IEnumerable<SelectListItem> stateList = (from m in _db.StateInfos select m).AsEnumerable().Select(m => new SelectListItem() { Text = m.S_Name, Value = m.S_ID.ToString() });
return new SelectList(stateList, "Value", "Text", istateid);
}
View :
<div class="editor-label">
#Html.LabelFor(m=> m.istateid)
</div>
<div class="editor-field">
#Html.DropDownListFor(m => m.istateid,Model.getState(),"--Choose your State--")
#Html.ValidationMessageFor(m => m.istateid)
</div>
<div class="editor-label">
#Html.LabelFor(m=> m.icityid)
</div>
<div class="editor-field">
#Html.DropDownListFor(m => m.icityid,Model.cityList,"--Choose your City--")
#Html.ValidationMessageFor(m => m.icityid)
</div>
Ajax:
$('#istateid').change(function(){
$.ajax({
type:"POST",
url:'#Url.Action("getCityJson","Member")',
data: { stateId : $("#istateid > option:selected").attr("value")},
success: function (data){
var items= [];
$.each(data,function(){
items.push("<option value=" + this.Value + ">" + this.Text + "</option>");
});
$("#icityid").html(items.join(' '));
}
})
});
And Controller:
[HttpPost]
public JsonResult getCityJson(string stateId, string selectCityId=null)
{
return Json(getCity(stateId, selectCityId));
}
public SelectList getCity(string stateId, string selectCityId = null)
{
var db = new clubDataContext();
IEnumerable<SelectListItem> cityList = new List<SelectListItem>();
if (!string.IsNullOrEmpty(stateId))
{
int _stateId = Convert.ToInt32(stateId);
cityList = (from m in db.CityInfos where m.S_ID == _stateId select m).AsEnumerable().Select(m => new SelectListItem() { Text = m.C_Name, Value = m.C_ID.ToString() });
}
return new SelectList(cityList, "Value", "Text", selectCityId);
}

MVC validation not working as expected in popup window

I'm trying to utilize the built-in validation in MVC and it doesn't appear to be working. If I leave the fields in my form empty I get back my "Successfully Saved." message.
Shouldn't the fields that are 'Required' be marked as required in the form?
Controller:
public ActionResult Create([DataSourceRequest] DataSourceRequest request, ACore.Asset assetForm)
{
var results = new
{
value = false,
Message = ""
};
if (ModelState.IsValid)
{
results = new
{
value = true,
Message = "Successfully Saved."
};
return Json(results);
}
results = new
{
value = false,
Message = "Please check the form values."
};
return Json(results);
}
View (condensed):
#using (Html.BeginForm("Create", "Asset", FormMethod.Post, new { id = "frmAsset"}))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<div class="tempStyle">
<div class="editor-label fl">
#Html.LabelFor(model => model.AssetName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.AssetName)
#Html.ValidationMessageFor(model => model.AssetName)
</div>
</div>
<div style="position: relative;">
<input type="button" value="Save" id="btnSave" />
</div>
JavaScript that handles my save:
var saveAsset = function (e) {
var form = $("#frmAsset");
$.ajax({
type: "POST",
url: "/Asset/Create",
data: $(form).serialize(),
success: function (data) {
if (data.value == true) {
alert(data.Message);
// Close popup window
var window = $('#AssetEditorPopUp').data("kendoWindow");
window.close();
// Refresh grid to show changes
$('#grid').data("kendoGrid").dataSource.read();
return;
}
alert(data.Message);
},
error: function () {
alert("There was an error editing the asset.");
}
});
};
Model:
public class Asset
{
[ScaffoldColumn(false)]
public int AssetId { get; set; }
[Required]
[Display(Name="Asset Name:")]
public string AssetName { get; set; }
public string AssetFormerName { get; set; }
public string Seg1Code { get; set; }
public string Seg3Code { get; set; }
public bool? ActiveFlag { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string AssetType { get; set; }
[Display(Name = "ZipCode:")]
[RegularExpression("([a-zA-Z0-9 .&'-]+)", ErrorMessage = "Enter only alphabets and numbers of First Name")]
public string ZipCode { get; set; }
}
This is what I get doing a POST rather than ajax.
your button is set to be of type button, it should be submit. you aren't firing the forms submit handler, so validation isn't being performed.
create your form like this:
#using (Html.BeginForm("Create", "Asset", FormMethod.Post, new { id = "frmAsset"}))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<div class="tempStyle">
<div class="editor-label fl">
#Html.LabelFor(model => model.AssetName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.AssetName)
#Html.ValidationMessageFor(model => model.AssetName)
</div>
</div>
<div style="position: relative;">
<input type="submit" value="Save" id="btnSave" />
</div>
Then you can use the following javascript:
$(function(){
$("#frmAsset").submit(function(evt){
var form = $(evt.currentTarget);
if(form.validate().isvalid()
{
// your handler here
}
evt.preventDefault(); // prevent the form from posting normally
return false;
};
};
I would look at the AJAX.BeginForm helper, it is more suited to what you are doing here.
nb. I haven't typed this in the ide, so i don't know if it is 100% valid, you will need to test it.

Filtering a WebGrid with a DropDownList in MVC4

I am using a WebGrid, which i bind to a List of objects containing information about deliveries. I want to be able to filter said WebGrid using a DropDownList containing Customers. When I select a Customer in the DropDownList the change-method sends an Ajax call which is supposed to get the new items for the WebGrid.
The call is successful, but nothing happens. The WebGrid doesn't change at all. I even tried sending an Ajax call identical to the ones sent when sorting the list. But nothing happens.
What am I doing wrong here?
ViewModel:
public class DeliveriesViewModel : PageViewModel<DeliveriesPage>
{
public DeliveriesViewModel(DeliveriesPage currentPage) : base(currentPage)
{
DeliveryItems = new List<DeliveryItem>();
}
public List<DeliveryItem> DeliveryItems { get; set; }
public List<SelectListItem> Customers { get; set; }
}
Controller:
public ActionResult Index(DeliveriesPage currentPage, string customer)
{
var model = new DeliveriesViewModel(currentPage);
model.Customers = _deliveryService.GetCustomers();
model.DeliveryItems = customer == null ? _deliveryService.GetDeliveryItems() : _deliveryService.GetDeliveryItems(customer);
return View(model);
}
View:
#model DeliveriesViewModel
<h1>#Model.CurrentPage.PageName</h1>
#Html.DropDownList("customerDropDown", Model.Customers)
#Html.Partial("_grid", Model)
<script type="text/javascript">
$("#customerDropDown").change(function () {
$.get("?Customer="+$("#customerDropDown").val());
});
</script>
_grid partial View:
#model DeliveriesViewModel
#{
var grid = new WebGrid(Model.DeliveryItems, canPage:true, canSort: true, ajaxUpdateContainerId:"container-grid");
}
<div id="container-grid">
#grid.GetHtml(
columns: grid.Columns(
grid.Column("DeliveryId"),
grid.Column("CustomerName"),
grid.Column("ShipNumber"),
grid.Column("ShipName"),
grid.Column("Product"),
grid.Column("PlannedWeight"),
grid.Column("TotalWeight"),
grid.Column("ShipStatus"),
grid.Column("TransportTo"),
grid.Column("TransportFrom"),
grid.Column("RevDate"),
grid.Column("ShipStemDept"),
grid.Column("ShipRealDept"),
grid.Column("ShipStemArr"),
grid.Column("ShipRealArr"),
grid.Column("TranspMonth"),
grid.Column("TranspYear")
))
</div>
$.get("?Customer="+$("#customerDropDown").val()); sends an AJAX call to the server and that's about it. You haven't subscribed to the success callback in order to update your DOM. So it is not surprising that nothing happens.
So try like this:
<script type="text/javascript">
$('#customerDropDown').change(function () {
var url = '#Url.Action("index")';
$.get(url, { customer: $(this).val() }, function(result) {
$('#container-grid').html(result);
});
});
</script>
Notice how I have used the UrlHelper to calculate the correct url to your controller action, I have then passed the selected value of the dropdown as second parameter to the $.get method and last but not least I have subscribed to the success callback of the ajax request and updated the #container-grid div with the results returned by the controller action.
Also since you are calling this action with AJAX, you should return only a PartialView from it and not an entire View. This partial view should contain your grid. Otherwise you will end up with duplicate layout injected into the div.
Model
public class EmployerTestResultsModel
{
[Display(Name = "Employer List")]
public IEnumerable<SelectListItem> EmployerList { get; set; }
[Required]
public string SelectedEmployerId { get; set; }
public List<EmployerTestResultsModel> EmployerGrid { get; set; }
public Int64 FileId { get; set; }
[Display(Name = "File Name")]
public string FileName { get; set; }
[DataType(DataType.Date)]
public DateTime Date { get; set; }
[Display(Name = "Scheme Id")]
public string SchemeId { get; set; }
public string Status { get; set; }
[Display(Name = "Validation Error Report")]
public string ValidationErrorReport { get; set; }
}
controller
[HttpGet]
public ActionResult EmployerTestResults()
{
EmployerTestResultsModel model = new EmployerTestResultsModel();
ViewBag.HideSection = true;
model.EmployerList = (from d in _context.Employers
select new System.Web.Mvc.SelectListItem
{
Text = d.EmployerName,
Value = d.EmployerId
});
model.EmployerGrid = (from efd in _context.EmployerFileDatas
// join efhd in _context.EmployerFileHeaderDetails on efd.FileDataIdentityKey equals efhd.FileDataIdentityKey
orderby efd.EmployerId , efd.Timestamp
select new EmployerTestResultsModel
{
FileId = efd.FileDataIdentityKey,
FileName = efd.FileName,
Date = efd.Timestamp,
//SchemeId = efhd.SchemeId,
Status = efd.ValidationStatus,
ValidationErrorReport = "View"
}).ToList();
return View("EmployerTestResults", model);
}
View:
#model EFITestHarness.Models.EmployerTestResultsModel
#using System.Web.Helpers;
#{
ViewBag.Title = "EmployerTestResults";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<script src="~/Scripts/jquery-1.7.1.js"></script>
<script src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
#using (Html.BeginForm("EmployerTestResults", "Home", FormMethod.Post, new { #class = "form-horizontal" }))
{
<div class="text-danger" style="font-size:large;">
#Html.ValidationSummary(true)
</div>
<div class="form-group ">
#Html.LabelFor(s => s.EmployerList, null, new { #class = "col-md-2 control-label" })
<div class="col-md-3">
#Html.DropDownListFor(s => s.SelectedEmployerId, Model.EmployerList, "----All----", new { style = "width:250px", id = "ddl", #class = "dropdown1" })
#Html.ValidationMessageFor(s => s.EmployerList, null, new { #class = "text-danger" })
</div>
</div>
<div id="EmployeeViewGrid">
#Html.Partial("~/Views/EmployerView.cshtml", Model.EmployerGrid)
</div>
}
<script type="text/javascript">
$('#ddl').change(function (e) {
var employer = $('#ddl').val();
$.get('#Url.Action("Filter")', { id: employer }, function (result) {
$('#EmployeeViewGrid').html(result);
});
e.preventDefault();
});
</script>
Controller:
[HttpGet]
public ActionResult Filter(string id)
{
EmployerTestResultsModel model = new EmployerTestResultsModel();
List<EmployerTestResultsModel> objEmployerDetails = new List<EmployerTestResultsModel>();
objEmployerDetails = _repository.getEmployerDetails(id);
model.EmployerGrid = objEmployerDetails;
return PartialView("~/Views/EmployerView.cshtml", model.EmployerGrid);
}
partial View:
#model IEnumerable<EFITestHarness.Models.EmployerTestResultsModel>
#using System.Web.Helpers;
#{
ViewBag.Title = "EmployerTestResultsModel";
//Layout = "~/Views/Shared/_Layout.cshtml";
}
<script src="~/Scripts/jquery-1.7.1.js"></script>
<div id="gridposition" style="overflow: scroll; height: 300px; overflow-x: hidden;">
#{
var grid = new WebGrid(Model, canPage: true, rowsPerPage: 5, selectionFieldName: "selectedRow", ajaxUpdateContainerId: "gridposition"); grid.Pager(WebGridPagerModes.NextPrevious);
#grid.GetHtml(tableStyle: "webGrid",
footerStyle: "foot",
headerStyle: "webGridHeader",
alternatingRowStyle: "webGridAlt",
htmlAttributes: new { id = "positionGrid" },
selectedRowStyle: "select",
fillEmptyRows: true,
columns: grid.Columns(
grid.Column("FileName"), //the model fields to display
grid.Column("Date"),
grid.Column("SchemeId"),
grid.Column("Status"),
grid.Column("ValidationErrorReport", format: (item => Html.ActionLink((string)(#item.ValidationErrorReport).ToString(), "EmployerValidationResults", new { FileId = #item.FileId, #style = "color:blue;" })))
))
}
</div>

Cascading drop-downs in MVC 3 Razor view

I am interested in how to implement cascading dropdown lists for addresses in a Razor view. My Site entity has a SuburbId property. Suburb has a CityId, and City has ProvinceId. I would like to display dropdowns for all of Suburb, City, and Province on the Site view, where e.g. the suburb dropdown will initially display "First select a City", and the City dropdown, "First select a province". On selecting a province, cities in the province are populated etc.
How can I achieve this? Where do I start?
Let's illustrate with an example. As always start with a model:
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; }
}
Next a controller:
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);
}
}
And finally a view:
#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>
As an improvement the javascript code could be shortened by writing a jquery plugin to avoid duplicating some parts.
UPDATE:
And talking about a plugin you could have something among the lines:
(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);
And then simply wire it up:
$(function () {
$('#SelectedProvinceId').cascade({
url: '#Url.Action("Cities")',
paramName: 'provinceId',
childSelect: $('#SelectedCityId')
});
$('#SelectedCityId').cascade({
url: '#Url.Action("Suburbs")',
paramName: 'cityId',
childSelect: $('#SelectedSuburbId')
});
});
Thanks Darin for your lead to the solution. It greatly helped me to arrive to the point. But as 'xxviktor' mentioned, I did got circular ref. error. To get rid of it, I've done this way.
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);
}
And to achieve auto cascading, I've slightly extended jQuery extension this way.
$('#ddlCountry').cascade({
url: '#Url.Action("GetCounties")',
paramName: 'countryID',
childSelect: $('#ddlState'),
childCascade: true
});
And the actual JS is using this parameter as below (inside JSON request).
// trigger child change
if (opts.childCascade) {
opts.childSelect.change();
}
Hope this helps someone with similar issue.
be aware, that this solution doesn't work directly with EF 4.0. It causes "A circular reference was detected while serializing..." error. Here are possible solutions http://blogs.telerik.com/atanaskorchev/posts/10-01-25/resolving_circular_references_when_binding_the_mvc_grid.aspx , I've used second one.
To implement cascading drop down lists that support MVC's built in validation and binding, you will need to do something a little different than what is done in the other answers here.
If your model has validation, this will support it. An excerpt from a model with validation:
[Required]
[DisplayFormat(ConvertEmptyStringToNull = false)]
public Guid cityId { get; set; }
In your controller you need to add a get method, so that your view will be able to get the relevant data later:
[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);
}
Now, to the View that I mentioned earlier:
In your view you have two drop downs similar to this:
<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>
The content in the drop downs are bound by the controller, and are automatically populated. Note: in my experience removing this binding and relying on java script to populate the drop downs make you lose validation. Besides, the way we are binding here plays nice with validation, so there is no reason to change it.
Now onto our jQuery plugin:
(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));
You can copy the above jQuery that i created, into <script>...</script> tags in your view, or in a separate script file if you wish (note I updated this to make it cross browser, however the scenario in which i was using is no longer required, it should work however).
In those same script tags, (not in a separate file) you can call the plugin by using the following javascript:
$(document).ready(function () {
var primaryDropDown = document.getElementById('stateId');
var secondaryDropdown = document.getElementById('cityId');
var actionUrl = '#Url.Action("GetData")'
$(primaryDropDown).cascade(secondaryDropdown, actionUrl);
});
Remember to add the $(document).ready part, the page must be fully loaded before you try to make the drop downs cascade.
<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>
From controller I am getting the values
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));
}

Resources