I have a progress bar that I update in a loop of many iterations.
https://jsfiddle.net/k29qy0do/32/
(open the console before you click the start button)
var progressbar = {};
$(function () {
progressbar = {
/** initial progress */
progress: 0,
/** maximum width of progressbar */
progress_max: 0,
/** The inner element of the progressbar (filled box). */
$progress_bar: $('#progressbar'),
/** Set the progressbar */
set: function (num) {
if (this.progress_max && num) {
this.progress = num / this.progress_max * 100;
console.log('percent: ' + this.progress + '% - ' + num + '/' + this.progress_max);
this.$progress_bar.width(String(this.progress) + '%');
}
},
fn_wrap: function (num) {
setTimeout(function() {
this.set(num);
}, 0);
}
};
});
$('#start_button').on('click', function () {
var iterations = 1000000000;
progressbar.progress_max = iterations;
var loop = function () {
for (var i = 1; i <= iterations; i++) {
if (iterations % i === 100) {
progressbar.set(i); //only updates the progressbar in the last iteration
//progressbar.fn_wrap(i); //even worse, since no output to the console is produced
}
}
}
//setTimeout(loop, 0);
loop();
});
The console is updated iteratively as expected.
However, the progressbar is not updating.
The problem is that the browser window seems to 'hang' until the loop finishes.
Only the console is updated, not the progressbar.
I have tried to add the setTimeout, as suggested below, in several places.
But that just makes things worse, because I then do not even get the console to output the progress while executing the loop.
Okay, I found a solution in the answer to this question:
Javascript: How to update a progress bar in a 'for' loop
var i = 0;
(function loop() {
i++;
if (iterations % i === 100) {
progressbar.set(i); //updates the progressbar, even in loop
}
if (i < iterations) {
setTimeout(loop, 0);
}
})();
My solution:
https://jsfiddle.net/ccvs4rer/3/
Lets break this down to steps
Step 1: Clean up HTML
Assuming the purpose of your question is to understand how to work the progress bar and not the styles or the labels (loading, please be patient, etc.). Lets just have the progress bar and the start button.
<div id='progressbar-outer' style="">
<div id='progressbar' style=""></div>
</div>
<button id="start_button">Start</button>
Step 2: The Styles
Lets make the progress bar visible to the user
#progressbar-outer {
height:2em;
border:5px solid #000;
width:15em;
}
#progressbar {
width:0%;
background-color:#F00;
height:100%;
}
Step 3: Using setTimeout where it belongs
In your code, you have used setTimeout to set the value of your progress bar. However, the for loop is still active.
for (var i = 1; i <= iterations; i++) {
if (iterations % i === 100) {
progressbar.set(i); //only updates the progressbar in the last iteration
//progressbar.fn_wrap(i); //even worse, since no output to the console is produced
//setTimeout(function() {
// progressbar.set(i);
//}, 0);
}
}
The use of setTimeout does not affect the rest of the code. Hence, the UI was held hostage till the loop ended. Try the following code.
$('#start_button').on('click', function () {
var iterations = 100;
progressbar.progress_max = iterations;
var loop = function (value) {
progressbar.set(value);
if (value < iterations) setTimeout(function () {
loop(value + 1)
}, 30);
else $('#progressbar').css('background-color', '#0F0');
}
loop(1);
});
Preview
Try this fiddle: https://jsfiddle.net/Ljc3b6rn/4/
What you really want is an Asynchronous loop to allow the browser to update the DOM in between iterations.
JSFiddle: http://jsfiddle.net/u5b6gr1w/
function delayedLoop(collection, delay, callback, context) {
context = context || null;
var i = 0,
nextInteration = function() {
if (i === collection.length) {
return;
}
callback.call(context, collection[i], i);
i++;
setTimeout(nextInteration, delay);
};
nextInteration();
}
Some HTML:
<div class="progress-bar"><div style="width: 0"></div></div>
A splash of CSS:
.progress-bar {
border: 1px solid black;
background-color: #f0f0f0;
}
.progress-bar div {
background-color: red;
height: 1.25em;
}
And some JavaScript to wire things together:
var progressBar = document.querySelector(".progress-bar div"),
items = [1,2,3,4,5,6,7,8,9,10];
delayedLoop(items, 500, function(item, index) {
var width = (item / items.length * 100) + "%";
progressBar.style.width = width;
progressBar.innerHTML = width;
});
My guess would be that all your progress updates are running in the same call stack. While JavaScript code is running, the DOM cannot update. Maybe this question will help you come up with a work-around.
What do you wnat to do? Why do you need it? You should only use a progressbar when you have to wait for something to finish. But we don't know what you do on your page.
If you want to display the progress of an ajax upload:
$.ajax({
...
xhr: function() {
var xhr = $.ajaxSettings.xhr();
$(xhr.upload).bind("progress", function(event) {
var e = event.originalEvent;
var percent = 0;
if (e.lengthComputable)
percent = Math.ceil(e.loaded/e.total*100);
$("#progress").width(percent+"%");
});
return xhr;
}
...
});
For images, you need an ajax call:
$.ajax({
method: "GET",
url: "http://example.com/path/image.jpg",
xhr: function() {/* see the code above*/ }
...
});
For getting the content of an uploaded file:
var reader = new FileReader();
reader.readAsText(uploadedFile);
$(reader).bind("progress", function(e) {
var percent = 0;
if (e.lengthComputable)
percent = Math.ceil(e.loaded/e.total*100);
$("#progress").css("width", percent+"%");
});
For large around of process, like math or appending a lot of divs that will take 10+ secons:
Main.js:
var worker = new Worker("Worker.js");
$(worker).bind("message", function(data) {
$("#progress").width((data*100)+"%");
});
Worker.js:
var total = 43483,
finished = 0,
doStuff = function() {
++finished;
return 1+1;
};
setInterval(function()
{
self.postMessage(finished/total);
}, 100);
for (var i = 0; i < total; ++i)
setTimeout(doStuff, i*10);
Because it's nice, and you want to tell the user there's a progress when there isn't, just animate the div:
$("#progress").animate({width: "100%"}, 3000);
You can use promises to wait until the width is set before continuing the loop.
Updating the progress bar for 1000000000 iterations will be slow if you go 1 by 1, so you might find it useful to decrease the update frequency.
Instead of a for loop, I used a recursive function that loops when the promise has been fulfilled.
set: function (num) {
var deferred = $.Deferred();
if (this.progress_max && num) {
this.progress = num / this.progress_max * 100;
var self = this;
self.$progress_bar.animate({"width": String(this.progress) + '%'}, "fast", function() {
deferred.resolve();
});
return deferred;
}
}
$('#start_button').on('click', function () {
var iterations = 1000000000;
var i = 0;
progressbar.progress_max = iterations;
var loop = function(){
i+=100000000;
if(i <= iterations){
progressbar.set(i).then(function(){
loop();
}); ;
}
};
loop();
});
https://jsfiddle.net/k29qy0do/34/
You have to use window.requestAnimationFrame, otherwise the browser will block until your loop is finished. The callback passed to requestAnimationFrame will get a timestamp as a parameter which you might be able to use for calculations of the progress.
This are my 2 takes on the question:
Using a web worker. The webworker blob code comes from here
Web worker code:
<script type="text/ww">
function loop(e) {
var data = JSON.parse(e.data);
var i = parseInt(data.i, 10);
var iterations = parseInt(data.iterations, 10);
while (iterations % ++i !== 100 && i <= iterations);
if(i <= iterations) {
self.postMessage(JSON.stringify({ i: i, iterations: iterations }));
}
}
self.onmessage = function(e) {
loop(e);
};
</script>
The code:
var ww = document.querySelector('script[type="text/ww"]'),
code = ww.textContent,
blob = new Blob([code], {type: 'text/javascript'}),
blobUrl = URL.createObjectURL(blob),
worker = new Worker(blobUrl);
worker.onmessage = function(e) {
var data = JSON.parse(e.data);
var i = parseInt(data.i, 10);
var iterations = parseInt(data.iterations, 10);
progressbar.set(i);
worker.postMessage(JSON.stringify({ i: i, iterations: iterations }));
}
$('#start_button').on('click', function () {
var iterations = 1000000000;
progressbar.progress_max = iterations;
worker.postMessage(JSON.stringify({ i: 0, iterations: iterations }));
});
The other idea hangs the UI thread, but changes the width visually, as I use requestAnimationFrame to break the counting, change width of the progressbar, and then continue the count.
function loopFrame(i, iterations) {
requestAnimationFrame(function() {
if (iterations % i === 100) {
progressbar.set(i);
}
if(i < iterations) {
loopFrame(i + 1, iterations);
}
});
}
$('#start_button').on('click', function () {
var iterations = 1000000000;
console.log(iterations);
progressbar.progress_max = iterations;
loopFrame(0, iterations);
});
Maybe this will be usefull.
var service = new Object();
//function with interrupt for show progress of operations
service.progressWhile = new Object();
service.progressWhile.dTime = 50; //step ms between callback display function
service.progressWhile.i = 0; //index
service.progressWhile.timer = 0; //start time for cycle
//#parametr arr - array for actions
//#parametr actionCallback - The function for processing array's elements
//#parametr progressCallback - function to display the array index
function progressWhile(arr, actionCallback, progressCallback) {
try {
var d = new Date();
service.progressWhile.timer = d.getTime();
log(service.progressWhile.i);
if (service.progressWhile.i >= arr.length) {
service.progressWhile.i = 0;
return;
}
while (service.progressWhile.i < arr.length) {
actionCallback(arr[service.progressWhile.i++]);
d = new Date();
if (d.getTime() - service.progressWhile.timer > service.progressWhile.dTime) {
break;
}
}
if (progressCallback != undefined)
progressCallback(service.progressWhile.i);
} catch (er) {
log(er);
return;
}
setTimeout(function () {
progressWhile(arr, actionCallback, progressCallback);
}, 0);
}
Here's updated fiddle
I used animate to make it a progress bar like look and feel.
Hope this will help you.
var progressbar = {};
$(function() {
progressbar = {
/** initial progress */
progress : 0,
/** maximum width of progressbar */
progress_max : 0,
/** The inner element of the progressbar (filled box). */
$progress_bar : $('#progressbar'),
/** Method to set the progressbar.*/
set : function(num) {
if (this.progress_max && num) {
this.progress = num / this.progress_max * 100;
console.log('percent: ' + this.progress + '% - ' + num + '/' + this.progress_max);
$('#progressbar').animate({
width : String(this.progress) + '%',
}, 500, function() {
// Animation complete.
});
}
},
fn_wrap : function(num) {
setTimeout(function() {
this.set(num);
}, 0);
}
};
});
$('#start_button').on('click', function() {
$('#progressbar').css('width', '0%');
var iterations = 1000000000;
progressbar.progress_max = iterations;
var loop = function() {
for (var i = 1; i <= iterations; i++) {
if (iterations % i === 100) {
progressbar.set(i);
//only updates the progressbar in the last iteration
}
}
}
loop();
});
Fiddler
[1]: https://jsfiddle.net/k29qy0do/21/
I am using the Peity js plugin to create donut charts on my page. I am trying to animate the chart for each of the .foo elements:
<span class="foo" data-value="10"></span>
$('.foo').each(function () {
var updateChart = $(this).peity('donut');
var text = "";
var i = 0;
function myLoop() {
setTimeout(function () {
text = i + "/12";
updateChart.text(text)
.change()
i = i + 0.2;
var maxValue = $(this).data("value");
if (i <= maxValue) myLoop();
}, 0.5)
}
myLoop();
});
However it won't work for some reason with no errors in console. If I remove the $('.foo').each(function () { ... } part (and all "this" instances) the code will work. Thanks in advance for any help.
The problem is the context inside the timer handler, the easiest fix here is to use a closure variable
$('.foo').each(function () {
var $this = $(this);
var updateChart = $this.peity('donut');
var text = "";
var i = 0;
function myLoop() {
setTimeout(function () {
text = i + "/12";
updateChart.text(text)
.change()
i = i + 0.2;
var maxValue = $this.data("value");
if (i <= maxValue) myLoop();
}, 0.5)
}
myLoop();
});
When the timeout callback is executed, the this context refer to window, because you are actually calling window.setTimeout method.
Try this:
$('.foo').each(function () {
var updateChart = $(this).peity('donut');
var text = "";
var i = 0;
function myLoop() {
setTimeout($.proxy(function () {
text = i + "/12";
updateChart.text(text)
.change()
i = i + 0.2;
var maxValue = $(this).data("value");
if (i <= maxValue) myLoop();
},this), 0.5)
}
myLoop();
});
I have several URLs in my array and I want to run it one by one,
but when I run it in a loop, it executes all at the same time and does not wait.
Here is what I tried:
<html>
<head>
<script>
function work(){
var otherStoryLinksArray = [];
otherStoryLinksArray[0] = 'http://google.com';
otherStoryLinksArray[1] = 'http://www.yahoo.com';
otherStoryLinksArray[2] = 'http://gmail.com';
for(var i=0;i<3;i++){
var openWindow = window.open(otherStoryLinksArray[i]);
setTimeout(function(){openWindow.close();},3000);
}
}
</script>
</head>
<body onload=work();>
</body>
</html>
I want it to open one URL, wait for 30 secs, close the popup, and then start another URL.
Waiting for your reply guys. Any help would be appreciated thanks...
Code: http://jsfiddle.net/WgR4y/1/
Demo: http://jsfiddle.net/WgR4y/1/show/ (make sure you disable popup blocker)
Works for unlimited number of URLs in array.
var otherStoryLinksArray = [
'http://google.com',
'http://www.yahoo.com',
'http://gmail.com'
],
timeToCloseWindow = 3000;
function work() {
if(otherStoryLinksArray.length==0) return;
var url = otherStoryLinksArray.shift();
var openWindow = window.open(url);
setTimeout(function () {
openWindow.close();
work();
}, timeToCloseWindow);
}
work();
You need to stagger your setTimeout calls and since setTimeout uses ms, 30 s = 30000 ms:
function work () {
var otherStoryLinksArray = [];
otherStoryLinksArray[0] = 'http://google.com';
otherStoryLinksArray[1] = 'http://www.yahoo.com';
otherStoryLinksArray[2] = 'http://gmail.com';
for(var i=0; i<3; i++) {
var openWindow;
setTimeout(function () {
openWindow = window.open(otherStoryLinksArray[i]);
}, 30000 * i); //Open this window
setTimeout(function () {
openWindow.close();
}, 30000 * i + 30000); //Close it 30 seconds from "now"
}
}
Use set interval instead of for-loop
function work(){
var otherStoryLinksArray = [];
otherStoryLinksArray[0] = 'http://google.com';
otherStoryLinksArray[1] = 'http://www.yahoo.com';
otherStoryLinksArray[2] = 'http://gmail.com';
var myVar=setInterval(function(){myTimer()},3000);
var i=0;
var openWindow;
function myTimer()
{
if(openWindow != null)
openWindow.close();
if(i < 3)
{
openWindow = window.open(otherStoryLinksArray[i]);
i++;
}
else
clearInterval(myVar);
}
}
var otherStoryLinksArray = [
'http://google.com',
'http://www.yahoo.com',
'http://gmail.com'];
var i = 0;
var openedWindow = null;
function loop(){
openedWindow && openedWindow.close();
if(otherStoryLinksArray[i]){
var openedWindow = window.open(otherStoryLinksArray[i]);
i++;
setTimeout(loop,30000);
}
}
I am not sure if I used the correct terminology exactly in the question but I am triggering a series of timed function calls using a loop in my playSequence() function and the setTimeOutFunction. This much works, but then I want to have a pause function that will pause all timers and a resume function that will resume all timers. The problem is that when I try to call the function object's pause method in the pauseAllTimers() function it gives error 'Uncaught TypeError: Object 0 has no method 'pause'. Any ideas?
var timers = new Array();
function Timer(callback, delay) {
var timerId, start, remaining = delay;
this.pause = function() {
window.clearTimeout(timerId);
remaining -= new Date() - start;
};
this.resume = function() {
start = new Date();
timerId = window.setTimeout(callback, remaining);
};
this.resume();
}
function pauseAllTimers()
{
for (var timer in timers)
{
timer.pause();
}
}
function resumeAllTimers()
{
for (var timer in timers)
{
timer.resume();
}
}
function playSequence()
{
var totaltimeout = 0;
for (var lesson_step_str in lesson_step)
{
var splitarr = lesson_step[lesson_step_str].split("|||");
var element = splitarr[0];
var txt = splitarr[1];
var timeout = splitarr[2];
totaltimeout += (timeout*1);
console.log(totaltimeout);
console.log(txt);
(function(a,b){
var timer = new Timer(function(){ displayText( a, b); }, totaltimeout * 1000);
timers.push(timer);
})(element, txt);
}
}
Well in for loops timer is returning the index instead of Timer object so you need to do it like this:
timers[timer].pause();
Following is working code:
var timers = new Array();
var Timer = function (callback, delay) {
this.timerId, this.start, this.remaining = delay;
this.pause = function () {
window.clearTimeout(this.timerId);
this.remaining -= new Date() - this.start;
};
this.resume = function () {
this.start = new Date();
this.timerId = window.setTimeout(callback, this.remaining);
};
this.resume();
}
function pauseAllTimers() {
for (var timer in timers) {
timers[timer].pause();
}
}
function resumeAllTimers() {
for (var timer in timers) {
timers[timer].resume();
}
}
function playSequence() {
var totaltimeout = 0;
for (var i=1;i<6; i++) {
var txt = "this is part "+i,
element="#div"+i, timeout=2;
totaltimeout += timeout;
(function (a, b) {
var timer = new Timer(function () {
$("#divTxt").html(b);
}, totaltimeout * 1000);
timers.push(timer);
})(element, txt);
}
}
$(function(){
$("#pauseAll").click(function(){
pauseAllTimers();
});
$("#resumeAll").click(function(){
resumeAllTimers();
});
playSequence();
});
I've got this code and I don't know why all the functions in the loop are called at once!
this.line = function($l) {
var $this = this;
$this.$len = 0;
$('.active').hide(0,function(){
$this.$len = $l.length;
var j = 1;
$.each($l,function(i,item){
var t = setTimeout(function(){
$this._echoLine($l[i]);
clearTimeout(t);
if (j >= $this.$len) $('.active').show();
j++;
},500*j);
});
});
}
It's because you only increment j inside the timeout function, but the delay (which depends on j) is still 1 at the time the timeout handler is registered.
Seeing as you have a loop index variable anyway (i), try this:
$l.each(function(i, item) {
setTimeout(function() {
$this._echoLine($l[i]);
}, 500 * (i + 1));
});
// a separate timeout to fire after all the other ones
setTimeout(function() {
$('.active').show();
}, ($l.length + 1) * 500);
There's no need for the clearTimeout line, so no need to declare or store t either.
Hope it works.
this.line = function($l) {
var $this = this;
$this.$len = 0;
$('.active').hide(0,function(){
$this.$len = $l.length;
var j = 1;
$.each($l,function(i,item){
var t = setTimeout(function(){
$this._echoLine($l[i]);
clearTimeout(t);
if (j >= $this.$len) $('.active').show();
j++;
});
});
});
}
setTimeout(function() { this.line(); }, 500);