elements unintentionally move on content change - html

I'm learning React and JavaScript generally via writing chess game and got to some intersting issue with elements changing position depending on whether content is empty or not.
I create simple CodePen to illustrate problem - https://codepen.io/anon/pen/KbGKrX?editors=0010
Clicking on each square will change it content.
Problem is that this square will change position, unless all squares in the same row have same content (null or text). Tried with empty strings and spaces instead null and got the same behaviour.
Inspecting in Dev Tools I see no change of CSS so I am puzzled and not sure whether its is some strange behaviour because of React or something else.
I would appreciate any help on this one.
JS part:
const BoardSquare = (props) => {
return (
<button
className={`board-square board-square-${props.squareColor}`}
onClick={props.onClick}
>
{props.piece}
</button>
);
};
const squares = new Array(8);
for (let i = 0; i < 8; i++) {
let row = new Array(8);
for (let j = 0; j < 8; j++) {
row[j] = {
rowIndex: i,
colIndex: j,
key: (j + 10).toString(36) + (8 - i).toString(),
color: (i + j) % 2 === 0 ? "light" : "dark",
piece: i === 1 ? j : null
};
}
squares[i] = row;
}
class Game extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: squares,
whiteMove: true
};
}
handleClickSquare(i, j) {
//alert("clicked:" + i + " " + j);
const squares = this.state.squares.slice();
squares[i][j].piece = squares[i][j].piece !== null ? null : j;
this.setState({ squares: squares, whiteMove: !this.state.whiteMove });
}
handleClickNewGame() {
const squares = this.state.squares.slice();
for (let i = 0; i < 8; i++) {
squares[1][i].piece = i;
squares[6][i].piece = i;
}
this.setState({ squares: squares, whiteMove: true });
}
render() {
return (
<div id="board">
{this.state.squares.map((row, rowIndex) => {
return (
<div>{
row.map((square, colIndex) => {
return (
<BoardSquare
key={square.key}
squareColor={square.color}
id={square.key}
piece={square.piece}
onClick={() => this.handleClickSquare(rowIndex, colIndex)}
/>
);
})}
</div>)
})}
</div>
)
}
}
// ========================================
ReactDOM.render(
<Game />,
document.getElementById('root')
);
CSS:
#board {
font-size: 0;
}
.board-square {
text-align: center;
align-items: center;
justify-content: center;
height: 20px;
width: 20px;
line-height: 20px;
font-size: 10px;
margin: 0;
padding: 0;
border: 0;
}
.board-square-dark {
background-color: green;
}
.board-square-light {
background-color: #E0AB76;
}

The reason for this behavior is because of the default baseline vertical alignment. Already well explained in this answer: https://stackoverflow.com/a/13550706/1790728
By changing the display to inline-block and setting vertical-align to top, the squares will be aligned without using a white-space.
Working example: https://codepen.io/anon/pen/MZLZYj

Related

How can I create a hover of n rows in an html table? Angular

