Update loading bar within jQuery .each() - javascript

I'm sure this must be a common problem, but after much searching I can't find an answer.
I have a twitter-bootstrap loading bar that I would like to update after each stage of a calculation is completed.
Here is the function for updating the loading bar:
var lb = $('#loading-bar');
var lbc = 0;
function increment_loading_bar(pc) {
setTimeout(function(){
lbc = lbc + pc;
lb.width(lbc+"%");;
}, 1);
}
And the calls to update the bar are within a .each() loop
var inc = 100/array.length();
$.each(array,function(index,element){
increment_loading_bar(inc/2);
//
//Gnarly processing ....
//
increment_loading_bar(inc/2);
}
However, this only updates after all the processing has finished. How can the redraw of the bar be forced as the code is executed?
Many thanks!

As I said in my question, the /redraw/ needs to be forced as the code is executed
To my knowledge, you can only indirectly force the Redraw by pausing your Process once in a while.
For example like this:
var inc = 100/array.length();
var processQueue = array;
var currentIndex;
setTimeout(runProcess, 5);
function runProcess() {
var element = processQueue[currentIndex];
// Process the element here
// ....
increment_loading_bar(inc);
currentIndex++;
if (currentIndex < processQueue.length) {
setTimeout(runProcess, 5);
} else {
// processing has finished
}
}
This way you give the browser some time (5ms in this example) between each step to redraw the loading bar.

Related

How to use timer in d3 V3?

I have a function triggerWave() which makes the points on the canvas animate in the wave form. I am using d3.ease('quad-in') for easing and I would like to use d3.timer() to make the triggerWave() function call over 200ms timeframe. I am out of luck in finding the tutorials or examples on d3.timer.
triggerWave() {
//function logic
let count = 0;
let xScale = d3.scale.linear().range([1,2]); // want the value to change from 1 to 2.
let zScale = d3.scale.linear().domain([0, 200]); // 200 ms.
let value = xScale(d3.ease('quad-in')(zScale(count)));
if(count < 200){
count++;
d3.timer(() => triggerWave());
} else {
// do something
}
this.wave.next({currentFrame: value});
}
When I call d3.timer() as above, the triggerWave() function gets called infinite times and never stops. I want to manipulate or control the time. In my case, I want the timer() to be triggered for 200ms.
How can I understand how to use the d3.timer() function?
(EDIT: I totally and completely missed the huge, big "V3" which is right there, in the title of the question. Sorry. I'll keep this answer here as reference for v4 users)
Since you are calling triggerWave inside the triggerWave function itself, you don't need d3.timer, but d3.timeout instead. According to the API, d3.timeout:
Like timer, except the timer automatically stops on its first callback. A suitable replacement for setTimeout that is guaranteed to not run in the background. The callback is passed the elapsed time.
Also, pay attention to the fact that you are reseting count every time the function runs, which will not work. Set its initial value outside the function.
Here is a demo with those changes. I'm calling the function every 200 ms, until count gets to 50:
var p = d3.select("p")
var count = 0;
triggerWave();
function triggerWave() {
p.html("Count is " + count)
if (count < 50) {
count++;
d3.timeout(triggerWave, 200)
} else {
return
}
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<p></p>
You can also keep track of the total elapsed time, using the argument passed to triggerWave by d3.timeout:
var p = d3.select("p")
var count = 0;
var elapsed = 0;
var format = d3.format(".2")
triggerWave();
function triggerWave(t) {
elapsed = t ? elapsed + t : elapsed;
p.html("Count is " + count + ", and the elapsed time is " + format(elapsed/1000) + " seconds")
if (count < 50) {
count++;
d3.timeout(triggerWave, 200)
} else {
return
}
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<p></p>
Since you are using D3 v3, and as there is no d3.timeout in v3, you can do the same approach using vanilla JavaScript: setTimeout.
Here is a demo:
var p = d3.select("p")
var count = 0;
triggerWave();
function triggerWave() {
p.html("Count is " + count)
if (count < 50) {
count++;
setTimeout(triggerWave, 200)
} else {
return
}
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<p></p>
In version 3, there is no d3.timer.stop() function. You have to return true after a certain period of time to stop the timer.
In Gerardo's answer, he explained fabulously how to use the d3 timeout which would be a valid solution to your problem i.e., to call the function over and over for a certain period of time. But looking at your comments on Gerardo's answer, I think you are looking for something else.
Here's what I came up with and I think this is what you are looking for:
You can create an another function called as activateTriggerWave() which will be invoked on the button click and inside this function, you can call your triggerWave() method using the d3 timer.
function activateTriggerWave() {
d3.timer(elapsed => {
this.triggerWave();
if(elapsed >= 200){
return true; // this will stop the d3 timer.
}
});
}
triggerWave() {
// here you can do whatever logic you want to implement.
}
I hope this helps.
I use d3.js v3, and the timer can be stopped by any user action. In the d3.js docs it is shown to use it as:
d3.timer(function(elapsed) {
console.log(elapsed);
return elapsed >= 1000;
});
I have few examples in which the animation is forever and there is no reason to set a limit on it. Checking the standalone d3.timer which comes with a stop(), I found that it behaves quite slow comparing it with the default timer included in the v3 toolset, probably for some version incompatibility.
The solution is use is to:
var timer_1_stop=false;
set it as global var accesible from the page scope. Then the timer:
const run=function(){
//...
d3.timer(function() {
voronoi = d3.geom.voronoi(points).map(function(cell) { return bounds.clip(cell); });
path.attr("d", function(point, i) { return line(resample(voronoi[i])); });
return timer_1_stop;
});
}
const stopVoro=function(){
timer_1_stop=true;
}
It allows to do:
class Menu extends React.Component {
render(){
return(<ul>
<li><span onClick={()=>{stopVoro()}}>StopVoro</span></li>
</ul>)
}
}

JS wait/pause in generated functions

What I am doing
I am in the middle of building a turtle graphics app using Blockly. The user can build a code from blocks, then the Blockly engine generates JS code, which draws to a canvas.
What my problem is
The Blockly engine generates the JS code, but returns it as a string, which I have to eval() to draw to the canvas.
I can change the code of the blocks to generate different output, but it's important to keep it as simple as possible, because the users can read the actual code behind the block input. So I would like not to mess it up.
What I would like to do
I have full control over the atomic operations (go, turn, etc.), so I would like to insert a small piece of code to the beginning of the functions, which delays the execution of the rest of the bodies of the functions. Something like:
function go(dir, dist) {
// wait here a little
// do the drawing
}
I think it should be something synchronous, which keeps the delay in the flow of the execution. I've tried to use setTimeout (async, fail), a promise (fail), timestamp checks in a loop (fail).
Is it even possible in JS?
You must not make the code wait synchronously. The only thing you will get is a frozen browser window.
What you need is to use the js interpreter instead of eval. This way you can pause the execution, play animations, highlight currently executing blocks, etc... The tutorial has many examples that will help you get started. Here is a working code, based on the JS interpreter example:
var workspace = Blockly.inject("editor-div", {
toolbox: document.getElementById('toolbox')
});
Blockly.JavaScript.STATEMENT_PREFIX = 'highlightBlock(%1);\n';
Blockly.JavaScript.addReservedWords('highlightBlock');
Blockly.JavaScript['text_print'] = function(block) {
var argument0 = Blockly.JavaScript.valueToCode(
block, 'TEXT',
Blockly.JavaScript.ORDER_FUNCTION_CALL
) || '\'\'';
return "print(" + argument0 + ');\n';
};
function run() {
var code = Blockly.JavaScript.workspaceToCode(workspace);
var running = false;
workspace.traceOn(true);
workspace.highlightBlock(null);
var lastBlockToHighlight = null;
var myInterpreter = new Interpreter(code, (interpreter, scope) => {
interpreter.setProperty(
scope, 'highlightBlock',
interpreter.createNativeFunction(id => {
id = id ? id.toString() : '';
running = false;
workspace.highlightBlock(lastBlockToHighlight);
lastBlockToHighlight = id;
})
);
interpreter.setProperty(
scope, 'print',
interpreter.createNativeFunction(val => {
val = val ? val.toString() : '';
console.log(val);
})
);
});
var intervalId = setInterval(() => {
running = true;
while (running) {
if (!myInterpreter.step()) {
workspace.highlightBlock(lastBlockToHighlight);
clearInterval(intervalId);
return;
}
}
}, 500);
}
#editor-div {
width: 500px;
height: 150px;
}
<script src="https://rawgit.com/google/blockly/master/blockly_compressed.js"></script>
<script src="https://rawgit.com/google/blockly/master/blocks_compressed.js"></script>
<script src="https://rawgit.com/google/blockly/master/javascript_compressed.js"></script>
<script src="https://rawgit.com/google/blockly/master/msg/js/en.js"></script>
<script src="https://rawgit.com/NeilFraser/JS-Interpreter/master/acorn_interpreter.js"></script>
<xml id="toolbox" style="display: none">
<block type="text"></block>
<block type="text_print"></block>
<block type="controls_repeat_ext"></block>
<block type="math_number"></block>
</xml>
<div>
<button id="run-code" onclick="run()">run</button>
</div>
<div id="editor-div"></div>
EDIT
Added variable running to control the interpreter. Now it steps over until the running variable is set to false, so the running = false statement inside the highlightBlock function essentially works as a breakpoint.
EDIT
Introduced lastBlockToHighlight variable to delay the highlighting, so the latest run statement is highlighted, not the next one. Unfortunately the JavaScript code generator doesn't have a STATEMENT_SUFFIX config similar to STATEMENT_PREFIX.
Recently I published a library that allows you to interact asynchronously with blockly, I designed this library for games like that.
In fact in the documentation you can find a game demo that is a remake of the maze game.
The library is called blockly-gamepad 🎮, I hope it's what you were looking for.
blockly-gamepad 🎮
live demo
Here is a gif of the demo.
How it works
This is a different and simplified approach compared to the normal use of blockly.
At first you have to define the blocks (see how to define them in the documentation). You don't have to define any code generator, all that concerns the generation of code is carried out by the library.
Each block generate a request.
// the request
{ method: 'TURN', args: ['RIGHT'] }
When a block is executed the corresponding request is passed to your game.
class Game{
manageRequests(request){
// requests are passed here
if(request.method == 'TURN')
// animate your sprite
turn(request.args)
}
}
You can use promises to manage asynchronous animations, as in your case.
class Game{
async manageRequests(request){
if(request.method == 'TURN')
await turn(request.args)
}
}
The link between the blocks and your game is managed by the gamepad.
let gamepad = new Blockly.Gamepad(),
game = new Game()
// requests will be passed here
gamepad.setGame(game, game.manageRequest)
The gamepad provides some methods to manage the blocks execution and consequently the requests generation.
// load the code from the blocks in the workspace
gamepad.load()
// reset the code loaded previously
gamepad.reset()
// the blocks are executed one after the other
gamepad.play()
// play in reverse
gamepad.play(true)
// the blocks execution is paused
gamepad.pause()
// toggle play
gamepad.togglePlay()
// load the next request
gamepad.forward()
// load the prior request
gamepad.backward()
// use a block as a breakpoint and play until it is reached
gamepad.debug(id)
You can read the full documentation here.
EDIT: I updated the name of the library, now it is called blockly-gamepad.
If i understood you!
You can build a new class to handle the executing of go(dir, dist) functions, and override the go function to create new go in the executor.
function GoExecutor(){
var executeArray = []; // Store go methods that waiting for execute
var isRunning = false; // Handle looper function
// start runner function
var run = function(){
if(isRunning)
return;
isRunning = true;
runner();
}
// looper for executeArray
var runner = function(){
if(executeArray.length == 0){
isRunning = false;
return;
}
// pop the first inserted params
var currentExec = executeArray.shift(0);
// wait delay miliseconds
setTimeout(function(){
// execute the original go function
originalGoFunction(currentExec.dir, currentExec.dist);
// after finish drawing loop on the next execute method
runner();
}, currentExec.delay);
}
this.push = function(dir, dist){
executeArray.push([dir,dist]);
run();
}
}
// GoExecutor instance
var goExec = new GoExecutor();
// Override go function
var originalGoFunction = go;
var go = function (dir, dist, delay){
goExec.push({"dir":dir, "dist":dist, "delay":delay});
}
Edit 1:
Now you have to call callWithDelay with your function and params,
the executor will handle this call by applying the params to the specified function.
function GoExecutor(){
var executeArray = []; // Store go methods that waiting for execute
var isRunning = false; // Handle looper function
// start runner function
var run = function(){
if(isRunning)
return;
isRunning = true;
runner();
}
// looper for executeArray
var runner = function(){
if(executeArray.length == 0){
isRunning = false;
return;
}
// pop the first inserted params
var currentExec = executeArray.shift(0);
// wait delay miliseconds
setTimeout(function(){
// execute the original go function
currentExec.funcNam.apply(currentExec.funcNam, currentExec.arrayParams);
// after finish drawing loop on the next execute method
runner();
}, currentExec.delay);
}
this.push = function(dir, dist){
executeArray.push([dir,dist]);
run();
}
}
// GoExecutor instance
var goExec = new GoExecutor();
var callWithDelay = function (func, arrayParams, delay){
goExec.push({"func": func, "arrayParams":arrayParams, "delay":delay});
}

User interaction conflict during SetTimeout in Javascript

I am working on a WordPress (Javascript) plugin that alters text fields based on user interaction with an HTML5 slider. One of its effects is to reveal a <span> string one character at a time using SetTimeout to create a delay (a few ms) so the effect is perceptible. I'm accomplishing this by getting the DOM element's contents and then rebuilding it one character at a time.
The problem is that since SetTimeout is aynsynchronous, the user can potentially move the slider faster than a single reveal loop can complete, resulting in half-empty DOM elements that never get corrected.
Is there a way to prevent this, or alternatively, a way to accomplish the task that avoids the conflict altogether? I have tried turning off the EventListener (for the HMTL5) at various points in the delay loop but cannot find a place that avoids the issue. The other possibility is to load all the <span> contents into arrays in order to retain intact copies of everything ... but something tells me there's a better way to do it that I don't know.
Here is example code. Initialize() is called when the HTML page involved loads.
function Initialize () {
document.getElementById(name).addEventListener('input', UpdateSlider);
}
function UpdateSlider()
{ if (
// conditions
)
{ var cols = document.getElementsByClassName(attr+i);
RevealTextLines (cols);
}
// 'delay' is a global variable to set the delay length
function RevealTextLines (cols)
{
[].forEach.call(cols, function(el) {
var snippet = el.innerHTML;
el.innerHTML = '';
el.style.display = 'inline';
(function addNextCharacter(h) {
el.innerHTML = snippet.substr(0,h);
h = h + numchars;
if (h <= snippet.length) {
setTimeout(function() {
addNextCharacter(h);
}, delay);
}
})(1);
});
}
The boolean flag suggested above does not work in this case, but it did inspire the following solution:
Provided the number of iterations are known in advance (which in this case they are), define a global counter variable outside the functions. Before the SetTimeout loop, set it to the number of iterations and decrease it by 1 every time through. Then have the calling function proceed only when the counter's value is zero.
var counter = 0;
function Initialize () {
document.getElementById(name).addEventListener('input', UpdateSlider);
}
function UpdateSlider()
{ if ( counter == 0)
{ var cols = document.getElementsByClassName(classname);
RevealTextLines (cols);
}
function RevealTextLines (cols)
{
[].forEach.call(cols, function(el) {
timer = el.length;
var snippet = el.innerHTML;
el.innerHTML = '';
el.style.display = 'inline';
(function addNextCharacter(h) {
el.innerHTML = snippet.substr(0,h);
h++;
if (h <= snippet.length) {
setTimeout(function() {
addNextCharacter(h);
timer--;
UpdateSlider();
}, delay);
}
})(1);
});
}
If anyone knows a more efficient solution, I would remain interested.

Worn out getting animation to sequence

This is originally from (Pause execution in while loop locks browser (updated with fiddles))
I have been at this all day and I can't figure out how to keep javascript from advancing to the next line and in essence executing all lines at once. I have tried every combination of delay / setTimeout I can think of to no avail.
I just want the elements in the array to flash once then pause, then do it again for another element in the array till all elements have been removed and the array is empty.
But because javascript is executing all lines at once I end up with the appearance of all elements flashing at the same time.
Here is the fiddle:
http://jsfiddle.net/ramjet/xgz52/7/
and the relevant code:
FlashElement: function () {
while (elementArray.length) {
alert('a ' + elementArray.length);
var $el = elementArray.eq(Math.floor(Math.random() * elementArray.length));
PageLoadAnimation.FlashBlast($el);
alert('delay complete');
elementArray = elementArray.not($el);
alert('array popped');
alert('z ' + elementArray.length);
}
},
ANSWER FOR THIS SITUATION. Hopefully it will help others.
As Zach Saucier points out the loop was really my problem...but not the only problem. I was the other problem(s).
Me first.
Fool that I am I was really causing my own complications with two things I was doing wrong.
First using jsfiddle my javascript would error due to syntax or some such thing but fiddle doesn't tell you that (to my knowledge) so my fiddle wouldn't run but I took it in pride as MY CODE IS FINE stupid javascript isn't working.
Second I was passing my function to setTimeout incorrectly. I was adding the function parens () and that is not correct either which would bring me back to issue one above.
WRONG: intervalTimer = setInterval(MyFunction(), 1500);
RIGHT: intervalTimer = setInterval(MyFunction, 1500);
As for the code. As Zach pointed out and I read here (http://javascript.info/tutorial/settimeout-setinterval) while he was responding setting a timeout in a loop is bad. The loop will iterate rapidly and with the timeout one of the steps in the loop we get into a circular firing squad.
Here is my implementation:
I created a couple variables but didn't want them polluting the global scope so I created them within the custom domain. One to hold the array of elements the other the handle to the setInterval object.
var PageLoadAnimation =
{
elementArray: null,
intervalTimer: null,
....
}
In my onReady function (the one the page calls to kick things off) I set my domain array variable and set the interval saving the handle for use later. Note that the interval timer is how long I want between images flashes.
onReady: function ()
{
elementArray = $('#PartialsContainer').children();
//black everything out just to be sure
PageLoadAnimation.BlackOutElements();
//flash & show
intervalTimer = setInterval(PageLoadAnimation.FlashElement, 1500);
},
Now instead of looping through the array I am executing a function at certain intervals and just tracking how many elements are left in the array to be flashed. Once there are zero elements in the array I kill the interval execution.
FlashElement: function ()
{
if(elementArray.length > 0) //check how many elements left to be flashed
{
var $el = PageLoadAnimation.GrabElement(); //get random element
PageLoadAnimation.FlashBlast($el); //flash it
PageLoadAnimation.RemoveElement($el); //remove that element
}
else
{
//done clear timer
clearInterval(intervalTimer);
intervalTimer = null;
}
},
So the whole thing is:
var PageLoadAnimation =
{
elementArray: null,
intervalTimer: null,
onReady: function () {
elementArray = $('#PartialsContainer').children();
//black everything out just to be sure
PageLoadAnimation.BlackOutElements();
//flash & show
intervalTimer = setInterval(PageLoadAnimation.FlashElement, 1500);
//NOT this PageLoadAnimation.FlashElement()
},
BlackOutElements: function () {
$('#PartialsContainer').children().hide();
},
FlashElement: function ()
{
if(elementArray.length > 0)
{
var $el = PageLoadAnimation.GrabElement();
PageLoadAnimation.FlashBlast($el);
PageLoadAnimation.RemoveElement($el);
}
else
{
//done clear timer
clearInterval(intervalTimer);
intervalTimer = null;
}
},
GrabElement: function()
{
return elementArray.eq(Math.floor(Math.random() * elementArray.length));
},
RemoveElement: function($el)
{ elementArray = elementArray.not($el); },
FlashBlast: function ($el) {
//flash background
$el.fadeIn(100, function () { $el.fadeOut(100) });
}
}
Hope that help others understand the way to go about pausing execution in javascript.
The reason why you were having trouble is because setTimeout function is non-blocking and will return immediately. Therefore the loop will iterate very quickly, initiating each of the timeouts within milliseconds of each other instead of including the previous one's delay
As a result, you need to create a custom function that will wait on the setInterval to finish before running again
FlashElement: function () { // Call it where you had the function originally
myLoop();
},
...
function myLoop() {
setTimeout(function () { // call a setTimeout when the loop is called
var $el = elementArray.eq(Math.floor(Math.random() * elementArray.length));
PageLoadAnimation.FlashBlast($el);
elementArray = elementArray.not($el);
if (0 < elementArray.length) { // if the counter < length, call the loop function
myLoop();
}
}, 1000)
}
Feel free to change the delay to whatever value you wish (3000ms to let each fade finish before the last at the moment). If you want to start the fade in of the next before the previous ends and keep them in their original positions you would have to animate the opacity using .css instead of using fadeIn and fadeOut
My answer is based on this answer from another SO question

Issues with WaitForSeconds

I'm learning how to use the WaitForSeconds function in Unityscript, and have had success with it before. But now I'm trying it in a script that is supposed to, when the object's health reaches 0, move the object (a box in this case) off-screen, then after a set amount of seconds have it reappear in a random position on-screen, and set the health back to its default value. What I have thus far is:
function Update ()
{
if(health <= 0)
{
RespawnWaitTime ();
var position = Vector3(Random.Range(-6,6),Random.Range(-4.5,4.5),0); //this is the onscreen range
transform.position = position;
health = 2;
}
}
function RespawnWaitTime ()
{
var offScreen = Vector3(10,10,0);
transform.position = offScreen;
yield WaitForSeconds(2);
}
However, it doesn't wait the 2 seconds at all. The box just goes straight to its new position as if the function wasn't there. I believe that it does go to its off-screen position, but just jumps straight back without waiting. I've tested to see if it's applying the wait at all by changing some of the code to:
function RespawnWaitTime ()
{
var offScreen = Vector3(10,10,0);
transform.position = offScreen;
print("I'm over here!");
yield WaitForSeconds(2);
print("I'm coming back!");
}
The first bit of text ends up printing right away, then after two seconds, the second bit of text appears as well, just as expected. So why doesn't the wait apply to the box also? Thanks for your help.
I suspect WaitForSeconds is asynchronously so when update calls RespawnWaitTime then RespawnWaitTime returns immediately. Could you try the following code to see how WaitForSeconds behaves?
function Update () {
print("1 in update before calling respandwaittime");
RespawnWaitTime ();
print("3 in update after calling respandwaittime");
}
function RespawnWaitTime (){
print("2 in in respainwaittime before calling waitforseconds");
yield WaitForSeconds(2);
print("4 in in respainwaittime after calling waitforseconds");
}
Since the output was 1,2,3,4 (as expected) you could reprogram like so:
function Update () {
if(health <= 0){
recover();
return;
}
}
function recover(){
var offScreen = Vector3(10,10,0);
transform.position = offScreen;
yield WaitForSeconds(2);
//this is the onscreen range
var position = Vector3(Random.Range(-6,6),Random.Range(-4.5,4.5),0);
transform.position = position;
health = 2;
}
You need to use "yield RespawnWaitTime();" - otherwise the function which called it will continue to execute at the same time as the coroutine is running.
Yielding the coroutine pauses execution of the code which called it, until the coroutine is finished, at which point the original code resumes from the line following the yield statement.
HOWEVER - as you can't put yield statements inside Update() - you would need to call an intermediate function, something like the following:
function Update ()
{
if(health <= 0)
{
Respawn ();
}
}
function Respawn ()
{
yield RespawnWaitTime ();
var position = Vector3(Random.Range(-6,6),Random.Range(-4.5,4.5),0);
transform.position = position;
health = 2;
}
function RespawnWaitTime () : IEnumerator
{
var offScreen = Vector3(10,10,0);
transform.position = offScreen;
yield WaitForSeconds(2);
}
Note I have also added ":IEnumerator" to RespawnWaitTime() - if you don't, the console will print an error for the yield statement that calls it. As I understand, the code will probably work despite the compile error being shown, but I prefer not to take chances ;-)

Categories