Show javascript execution progress - javascript

I have some javascript functions that take about 1 to 3 seconds. (some loops or mooML templating code.)
During this time, the browser is just frozen. I tried showing a "loading" animation (gif image) before starting the operation and hiding it afterwords. but it just doesnt work. The browser freezes before it could render the image and hides it immediately when the function ends.
Is there anything I can do to tell the browser to update the screen before going into javascript execution., Something like Application.DoEvents or background worker threads.
So any comments/suggestions about how to show javascript execution progress. My primary target browser is IE6, but should also work on all latest browsers

This is due to everything in IE6 being executed in the same thread - even animating the gif.
The only way to ensure that the gif is displayed prior to starting is by detaching the execution.
function longRunningProcess(){
....
hideGif();
}
displayGif();
window.setTimeout(longRunningProcess, 0);
But this will still render the browser frozen while longRunningProcess executes.
In order to allow interaction you will have to break your code in to smaller fragments, perhaps like this
var process = {
steps: [
function(){
// step 1
// display gif
},
function(){
// step 2
},
function(){
// step 3
},
function(){
// step 4
// hide gif
}
],
index: 0,
nextStep: function(){
this.steps[this.index++]();
if (this.index != this.steps.length) {
var me = this;
window.setTimeout(function(){
me.nextStep();
}, 0);
}
}
};
process.nextStep();

Perhaps you can put in a delay between showing the animated gif and running the heavy code.
Show the gif, and call:
window.setTimeout(myFunction, 100)
Do the heavy stuff in "myFunction".

You have to use a little more sophisticated technique to show the progress of the long running function.
Let's say you have a function like this that runs long enough:
function longLoop() {
for (var i = 0; i < 100; i++) {
// Here the actual "long" code
}
}
To keep the interface responsive and to show progress (also to avoid "script is taking too long..." messages in some browsers) you have to split the execution into the several parts.
function longLoop() {
// We get the loopStart variable from the _function_ instance.
// arguments.callee - a reference to function longLoop in this scope
var loopStart = arguments.callee.start || 0;
// Then we're not doing the whole loop, but only 10% of it
// note that we're not starting from 0, but from the point where we finished last
for (var i = loopStart; i < loopStart + 10; i++) {
// Here the actual "long" code
}
// Next time we'll start from the next index
var next = arguments.callee.start = loopStart + 10;
if (next < 100) {
updateProgress(next); // Draw progress bar, whatever.
setTimeout(arguments.callee, 10);
}
}
I haven't tested this actual code, but I have used this technique before.

Try setting a wait cursor before you run the function, and removing it afterwards. In jQuery you can do it this way:
var body = $('body');
body.css("cursor", "wait");
lengthyProcess();
body.css("cursor", "");

Related

How to change instanly a DOM element?

