27.05.2009 MVC ASP.NET grid - Editable rows. Using html table, jQuery
and Ajax
Business case
A product manager need to change a id, name and price for products he is responsible
of. He would like to have a grid with those fields. All columns should be editable
on the fly, without any post-backs. After editing a manager clicks a Save button
to save all changed rows.
This business case is very similar to the previous article - MVC
ASP.NET grid – Editable column(cell) using Ajax
Main difference is that the manager should be able to edit all cells in the table.
We have several possible solutions here. Let’s consider them and find the best one.
Solution 1. Similar to the previous article.
- Adding a callback to all cells in the table.
- The callback function sends an ajax request on the server with a new cell’s
value.
- The server saves the value in a user’s session.
- When a user clicks the Save button, a callback function sends an ajax request
to save those values to a data store.
Disadvantages –
- Ajax request is preformed every time a cell is changed. It is a small request
but there would be a lot of them.
- User session can expire and lost the data.
- Every cell have a callback function. The html is much bigger and not so
easy to read.
Solution 2. Working with data store directly.
The same as the first one but with saving a value directly to a data store.
Disadvantages –
- Ajax request is preformed every time a cell is changed. It is a small request
but there would be a lot of them.
- There are a lot of small requests to a data store.
- Every cell have a callback function. The html is much bigger and not so
easy to read.
Solution 3. Working with rows.
- Every row in a table have Edit and Save buttons.
- All rows have a callback.
- The callback function sends an ajax request on the server with new row’s values.
- The server saves the values in a data store.
Disadvantages –
- A user should click Edit first then Save after. It is not convenient when he needs
to update a one column only, for instance - prices.
Solution 4. Saving a whole table at once.
- Save button has a callback on click event.
- The callback iterates all cells in the table and finds the values then send ajax
request with those values to the server.
- The server parses the values, creates a list of business objects and passes it to
save in a data store.
Disadvantages – I didn’t find such.
So, let’s create a sample for Solution 4.
The data store, the model and the product service.
They are similar to the previous article -
MVC ASP.NET grid – Editable column(cell) using Ajax
The products controller.
It performs parsing of JSON array strings with values and saves it to a data store.
public class ProductController : Controller
{
//
// GET: /Products/
private List<Hashtable> ParseJson(string[] array, int rowsCount)
{
var result = new List<Hashtable>();
List<string[]> list = new List<string[]>();
foreach (var item in array)
{
int comma = item.IndexOf(',');
list.Add(new string[] { item.Substring(0, comma), item.Substring(comma + 1) });
}
int i = 0;
while (i < list.Count)
{
var dict = new Hashtable();
int j = i + rowsCount;
for (; i < j; i++)
{
dict.Add(list[i][0], list[i][1]);
}
result.Add(dict);
}
return result;
}
public ActionResult Index()
{
return View(ProductService.Get());
}
public void Save(string[] inputs, int columnsCount)
{
List<Hashtable> r = ParseJson(inputs, columnsCount);
List<Product> products = new List<Product>();
foreach (var item in r)
{
Product p = new Product();
p.ProductId = Convert.ToInt32(item["ProductId"]);
p.Name = (string)item["Name"];
p.Price = Convert.ToDouble(item["Price"]);
products.Add(p);
}
ProductService.Save(products);
}
}
private List ParseJson(string[] array, int rowsCount) – parses array of strings to a list of key/value pairs. Each hashtable includes the pairs only for one business object. For instance, we have 6 strings – “ProductId,0”, “Name, Product 0”, “Price, 876,00”,“ProductId,1”, “Name, Product 1”, “Price, 901,20”. The resulted list would include 2 hashtables with 3 pairs.
public void Save(string[] inputs, int columnsCount) – gets array of strings from
UI, calls ParseJson function, creates a list of products and saves it to a data
store.
The products’ view.
It shows an html table with input elements inside its cells. After clicking on the
Save button, it provides iterating through the table, collects values and sends
them to a server by Ajax request.
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<script src="../../Scripts/jquery-1.3.2.js" type="text/javascript"></script>
<script type="text/javascript">
function SaveAllClick() {
var i = 0;
var inputs = new Array();
$("#ProductsTable").find("input").each(function() {
inputs[i] = [this.id, this.value];
i++;
});
var columnsCount = $("#ProductsTable").find("th").length;
$.post("Product/Save", { inputs: inputs, columnsCount: columnsCount });
Border();
alert("Saved");
}
var prevRow;
function Border(row) {
if (prevRow != null) {
$(prevRow).find("input").attr("style", "border-style: none");
}
if (row != null) {
var i = $(row).find("input");
i.attr("style", "border-style: inset");
}
prevRow = row;
}
</script>
<h2>
Index</h2>
<table id="ProductsTable">
<tr>
<th>
ProductId
</th>
<th style="width: 300px">
Name
</th>
<th>
Price
</th>
</tr>
<% foreach (var item in Model)
{ %>
<tr onclick="Border(this)">
<td>
<input id="ProductId" type="text"
style="border-style: none" value="<%= item.ProductId %>" />
</td>
<td>
<input id="Name" type="text" style="border-style: none" value="<%= item.Name %>" />
</td>
<td>
<input id="Price" type="text" style="border-style: none"
value="<%= String.Format("{0:F}", item.Price) %>" />
</td>
</tr>
<% } %>
</table>
<p>
<input id="SaveAll" type="button" value="Save" onclick="SaveAllClick()" />
</p>
</asp:Content>
function SaveAllClick() – iterates through the table, collects values and sends
them to a server by Ajax request.
function Border(row)- switch on/off a border around the edited row.
Conclusions.
It is really simple and flexible. It requires some more coding then with ASP.NET
controls but gives you much more control over html rendering.
The example can be improved by automatic binding the products in Save method of
the products controller using reflection. But it is out of the bounds of this sample
and I’m sure you can do it easily without my help.
Take care,
Anton Venger