I am trying to create a preview on a table that shows events, I previously asked for help here and it solved the issue of positioning elements on the table.
With that same logic I try to create a preview according to a duration in hours and according to the column take the location and save it in the list of events.
When I try to make the preview it does not take me well the element sometimes appears and in others disappears I have been several hours but I still can not solve it, besides after showing the preview and click the event should be saved.
Why is the preview blinking?
This is my code:
Stackblitz:
https://stackblitz.com/edit/angular-ivy-k22v8c?file=src/app/app.component.ts
HTML:
<div class="calendar-container">
<table
(mouseenter)="activatePreview()"
(mouseleave)="deactivatePreview()"
(mousemove)="calculatePreview($event)"
>
<tr>
<th></th>
<th class="cell-place" *ngFor="let place of places">{{ place }}</th>
</tr>
<tr *ngFor="let hour of hours">
<td class="cell-hour">{{ hour }}</td>
<td class="cell" *ngFor="let place of places"></td>
</tr>
</table>
<div *ngIf="previewActive">
<div class="preview" [ngStyle]="preview.position">
<p>{{ preview.location }}</p>
</div>
</div>
<div *ngFor="let event of events">
<div class="event" [ngStyle]="event.position">
<p>{{ event.location }}</p>
</div>
</div>
</div>
TS:
export class AppComponent implements AfterViewInit {
hours = [
'8:00 AM',
'9:00 AM',
'10:00 AM',
'11:00 AM',
'12:00 PM',
'1:00 PM',
'2:00 PM',
'3:00 PM',
'4:00 PM',
'5:00 PM',
];
places = ['P1', 'P2', 'P3', 'P4', 'P5'];
events: any = [];
cellWidth = 0;
cellWidthHour = 0;
cellHeight = 0;
previewActive = false;
previewDuration = 2;
preview: any = {};
currentCell = null;
activatePreview() {
this.previewActive = true;
}
deactivatePreview() {
this.previewActive = false;
this.currentCell = null;
this.preview = {};
}
calculatePreview(event: any) {
if (!this.previewActive) {
return;
}
// Get the position of the cursor
const x = event.clientX;
const y = event.clientY;
// Calculate the column (location) of the preview
const columns = document.getElementsByClassName('cell-place');
let column;
for (const col of Array.from(columns)) {
if (
col.getBoundingClientRect().left <= x &&
x <= col.getBoundingClientRect().right
) {
column = col;
break;
}
}
if (!column) {
return;
}
console.log(column);
// Calculate the start and end times of the preview
const rows = document.getElementsByClassName('cell-hour');
let startIndex = -1;
let endIndex = -1;
for (let i = 0; i < rows.length; i++) {
const row = rows.item(i);
if (
row.getBoundingClientRect().top <= y &&
y <= row.getBoundingClientRect().bottom
) {
startIndex = i;
endIndex = i + this.previewDuration - 1;
break;
}
}
console.log(startIndex, endIndex);
// Check if the preview goes beyond the limit of the table
if (startIndex === -1 || endIndex === -1 || endIndex >= rows.length) {
return;
}
// Check if the cursor is in a new cell
const newCell = startIndex + '-' + endIndex;
console.log(newCell);
if (newCell === this.currentCell) {
return;
}
this.currentCell = newCell;
// Update the preview based on the calculations
const startTime = this.hours[startIndex];
const endTime = this.hours[endIndex];
const location = column.innerHTML;
this.preview = { startTime, endTime, location };
this.preview.position = this.calculateEventPosition(
startTime,
endTime,
location
);
console.log(this.preview);
}
constructor() {}
ngAfterViewInit() {
// this.getCellSize();
this.createEvent('11:00 AM', '12:00 PM', 'P3');
}
createEvent(startTime: string, endTime: string, location: string) {
const event: any = { startTime, endTime, location };
event.position = this.calculateEventPosition(startTime, endTime, location);
this.events.push(event);
}
calculateEventPosition(startTime: string, endTime: string, location: string) {
const rect = document
.getElementsByTagName('table')[0]
.getBoundingClientRect();
const columns = document.getElementsByClassName('cell-place');
const rows = document.getElementsByClassName('cell-hour');
const column = Array.from(columns).find((x) => x.innerHTML == location);
const start = rows.item(this.hours.indexOf(startTime));
const end = rows.item(this.hours.indexOf(endTime));
const left = column.getBoundingClientRect().left - rect.left;
const top = start.getBoundingClientRect().top - rect.top;
const width = column.getBoundingClientRect().width;
const height =
end.getBoundingClientRect().top - start.getBoundingClientRect().top;
return {
height: height + 'px',
top: top + 'px',
left: left + 'px',
width: width + 'px',
};
}
}
CSS:
.calendar-container {
position: relative;
}
table {
border-collapse: collapse;
width: 100%;
}
td,
th {
border: 1px solid #dddddd;
text-align: center;
}
table td {
height: 25px;
}
.event {
box-sizing: border-box;
position: absolute;
background-color: #f2f2f2;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
}
.preview {
box-sizing: border-box;
position: absolute;
background-color: yellow;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
z-index: 9;
}
I have created part of the preview logic but it does not work as it should, it gives me a flicker error and sometimes with delay, also the preview should be only about the hours, the other thing that generates me doubts is how I can then save with a click, the event has n duration for now is in hard code and must be according to the location column.
The bug is here
<table
(mouseenter)="activatePreview()"
(mouseleave)="deactivatePreview()"
(mousemove)="calculatePreview($event)"
>...</table>
the mouseenter and mouseleave event bindings get triggered simultaneously, whenever the mouse move from one cell to another, which calls mouseleave event where that event handler unsets variable currentCell.
Solution:
Removing those two event listeners (mouseenter) and (mouseleave) and removing the below code from calculatePreview method would do the trick.
// if (newCell === this.currentCell) {
// return;
// }
Please check and let me know whether it resolves your issue.
I feel you are overcomplicated your code. If only want to get the position of the "hovered cell" you can use
<!--see the template reference variable #cell-->
<td #cell class="cell" *ngFor="let place of places"
(mouseenter)="getPosition(cell)"></td>
getPosition(el:HTMLElement)
{
console.log(el.getBoundingClientRect())
}