I know that this might be a stupid question but it drives me crazy. I'm trying to change the innerHTML of a DOM element but it doesn't change until the end of the function's execution. For example:
function test(){
let testEl = document.getElementById('testEl')
for (let i = 0; i < 5; i++)
{
testEl.innerHTML = 'Count: ' + i;
alert(i);
}
}
Even if I have put an alert in the loop, the text of the element will not change until the end of the function's execution. How can the change be applied instantly (for example I mean during the loop)?
You can update the number every period of time using setInterval:
function test(){
let testEl = document.getElementById('testEl');
let i = 0;
const interval = setInterval(function(){
testEl.innerHTML = `Count: ${i++}`;
if(i === 5)
clearInterval(interval);
}, 1000);
}
test();
<p id="testEl"></p>
JavaScript runs in a single-threaded environment. This means that only one execution context can ever be running at any single point in time. Asynchronous code executes outside of the JavaScript runtime environment (in this case by the browser's native processing) and only when the JavaScript thread is idle can the results of an asynchronous request be executed (i.e. callbacks).
Below is an example that updates a DOM element approximately every second, creating a clock. However, if you click the button, it will ask the browser to render an alert, which is handled outside of the JavaScript runtime and is a blocking UI element, so the clock will stop. Once you clear the alert, you will see the time jump to be roughly current.
As you'll see, the asynchronous API call to window.setInterval() allows for the function to run repeatedly, every so often, and therefore not continuously. This replaces the need for a loop that runs in its entirety every time its accessed. Because of this, you can see updates to the webpage instead of the last value of your loop.
See the comments for more details:
const clock = document.querySelector("span");
// setInterval is not JavaScript. It's a call to a browser
// API asking the JS runtime to run the supplied function every
// 900 milliseconds, but that's just a request. After 900
// milliseconds, the browser will place the function on the
// JavaScript event queue and only when the JavaScript thread
// is idle will anything on the queue be executed. This is why
// the 900 milliseconds is not a guarantee - - it's just the
// minimum amount of time you'll have to wait for the function
// to run, but it could be longer if what's already running
// on the JavaScript thread takes longer than 900 milliseconds
// to complete.
window.setInterval(function(){
// Update the DOM
clock.textContent = new Date().toLocaleTimeString();
}, 900);
document.querySelector("button").addEventListener("click", function(){
// An alert is also not JavaScript, but another browser API that is executed
// by the browser, not JavaScript. However, it is a blocking (modal) UI element.
// The rest of the browser interface (including the web page) cannot update
// while the alert is present. As soon as the alert is cleared, the UI will update.
window.alert("I'm a UI blocking construct rendered by the browser, not JavaScript");
});
<div>Current time is: <span></span></div>
<button>Click for alert</button>
Another way to achieve it is by using async - Promise like this
async function test() {
let testEl = document.getElementById('testEl')
for (let i = 0; i < 5; ++i) {
testEl.innerHTML = 'Count: ' + i;
await new Promise((resolve, _) => {
setTimeout(function() {
resolve("");
}, 1000 /* your preferred delay here */);
});
}
}

Automating scrolling at randomly timed intervals in JS based on events

I'm writing a Chrome extension that scrolls & listens for newly added child nodes to a parent node.
It then waits a random amount of time, then scrolls down again if more children are added, and stops if in the next 5 seconds nothing appears via ajax (when the list of results has been exhausted, for example).
My question is how I should handle waiting variable amounts of time between each event scroll reaction.
I'd like for it to work politely (yet not fail to scroll down if all 50 elements are loaded at once and the scrolls generated aren't quite enough to get to the next ajax load point).
Any ideas or ways I should think about this? (This is for a totally benign use case btw)
A good solution is a little tricky, but totally works:
var canPoll = true;
var timeout = ... whatever ...; // if we want an absolute timeout
var startTime = (new Date()).getTime();
function randomWait(t) {
// ... waits a random period of time
}
function isDone() {
// ... check if done. returns boolean
}
function complete() {
// ... run when everything is done
}
(function recursive() {
// check for our absolute walltime timeout
canPoll = ((new Date).getTime() - startTime) <= timeout;
// check other conditions too
if (!fn() && canPoll) {
// repeat!
setTimeout(recursive, randomWait(interval));
} else {
// we're done
complete();
}
})();
Adapted from this wonderful answer.

Display a series of colors sequencially with JavaScript

I need to display a series of RGB colors inside my ASP.NET web page. I was hoping to accomplish this using an ASP Update Panel but the update has to take place in sequence without the user intervention, every 4 or 5 seconds.
My initial JavaScript code looks like this :
document.getElementById('div').style.backgroundColor = "rgb(0,0,255)";
Problem is getting the next RGB color combination to display.
I experimented with a wait 'loop' found here in another post :
wait(7000); //7 seconds in milliseconds
function wait(ms) {
var start = new Date().getTime();
var end = start;
while (end < start + ms) {
end = new Date().getTime();
}
}
Problem is that, it does not work. I tried with :
document.getElementById('fondRGB').style.backgroundColor = "rgb(0,0,255)";
wait(7000);
document.getElementById('fondRGB').style.backgroundColor = "rgb(128,128,128)";
wait(7000);
document.getElementById('fondRGB').style.backgroundColor = "rgb(0,0,0)";
but only the very first rgb color is ever honored?
running a blocking while loop in javascript is a really bad idea. There is only one thread so if you do that the user will not be able to interact with your application until your while loop is complete.
A better solution is to use setTimeout to run the timer asynchronously and call the callback when the timer is complete.
var el = document.getElementById('fondRGB')
function wait(ms, callback) {
setTimeout(callback, ms)
}
wait(7000, function(){
el.style.backgroundColor = "rgb(0,0,255)"
})
wait(14000, function(){
el.style.backgroundColor = "rgb(128,128,128)"
})
wait(21000, function(){
el.style.backgroundColor = "rgb(0,0,0)"
})
So even if that did work it wouldn't be what you want since the wait function is attempting to block the only execution thread which would just make the whole browser hang. The wait method you are looking for is called setTimeout and it works a little different than you are probably used to.
setTimout lets you specify a function and an amount of milliseconds before it will execute that function so in your case if you want to accomplish changing the background color every 7 seconds you would probably do something like this:
var colors = ['rgb(0,0,255)', 'rgb(128,128,128)', 'rgb(0,0,0)'];
for (var i = 0; i < colors.length; i++) {
setTimeout(function(color) {
document.getElementById('fondRGB').style.backgroundColor = color;
}.bind(null, colors[i]), i * 7000);
}
Sorry that the code got a little complex, look up closures if you are wondering why the .bind part is necessary (if you are not already familiar).

