2024-06-07
Learn how to create a dynamic, nested HTML table from JSON data using JavaScript and jQuery.
When working with JSON data, it’s often necessary to display it in a structured format. An HTML table is a great choice for this task as it allows for clear and organized data presentation. In this guide, we’ll explore how to build a nested HTML table from JSON data, making it expandable to show nested objects.
Consider the following HTML and espacially CSS structure for dynamic HTML tables which increases user experience:
<style type="text/css">
table {
border: 1px solid #ccc;
border-radius: 8px;
border-spacing: 0;
width: 100%;
box-shadow: 0 2px 5px grey;
overflow: hidden;
}
th,
td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #eee;
}
th {
vertical-align: top !important;
background-color: #f7f7f7;
user-select: none;
transition: background-color 0.3s ease;
position: relative;
padding-right: 24px;
}
td {
background-color: #fff;
transition: background-color 0.3s ease;
}
.clickable-row:hover > th,
.clickable-row:hover > td {
background-color: #f0f0f0;
}
.clickable-row:hover > th {
cursor: pointer;
background-color: #d6eeee !important;
}
.clickable-row > th::after {
content: "▼";
position: absolute;
right: 10px;
font-size: 12px;
color: grey;
transition: transform 0.3s ease;
}
.clickable-row:hover > th::after {
transform: rotate(-90deg);
}
</style>
<div id="nestedTableDiv">
</div>
<script type="text/javascript">
$(document).ready(function () {
var url = "/a/url";
$.blockUI({ message: "Please wait..." });
$.get(url, { id: 1 }, function (data) {
utils.createTableFromJson(data, $("#nestedTableDiv"));
}).always(function (jqXHR) {
$.unblockUI();
});
});
</script>
Implementing dynamic HTML tables can be achieved with the following JavaScript code with jQuery:
/**
* Creates a nested HTML table from JSON data and appends it to the specified div.
* @param {string|Object} jsonData - The JSON data as a string or an object.
* @param {Object} divRef - The div element to which the table will be appended.
*/
createTableFromJson: function (jsonData, divRef) {
/**
* Adds a table header (th) element to the given table row (tr).
* @param {Object} tr - The table row to which the header will be added.
* @param {string} key - The key to be displayed in the header.
*/
function addTableHeader(tr, key) {
tr.append("<th>" + key + "</th>");
}
/**
* Adds a table data (td) element to the given table row (tr).
* @param {Object} tr - The table row to which the data will be added.
* @param {string} value - The value to be displayed in the data cell.
*/
function addTableData(tr, value) {
tr.append("<td>" + value + "</td>");
}
/**
* Recursively creates nested tables for objects within the JSON data.
* @param {Object} table - The table element to which nested tables will be added.
* @param {Object} obj - The current object to be processed.
*/
function createNestedTables(table, obj) {
for (const key in obj) {
var tr = $("<tr>");
addTableHeader(tr, key);
if (obj[key] !== null && typeof obj[key] === "object") {
// If the property is an object, create a nested table recursively
tr.addClass("clickable-row");
var nestedTable = $("<table>").hide();
createNestedTables(nestedTable, obj[key]);
var td = $("<td>").append(nestedTable);
tr.append(td);
// Add toggle functionality for nested table
tr.find("th:first").on("click", function () {
$(this).closest("tr").find("table:first").toggle();
});
} else {
addTableData(tr, obj[key]);
}
table.append(tr);
}
}
/**
* Creates the main table and adds functionality to expand all nested tables.
* @param {Object} jsonObject - The parsed JSON object.
* @returns {Object} - The div element containing the table.
*/
function createTable(jsonObject) {
var table = $("<table>");
createNestedTables(table, jsonObject);
// Create a div to hold the table and the expand button
var div = $("<div style='width: 98%; margin: auto;'>");
// Add a button to expand all nested tables if any exist
var tables = table.find("table");
if (tables && tables.length > 0) {
var button = $("<button class='btn btn-sm btn-default' style='margin:5px'>")
.text("Open All")
.on("click", function () {
tables.show();
});
div.append(button);
}
div.append(table);
return div;
}
/**
* Recursively parses nested JSON strings within an object.
* @param {Object} obj - The object to be processed.
*/
function parseNestedJSON(obj) {
for (const key in obj) {
if (obj[key] !== null && typeof obj[key] === "object") {
// If the property is an object, continue recursively
parseNestedJSON(obj[key]);
} else if (typeof obj[key] === "string") {
try {
const parsed = JSON.parse(obj[key]);
obj[key] = parsed;
// If the parsed value is an object, continue recursively
if (typeof parsed === "object" && parsed !== null) {
parseNestedJSON(parsed);
}
} catch (e) {
// Ignore if the string is not a valid JSON
}
}
}
}
try {
if (!jsonData) {
return;
}
if (typeof jsonData === "string") {
jsonData = jsonData.trim();
}
if (!jsonData) {
return;
}
// Determine if jsonData is already an object or a serialized JSON string
var jsonObject;
if (typeof jsonData === "object") {
jsonObject = jsonData;
} else {
jsonObject = JSON.parse(jsonData);
// Uncomment this if nested JSON strings need to be parsed
//parseNestedJSON(jsonObject);
}
divRef.html(createTable(jsonObject));
} catch (e) {
console.error(e);
toastr.error("Error happened.");
}
}