Kendo grid by Telerik allows changing records in two modes: individually or in batch. In this blog I will show the batch. In batch you can perform CRUD operations on multiple rows and commit these changes to your database in one submit. Below is a typical Customer/Orders view where we first lookup customer orders by customer ID. The orders are populated in the grid then user can create/update/delete multiple orders and commit those changes by pressing “Save changes” button
To put the grid in batch mode, it needs to have the following in its markup:
- Batch property set to true – Batch(true)
- GridEditMode property set to InCell mode (as opposed to InLine mode) – GridEditMode.InCell
- Define the CRUD handlers (Read, Update, Create, Destroy)
- Show the “Add new record” button
- Show the “Save changes” button
Here is the markup for the grid with the above settings in highlight:
@(Html.Kendo().Grid<Order>().Name("grid")
.Columns(col =>
{
col.Bound(o => o.OrderID).Width(100);
col.Bound(o => o.OrderDate).EditorTemplateName("Date").Format("{0:d}").Width(100);
col.Bound(o => o.Freight).Width(20);
col.Bound(o => o.ShipCity).Width(100);
col.Bound(o => o.ShipCountry).Width(100);
col.Command(command =>
{
command.Destroy(); // Show Delete button
}
);
}
)
.Editable(editable => editable.Mode(GridEditMode.InCell)) // Edit in Cell
.ToolBar(tools =>
{
tools.Create(); // "Add new record" button
tools.Save(); // "Save changes" button
}
)
.Pageable().Sortable().Filterable()
.DataSource(source => source.Ajax().Model(model =>
{
model.Id(o => o.OrderID);
model.Field(o => o.OrderID).Editable(false); // Order Id is read-only
}
)
.Read(read => read.Action("Read_Orders", "Home", new { customerId = Model.CustomerID }))
.Update(update => update.Action("Update_Orders", "Home", new { customerId = Model.CustomerID }))
.Create(create => create.Action("Create_Orders", "Home", new { customerId = Model.CustomerID }))
.Destroy(destroy => destroy.Action("Delete_Orders", "Home"))
.Batch(true) // If you don't set it to true, the list of rows passed into the Update/Create/Update handlers would be null
)
)
From the view above you can also press the Delete button on each row to delete it
On the controller side, we provide the handlers. Here’s the Read handler that gets called when a user types in a Customer Id and clicks the Lookup Orders button. This handlers gets also called when refreshing the grid after an add/delete/update operation:
// Read handlers. Fired when when grid needs to fetch rows
public ActionResult Read_Orders([DataSourceRequest] DataSourceRequest request, string customerId)
{
using (NorthwindEntities _database = new NorthwindEntities())
{
var orders = _database.Orders.Where(o => o.CustomerID == customerId).ToList();
return Json(orders.ToDataSourceResult(request, e => new Order
{
OrderID = e.OrderID,
OrderDate = e.OrderDate,
RequiredDate = e.RequiredDate,
ShippedDate = e.ShippedDate,
ShipVia = e.ShipVia,
Freight = e.Freight,
ShipName = e.ShipName,
ShipAddress = e.ShipAddress,
ShipCity = e.ShipCity,
ShipRegion = e.ShipRegion,
ShipPostalCode = e.ShipPostalCode,
ShipCountry = e.ShipCountry,
}), JsonRequestBehavior.AllowGet);
}
}
This is the Add handler that gets called when user adds new orders with the “Add new record”:
// Create handler
public ActionResult Create_Orders([DataSourceRequest] DataSourceRequest request, string customerId, [Bind(Prefix = "models")] IEnumerable<Order> orders)
{
if (ModelState.IsValid)
{
orders.ForEach(o => o.CustomerID = customerId); // must set parent Id
using (NorthwindEntities _database = new NorthwindEntities())
{
_database.Orders.AddRange(orders);
_database.SaveChanges();
}
}
return Json(orders.ToDataSourceResult(request, ModelState, e => new Order
{
OrderID = e.OrderID,
OrderDate = e.OrderDate,
RequiredDate = e.RequiredDate,
ShippedDate = e.ShippedDate,
ShipVia = e.ShipVia,
Freight = e.Freight,
ShipName = e.ShipName,
ShipAddress = e.ShipAddress,
ShipCity = e.ShipCity,
ShipRegion = e.ShipRegion,
ShipPostalCode = e.ShipPostalCode,
ShipCountry = e.ShipCountry,
}));
}
This is the Update handler that gets called when user goes into a cell, changes its value then presses the “Save changes” button:
// Update handler
public ActionResult Update_Orders([DataSourceRequest] DataSourceRequest request, [Bind(Prefix = "models")] IEnumerable<Order> orders, string customerId)
{
List<Order> updatedOrders = new List<Order>();
if (orders != null && ModelState.IsValid)
{
using (NorthwindEntities _database = new NorthwindEntities())
{
foreach (Order order in orders)
{
var target = _database.Orders.FirstOrDefault(o => o.OrderID == order.OrderID);
if (target != null)
{
target.CustomerID = customerId; // must set parent id
target.OrderDate = order.OrderDate;
target.Freight = order.Freight;
target.ShipCity = order.ShipCity;
target.ShipCountry = order.ShipCountry;
updatedOrders.Add(target);
_database.Orders.Attach(target);
_database.Entry(target).State = EntityState.Modified;
}
}
_database.SaveChanges();
}
return Json(updatedOrders.ToDataSourceResult(request, ModelState, product => new Order
{
OrderDate = product.OrderDate,
Freight = product.Freight,
ShipCity = product.ShipCity,
ShipCountry = product.ShipCountry,
}));
}
Finally we have the delete handlers:
// Delete handler
public ActionResult Delete_Orders([DataSourceRequest] DataSourceRequest request, [Bind(Prefix = "models")] IEnumerable<Order> orders)
{
if (orders != null && ModelState.IsValid)
{
using (NorthwindEntities _database = new NorthwindEntities())
{
orders.ForEach(d =>
{
// must delete order details first so we don't get a constraint violation
var orderDetailsToDelete = _database.Order_Details.Where(f => f.OrderID == d.OrderID);
_database.Order_Details.RemoveRange(orderDetailsToDelete);
var orderToDelete = _database.Orders.AsQueryable().FirstOrDefault(f => f.OrderID == d.OrderID);
_database.Entry(orderToDelete).State = System.Data.Entity.EntityState.Deleted;
});
_database.SaveChanges();
}
}
return Json(ModelState.ToDataSourceResult());
}
Keep in mind the above handlers aren’t executed until the “Save changes” button is pressed; that’s the objective of batch committing the changes to the database. The handlers execute asynchronously; you will observe that if you step through them in the debugger.
Let’s briefly run this thing. We start with this customer/orders view:
This customer has 10 existing orders. Let’s make make some changes here. We’ll add a couple of orders by clicking the “Add new record,” we change the Freight in a couple of existing orders and we delete the last two orders using the Delete button associated with each order:
We click “Save changes”.
If you set a breakpoint in each handler in the controller, you will witness how the handlers run asynchronously. Below is the view after we save the changes and the grid reloads.
It’s worth observing that the grid’s “Save changes” button does not cause a complete view post; the postback Edit method in the Controller won’t get called when pressing save.
Summary
Kendo grid offers easy out-of-the-box built-in buttons and handlers to perform CRUD operations on multiple records then committing those changes in on shot. Fine and dandy until we want other components on the view and thus need to use a page submit to save the grid rows. The grid is limited in this sense and custom coding is required to achieve that; in the next hlog article I demonstrate how to submit the grid rows with a Submit button.