Why does omitting a 0ms sleep break my css transition?

I was trying to implement the FLIP animation, to see if I understood it properly.
In this codepen (pardon the awful code, I was just hacking around), if I comment out the sleep, the smooth transition no longer works. The div changes position abruptly. This is strange because the sleep is for 0ms.
import React, { useRef, useState } from "https://esm.sh/react#18";
import ReactDOM from "https://esm.sh/react-dom#18";
let first = {}
let second = {}
const sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const App = () => {
const [start, setStart] = useState(true);
const boxRefCb = async el => {
if (!el) return;
el.style.transition = "";
const x = parseInt(el?.getBoundingClientRect().x, 10);
const y = parseInt(el?.getBoundingClientRect().y, 10);
first = { x: second.x, y: second.y };
second = { x, y };
const dx = first.x - second.x;
const dy = first.y - second.y;
const transStr = `translate(${dx}px, ${dy}px)`;
el.style.transform = transStr;
await sleep(0); // comment me out
el.style.transition = "transform .5s";
el.style.transform = "";
}
return (
<>
<div style={{ display: "flex", gap: "1rem", padding: "3rem"}}>
<div ref={ start ? boxRefCb : null } style={{ visibility: start ? "" : "hidden", width: 100, height: 100, border: "solid 1px grey" }}></div>
<div ref={ !start ? boxRefCb : null } style={{ visibility: !start ? "" : "hidden", width: 100, height: 100, border: "solid 1px grey" }}></div>
</div>
<button style={{ marginLeft: "3rem"}} onClick={() => setStart(start => !start)}>start | {start.toString()}</button>
</>
);
}
ReactDOM.render(<App />,
document.getElementById("root"))
I suspect this is some event loop magic that I don't understand. Could someone shed some light onto this for me please?
What happens is that the browser may have time to recalculate the CSSOM boxes (a.k.a "perform a reflow"), during that sleep. Without it, your transform rule isn't ever really applied.
Indeed, browsers will wait until it's really needed before applying the changes you made, and update the whole page box model, because doing so can be very expensive.
When you do something like
element.style.color = "red";
element.style.color = "yellow";
element.style.color = "green";
all the CSSOM will see is the latest state, "green". The other two are just discarded.
So in your code, when you don't let the event loop actually loop, the transStr value is never seen either.
However, relying on a 0ms setTimeout is a call to issues, there is nothing that does ensure that the styles will get recalculated at that time. Instead, it's better to force a recalc manually. Some DOM methods/properties will do so synchronously. But remember that a reflow can be a very expensive operation, so be sure to use it sporadically, and if you have multiple places in your code in need of this, be sure to concatenate them all so that a single reflow is performed.
const el = document.querySelector(".elem");
const move = () => {
el.style.transition = "";
const transStr = `translate(150px, 0px)`;
el.style.transform = transStr;
const forceReflow = document.querySelector("input").checked;
if (forceReflow) {
el.offsetWidth;
}
el.style.transition = "transform .5s";
el.style.transform = "";
}
document.querySelector("button").onclick = move;
.elem {
width: 100px;
height: 100px;
border: 1px solid grey;
}
.parent {
display: flex;
padding: 3rem;
}
<label><input type=checkbox checked>force reflow</label>
<button>move</button>
<div class=parent>
<div class=elem></div>
</div>
Or with OP's code.
You're approaching this problem with a vanilla JavaScript solution, but React uses a virtual DOM and expects DOM elements to be re-rendered when state has been changed. Therefore, I'd recommend leveraging React state to update the element's XY position within the virtual DOM, but while still using CSS.
Working demo here or the code can be found here:
import { useState, useRef, useLayoutEffect } from "react";
import "./styles.css";
type BoxXYPosition = { x: number; y: number };
export default function App() {
const startBox = useRef<HTMLDivElement | null>(null);
const startBoxPosition = useRef<BoxXYPosition>({ x: 0, y: 0 });
const endBox = useRef<HTMLDivElement | null>(null);
const [boxPosition, setBoxPosition] = useState<BoxXYPosition>({
x: 0,
y: 0
});
const { x, y } = boxPosition;
const hasMoved = Boolean(x || y);
const updatePosition = () => {
if (!endBox.current) return;
const { x: endX, y: endY } = endBox.current.getBoundingClientRect();
const { x: startX, y: startY } = startBoxPosition.current;
// "LAST" - calculate end position
const moveXPosition = endX - startX;
const moveYPosition = endY - startY;
// "INVERT" - recalculate position based upon current x,y coords
setBoxPosition((prevState) => ({
x: prevState.x !== moveXPosition ? moveXPosition : 0,
y: prevState.y !== moveYPosition ? moveYPosition : 0
}));
};
useLayoutEffect(() => {
// "FIRST" - save starting position
if (startBox.current) {
const { x, y } = startBox.current.getBoundingClientRect();
startBoxPosition.current = { x, y };
}
}, []);
// "PLAY" - switch between start and end animation via the x,y state and a style property
return (
<main className="app">
<h1>Transition Between Points</h1>
<div className="container">
<div
ref={startBox}
className="box start-point"
style={{
transform: hasMoved
? `translate(${x}px, ${y}px) rotateZ(360deg)`
: ""
}}
>
{hasMoved ? "End" : "Start"}
</div>
<div className="end-container">
<div ref={endBox} className="box end-point" />
</div>
</div>
<button
type="button"
onClick={updatePosition}
>
Move to {hasMoved ? "Start" : "End"}
</button>
</main>
);
}

