<main>
<h1>JSON-Powered Responsive Table with Semantics Retained by ARIA</h1>
<p>
A variation on the previous example, using vanilla JavaScript to construct the table from a pile of JSON.
</p>
<div id="BookTable" role="region" aria-labelledby="BookTableCaption" tabindex="0">
<p>
The JSON is probably invalid or the script that parses it is broken. Either way, my fault.
</p>
</div>
<p>
Note that this is an <em>accessible</em> (keyboard and screen reader) responsive (width and print) table generated with vanilla JavaScript from a pile of JSON. It retains table semantics for screen readers thanks to the liberal application of ARIA roles. You can <a href="http://adrianroselli.com/2018/04/tables-json-css-aria-rwd-tlas.html">read more at my site</a>.
</p>
</main>
@import url("https://fonts.googleapis.com/css?family=Lato:400,400i,700");
body {
background-color: #6d695c;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAACVBMVEUAAAAAAAAAAACDY+nAAAAAA3RSTlMmDQBzGIDBAAAAG0lEQVR42uXIIQEAAADCMHj/0NdkQMws0HEeAqvwAUGJthrXAAAAAElFTkSuQmCC);
font-size: 100%;
color: #333;
font-family: Lato, Arial, sans-serif;
padding: 0;
margin: 0;
}
main {
display: block;
box-sizing: border-box;
width: auto;
padding: 1em 2vw;
margin: 1em 2vw;
color: #000;
background-color: rgba(204, 204, 204, 0.7);
border: 0.07em solid rgba(0, 0, 0, 0.5);
border-radius: 0.5em;
}
table {
margin: 1em 0;
border-collapse: collapse;
/* width: 100%; */
}
caption {
text-align: left;
font-style: italic;
padding: 0.25em 0.5em 0.5em 0.5em;
}
th,
td {
padding: 0.25em 0.5em 0.25em 1em;
vertical-align: text-top;
text-align: left;
text-indent: -0.5em;
}
th {
vertical-align: bottom;
background-color: rgba(0, 0, 0, 0.5);
color: #fff;
font-weight: bold;
}
td::before {
display: none;
}
tr:nth-child(even) {
background-color: rgba(255, 255, 255, 0.25);
}
tr:nth-child(odd) {
background-color: rgba(255, 255, 255, 0.5);
}
td:nth-of-type(2) {
font-style: italic;
}
th:nth-of-type(3),
td:nth-of-type(3) {
text-align: right;
}
div {
overflow: auto;
}
@media screen and (max-width: 37em), print and (max-width: 5in) {
table,
tr,
td {
display: block;
}
tr {
padding: 0.7em 2vw;
}
th,
tr:first-of-type {
display: none;
}
td::before {
display: inline;
font-weight: bold;
}
td {
display: grid;
grid-template-columns: 4em auto;
grid-gap: 1em 0.5em;
}
caption {
font-style: normal;
background-color: rgba(0, 0, 0, 0.35);
color: #fff;
font-weight: bold;
}
td:nth-of-type(3) {
text-align: left;
}
td:nth-of-type(4), td:nth-of-type(5) {
text-align: right;
width: 12em;
}
td:nth-of-type(4)::before, td:nth-of-type(5)::before {
text-align: left;
}
td:nth-of-type(2)::before {
font-style: normal;
}
}
@media print {
body {
font-size: 6pt;
color: #000;
background-color: #fff;
background-image: none;
}
body,
main {
margin: 0;
padding: 0;
background-color: #fff;
border: none;
}
table {
page-break-inside: avoid;
}
div {
overflow: visible;
}
th {
color: #000;
background-color: #fff;
border-bottom: 1pt solid #000;
}
tr {
border-top: 1pt solid #000;
}
}
@media print and (max-width: 5in) {
caption {
color: #000;
background-color: #fff;
border-bottom: 1pt solid #000;
}
table {
page-break-inside: auto;
}
tr {
page-break-inside: avoid;
}
}
button {
font-size: 90%;
font-family: Lato, Arial, sans-serif;
color: #fff;
background-color: #00c;
border: .1em solid #00f;
border-radius: .5em;
font-weight: bold;
padding: .5em;
text-align: center;
box-shadow: .25em .25em .5em rgba(0, 0, 0, .5);
}
button[disabled] {
background-color: #ccc;
border-color: #ccc;
color: #666;
}
button:not([disabled]):focus,
button:not([disabled]):hover {
color: #00f;
background-color: #fff;
outline: 0;
}
button:active {
box-shadow: 0 0 .5em rgba(0, 0, 0, .5);
}
// Verify the JSON is JSON
// https://jsonformatter.curiousconcept.com/
// Minify the JSON for use below
// https://www.cleancss.com/json-minify/
// Minify the HTML node
// https://www.willpeavy.com/minifier/
// Escape the HTML for the JSON
// https://www.freeformatter.com/json-escape.html
var tableData =
'{"items":[{"table":"BooksJSON","caption":"Books I May or May Not Have Read","row":[{"Author":"Miguel De Cervantes","Title":"The Ingenious Gentleman Don Quixote of La Mancha","Year":"1605","ISBN-13":"9783125798502","ISBN-10":"3125798507"},{"Author":"Mary Shelley","Title":"Frankenstein; or, The Modern Prometheus","Year":"1818","ISBN-13":"9781530278442","ISBN-10":"1530278449"},{"Author":"Herman Melville","Title":"Moby-Dick; or, The Whale","Year":"1851","ISBN-13":"9781530697908","ISBN-10":"1530697905"},{"Author":"Emma Dorothy Eliza Nevitte Southworth","Title":"The Hidden Hand","Year":"1888","ISBN-13":"9780813512969","ISBN-10":"0813512964"},{"Author":"F. Scott Fitzgerald","Title":"The Great Gatsby","Year":"1925","ISBN-13":"9780743273565","ISBN-10":"0743273567"},{"Author":"George Orwell","Title":"Nineteen Eighty-Four","Year":"1948","ISBN-13":"9780451524935","ISBN-10":"0451524934"}]}]}';
function ParseJSON(data, elmID, nodeType) {
try {
var JSONdata = JSON.parse(data);
var divContainer = document.getElementById(elmID);
divContainer.innerHTML = "";
var filteredNodes = JSONdata.items;
for (var a = 0; a < filteredNodes.length; a++) {
var json = filteredNodes[a];
if (json.table === nodeType) {
// data grid
if (json.caption !== undefined && json.row !== undefined) {
var table = document.createElement("table");
var caption = document.createElement("caption");
caption.innerHTML = json.caption;
caption.setAttribute('id', elmID+'Caption');
table.appendChild(caption);
table.setAttribute('id', json.table);
table.setAttribute('role','table');
var tableHead = Object.keys(json.row[0]);
var tableRows = Object.values(json.row);
var tr = table.insertRow(-1);
tr.setAttribute('role','row');
for (var i = 0; i < tableHead.length; i++) {
var th = document.createElement("th");
th.setAttribute('role','columnheader');
th.innerHTML = tableHead[i];
tr.appendChild(th);
}
for (var j = 0; j < tableRows.length; j++) {
var tableCells = Object.values(json.row[j]);
var tr = table.insertRow(-1);
tr.setAttribute('role','row');
for (var k = 0; k < tableCells.length; k++) {
var tabCell = tr.insertCell(-1);
tabCell.setAttribute('role','cell');
tabCell.innerHTML = tableCells[k];
}
}
}
// build it
if (json.caption !== undefined && json.row !== undefined) {
divContainer.appendChild(table);
}
}
}
} catch (e) {
console.log("ParseJSON(): " + e);
}
}
function ResponsiveCellHeaders(elmID) {
try {
var THarray = [];
var table = document.getElementById(elmID);
var ths = table.getElementsByTagName("th");
for (var i = 0; i < ths.length; i++) {
var headingText = ths[i].innerHTML;
THarray.push(headingText);
}
var styleElm = document.createElement("style"),
styleSheet;
document.head.appendChild(styleElm);
styleSheet = styleElm.sheet;
for (var i = 0; i < THarray.length; i++) {
styleSheet.insertRule(
"#" +
elmID +
" td:nth-child(" +
(i + 1) +
')::before {content:"' +
THarray[i] +
': ";}',
styleSheet.cssRules.length
);
}
} catch (e) {
console.log("ResponsiveCellHeaders(): " + e);
}
}
ParseJSON(tableData, "BookTable", "BooksJSON");
ResponsiveCellHeaders('BooksJSON');
Also see: Tab Triggers