This example demonstrates how to create cascading combo box editors and use them to edit grid data.
Overview
Follow the steps below to implement cascading combo boxes in the grid's edit form:
- Call a column's MVCxGridViewColumn.EditorProperties method to add a combo box editor to the column.Razor
settings.Columns.Add(c => c.CountryId, country =>{ country.Caption = "Country"; country.EditorProperties().ComboBox(cs => cs.Assign(ComboBoxPropertiesProvider.Current.CountryComboBoxProperties)); }); settings.Columns.Add(c => c.CityId, city =>{ city.Caption = "City"; city.EditorProperties().ComboBox(cs => cs.Assign(ComboBoxPropertiesProvider.Current.CityComboBoxProperties)); });
- Add a MVCxColumnComboBoxProperties object to specify an editor's settings and call the MVCxColumnComboBoxProperties.BindList method to bind the column to a data source.C#
MVCxColumnComboBoxProperties countryComboBoxProperties; public MVCxColumnComboBoxProperties CountryComboBoxProperties { get { if(countryComboBoxProperties == null) countryComboBoxProperties = CreateCountryComboBox(); return countryComboBoxProperties; } } protected MVCxColumnComboBoxProperties CreateCountryComboBox() { MVCxColumnComboBoxProperties cs = new MVCxColumnComboBoxProperties(); cs.CallbackRouteValues = new { Controller = "Home", Action = "ComboBoxCountryPartial" }; // ... cs.ClientSideEvents.SelectedIndexChanged = "CountriesCombo_SelectedIndexChanged"; cs.BindList(WorldCities.Countries.ToList()); return cs; }
- Specify the secondary editor's
CallbackRouteValue
parameters. - Handle the primary editor's
SelectedIndexChanged
event. In the handler, call the secondary editor'sPerformCallback
method to update the editor's data.JavaScriptfunction CountriesCombo_SelectedIndexChanged(s, e) { customCallback = true; grid.GetEditor('CityId').PerformCallback(); }
- Handle the secondary editor's client-side
BeginCallback
event and pass the selected value of the secondary editor as a parameter.JavaScriptfunction CitiesCombo_BeginCallback(s, e) { e.customArgs['CountryId'] = grid.GetEditor('CountryId').GetValue(); }
- Use the grid's GetComboBoxCallbackResult method to get the result of callback processing.C#
public ActionResult ComboBoxCountryPartial(){ return GridViewExtension.GetComboBoxCallbackResult(ComboBoxPropertiesProvider.Current.CountryComboBoxProperties); }
- Call the secondary editor's
CallbackRouteValues.Action
method to populate the editor with values based on the passed parameter.
Files to Review
- HomeController.cs (VB: HomeController.vb)
- Global.asax (VB: Global.asax)
- Global.asax.cs (VB: Global.asax.vb)
- City.cs (VB: City.vb)
- ComboBoxPropertiesProvider.cs (VB: ComboBoxPropertiesProvider.vb)
- Country.cs (VB: Country.vb)
- Customer.cs (VB: Customer.vb)
- WoldCitiesModel.Context.cs (VB: WoldCitiesModel.Context.vb)
- WoldCitiesModel.cs (VB: WoldCitiesModel.vb)
- GridViewPartial.cshtml
- Index.cshtml
Documentation
- MVC ComboBox Extension - How to implement cascaded combo boxes
- Passing Values to a Controller Action through Callbacks
More Examples
Does this example address your development requirements/objectives?
(you will be redirected to DevExpress.com to submit your response)
Example Code
C#using System;
using System.Web.Mvc;
using E4425.Models;
using DevExpress.Web.Mvc;
using System.Linq;
namespace E4425.Controllers
{
public class HomeController : Controller
{
WorldCitiesEntities entity = new WorldCitiesEntities();
public ActionResult Index()
{
return View(entity.Customers.ToList());
}
public ActionResult GridViewPartial()
{
return PartialView(entity.Customers.ToList());
}
public ActionResult GridViewEditPartial(Customer customerInfo)
{
if (ModelState.IsValid)
{
try
{
entity.Customers.Attach(customerInfo);
var entry = entity.Entry(customerInfo);
entry.Property(e => e.CityId).IsModified = true;
entry.Property(e => e.CountryId).IsModified = true;
entry.Property(e => e.CustomerName).IsModified = true;
// uncomment the next line to enable database updates
// entity.SaveChanges();
}
catch (Exception e)
{
ViewData["EditError"] = e.Message;
}
}
else
ViewData["EditError"] = "Please, correct all errors.";
return PartialView("GridViewPartial", entity.Customers.ToList());
}
public ActionResult GridViewInsertPartial(Customer customerInfo)
{
if (ModelState.IsValid)
{
try
{
entity.Customers.Add(customerInfo);
// uncomment the next line to enable database updates
// entity.SaveChanges();
}
catch (Exception e)
{
ViewData["EditError"] = e.Message;
}
}
else
ViewData["EditError"] = "Please, correct all errors.";
return PartialView("GridViewPartial", entity.Customers.ToList());
}
public ActionResult GridViewDeletePartial(int CustomerId)
{
if (ModelState.IsValid)
{
try
{
entity.Customers.Remove(entity.Customers.Find(CustomerId));
// uncomment the next line to enable database updates
//entity.SaveChanges();
}
catch (Exception e)
{
ViewData["EditError"] = e.Message;
}
}
else
ViewData["EditError"] = "Please, correct all errors.";
return PartialView("GridViewPartial", entity.Customers.ToList());
}
public ActionResult ComboBoxCountryPartial()
{
return GridViewExtension.GetComboBoxCallbackResult(ComboBoxPropertiesProvider.Current.CountryComboBoxProperties);
}
public ActionResult ComboBoxCityPartial()
{
return GridViewExtension.GetComboBoxCallbackResult(ComboBoxPropertiesProvider.Current.CityComboBoxProperties);
}
}
}
Code<%@ Application Codebehind="Global.asax.cs" Inherits="E4425.MvcApplication" Language="C#" %>
C#using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace E4425
{
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=9394801
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("{resource}.ashx/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
ModelBinders.Binders.DefaultBinder = new DevExpress.Web.Mvc.DevExpressEditorsBinder();
}
}
}
C#//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace E4425.Models
{
using System;
using System.Collections.Generic;
public partial class City
{
public City()
{
this.Countries = new HashSet<Country>();
this.Customers = new HashSet<Customer>();
}
public int CityId { get; set; }
public string CityName { get; set; }
public Nullable<int> CountryId { get; set; }
public virtual Country Country { get; set; }
public virtual ICollection<Country> Countries { get; set; }
public virtual ICollection<Customer> Customers { get; set; }
}
}
C#using DevExpress.Web;
using DevExpress.Web.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI.WebControls;
namespace E4425.Models
{
public class ComboBoxPropertiesProvider : IDisposable
{
static ComboBoxPropertiesProvider current;
public static ComboBoxPropertiesProvider Current
{
get
{
if (current == null)
current = new ComboBoxPropertiesProvider();
return current;
}
}
public int EditableCountryID
{
get
{
string rawCountryId = HttpContext.Current.Request["CountryId"];
return string.IsNullOrEmpty(rawCountryId) ? -1 : int.Parse(rawCountryId);
}
}
MVCxColumnComboBoxProperties cityComboBoxProperties;
public MVCxColumnComboBoxProperties CityComboBoxProperties
{
get
{
if (cityComboBoxProperties == null)
cityComboBoxProperties = CreateCityComboBox();
return cityComboBoxProperties;
}
}
MVCxColumnComboBoxProperties countryComboBoxProperties;
public MVCxColumnComboBoxProperties CountryComboBoxProperties
{
get
{
if (countryComboBoxProperties == null)
countryComboBoxProperties = CreateCountryComboBox();
return countryComboBoxProperties;
}
}
WorldCitiesEntities worldCities;
protected WorldCitiesEntities WorldCities
{
get
{
if (worldCities == null)
worldCities = new WorldCitiesEntities();
return worldCities;
}
}
protected MVCxColumnComboBoxProperties CreateCountryComboBox()
{
MVCxColumnComboBoxProperties cs = new MVCxColumnComboBoxProperties();
cs.CallbackRouteValues = new { Controller = "Home", Action = "ComboBoxCountryPartial" };
cs.Width = Unit.Percentage(100);
cs.TextField = "CountryName";
cs.ValueField = "CountryId";
cs.ValueType = typeof(int);
cs.IncrementalFilteringDelay = 1000;
cs.IncrementalFilteringMode = IncrementalFilteringMode.Contains;
cs.FilterMinLength = 2;
cs.CallbackPageSize = 20;
cs.ClientSideEvents.SelectedIndexChanged = "CountriesCombo_SelectedIndexChanged";
cs.BindList(WorldCities.Countries.ToList());
return cs;
}
protected MVCxColumnComboBoxProperties CreateCityComboBox()
{
MVCxColumnComboBoxProperties cs = new MVCxColumnComboBoxProperties();
cs.CallbackRouteValues = new { Controller = "Home", Action = "ComboBoxCityPartial" };
cs.Width = Unit.Percentage(100);
cs.CallbackPageSize = 20;
cs.TextField = "CityName";
cs.ValueField = "CityId";
cs.ValueType = typeof(int);
cs.IncrementalFilteringDelay = 1000;
cs.IncrementalFilteringMode = IncrementalFilteringMode.Contains;
cs.FilterMinLength = 2;
cs.BindList(OnItemsRequestedByFilterCondition, OnItemRequestedByValue);
cs.ClientSideEvents.BeginCallback = "CitiesCombo_BeginCallback";
cs.ClientSideEvents.EndCallback = "CitiesCombo_EndCallback";
return cs;
}
protected object OnItemRequestedByValue(ListEditItemRequestedByValueEventArgs e)
{
int id;
if (e.Value == null || !int.TryParse(e.Value.ToString(), out id))
return null;
var query = WorldCities.Cities.Where(city => city.CityId == id);
return query.ToList();
}
protected object OnItemsRequestedByFilterCondition(ListEditItemsRequestedByFilterConditionEventArgs e)
{
IQueryable<City> query;
var skip = e.BeginIndex;
var take = e.EndIndex - e.BeginIndex + 1;
if (EditableCountryID > -1)
query = WorldCities.Cities.Where(city => city.CityName.Contains(e.Filter) && city.Country.CountryId == EditableCountryID).OrderBy(city => city.CityId);
else
query = WorldCities.Cities.Where(city => city.CityName.Contains(e.Filter)).OrderBy(city => city.CityId);
query = query.Skip(skip).Take(take);
return query.ToList();
}
#region IDisposable Members
public void Dispose()
{
if (this.worldCities != null)
worldCities.Dispose();
}
#endregion
}
}
C#//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace E4425.Models
{
using System;
using System.Collections.Generic;
public partial class Country
{
public Country()
{
this.Cities = new HashSet<City>();
this.Customers = new HashSet<Customer>();
}
public int CountryId { get; set; }
public string CountryName { get; set; }
public Nullable<int> CapitalId { get; set; }
public virtual ICollection<City> Cities { get; set; }
public virtual City City { get; set; }
public virtual ICollection<Customer> Customers { get; set; }
}
}
C#//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace E4425.Models
{
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
public partial class Customer
{
public int CustomerId { get; set; }
[Required(ErrorMessage = "Customer cannot be empty")]
public string CustomerName { get; set; }
[Required(ErrorMessage = "Country should be chosen")]
public Nullable<int> CountryId { get; set; }
[Required(ErrorMessage = "City should be chosen")]
public Nullable<int> CityId { get; set; }
public virtual City City { get; set; }
public virtual Country Country { get; set; }
}
}
C#//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace E4425.Models
{
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
public partial class WorldCitiesEntities : DbContext
{
public WorldCitiesEntities()
: base("name=WorldCitiesEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<City> Cities { get; set; }
public DbSet<Country> Countries { get; set; }
public DbSet<Customer> Customers { get; set; }
}
}
C#//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
Razor@using E4425.Models;
@(Html.DevExpress().GridView<Customer>(settings =>
{
settings.Name = "grid";
settings.KeyFields(c => c.CustomerId);
settings.Width = System.Web.UI.WebControls.Unit.Pixel(500);
settings.CallbackRouteValues = new { Controller = "Home", Action = "GridViewPartial" };
settings.SettingsEditing.Mode = GridViewEditingMode.EditForm;
settings.SettingsEditing.UpdateRowRouteValues = new { Controller = "Home", Action = "GridViewEditPartial" };
settings.SettingsEditing.AddNewRowRouteValues = new { Controller = "Home", Action = "GridViewInsertPartial" };
settings.SettingsEditing.DeleteRowRouteValues = new { Controller = "Home", Action = "GridViewDeletePartial" };
settings.CommandColumn.Visible = true;
settings.CommandColumn.ShowNewButtonInHeader = true;
settings.CommandColumn.ShowDeleteButton = true;
settings.CommandColumn.ShowEditButton = true;
settings.Columns.Add(c => c.CustomerId).Visible = false;
settings.Columns.Add(c => c.CustomerName);
settings.SettingsEditing.ShowModelErrorsForEditors = true;
settings.Columns.Add(c => c.CountryId, country =>
{
country.Caption = "Country";
country.EditorProperties().ComboBox(cs => cs.Assign(ComboBoxPropertiesProvider.Current.CountryComboBoxProperties));
});
settings.Columns.Add(c => c.CityId, city =>
{
city.Caption = "City";
city.EditorProperties().ComboBox(cs => cs.Assign(ComboBoxPropertiesProvider.Current.CityComboBoxProperties));
});
settings.CellEditorInitialize = (s, e) =>
{
ASPxEdit editor = (ASPxEdit)e.Editor;
editor.ValidationSettings.Display = Display.Dynamic;
};
settings.ClientSideEvents.BeginCallback = "onBeginCallback";
settings.ClientSideEvents.EndCallback = "onEndCallback";
}).SetEditErrorText(ViewData["EditError"] as string).Bind(Model).GetHtml())
Razor@{
ViewBag.Title = "GridView - How to implement cascading comboboxes in the EditForm";
}
<script type="text/javascript">
var customCallback = false;
var editingStart = false;
var previousValue = null;
function onBeginCallback(s, e) {
if (e.command = "STARTEDIT")
editingStart = true;
}
function onEndCallback(s, e) {
if (editingStart) {
customCallback = true;
var editor = grid.GetEditor('CityId');
if (editor) {
previousValue = editor.GetValue();
editor.PerformCallback();
}
}
}
function CountriesCombo_SelectedIndexChanged(s, e) {
customCallback = true;
grid.GetEditor('CityId').PerformCallback();
}
function CitiesCombo_BeginCallback(s, e) {
e.customArgs['CountryId'] = grid.GetEditor('CountryId').GetValue();
}
function CitiesCombo_EndCallback(s, e) {
var editor = MVCxClientComboBox.Cast(s);
if (customCallback) {
customCallback = false;
if (previousValue) {
editor.SetValue(previousValue);
previousValue = null;
} else {
editor.SetSelectedIndex(0);
}
}
}
</script>
@using (Html.BeginForm())
{
@Html.Partial("GridViewPartial")
}