The problem:
In situations where you are allowing the user to edit html tables client side by adding or removing rows or editing existing rows, you find that you need a way to get these changes back to the server for processing. You could write script to ramble your way through the table and glean the data and try to chunk it into some sort of format that you can then send to the server, but why do that when you can use JSON and the tabletojson jQuery plugin?
The idea:
What I usually do in a case like this is convert the html table to JSON in the format that JSON.NET understands so that I can serialize this directly to a business object to be used by .NET. It’s a sweet and light way to do this in my opinion and it’s fairly easy to implement. It works for any ASP.NET control that renders as a table using at least THEAD and TBODY tags. The steps are as follows after you have configured the plug-in:
1) User edits the table.
2) On save you can attach a client side event to the ASP.NET save button onClientClick and you can also attach whatever server side event you want (OnClick). As it turns out, the client side function will be ran first and if it returns false, the server side event will not be ran. Nice fact to know when doing client side validation, or the like. I use this to bundle my table in a JSON string and write it to a hidden field for the purpose of posting to the server. NOTE: I just so happen to be using this hidden field for posting, but you could easily pass the JSON via a jQuery AJAX call to a web service method or web method. The would be a good reason to use the built in callback function to submit the data upon JSON conversion completion.
3) On the server side, snag the JSON string from the hidden field and run the JSON.NET serialize function. This will populate your business object and you’re good to go.
But wait, there’s more:
So, suppose, the business object has EmployeeName, BonusName, CustomerID and OrderID and I actually display Employee, Salary, Bonus and Supervisor as field names to the user as shown in Figure 1 below:
Well, without configuring the plug-in, it will just use the fields names from THEAD (Employee, Salary, Bonus, and Supervisor), but if you need something different, I offer a configuration property called headers.
headers: "{'1':'EmployeeName', '3':'BonusName'}",
If you pass the zero based column number and the new name you want for that column, it will use this name instead.
OK, so…that’s fairly deluxe I think, but now, what if you wanted to tuck extra data that you aren’t showing to the user somewhere in the grid and have this be picked up by the plug-in as well? Well, I offer another configuration property called attributes. This one is a tad different, but essentially, if you pass in JSON style name-value pairs so that the plug-in knows what attribute(s) to look for and what you want the custom field name to be, it will include these as fields in the JSON object.
attributes: "{'customerID':'CustomerID', 'orderID':'OrderID'}",
OK, so now what?
So, now you may wonder how you get the JSON string from this plug-in…I offer three fun filled solutions. The first is just by assigning the return to a variable. This is fine if you don’t mind breaking the jQuery chain. The second is, if you pass in an object like say ‘#hiddenfieldX’ to the dumpElement configuration property, it will just dump directly there. If you need more than that, you can pass a callback function to the configuration property called onComplete. This allows for an asynchronous callback, passing the JSON string to your function for you to do with as you please as soon as it’s both good and ready. In the case below, I simply alert x which contains the JSON string.
dumpElement: null,
onComplete: function (x) {
alert(x);
}
I could have also passed in a jQuery selector to dumpElement. It should be noted that you can do all three, two 1 or none of the outputs. Below, I only dump the string to a field as mentioned above.
dumpElement: '#hiddenfieldX',
onComplete: null
Ok, and now the code:
(function ($) {
$.fn.extend({
//pass the options variable to the function
tabletojson: function (options) {
//Set the default values, use comma to separate the settings, example:
var defaults = {
headers: null, //supply headers you want to include plus column position 0 based.
attributes: null, //supply attributes you want to include, attribute name and then how you want it to appear in JSON string.
onComplete: null, //supply callback function, called when json build is complete
dumpElement: null
};
options = $.extend(defaults, options);
return this.each(function () {
var o = options;
var $tbl = $(this);
var headerList = [];
var attribList = [];
var headerArray = eval("(" + o.headers + ")");
var attribArray = eval("(" + o.attributes + ")");
//in this case, if custom headers, build them, else just use table headers.
if (o.headers !== null) {
for (h in headerArray) {
nvp = {};
nvp.Name = h;
nvp.Value = headerArray[h];
headerList[headerList.length] = nvp;
}
} else {
headerList = getHeaders($tbl);
}
//and here, if attributes are indicated, collect them.
if (o.attributes !== null) {
for (h in attribArray) {
nvp = {};
nvp.Name = h;
nvp.Value = attribArray[h];
attribList[attribList.length] = nvp;
}
}
//now build the json and dump.
var json = buildJSON($tbl, attribList, headerList);
$(o.dumpElement).val(json);
if (o.onComplete !== null) {
o.onComplete(json);
}
return this;
});
}
});
function buildJSON($table, a, h) {
var sb = new StringBuilder(); //using stringbuilder for concat efficiency.
var sbv = new StringBuilder();
var values = [];
var rows = [];
sb.append("[");
//get header/values
$table.find("tbody tr:not(:has('th'))").each(function () {
sbv.clear();
sbv.append("{");
values.length = 0;
// first iterate headers and build json string
for (x = 0; x < h.length; x++) {
values[values.length] = "\"" + h[x].Value + "\":\"" + $(this).find("td").eq(h[x].Name).text() + "\"";
}
//now iterate attributes and build json strin
for (x = 0; x < a.length; x++) {
var name = $(this).attr(a[x].Name);
var val = a[x].Value;
name = typeof (name) == 'undefined' ? "" : name;
val = typeof (val) == 'undefined' ? "" : val;
values[values.length] = "\"" + val + "\":\"" + name + "\"";
}
//at each data item, use join to create a comma delimited list or data items.
sbv.append(values.join(","));
sbv.append("}");
rows[rows.length] = sbv.toString();
});
//at each row, use join to create a comma delimited list of rows
sb.append(rows.join(","));
sb.append("]");
return sb.toString();
}
function getHeaders($table) {
var h = [];
var cnt = 0;
//just iterate th's and dump data to headerlist
$table.find("tr th").each(function () {
var nvp = {};
nvp.Name = String(cnt);
nvp.Value = $(this).text();
h[h.length] = nvp;
});
return h;
}
})(jQuery);
The HTML implementation:
Points of interest here are that I have added some attributes to the rows, namely customerid and orderid. These aren’t real HTML attributes, but that doesn’t matter, they can still be used.
<table id="table1" border="1" summary="Employee Pay Sheet">
<thead>
<tr>
<th>
<input type="checkbox" />
</th>
<th>Employee </th>
<th>Salary </th>
<th>Bonus </th>
<th>Supervisor </th>
</tr>
</thead>
<tbody>
<tr customerid="222" orderid="1222">
<td>
<input type="checkbox" />
</td>
<td>Stephen C. Cox </td>
<td>$300 </td>
<td>$50 </td>
<td>Bob </td>
</tr>
<tr customerid="223" orderid="1223">
<td>
<input type="checkbox" />
</td>
<td id='whatis'>ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890
</td>
<td>$150 </td>
<td>- </td>
<td>Annie </td>
</tr>
<tr customerid="224" orderid="1224">
<td>
<input type="checkbox" />
</td>
<td>Joyce Ming </td>
<td>$200 </td>
<td>$35 </td>
<td>Andy </td>
</tr>
<tr customerid="225" orderid="1225">
<td>
<input type="checkbox" />
</td>
<td>James A. Pentel </td>
<td>$175 </td>
<td>$25 </td>
<td>Annie </td>
</tr>
</tbody>
</table>
<input type="hidden" id="hiddenfieldX" />
Script Implementation:
This an example of both dumping the data to the hidden field “hiddenfieldX” as well as passing the json string via x to the anonymous function. I then alert the value. I could also have just said var json = $(“#superTable).tabletojson(… and that would have return it to json, but…the jQuery chain would be broken. :(
$(function () {
$("#table1").tabletojson({
headers: "{'1':'EmployeeName', '3':'BonusName'}", //supply headers you want to include plus column position 0 based.
attributes: "{'customerID':'CustomerID', 'orderID':'OrderID'}", //supply attributes you wan to include, attribute name and then how you want it to appear in JSON string.
dumpElement: '#hiddenfieldX',
onComplete: function (x) {
alert(x);
}
});
});
JSON Output:
Notice here that the attributes have been added using the custom field names as well as the custom field names as indicated for the table headers.
[{"EmployeeName":"Stephen C. Cox ","BonusName":"$50 ","CustomerID":"222","OrderID":"1222"},
{"EmployeeName":"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890 ","BonusName":"- ","CustomerID":"223","OrderID":"1223"},
{"EmployeeName":"Joyce Ming ","BonusName":"$35 ","CustomerID":"224","OrderID":"1224"},
{"EmployeeName":"James A. Pentel ","BonusName":"$25 ","CustomerID":"225","OrderID":"1225"}]
Additionally:
Javascript usage:
I had mentioned earlier that you can use this with JSON.NET, but we are in the wide world of Javascript so we can eval the JSON and use it as an object…check out Figure 2:
JSON.NET usage:
I mentioned earlier about using this with the JSON.NET component. I need to create or have had created an object like the one below to map up with the JSON object. I say this because you can either make the .NET object match the JSON object or visa versa. The below code is C# just to remind you to switch gears a bit.
public struct NuggetData
{
public string BonusName { get; set; }
public string CustomerID { get; set; }
public string EmployeeName { get; set; }
public string OrderID { get; set; }
}
After you set this up, you can simply run the following code:
private List<NuggetData> _NuggetList;
private void Save()
{
string dataVals = Request.Form[hiddenfieldX.UniqueID];
_NuggetList;= JsonConvert.DeserializeObject<List<NuggetData>>(dataVals);
}
Notice here that I snag the JSON string from the hidden field “hiddenfieldX” and then I serialize to the list of Nuggets.
Ding! Fries are done! Download and try it out!
tabletojson.zip (2.51 kb)
ca0c9b81-7506-4bd0-98e2-5d4c82169844|1|5.0