Display message before script block

I have a hefty script to run synchronously and I want to display a message like "Processing, please wait..." before it runs so the user isn't wondering why the page is frozen for a few seconds. I'm trying to do something like:
messageBox.html("Processing, please wait...");
// run hefty script
messageBox.html("Finished!");
But the page blocks before the message is displayed, even though the messageBox.html() statement comes first. Why is this?
Sometimes it makes sense to fire the "hefty script" in a timeout.
messageBox.html("Processing, please wait...");
setTimeout(function () {
heftyScript();
messageBox.html("Finished!");
}, 1);
The reason this happens is because it often holds UI updates until the end of the event loop (after your "hefty" script has finished). Setting a timeout ensures that hefty script doesn't run until a subsequent iteration of the event loop (letting the UI update at the end of the current iteration beforehand).
I would consider Web Workers in this case: http://www.html5rocks.com/en/tutorials/workers/basics/
UPDATE:
If for some reason you cannot use them then you should split your processing on smaller chunks and run them asynchronously. Locking UI is not an option.
Here is what you can do:
function heftyScript() {
var arr = [...];
var chunk_start = 0;
function do_chunk() {
for( var i = 0; i < 100; ++i ) { // 100 items per chunk
if( chunk_start >= arr.length)
return;
process( arr[chunk_start++] ); // process one element
}
window.setTimeout(do_chunk,10);
}
do_chunk();
}
It depends where the time is being spent. If it's downloading and parsing the JavaScript file, the messageBox.html() must be pure html or you do it in a script block before referencing the external file. If the time spent is running that long function then setTimeout(function () { heftyScript(); messageBox.html('finished'); }, 1); works wonderfully.

setInterval works then hangs browser javascript

I'm wanting to animate an element using setInterval. I've put my code into an object with 2 functions one to initialize and one to animate using setInterval. When I try to run the code the animation works once then causes the browser to hang. The only thing I can think of is an infinite loop being created somewhere however I can't see anywhere in my code that would cause this.
What is causing the browser to crash, how can this be overcome ?
<div id='box' style='position:absolute;height:100px;width:100px;background-color:#44e'>box</div>
<script>
var box = {
init : function(elemId) {
box.elem = document.getElementById(elemId);
box.timer = setInterval;
box.tick = 0;
box.animate();
},
animate: function() {
if(box.tick < 100) {
box.elem.style.top = box.tick +'px';
box.elem.style.left = box.tick +'px';
box.tick++;
} else {
clearInterval(box.timer);
}
var timer = setInterval(box.animate, 50)
}
}
box.init('box');
</script>
setInterval sets up a function that will be called repeatedly by the browser until you cancel the interval timer. Your code isn't doing that, because the only call to clearInterval is using box.timer, which is never set to a timer handle (the return value from setInterval). So you end up scheduling thousands of calls (a new series every time animate is called) and bringing the browser to its kneeds.
At the very least, this:
var timer = setInterval(box.animate, 50)
should probably be:
box.timer = setInterval(box.animate, 50);
Or you may want setTimeout (which schedules only one call back).

Categories