Table of Contents
Introduction
Task of filling table with JSON objects seems to be trivial. Indeed, it is in cases when object’s structure (set of keys) is known.
But what if we want function to be universal and be able to handle "table row" objects with unknown upfront structure? That alone will require a little bit more effort from the developer.
Let’s go even further. What if we want to have universal function that can also do the following:
- Accepts input JSON in its
string
representation or in (parsed) object form - Accepts input JSON in different formats, in particular, as array of "table row" objects or as object with such array as key’s value
- Fills existing table or creates, fills and returns the new one
- Adds rows to various existing table’s sections or replaces section's existing content
- Considers most of the input parameters as optional
Here, we will build exactly that kind of function that hopefully will be the only function we need whenever task of filling html table with JSON object is encountered.
Input JSON Data Format Definition/Requirement
Central element of input data is JSON object ("table row" object), which with its key/value pairs forms table columns. We target filling of one table. Therefore, we will handle one set (array) of "table row" objects. Since JSON is built on two structures, we want our universal function to handle both of them.
Requirements for the input JSON data are:
- Structurally JSON input is expected to be in one of two formats:
- Array of "table row" objects
- Object (name/value pair) where value of (first) key is array of "table row" objects
- All "table row" objects have same set of keys though not necessary in the same order
Input Parameters and Return Value
In order to add versatility to the function, let's create input parameters list that presumable covers most practical tasks of filling HTML table with JSON data.
Complete list of parameters in order of how they appear in function's call and their explanation is provided below. All parameters except aJSON
are optional and have default values. Optional parameters must form a group at the end of parameter list. In other words, if, for example, aTable
parameter is missing, then all parameters after it must be missing too.
aJSON
(required parameter)
String
representation or parsed JSON object in one of the following structures:
- Array of "table row" objects
- Object with first (others are ignored) key-value pair where value is array of "table row" objects
- Note that "table row" objects must have identical set of keys.
false
(by default) - does not change content of the head true
- forms "one row" head with "table row" JSON object keys replacing existing head content if any
null
(by default) - function will create and return table object with single body section filled and possible head (see aAddHead
above) - Existing table object - will fill that table in the way controlled by other input parameters
false
(by default) - rows will be added to the end of the table's section being filled. true
- before filling, all existing rows will be removed from the section - This parameter is ignored if new table is created
null
(by default) - will fill last tBody
section creating one if none exists - Particular
TableSectionObj
- will fill that table section. Usually (one of the) body but theoretically can be foot or head. - This parameter is ignored if new table is created (new single body will be filled)
false
(by default) true
- before filling table, removes rows from all table's sections without removing sections - This parameter is ignored if new table is created
false
(by default) true
- removes all rows (if any) from tHead
section without removing section - This parameter is ignored if
aClearEntireTable == true
or aAddHead == true
or new table is created
false
(by default) true
- removes all rows (if any) from all tBody
sections without removing sections - This parameter is ignored if
aClearEntireTable == true
or new table is created
false
(by default) true
- removes all rows (if any) from tFoot
section without removing section - This parameter is ignored if
aClearEntireTable == true
or new table is created
- If filling existing table, then function returns same table object that was passed as input parameter
aTable
- If creating and filling new table, then function returns newly created table object.
Code Explained
Handling Input Parameters
First of all, let's make sure required parameter is specified.
if (!Boolean(aJSON))
throw "jsonObjToHtmlTable: Required parameter aJSON is not specified";
We will translate input parameters into the set of local variables that will actually be used to control the process. Translation logic also provides default values for optional parameters.
First two are obvious:
var addHead = Boolean(aAddHead);
var createNewTable = !Boolean(aTable);
Next group is related to clearing table or its sections. Obviously, we do not need to clear newly created table. Also, we will not need code that explicitly handles clearing of entire table because we consider that operation as "sum" of "clear head", "clear all bodies" and "clear foot".
var clearSectionToFill = !createNewTable && Boolean(aClearSectionToFill);
var clearTable = !createNewTable && Boolean(aClearEntireTable);
var clearHead = !createNewTable && (clearTable || addHead || Boolean(aClearHead));
var clearAllBodies = !createNewTable && (clearTable || Boolean(aClearAllBodies));
var clearFoot = !createNewTable && (clearTable || Boolean(aClearFoot));
Last group is related to table object and its section elements. Some of them can get null
values and those cases will be handled down the road correspondingly.
var tblToFill = createNewTable ? document.createElement("table") : aTable;
var tblHead = tblToFill.tHead;
var tblFoot = tblToFill.tFoot;
var tblBodies = tblToFill.tBodies;
var sectionToFill = createNewTable ? null : aSectionToFill;
We would like to figure out which exactly table's section rows must be added to. If at this point sectionToFill
is not specified, then by default, we will use the last available body section creating one if it does not exist.
if (!sectionToFill) {
if (!(Boolean(tblBodies) && (tblBodies.length > 0))) {
tblToFill.appendChild(document.createElement("TBODY"));
tblBodies = tblToFill.tBodies;
}
sectionToFill = tblBodies[tblBodies.length - 1];
}
Avoiding Nested Functions
We need the ability to clear (delete rows of) various table sections. A straightforward solution would be nested function usage. However there is a suggestion to avoid nested functions in JavaScript since often that can harm performance and/or memory usage. Even though our case seems not to be one of the "harmful" ones, we still will follow that suggestion but using other solution. Let's define that sub-function as a method of our jsonObjToHtmlTable
function object:
if (!jsonObjToHtmlTable.clearTableSection)
jsonObjToHtmlTable.clearTableSection =
function (aTblSection) {
for (var i = aTblSection.rows.length - 1; i >= 0; i--)
aTblSection.deleteRow(i);
};
Clearing Table or its Sections
Before actual table filling, we need to clear table or its particular sections if it was requested. All possible kinds of clearing were defined on input parameter interpretation stage. Now, if some particular kind of clearing is requested and corresponding table's section exists, then we will clear it.
if (clearHead && Boolean(tblHead))
jsonObjToHtmlTable.clearTableSection(tblHead);
if (clearAllBodies && Boolean(tblBodies) && (tblBodies.length > 0))
for (var i = tblBodies.length - 1; i >= 0; i--)
jsonObjToHtmlTable.clearTableSection(tblBodies[i]);
if (clearFoot && Boolean(tblFoot))
jsonObjToHtmlTable.clearTableSection(tblFoot);
if (clearSectionToFill)
jsonObjToHtmlTable.clearTableSection(sectionToFill);
Handling String or Object Input
We want our function to be universal and accept input object of different types. Since we are dealing with JSON here, we can think of two possibilities for the input object, specifically, object comes in text/string format or as already parsed JavaScript object. Therefore, let's check whether input is a string
and parse it or use it as is otherwise:
var inputJSObj = (Object.prototype.toString.call(aJSON) === "[object String]") ?
JSON.parse(aJSON) : aJSON;
Using Object.keys() Method
As it was already pointed out, we want to handle "table row" objects with unknown upfront structure, i.e., unknown number of properties/keys and their names. But obviously, we still need to have access to property/key values. Object.keys(obj)
method, which returns an array of a given object's property/key names, can be helpful here. Considering that not all browsers support this method, for instance Internet Explorer before ver 9, let's handle such cases defining method by ourselves if necessary:
if(!Object.hasOwnProperty("keys")){
Object.keys = function(aObj){
var keyNameArray = [];
for (keyName in aObj){
if (aObj.hasOwnProperty(keyName)){
keyNameArray[keyNameArray.length] = keyName;
}
}
return keyNameArray;
};
}
Handling Array or key/value Input
The next step is to figure out the structure of input object. Since according to previously formulated requirement, input can be of format of array or "key/value" pair, let's just check whether it is array and use it as is or extract value of (first) key, which should be array too.
var allRowsAsArrayOfObjects = (Object.prototype.toString.call(inputJSObj) === "[object Array]") ?
inputJSObj : inputJSObj[Object.keys(inputJSObj)[0]];
At this point, we have array of objects that will form table's rows. We will proceed with the rest of the steps only if this array is not empty (allRowsAsArrayOfObjects.length > 0
).
Since we require all "table row" objects to have the same key set, we can get the set from any object, in particular, the first one:
var rowKeyNames = Object.keys(allRowsAsArrayOfObjects[0]);
Forming Table's Head Section
If table's head forming is requested and tHead
section exists, then it should be already cleared earlier (see logic for clearHead
). If tHead
does not exist, then we will create one. After that, we just need to add row to the head and fill its cells with values from above rowKeyNames
array. Just one inconvenience, since tHead.insertCell()
method creates <td>
cells and we want <th>
cells for the head, we need to use little bit longer code:
var headRow, headRowCell;
if (addHead) {
tblHead = tblHead ? tblHead : tblToFill.createTHead();
headRow = tblHead.insertRow(-1);
for (var j = 0; j < rowKeyNames.length; j++) {
headRowCell = document.createElement("th");
headRowCell.appendChild(document.createTextNode(rowKeyNames[j]));
headRow.appendChild(headRowCell);
}
}
Adding Data Rows
Finally, we are ready to actually fill the table with data rows. In the previous stages, it was determined which exactly table section is supposed to be filled and section was cleared if necessary. The rest is simple. For each "table row" object, add row to the section creating necessary row’s cells and assigning their values. In order to use a uniform approach, we will create <td>
cells in the same way we create <th>
cells for table's head though we could use insertCell()
here.
var oneRowAsObject, tableRow, rowCell;
for (var i = 0; i < allRowsAsArrayOfObjects.length; i++) {
oneRowAsObject = allRowsAsArrayOfObjects[i];
tableRow = sectionToFill.insertRow(-1);
for (var j = 0; j < rowKeyNames.length; j++) {
rowCell = document.createElement("td");
rowCell.appendChild(document.createTextNode(oneRowAsObject[rowKeyNames[j]]));
tableRow.appendChild(rowCell);
}
}
Complete Code of the Function
Complete code, that includes all discussed above pieces, is provided below:
function jsonObjToHtmlTable(aJSON, aAddHead, aTable, aClearSectionToFill, aSectionToFill,
aClearEntireTable, aClearHead, aClearAllBodies, aClearFoot) {
if (!Boolean(aJSON))
throw "jsonObjToHtmlTable: Required parameter aJSON is not specified";
var addHead = Boolean(aAddHead);
var createNewTable = !Boolean(aTable);
var clearSectionToFill = !createNewTable && Boolean(aClearSectionToFill);
var clearTable = !createNewTable && Boolean(aClearEntireTable);
var clearHead = !createNewTable && (clearTable || addHead || Boolean(aClearHead));
var clearAllBodies = !createNewTable && (clearTable || Boolean(aClearAllBodies));
var clearFoot = !createNewTable && (clearTable || Boolean(aClearFoot));
var tblToFill = createNewTable ? document.createElement("table") : aTable;
var tblHead = tblToFill.tHead;
var tblFoot = tblToFill.tFoot;
var tblBodies = tblToFill.tBodies;
var sectionToFill = createNewTable ? null : aSectionToFill;
if (!sectionToFill) {
if (!(Boolean(tblBodies) && (tblBodies.length > 0))) {
tblToFill.appendChild(document.createElement("TBODY"));
tblBodies = tblToFill.tBodies;
}
sectionToFill = tblBodies[tblBodies.length - 1];
}
if (!jsonObjToHtmlTable.clearTableSection)
jsonObjToHtmlTable.clearTableSection =
function (aTblSection) {
for (var i = aTblSection.rows.length - 1; i >= 0; i--)
aTblSection.deleteRow(i);
};
if (clearHead && Boolean(tblHead))
jsonObjToHtmlTable.clearTableSection(tblHead);
if (clearAllBodies && Boolean(tblBodies) && (tblBodies.length > 0))
for (var i = tblBodies.length - 1; i >= 0; i--)
jsonObjToHtmlTable.clearTableSection(tblBodies[i]);
if (clearFoot && Boolean(tblFoot))
jsonObjToHtmlTable.clearTableSection(tblFoot);
if (clearSectionToFill)
jsonObjToHtmlTable.clearTableSection(sectionToFill);
var inputJSObj, allRowsAsArrayOfObjects, oneRowAsObject, rowKeyNames;
var headRow, headRowCell, tableRow, rowCell;
if(!Object.hasOwnProperty("keys")){
Object.keys = function(aObj){
var keyNameArray = [];
for (keyName in aObj){
if (aObj.hasOwnProperty(keyName)){
keyNameArray[keyNameArray.length] = keyName;
}
}
return keyNameArray;
};
}
inputJSObj = (Object.prototype.toString.call(aJSON) === "[object String]") ?
JSON.parse(aJSON) : aJSON;
allRowsAsArrayOfObjects = (Object.prototype.toString.call(inputJSObj) === "[object Array]") ?
inputJSObj : inputJSObj[Object.keys(inputJSObj)[0]];
rowKeyNames = [];
if (allRowsAsArrayOfObjects.length > 0){
rowKeyNames = Object.keys(allRowsAsArrayOfObjects[0]);
if (addHead) {
tblHead = tblHead ? tblHead : tblToFill.createTHead();
headRow = tblHead.insertRow(-1);
for (var j = 0; j < rowKeyNames.length; j++) {
headRowCell = document.createElement("th");
headRowCell.appendChild(document.createTextNode(rowKeyNames[j]));
headRow.appendChild(headRowCell);
}
}
for (var i = 0; i < allRowsAsArrayOfObjects.length; i++) {
oneRowAsObject = allRowsAsArrayOfObjects[i];
tableRow = sectionToFill.insertRow(-1);
for (var j = 0; j < rowKeyNames.length; j++) {
rowCell = document.createElement("td");
rowCell.appendChild(document.createTextNode(oneRowAsObject[rowKeyNames[j]]));
tableRow.appendChild(rowCell);
}
}
}
return tblToFill;
}
Downloading Source Code
The following source code, all of which is in one zip file, is available for download at the top of this page:
- Code of JSON object to HTML function in jsonObjToHtmlTable.js file.
- Test/Demo application in jsonObjToHtmlTable_Test.html file. This application, which is in fact html page, allows demonstration and testing of all features of the function. It references the above external file containing tested function assuming that it is located in the same directory.
History
Version 1.0 (2015-01-25)
Extensive experience developing pure software and combined soft-hardware systems using variety of languages and tools.