Solving Progress Indication Issues in a Legacy Web Application with JavaScript
by nikgrozev
HTML
<html>
<body>
<p>Clicking the buttons will change their style and open a "spinner"</p>
<div>
<button onclick="samleXMLHttpRequest()">
XMLHttpRequest button
</button>
</div>
<div>
<button onclick="fetch('https://jsonplaceholder.typicode.com/posts')">
Fetch button
</button>
</div>
<div>
<button onclick="console.log('I was clicked')">
Console Log button
</button>
</div>
<div>
<a href="https://example.com">Navigate away (shows "spinner")</a>
</div>
<p>Dynamic buttons will be added below with the same behaviour:</p>
<div id="spinner-id" class="hidden">
Loading...
</div>
</body>
</html>
CSS
.button-clicked {
opacity: 0.6;
background: black;
color: white;
}
.hidden {
display: none;
}
.running-spinner {
position: fixed;
width: 100%;
height: 100vh;
background: gray;
top: 0;
left: 0;
opacity: 0.7;
color: black;
text-align: center;
padding-top: 50vh;
z-index: 100;
}
JavaScript 1.7
/* ======================================================================================= */
/* ===================== Test Code - adds a button every N seconds ======================= */
function samleXMLHttpRequest(){
const req = new XMLHttpRequest();
req.open("GET", "https://jsonplaceholder.typicode.com/posts");
req.send();
}
let dynamicButtonCount = 1;
setInterval(() => {
var button = document.createElement("button");
button.innerHTML = "Dynamic button " + dynamicButtonCount++;
button.onclick=samleXMLHttpRequest;
var div = document.createElement("div");
div.appendChild(button);
document.getElementsByTagName("body")[0].appendChild(div)
}, 10 * 1000)
/* ========================================================================================= */
/* ========================================================================================= */
/* ============ Change the style of every button (dynamic or static) on click ============== */
const timeout = 500;
const processButton = (btn) => {
// If we processed it - skip
if (!btn || btn.getAttribute("data-single-click-listener-added" === "true")) {
return
}
// Callback to enable, disable after timeout
const clickCallback = () => {
btn.classList.add("button-clicked");
setTimeout(() => {
btn.classList.remove("button-clicked");
}, timeout);
}
// Add the listener
btn.addEventListener("click", () => setTimeout(clickCallback, 0), {capture: true}) ;
// mark it as processed
btn.setAttribute("data-single-click-listener-added", "true");
}
// Get all buttons on the page after load:
window.addEventListener("DOMContentLoaded", () => {
const buttons = document.getElementsByTagName('button');
for (let i = 0; i < buttons.length; i++) {
processButton(buttons[i])
}
})
// Traverse a node recursively to get all the buttons
const getButtonChildren = (node) => {
if (!node || !node.tagName) {
return [];
}
if (node.tagName.toLowerCase() === "button") {
...