How do I make letter size page break in html and css with contenteditable div?

I have a <div contenteditable> element that I want to have 8.5in by 11in page breaks like in Google Docs.
Here is some of my code:
<div id="txt" class="editor" contenteditable="true"></div>
It is not deployed yet, but the link is here.
Okay check the snippet
window.addEventListener('keypress', (e) => {
// check the classname first 'myElement'
if (e.target.className === 'myElement') {
// get current element where user typing
let currentElement = e.target
const allElement = document.querySelectorAll('.myElement');
let allElementArray = [];
allElement.forEach(el => allElementArray.push(el));
const nextElementIndex = allElementArray.indexOf(currentElement) + 1
const nextElement = allElementArray[nextElementIndex];
// page height
const pageHeight = 8.5 * 96; // 1 inch = 96 px
// stop typing if a new page already created and it is crosing the height
if (currentElement.offsetHeight >= pageHeight && nextElement && e.which === 13) {
// it only prevent hitting "enter" key from keyboard.
// make it more flexible. Because not if a user don't
// hit enter key from keyboad, but keep writing, it will cross the line
// and it will increate the page or div height.
e.preventDefault()
}
if (currentElement.offsetHeight >= pageHeight && !nextElement) {
// add new page
currentElement.insertAdjacentHTML('afterend',
`<div class="myElement" contenteditable></div>`)
// focus on new page
const newElement = document.querySelectorAll('.myElement')[nextElementIndex];
newElement.focus()
}
}
})
.myElement {
background: tomato;
color: white;
min-height: 30px;
width: 500px;
margin: 0 auto 20px auto
}
.myElement:focus {
outline: 3px solid dodgerblue
}
<div class="myElement" contenteditable>Start typing here</div>

How to put several elements on one line using only HTML?

