The KendoUI grid is great for displaying data and manipulating it one row at a time. There are several options available including posting the edited data back via AJAX, performing in-line editing, etc. However, there is no built-in support for posting the grid data on form submission because the grid isn't an actual form control. This came up recently when designing a feature for an enterprise contact list. I had a view that should allow the user to select two contacts and merge them into one. The user needed to be able to select which addresses, phone numbers, etc. from both contact should be kept in the merged contact. A grid seemed like a perfect interface match since it supports multiple columns and I could (presumably) add a checkbox to the first (or last) one for the user to select. This turned out to be much more complicated than I had anticipated and there isn't a lot of help out there, so I'll walk you through the process.
The Model
My model is simple:
public class ContactInfoGridItem { public string ContactInfoId { get; set; } public bool Keep { get; set; } public string Contact { get; set; } public string Type { get; set; } public string Content { get; set; } }
The important fields are ContactInfoId
and Keep
– Contact
, Type
, and Content
are just bits of domain data for display to the user but won't be posted back on submit (they'll get default values after model binding).
The Action
This is the action we eventually want to receive the data:
public virtual ActionResult Index(IEnumerable<ContactInfoGridItem> contactInfo) { //... }
We want the grid to send us a collection of ContactInfoGridItems
. In my collection I only need the ContactInfoId
and Keep
fields to be set.
The View
The grid is declared like this (using KendoUI for ASP.NET MVC):
@(Html.Kendo().Grid(new List<ContactInfoGridItem>()) .Name("grid") .DataSource(d => d.Ajax() .Read(r => r.Action(MVC.ContactList.Merge.ContactInfoGridRead())) .ServerOperation(false)) .Columns(c => { c.Bound(m => m.ContactInfoId).Hidden() .ClientTemplate("<input type='hidden' " + "name='contactInfo[#= gridIndex(data) #].ContactInfoId' " + "value='#= ContactInfoId #' />"); c.Bound(m => m.Keep) .ClientTemplate("<input type='checkbox' " + "name='contactInfo[#= gridIndex(data) #].Keep' " + "value='true' " + "#if (Keep) { #checked='checked'# } #/>" + "<input type='hidden' value='false' " + "name='contactInfo[#= gridIndex(data) #].Keep' />"); c.Bound(m => m.Contact); c.Bound(m => m.Type); c.Bound(m => m.Content); }))
So what's going on here? Don't worry about the Action – if you've never seen this syntax before, I'm just using T4MVC (which I love) to eliminate magic strings. The first important note is that I'm declaring ServerOperation(false)
on the DataSource
. This ensures that KendoUI properly sends the data back to the server. I found this example very helpful in understanding how to rig up posting of grid data. Next, notice the client templates for the ContactInfoId
and Keep
columns. The general principle here is that if we stick form controls (even hidden ones) directly inside the grid then they will get posted to the server like any other form control. The trick to getting a grid to work though is knowing how to identify and name form controls in such a way that ASP.NET MVC understands how to turn them into a collection. This can be accomplished by adding brackets after the form control name with an index – see this blog post for more details.
What I've covered so far works great for posting back form controls such as combo boxes and text entries as well as posting back hidden form controls with the initial data for a row and column. However, there is one more trick we need to use to get checkboxes to post successfully. The problem is that unlike other form controls, checkbox inputs do not post their data in the same way. Checking a checkbox does not actually change it's value. It took some digging to figure out how to get this to work, but I eventually stumbled upon a very helpful Stack Overflow post. It turns out that ASP.NET MVC and the default model binder use a little hack to get checkbox values bound on post. They create the checkbox with a value of true, but then also create a hidden input with the same name and a value of false. When a form is submitted, the model binder knows how to reconcile the two identically named form inputs to come up with the actual value of the checkbox based on it's checked state. We just need to use the same technique in our column client template and let the default model binder do it's thing.
Now when I post the form that contains this grid I'll get a collection of ContactInfoGridItem
models with the correct ContactInfoId
and Keep
fields.