jQuery, HTML Table to JSON Version 2.0!

17. December 2010

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)

jQuery-Unobtrusively Animated Add and Remove Table Rows

9. June 2010

One of the things that you can pretty easily do with jQuery is manipulate the DOM and add and remove rows to/from a table...but what if you want to do this and take advantage of jQuery's animation capabilities?  What if you wanted a row to slide into view when you add it and perhaps fade out when you remove it?

I've done some research and came up with a plug-in that will do just that.  I've seen some cool ideas of how to do this where you wrap your table cell contents with div elements and animate the divs. That is essentially what this plug-in requires, but the difference is, I dynamically wrap the content, run the animation and then remove the divs.  This is required because being forced to wrap your content with divs becomes a bit of a pain and can become a performance issue when you want to do this with ASP.NET grids/tables which now with this plug-in is totally possible and pretty dang easy!

Below is the feature list:

Adding Rows

  • Add row to top or bottom of table
  • Pass in row to be added
  • Set animation speed, defaults to 300

 

Removing Rows

  • Remove row from top or bottom of table
  • Set animation speed, defaults to 300

 

The Code

 

(function ($) {
    var defaults = {
        rowSpeed: 300,
        newRow: null,
        addTop: true,
        removeTop: true
    };
    var newClasses = "newRow"
    var options = $.extend(defaults, options);
    $.fn.addRow = function (options) {
        opts = $.extend(defaults, options);
        var $table = $(this);
        var $tableBody = $("tbody", $table);
        var t = $(opts.newRow).find("td").wrapInner("<div style='display:none;'/>").parent()
        if (opts.addTop) t.appendTo($tableBody);
        else t.prependTo($tableBody);
        t.attr("class", newClasses).removeAttr("id").show().find("td div").slideDown(options.rowSpeed, function () {
            $(this).each(function () {
                var $set = jQuery(this);
                $set.replaceWith($set.contents());
            }).end()
        })
        return false;
    };
    $.fn.removeRow = function (options) {
        opts = $.extend(defaults, options);
        var $table = $(this);
        var t
        if (opts.removeTop) t = $table.find('tbody tr:last')
        else t = $table.find('tbody tr:first');
        t.find("td")
        .wrapInner("<div  style='DISPLAY: block'/>")
        .parent().find("td div")
        .slideUp(opts.rowSpeed, function () {
            $(this).parent().parent().remove();
        });
        return false;
    };
    return this;
})(jQuery);

 

So what’s the deal?

So to use the plug-in, I’ve set up a few options.  Here’s what you can do with them:

rowSpeed Sets the speed of the animation.  Default: 300
newRow HTML markup for new row, see demo.  Default:null
addTop If true adds to top, if false, it adds to the bottom. Default: true
removeTop if true removes from top, if false, removes from the bottom. Default: true

In a future release, I plan to make it so that you can remove rows by passing in the selector.  This will be helpful in a case where you have highlighted several rows by selecting them and want to use this to delete the row.

Additionally, I was gonna make it so that you could pass in the row object to be added but then decided that being able to pass in the html tr markup allows for more control.

I hope you find this useful…

See the Demo

Click here to check out the demo.

Download

You can download the whole wad here:  anirows.zip

 

 

HTML , ,

jQuery, Convert HTML Table to JSON - Depricated, See V2!

6. June 2010

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 propagate these changes back to the business layer and downstream to the database as the case may be.  At first thought, you might consider that upon post, you could just access your changes server side.  This is not usually possible.  Or you might not want to post, what will you do?  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 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 grid 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 grid 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 fieldA, fieldB and fieldC and I actually display Name, Date, and Load as field names to the user.  Well, without configuring the plug-in, it will just use the fields names from THEAD (fieldA, fieldB and fieldC), but if you need something different, I offer a configuration property called headers.  If you pass this your custom headers comma delimited, it will use these 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 attribHeaders.  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. 

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 returnElement 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 complete.  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.

The Code:

(function ($) {
    $.fn.tabletojson = function (options) {
        var defaults = {
            headers: null,
            attribHeaders: null,
            returnElement: null,
            complete: null
        };

        var options = $.extend(defaults, options);
        var selector = this;
        var jsonRowItem = "";
        var jsonItem = new Array();
        var jsonRow = new Array();
        var heads = [];
        var rowCounter = 1;
        var comma = ",";
        var json = "";

        if (options.headers != null) {
            options.headers = options.headers.split(' ').join(''); 
            heads = options.headers.split(",");
        }

        var rows = $(":not(tfoot) > tr", this).length;
        $(":not(tfoot) > tr", this).each(function (i, tr) {
            jsonRowItem = "";

            if (this.parentNode.tagName == "TFOOT") {
                return;  
            }
            if (this.parentNode.tagName == "THEAD") {
                if (options.headers == null) { 
                    $('th', tr).each(function (i, th) {
                        heads[heads.length] = $(th).html();
                    });
                }
            }
            else {

                if (options.attribHeaders != null) {
                    var h = eval("(" + options.attribHeaders + ")");

                    for (z in h) {
                        heads[heads.length] = h[z];
                    }
                }

                rowCounter++
                var headCounter = 0;
                jsonRowItem = "{";
                jsonItem.length = 0;
                $('td', tr).each(function (i, td) {
                    var re = /&nbsp;/gi;
                    var v = $(td).html().replace(re, '')
                    jsonItem[jsonItem.length] = "\"" + heads[headCounter] + "\":\"" + v + "\"";
                    headCounter++;
                });

                if (options.attribHeaders != null) {
                    for (z in h) {
                        jsonItem[jsonItem.length] = "\"" + heads[headCounter] + "\":\"" + tr[z] + "\"";
                        headCounter++;
                    }
                }
                
                jsonRowItem += jsonItem.join(",");
                jsonRowItem += "}";
                jsonRow[jsonRow.length] = jsonRowItem;
            }
        });
        json += "[" + jsonRow.join(",") + "]";
        
        if (options.complete != null) {
            options.complete(json);
        }

        if (options.returnElement == null)
            return json;
        else {
            $(options.returnElement).val(json);
            return this;
        }

    }
})(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="superTable" summary="Employee Pay Sheet">
    <thead>
          <tr>
            <th>Employee</th>
            <th>Salary</th>
            <th>Bonus</th>
            <th>Supervisor</th>
        </tr>
    </thead>
    <tbody>
        <tr customerID="222" orderID="1222">
            <td>Stephen C. Cox</td>
            <td>$300</td>
            <td>$50</td>
            <td>Bob</td>
        </tr>
        <tr customerID="223" orderID="1223">
            <td>Josephin Tan</td>
            <td>$150</td>
            <td>-</td>
            <td>Annie</td>
        </tr>
        <tr  customerID="224" orderID="1224">
            <td>Joyce Ming</td>
            <td>$200</td>
            <td>$35</td>
            <td>Andy</td>
        </tr>
        <tr  customerID="225" orderID="1225">
            <td>James A. Pentel</td>
            <td>$175</td>
            <td>$25</td>
            <td>Annie</td>
        </tr>
    </tbody>
</table>
<input type="hidden" id="hf" />

Script Implementation:

I suggest that you put script that runs on load after the markup just to make sure that your markup has been rendered before the script tries to work with it.  This is an example of both dumping the data to the hidden field ‘hf’ 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. :(

     <script type="text/javascript">
            $("#superTable").tabletojson({
                headers: "Employee1,Salary1,Bonus1,Supervisor1",
                attribHeaders: "{'customerID':'CustomerID','orderID':'OrderID'}",
                returnElement: "#hf",
                complete: function(x){
                    alert(x);
                }
            })
    </script>

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.

[{"Employee1":"Stephen C. Cox",
"Salary1":"$300",
"Bonus1":"$50",
"Supervisor1":"Bob",
"CustomerID":"222",
"OrderID":"1222"},
{"Employee1":"Josephin Tan",
"Salary1":"$150",
"Bonus1":"-",
"Supervisor1":"Annie",
"CustomerID":"223",
"OrderID":"1223"},
{"Employee1":"Joyce Ming",
"Salary1":"$200",
"Bonus1":"$35",
"Supervisor1":"Andy",
"CustomerID":"224",
"OrderID":"1224"},
{"Employee1":"James A. Pentel",
"Salary1":"$175",
"Bonus1":"$25",
"Supervisor1":"Annie",
"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 this out:

This is showing the results from eval(x)…x being my json string.

JSON.NET usage:

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.

 

   public struct NuggetData
    {
        public string Bonus1 { get; set; }
        public string CustomerID { get; set; }
        public string Employee1 { get; set; }
        public string OrderID { get; set; }
        public string Salary1 { get; set; }
        public string Supervisor1 { get; set; }
    }

After you do this you can run the following code:

private List<NuggetData> _NuggetList;

        private void Save()
        {
            string dataVals = Request.Form[hf.UniqueID];
            _NuggetList;= JsonConvert.DeserializeObject<List<NuggetData>>(dataVals);
            
        }

Notice here that I snag the JSON string from the hidden field “hf” and then I serialize to the list of Nuggets. 

Ding!  Fries are done!  Download and try it out!

jQuery, JSON