I am trying to put three elements that contain text on one line using only HTML tags and the style property. One of the elements is a counter that is counting up. Unfortunately the elements are too far from each other in my solution as I am trying to get them to stick together seamlessly. Cold you please help me out?
FYI: I have read several posts here on SO before posting and tried my best to make a solution below.
<span style="display: flex; justify-content: space-between;">
<span> There is </span>
<strong><span class="counter" style="font-family:Courier New; style:bold" data-target="100">0</span></strong>
<span>kg spam.</span>
</span>
<script>
const counters = document.querySelectorAll('.counter');
for(let n of counters) {
const updateCount = () => {
const target = + n.getAttribute('data-target');
const count = + n.innerText;
const speed = 5000; // change animation speed here
const inc = target / speed;
if(count < target) {
n.innerText = Math.ceil(count + inc);
setTimeout(updateCount, 1);
} else {
n.innerText = target;
}
}
updateCount();
}
</script>
Removing justify-content: space-between; and adding padding to the left and right of the counter should work:
<span style="display: flex;">
<span>There is </span>
<strong><span class="counter" style="font-family: Courier New; style: bold; padding: 0 0.2em" data-target="100">0</span></strong>
<span> kg spam.</span>
</span>
<script>
const counters = document.querySelectorAll('.counter');
for(let n of counters) {
const updateCount = () => {
const target = + n.getAttribute('data-target');
const count = + n.innerText;
const speed = 5000; // change animation speed here
const inc = target / speed;
if(count < target) {
n.innerText = Math.ceil(count + inc);
setTimeout(updateCount, 1);
} else {
n.innerText = target;
}
}
updateCount();
}
</script>
You might need to tweak the margin size, but this is the best idea I came up with.
If your goal is to form a sentence then you don't need these many spans they are all inline elements. I think one span is enough:
<style>
.counter {
style="font-family:Courier New;"
}
</style>
<span>
There is <em class="counter" data-target="100">0</em>kg spam.
And <em class="counter" data-target="200">0</em> buns.
</span>
<script>
const counters = document.querySelectorAll('.counter');
for (let n of counters) {
const updateCount = () => {
const target = +n.getAttribute('data-target');
const count = +n.innerText;
const speed = 5000; // change animation speed here
const inc = target / speed;
if (count < target) {
n.innerText = Math.ceil(count + inc);
setTimeout(updateCount, 15);
} else {
n.innerText = target;
}
}
updateCount();
}
</script>
If screen width reduces the line will wrap automatically. Same text with 100px wide box wraps perfectly. You don't have to do anything:
<style>
.counter {
style="font-family:Courier New;"
}
div{
width: 100px;
border: 1px solid gray;
}
</style>
<div><span>
There is <em class="counter" data-target="100">0</em>kg spam.
And <em class="counter" data-target="200">0</em> buns.
</span></div>
<script>
const counters = document.querySelectorAll('.counter');
for (let n of counters) {
const updateCount = () => {
const target = +n.getAttribute('data-target');
const count = +n.innerText;
const speed = 5000; // change animation speed here
const inc = target / speed;
if (count < target) {
n.innerText = Math.ceil(count + inc);
setTimeout(updateCount, 15);
} else {
n.innerText = target;
}
}
updateCount();
}
</script>

Static/sticky Header Using Dynamic Table

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.
Avoid 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.
Use CSS classes via classList instead of setting individual style properties using .style.
The current JavaScript ecosystem also generally uses 1TBS instead of the Allman style.
Prefer === (exactly-equals) instead of == (equals) because JavaScript's type coercion can be surprising).
Avoid using innerHTML wherever possible. Use .textContent for setting normal text content (and avoid using .innerText too). Misuse of innerHTML leads to XSS vulnerabilities.
It's 2020. STOP USING JQUERY!!!!!!!!!!
Cite
Cite
Cite
Cite
DONT USE ALL-CAPS IN YOUR JAVASCRIPT COMMENTS BECAUSE IT LOOKS LIKE THE AUTHOR IS SHOUTING AT YOU NEEDLESSLY AND IT GETS QUITE ANNOYING FOR OTHER READERS ARRRRGGGHHHHH
You need to handle HTTP request responses correctly (e.g. to check for succesful responses with the correct Content-Type).
Avoid using 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>