Please can someone guide me on how to implement a static (sticky) header to this dynamically created table?
I have tried multiple things from Stackoverflow threads for a while now but lack HTML/CSS knowledge and I'm obviously missing something simple.
I have managed to get it working using a table created directly in the main body of the code, but when I use my dynamically created tables from JSON I can't get anything to 'stick'.
Below the code:
<!DOCTYPE html>
<html>
<meta name="viewport" content="width=device-width, initial-scale=0.50, maximum-scale=1, user-scalable=0"/>
<head>
<title>iNews HTML Running Order</title>
<style>
table
{
border: solid 1px #CCCCCC;
border-collapse: collapse;
text-align: left;
font:30px Arial;
}
tr, th, td
{
white-space: nowrap;
padding-right: 50px;
}
tr
{
background-color: #ffffff;
border: solid 1px #CCCCCC;
}
th
{
background-color: #CCCCCC;
}
#container
{
text-align: center;
max-width: 100%;
}
</style>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body onload="initialisePage('LW')">
<p id="showData">Loading Running Order...</p>
</body>
<script>
var loop;
var filename;
var table;
function updateJSONData(filename)
{
getDataFromJSON(filename)
loop = setInterval(function(){getDataFromJSON(filename);}, 500);
}
function initialisePage(newFilename)
{
filename = newFilename;
updateJSONData(filename)
}
function setFileName(newFilename)
{
clearInterval(loop)
filename = newFilename;
updateJSONData(filename)
}
function getDataFromJSON(filename)
{
$.get( "http://10.142.32.72/dashboard/"+filename+".json", function( data ) {
var myBooks = JSON.parse(data);
CreateTableFromJSON(myBooks)
});
}
function CreateTableFromJSON(myBooks)
{
var title = ["Page", "Slug", "Pres 1", "Pres 2", "CAM", "Format", "Clip Dur", "Total", "Backtime"];
var col = ["page-number", "title", "pres1", "pres2", "camera", "format", "runs-time", "total-time", "back-time"];
// CREATE DYNAMIC TABLE.
table = document.createElement("table");
// CREATE HTML TABLE HEADER ROW USING THE EXTRACTED HEADERS ABOVE.
var tr = table.insertRow(-1); // TABLE ROW.
for (var i = 0; i < col.length; i++) {
var th = document.createElement("th"); // TABLE HEADER.
th.innerHTML = title[i];
tr.appendChild(th);
}
// ADD JSON DATA TO THE TABLE AS ROWS.
for (var i = 0; i < myBooks.length; i++) {
tr = table.insertRow(-1);
if (myBooks[i]["floated"] == "true"){
tr.style.color = "#ffffff";
tr.style.background = "blue";
}
if ((myBooks[i]["break"] == "true") && (myBooks[i]["floated"] == "false")){
tr.style.background = "#00ff00";
}
for (var j = 0; j < col.length; j++) {
var tabCell = tr.insertCell(-1);
tabCell.innerHTML = myBooks[i][col[j]];
}
}
// FINALLY ADD THE NEWLY CREATED TABLE WITH JSON DATA TO A CONTAINER.
var divContainer = document.getElementById("showData");
divContainer.innerHTML = "";
divContainer.appendChild(table);
console.log("Refreshed: " + filename);
}
</script>
</html>
Many thanks in advance,
Joe
Remove <body onload="initialisePage('LW')">
and use DOMContentLoaded
instead as it happens much sooner than the document load
event.
load
is only fired after ALL resources/content has been loaded, including "non-essential" (non-DOM) content like images and external content like ad-banners, which means the load
event may be fired tens-of-seconds after DOMContentLoaded
which makes the load
event kinda useless today).Change your CSS to this:
table > thead > tr > th {
position: sticky;
top: 0;
z-index: 10;
}
table > tbody > tr.floated {
color: '#ffffff';
background-color: 'blue';
}
table > tbody > tr.broken {
background-color: '#00ff00';
}
JavaScript uses camelCase
for functions, values (variables and parameters) and properties, not PascalCase
.
var
and use const
and let
in scripts where appropriate instead. Note that const
means "unchanging reference" (kinda like C++); it does not mean "immutable" or "compile-time constant value". I think this definition of const
was a mistake by the JavaScript language designers, but that's just, like, my opinion, man.classList
instead of setting individual style properties using .style
.===
(exactly-equals) instead of ==
(equals) because JavaScript's type coercion can be surprising).innerHTML
wherever possible. Use .textContent
for setting normal text content (and avoid using .innerText
too). Misuse of innerHTML
leads to XSS vulnerabilities.Content-Type
).j
as an iterable variable name because it's too visually similar to i
.Change your JavaScript to this:
<script>
// You should put all of your own application-specific top-level page script variables in their own object so you can easily access them separately from the global `window` object.
const myPageState = {
loop : null,
fileName: null,
table : null
};
window.myPageState = myPageState; // In the top-level function, `const` and `let`, unlike `var`, do not create a global property - so you need to explicitly set a property like so: `window.{propertyName} = ...`.
window.addEventListener( 'DOMContentLoaded', onDOMLoaded );
function onDOMLoaded( ev ) {
window.myPageState.fileName = "LW";
window.myPageState.loop = setInterval( refreshTable, 500 );
}
async function refreshTable() {
if( typeof window.myPageState.fileName !== 'string' || window.myPageState.fileName.length === 0 ) return;
const url = "http://10.142.32.72/dashboard/" + window.myPageState.fileName + ".json";
const resp = await fetch( url );
if( resp.status === 200 && resp.headers['ContentType'] === 'application/json' ) {
const deserialized = await resp.json();
ceateAndPopulateTableFromJSONResponse( deserialized );
}
else {
// Error: unexpected response.
// TODO: error handling
// e.g. `console.error` or `throw new Error( "Unexpected response." )`, etc.
}
}
function ceateAndPopulateTableFromJSONResponse( myBooks ) {
// TODO: Verify the `myBooks` object layout (i.e. schema-verify `myBooks`).
const columnTitles = ["Page", "Slug", "Pres 1", "Pres 2", "CAM", "Format", "Clip Dur", "Total", "Backtime"];
const columnNames = ["page-number", "title", "pres1", "pres2", "camera", "format", "runs-time", "total-time", "back-time"];
const table = window.myPageState.table || document.createElement( 'table' );
if( window.myPageState.table !== table ) {
window.myPageState = table;
document.getElementById("showData").appendChild( table );
}
// Create the <thead>, if nnecessary:
if( table.tHead === null )
{
table.tHead = document.createElement( 'thead' );
const tHeadTR = table.tHead.insertRow(-1);
for( let i = 0; i < columnNames.length; i++ ) {
const th = document.createElement('th');
th.textContent = columnTitles[i];
tHeadTR.appendChild( th );
}
}
// Clear any existing tbody:
while( table.tBodies.length > 0 ) {
table.removeChild( table.tBodies[0] );
}
// Populate a new <tbody>:
{
const tbody = document.createElement('tbody');
for( let i = 0; i < myBooks.length; i++ ) {
const tr = table.insertRow(-1);
tr.classList.toggle( 'floated', myBooks[i]["floated"] === "true" );
tr.classList.toggle( 'broken' , myBooks[i]["break" ] === "true" && myBooks[i]["floated"] === "false" );
for( let c = 0; c < columnNames.length; c++ ) {
const td = tr.insertCell(-1);
const colName = columnNames[c];
td.textContent = myBooks[i][ colName ];
}
}
table.appendChild( tbody );
}
console.log( "Refreshed: " + window.myPageState.fileName );
}
</script>