JavaScript page update while a script is running - javascript

I have a web page that does a lot of calculations as part of it's loading.
I want to display a spinner bar while the page is "thinking" so that the end user knows the delay is to be expected.
The way I was going to do this is to display a GIF file that would be a spinning wheel, and hide the table that would be my output.
The problem is, once the function starts, updates to the page seem to freeze until the function is done. Therefore, the end user never sees the "In Process" section.
The sample code I put together to demonstrate my problem is:
<!DOCTYPE html>
<html>
<body>
<script>
function show(id, value) {
document.getElementById(id).style.display = value ? 'block' : 'none';
}
function Processing(x)
{
if (x === true)
{
show('page', false);
show('loading', true);
}
else
{
show('page', true);
show('loading', false);
}
}
function MainProcess()
{
Processing(true) // Set to "Show processing..."
var start = new Date().getTime(); // Sleep a few seconds
for (var i = 0; i < 5; i++) {
if ((new Date().getTime() - start) < 3000) { i = 1 }
}
Processing(false) // Set to "Show Completed processing..."
}
window.onload = function() {
show('page', false);
show('loading', false);
};
</script>
<div id="loading">
<h1>Processing your request.</h1>
</div>
<div id="page">
<DIV class="body">
<h3>Done Processing your request.</h3>
</DIV>
</div>
<div id="buttons">
<button onclick="MainProcess();">Start Timer</button>
<button onclick="Processing(false);">Set to Completed</button>
<button onclick="Processing(true);">Set to Processing</button>
</body>
</html>
When I run this, it presents three buttons.
When you hit "Start Timer", it should show processing for a few seconds, then display done. Instead, the button changes colors and otherwise appears to do nothing till the timer is done, and it displays complete.
(I am new to JavaScript but a seasoned pro in several other languages. Is there a "DoEvents" type of call I can make like in Visual Basic or Powershell?)

The problem seems to be that the DOM update (i.e. changing the display properties on your DIVs) doesn't complete before your processing function starts. One option would be to use window.setTimeout to delay the start of your processing function to allow the DOM update to complete:
function MainProcess()
{
Processing(true); // Set to "Show processing..."
// Pause for 100 ms before starting time-consuming code to allow dom update to c
var domUpdateDelay = 100;
window.setTimeout(function() {
var start = new Date().getTime(); // Sleep a few seconds
for (var i = 0; i < 5; i++) {
if ((new Date().getTime() - start) < 3000) { i = 1 }
}
Processing(false) // Set to "Show Completed processing..."
}, 100);
}
window.setTimeout takes two arguments: the first argument is a function that runs after x milliseconds, where x is the second argument.
The problem with this approach is that an appropriate delay for setTimeout will vary across machines/users/browsers/runs. However, my guess is that this will probably not be an issue in 99% of cases.

I think you can take advantage of a function like setTimeout (https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout), say:
setTimeout(function() {
Processing(false);
}, 5000);
I think what's happening is you're nearly crashing the browser with that loop. I may have misunderstood what parts of your question were just there for demonstration purposes.

Related

Button won't respond when execute the setInterval function

I would like to have the second button ( "free five") to keep checking the condition that it would reappear when the number reached 5 again after I clicked it. However, it never showed up again after I pressed it, unless I press the first button.
Game instruction is in the p section.
let birdNum = document.getElementById("birdNum")
let catchBtn = document.getElementById("catch")
let freeBtn = document.getElementById("free")
function catchBird() {
birdNum.innerHTML++;
if (birdNum.innerHTML > 4) {
freeBtn.classList.add("fadeIn");
freeBtn.disabled = false;
}
}
document.getElementById("catch").addEventListener("click", catchBird);
function freeBird() {
birdNum.innerHTML -= 5;
setInterval(() => {
birdNum.innerHTML++;
}, 1000);
if (birdNum.innerHTML <= 4) {
freeBtn.classList.remove("fadeIn");
freeBtn.disabled = true;
}
}
document.getElementById("free").addEventListener("click", freeBird);
#free {
opacity: 0;
}
#free.fadeIn {
opacity: 1;
}
<P>
press "catch one" to catch one bird. <br> press "free five" to free five bird. <br>
<br> Once you free five birds, they will come back with their kids, one bird per second.
</P>
<span id="birdNum">0</span>
<button id="catch">
catch one
</button>
<button id="free" disabled=t rue>
free five
</button>
Try the following code, see if it works for you:
Edit: I realised your issue, I edited to work now.
So in your code, you were only checking whether or not to make the 'free' button visible once. And you only checked when it was pressed. For example, we are playing your game and get to 6. We press the 'free birds' button and we go down to one and the 'free birds' button disappears. Now the setInterval keeps making the number of birds go up. We get to 5 and the button does not reappear because we are not running any code to make it reappear. In the setInterval, as well as incrementing the counter, we need to check whether or not we can make the 'free birds' button visible again. See the resetInterval() function. In the setInterval's callback, we are now checking whether of not the birds counter is above 5. If the condition returns true, we will show the 'free birds' button.
With the below JS, it should work nicely.
const birdNum = document.getElementById("birdNum");
const catchBtn = document.getElementById("catch");
const freeBtn = document.getElementById("free");
const initialTimeout = 1000;
let timesBirdsFreed = 0;
let intervalId;
function resetInterval(delay) {
if (delay < 10) delay = 10;
if (intervalId) clearInterval(intervalId);
intervalId = setInterval(() => {
birdNum.innerHTML++;
// if statement from catch bird function is needed here too
if (birdNum.innerHTML >= 5) {
freeBtn.classList.add("fadeIn");
freeBtn.disabled = false;
}
}, delay);
}
function catchBird() {
birdNum.innerHTML++;
if (birdNum.innerHTML >= 5) {
freeBtn.classList.add("fadeIn");
freeBtn.disabled = false;
}
}
catchBtn.addEventListener("click", catchBird);
function freeBird() {
timesBirdsFreed++;
birdNum.innerHTML -= 5;
resetInterval(initialTimeout/timesBirdsFreed);
if (birdNum.innerHTML <= 4) {
freeBtn.classList.remove("fadeIn");
freeBtn.disabled = true;
}
}
freeBtn.addEventListener("click", freeBird);
#free {
opacity: 0;
}
#free.fadeIn {
opacity: 1;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Bird Game</title>
<link href="style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<p>press "catch one" to catch one bird. <br> press "free five" to free five bird. <br><br> Once you free five birds, they will come back with their kids, one bird per second.</p>
<span id="birdNum">0</span>
<button id="catch">catch one</button>
<button id="free" disabled=true>free five</button>
<script src="script.js"></script>
</body>
</html>
Edit 2: Further explanation and added links to setInterval() method and clearInterval() method.
function resetInterval(delay) {
// if delay is too small (less than 10 ms) make delay 10 ms
if (delay < 10) delay = 10;
// in beginning of the program, intervalId is undefined
// so we will only use clearInterval if intervalId is not undefined (if we've used setInterval already)
if (intervalId) clearInterval(intervalId);
// use setInterval method with a delay which was passed to the function as an argument
intervalId = setInterval(() => {
// make birds go up by one
birdNum.innerHTML++;
// if statement from catch bird function is needed here too
// (if birds counter is 5 or more, show 'free birds' button)
if (birdNum.innerHTML >= 5) {
freeBtn.classList.add("fadeIn");
freeBtn.disabled = false;
}
}, delay);
// all of this resets the interval but at a smaller delay so birds go up faster each time
}
The resetInterval() function just stops an existing setInterval() from executing code by using clearInterval(intervalId) which uses the ID returned when setInterval() when it was called. An ID can be, for example, 239 or maybe 146. After the existing setInterval() was cleared, another setInterval() is called and its ID is stored in the variable (intervalId) to be used to clear the new setInterval() the next time resetInterval() is called (the next time you free five birds). The difference though is with each call of the resetInterval() function, the setInterval's delay which was passed into the function is smaller, making the number of birds go up faster.
The way this is done is when the resetInterval function is called, a delay is passed into the function as a parameter like so resetInterval(initialTimeout/timesBirdsFreed). initialTimeout is set to always be 1000 (milliseconds). timesBirdsFreed is a number which tells you how many times you clicked the 'free birds' button. If you clicked 'free birds' once, initialTimeout/timesBirdsFreed will be 1000/1 which is 1000 (ms). So the first time you clicked 'free birds', the number of birds will go up by one bird every second. The second time you click 'free birds', initialTimeout/timesBirdsFreed will be 1000/2 which is 500 (ms), meaning now birds will go up by one every half a second.
Because of this, the more you click 'free birds', the faster they will go up (up to a limit - smallest delay possible is 10ms, so it won't go any faster than that).
References
MDN Web Docs
setInterval() method
W3Schools
setInterval() method
clearInterval() method
If you need to stop the setInterval you should keep its instance and then call clearInterval(instance)
I'm not sure what you want to achieve but you may get the idea from the this code:
let intervalInstance;
function freeBird() {
birdNum.innerHTML -= 5;
if (intervalInstance) {
clearInterval(intervalInstance);
}
intervalInstance = setInterval(() => {
birdNum.innerHTML++;
}, 1000);
if (birdNum.innerHTML <= 4) {
freeBtn.classList.remove("fadeIn");
freeBtn.disabled = true;
}
}

HTML page doesn't update while a javascript function running

Chrome, FF. Opera and probably others browser show only the 100000 number at end of process, but i want see displayed in sequence 1..2..3..4...100000.
This code doesn't work well:
<html>
<head>
</head>
<body>
<button type="button" onclick="showSequence();">Show the numbers one at a time!</button>
<p id="herethenumbers">00</p>
<script>
function showSequence() {
el = document.getElementById('herethenumbers');
for(var nn=0;nn<=100000;nn++){
el.innerHTML = nn;
}
}
</script>
</body>
</html>
window.setTimeout isn't possible using when you don't know the execution time of a given process and even changing the attributes of the main div object (visibility e.g.) does not work for me.
Thanks at all.
UPDATE
Here a partial solution.
Allows you to view the status of a long process, for now, unfortunately only the beginning and the end of the (single) process.
<html>
<head>
</head>
<body>
<button type="button" onclick="executeMultiLongProcess();">Launch and monitoring long processes!</button>
<p id="statusOfProcess"></p>
<script>
var el = document.getElementById('statusOfProcess');
function executeMultiLongProcess() {
processPart1();
}
function processPart1() {
el.innerHTML = "Start Part 1";
setTimeout(function(){
for(var nn=0;nn<=100000000;nn++){ //..
}
el.innerHTML = "End Part 1";
window.setTimeout(processPart2, 0);
},10);
}
function processPart2() {
el.innerHTML = "Start Part 2";
setTimeout(function(){
for(var nn=0;nn<=100000000;nn++){ //..
}
el.innerHTML = "End Part 2";
window.setTimeout(processPartN, 0);
},10);
}
function processPartN() {
el.innerHTML = "Start Part N";
setTimeout(function(){
for(var nn=0;nn<=100000000;nn++){ //..
}
el.innerHTML = "End Part N";
},10);
}
</script>
</body>
</html>
I want to suggest using window.requestAnimationFrame rather than setTimeout or setInterval as it allows you to wait for the browser to render changes and right after that, execute some code. So basically you can do:
window.requestAnimationFrame( () => {
el.innerHTML = nn;
} );
I changed your function to be recursive. This way I can call the function to render the next number inside the window.requestAnimationFrame callback. This is necessary, as we ought to wait for the browser to render the current number and just after that, instruct the browser to render the next one. Using window.requestAnimationFrame inside the for loop would not work.
el = document.getElementById( 'herethenumbers' );
function showSequence( nn=0 ) {
if( nn <= 100000 ) {
window.requestAnimationFrame( () => {
el.innerHTML = nn;
showSequence( nn + 1 );
} );
}
}
<button type="button" onclick="showSequence();">
Show the numbers one at a time!
</button>
<p id="herethenumbers">00</p>
Use window.setInterval:
function showSequence() {
var el = document.getElementById('herethenumbers');
var nn = 0;
var timerId = setInterval(countTo100, 80);
function countTo100(){
el.innerHTML = nn;
nn++;
if (nn>100) clearTimeout(timerId);
}
}
<button type="button" onclick="showSequence();">
Show the numbers one at a time!
</button>
<p id="herethenumbers">00</p>
Update
the scenario is a bit different. You have a javascriot process that starts and ends without interruptions, it can work several minutes in the meantime, inside it, must show on screen the status of its progress.
JavaScript is single-threaded in all modern browser implementations1. Virtually all existing (at least all non-trivial) javascript code would break if a browser's javascript engine were to run it asynchronously.
Consider using Web Workers, an explicit, standardized API for multi-threading javascript code.
Web Workers is a simple means for web content to run scripts in background threads. The worker thread can perform tasks without interfering with the user interface. In addition, they can perform I/O using XMLHttpRequest (although the responseXML and channel attributes are always null). Once created, a worker can send messages to the JavaScript code that created it by posting messages to an event handler specified by that code (and vice versa).
For more information, see MDN Web API Reference - Web Workers.
Sample code below using setTimeout, only counts up to 100 for the sample.
Might want to check out Difference between setTimeout with and without quotes and parentheses
And then there is also: 'setInterval' vs 'setTimeout'
var delayId = null, frameTime = 25, countTo = 100, el, nn = 0;
function increment(e) {
if (delayId) {
window.clearTimeout(delayId);
}
el.textContent = nn++;
if (nn <= countTo) {
delayId = window.setTimeout(increment,frameTime);
}
}
window.onload = function() {
el = document.getElementById('herethenumbers');
var b = document.getElementById('start');
b.addEventListener("click",increment,false);
}
<button type="button" id="start">Show the numbers one at a time!</button>
<p id="herethenumbers">00</p>
Using requestAnimationFrame as suggested by Jan-Luca Klees is the solution to my problem, here a simple example of use of requestAnimationFrame. Allows you to run one or more long-duration processes with the interaction with objects on screen (a popup for example or others), requestAnimationFrame tells the browser you want to run an animation and you want the browser to call a specific function to update a animation.
<html>
<head>
</head>
<body>
<button type="button" onclick="startProcesses();">Start long duration process!</button>
<p id="status">Waiting to start processes</p>
<script>
el = document.getElementById('status');
var current_process = 1;
var total_process = 2;
var startEnd = 'S';
function startProcesses() {
function step(timestamp) {
if(current_process==1 && startEnd=='E') res = process1();
if(current_process==2 && startEnd=='E') res = process2();
//..n processes
if(startEnd=='S') el.innerHTML = "Process #"+current_process+" started..";
if(startEnd=='E') el.innerHTML = "Process #"+current_process+" "+res;
if(startEnd=='S' || current_process<total_process) {
if(startEnd=='E') current_process++;
startEnd = (startEnd=='S'?'E':'S');
window.requestAnimationFrame(step);
}else{
el.innerHTML = "Process #"+current_process+" "+res;
}
}
window.requestAnimationFrame(step);
}
function process1() {
for(var nn=0;nn<=10000;nn++){
console.log(nn);
}
return "Success!"; //or Fail! if something went wrong
}
function process2() {
for(var nn=0;nn<=10000;nn++){
console.log(nn);
}
return "Success!"; //or Fail! if something went wrong
}
</script>
</body>
</html>

setTimeout executes itself right away/on clear

I'm making a webpage where user events are logged in.
To test the feature I made a small, independant webpage with a teaxtarea and a text input. The events logged are those performed on the input element.
I want to prevent the same event text to be shown multiple times in a row, but I can't seem to prevent them from showing up!
I also want to add a line to separate event groups 0.5 seconds after no other event happened, but the line seems to appear on every event trigger, evenif I use clearTimeout with the timeout ID.
Basically: I don't want any line to be repeated. If the last line is a separator line, then it must not add another one. Yet it doesn't see to work.
JSFiddle Demo
Here is my code:
JavaScript
var timerID = 0;
function addSeparateLine()
{
document.getElementById('listeEvenements').value += "--------------------\n";
}
function show(newEventText)
{
var eventListField = document.getElementById('listeEvenements');
var eventList = [];
if (eventListField.value.length > 0)
{
eventList = eventListField.value.split("\n");
}
var eventCounter = eventList.length;
if (eventList[eventCounter - 2] == newEventText)
{
clearTimeout(timerID);
newEventText = "";
}
timerID = setTimeout(addSeparateLine, 500);
if (newEventText !== "")
{
eventListField.value += newEventText + "\n";
}
return true;
}
HTML
<fieldset id="conteneurLogEvenements">
<legend>Events called from HTML attribute</legend>
<textarea id="listeEvenements" rows="25"></textarea>
<input id="controleEcoute" type="text" onBlur="show('Blur');" onchange="show('Change');" onclick="show('Click');" onfocus="show('Focus');" onMousedown="show('MouseDown');" onMousemove="show('MouseMove');" onMouseover="show('MouseOver');" onkeydown="show('KeyDown');"
onkeypress="show('KeyPress');" onkeyup="show('KeyUp');" />
</fieldset>
http://jsfiddle.net/z6kb4/2/
It sounds like what you want is a line that prints after 500 milliseconds of inactivity, but what your code currently says to do is "print a line 500 milliseconds after any action, unless it gets canceled". You can get better results by structuring the code more closely to your intended goal.
Specifically, instead of scheduling a new timeout every time an event occurs, simply start a loop when the first event occurs that checks the time that has elapsed since the most recent event received and then prints a line when the elapsed time exceeds the desired threshold (500 milliseconds). Something like:
function addSeparateLine() {
var elapsed = new Date().getTime() - lastEventTime;
if (elapsed >= 500) {
document.getElementById('listeEvenements').value += "--------------------\n";
clearInterval(timerID);
timerID = -1;
}
}
...and then you schedule it like:
if(newEventText !== "") {
lastEventTime = new Date().getTime();
eventListField.value += newEventText+"\n";
if (timerID == -1) {
timerID = setInterval(addSeparateLine,100);
}
}
Working example here: http://jsfiddle.net/z6kb4/4/
Because you are not actually stopping the show function in any way. The clearTimeout only applies to the separator add. I have updated your fiddle. You need to wrap your function with
if (+new Date() - lastfire < 500) return;
and
lastfire = +new Date();
(before the last return--see the updated fiddle). Also, make sure to stick the global definition var lastfire = -1; somewhere up top.

Count the number of mouse clicks in one second

Hi I'm working on an application that I want to improve the performance.(I know the question is kinda lengthy one- I apologize.)
I will explain in detail its a bidding application that uses only qtscript/qscript(kinda javascript) and no html.
When a user click on a Button, I want to point to a text field(For a normal user its okay like -1 or 2 clicks per second). But the user crazily click on button(5 -10 clicks per second - yeah some people click like that), it decreases the performance like the amount take delay to display because every click it points to focus on text field.
I'm thinking of some work around like if the user clicks more than 3 times in 1 second we call the focus function only after the last click- I don't know this is a right solution if you guys know anything better please suggest. Another problem is I can't use setInterval() and clearInterval().
Any help would be greatly appreciated.
I would take a look at Underscore.js's _.throttle function.
_.throttle = function(func, wait, options) {
var context, args, result;
var timeout = null;
var previous = 0;
options || (options = {});
var later = function() {
previous = options.leading === false ? 0 : new Date;
timeout = null;
result = func.apply(context, args);
};
return function() {
var now = new Date;
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0) {
clearTimeout(timeout);
timeout = null;
previous = now;
result = func.apply(context, args);
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
};
It looks really complex, but a basic example would be:
var func = function(){alert("Only do this once every second");},
throttled = _.throttle(func, 1000);
// Call func() three times in under a second, and
// you get 3 message boxes
func(); // alerts
func(); // alerts
func(); // alerts
// Call throttled() three times in under a second, and
// you only get a message box the first time
func(); // alerts
throttled(); // does nothing
throttled(); // does nothing
// ... wait >1 second ...
func(); // alerts
throttled(); // does nothing
throttled(); // does nothing
example.xhtml - No frameworks, no script elements in the body and counts both left and right clicks.
Additionally you can add e.preventDefault(); at the end of the anonymous onclick event function. Keep in mind that if you're trying to protect content you will ultimately fail against anyone smart enough to realize that if it's already on their computer (memory, cache, etc) that they already have it in their possession. If you're trying to protect images you must use watermarks.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>Click Counter</title>
<script type="application/javascript">
//<![CDATA[
var click_left = 0;
var click_right = 0;
window.onclick = function(e)
{
if (e.which==1) {click_left++;}
else if (e.which==3) {click_right++;}
alert('Left clicks: '+click_left+'\n\nRight Clicks: '+click_right);
}
//]]>
</script>
</head>
<body>
<div><p>Left or right click</p></div>
</body>
</html>
First of all you should add check that the text edit you want to select after user clicked certain button already has focus. That will dramatically reduce loading on event queue.
Second you can implement your own button (by subclassing) and play around with options, like for example just ignore clicks which came within certain (small) interval. In case user starts spawning clicks very fast, you can also 'visualise' it on a button in certain way, showing to a user that your application had limit reactions on user input, switching it off after specified timeout.

Issue with setTimeOut and non-response script in IE8

I am getting an issue with a large amount of processing causing the non-responsive script error in IE8 (and no, I cannot make the users use a better browser).
I then read that it should be possible to split up the tasks and cede control back to the browser in between different parts of the validation. So I decided to make a simple example based on some code I found to figure out where the breaking points are. The real code is doing lots of jquery validationengine processing.
I tried to use jsFiddle but I can't get jsFiddle to run in IE8. Bummer. So, I'll have to share inline here.
When I first load it, it seems to work just fine. I push the button and both functions finish without a problem. However, subsequent pushes causes an unresponsive script error. I've played around with the number of loops in my simulated work function. Much more than 1.25 million loops and it dies with unresponsive script.
Shouldn't separate calls to the onClick start the non-responsive counter anew? What am I missing here?
<html>
<head>
<script>
var progress = null;
var goButton = null;
window.onload = function() {
progress = document.getElementById("progress");
goButton = document.getElementById("goButton");
}
function runLongScript(){
// clear status
progress.value = "";
goButton.disabled=true;
var tasks = [function1, function2];
multistep(tasks,null,function() {goButton.disabled=false;});
}
function function1() {
var result = 0;
var i = 1250000;
for (;i>0; i--) {
result = result + 1;
}
progress.value = progress.value + "f1 end ";
}
function function2() {
var result = 0;
var i = 1250000;
for (;i>0; i--) {
result = result + 1;
}
progress.value = progress.value + "f2 end";
}
function multistep(tasks, args, callback){
var tasksClone = tasks.slice(0); //clone the array
setTimeout(function(){
//execute the next task
var task = tasksClone.shift();
task.apply(null, args || []);
//determine if there's more
if (tasksClone.length > 0){
setTimeout(function () {
multistep(tasksClone, args, callback);
}, 100);
} else {
callback();
}
}, 100);
}
</script>
</head>
<body>
<p><input type="button" id="goButton" onClick="runLongScript();" value="Run Long Script" /></p>
<input type="text" id="progress" />
</body>
</html>
You're never calling clearTimeout() to remove the one currently running when the button has been pressed already. Add an if statement before you start another setTimeout and check to see if one is already running, clear it if it is, and then continue. Here's a link that should help you if you have any questions: https://developer.mozilla.org/en-US/docs/Web/API/window.clearTimeout

Categories