Pause execution in while loop locks browser (updated with fiddles) - javascript

I know my problem I just not sure how to resolve it. I have a custom domain and in a function call a while loop executes. In that loop i wanted an animation to occur in order.
So the first problem is that javascript by its nature executes every line thus item 2 starts before item 1 completes. Now the effect is so short that it "appears" to happen to all elements at once but in the debugger it is just looping one at a time.
Now my typical resolution would be to use SetTimeout() but that is causing the browser to lock. Reading this post (Trying to delay/pause/slow a while loop in jQuery) it makes sense that the browser is getting into an endless loop.
So how can I get a pause between element1 and element2 events? I thought perhaps to add a callback function to my custom domain but not sure if that will work as desired besides not being sure how to do it.
In the head of the page and read the comments for anything else I may be doing wrong or could do better.
$(document).ready(function ()
{
//pause long enough for person to visually take in page before starting
setTimeout(function () { PageLoadAnimation.onReady(); }, 1000);
});
My custom domain:
var PageLoadAnimation =
{
onReady: function ()
{
//black everything out just to be sure
PageLoadAnimation.BlackOutElements();
//flash & show
PageLoadAnimation.FlashElement();
},
BlackOutElements: function ()
{
$('#ParentContainer').children().hide();
},
FlashElement: function ()
{
//get array of all elements and loop till all are visible
var elementArray = $('#ParentContainer').children();
var $els = $('#PartialsContainer').children();
while (elementArray.length)
{
var $el = elementArray.eq(Math.floor(Math.random() * elementArray.length));
//if I put set timeout here is causes the infinite loop
PageLoadAnimation.FlashBlast($el);
elementArray = elementArray.not($el);
//if I put by itself it no diff as the while loop continues regardless
//setTimeout(1500);
}
},
FlashBlast: function ($el)
{
//flash background
$el.fadeIn(200, function () { $el.fadeOut(200) });
}
}
I'm not sure if it isn't working or if I am doing something wrong so I created these fiddles:
Original Fiddle
With Johan Callbacks
Using is animating property
WARNING THIS ONE WILL HANG YOUR BROWSER!
I don't think I am checking the isAnimating property the way Johan had in mind??
ANSWER FOR THIS SITUATION. Hopefully it will help others.
setTimeout in a 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 I read here (http://javascript.info/tutorial/settimeout-setinterval) 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.

A callback example that might help:
FlashBlast: function ($el, fadeInComplete, fadeOutComplete)
{
if(arguments.length === 3){
$el.fadeIn(200, function () {
fadeInComplete();
$el.fadeOut(200, fadeOutComplete);
});
}
}
Usage:
PageLoadAnimation.FlashBlast($el, function(){
//fadein complete
}, function(){
//fadeout complete
});
Another idea that might help:
isAnimating: false,
FlashBlast: function ($el)
{
var dfd = $.Deferred(),
that = this;
that.isAnimating = true;
$el.fadeIn(200, function () {
$el.fadeOut(200, function(){
dfd.resolve();
})
});
dfd.done(function(){
that.isAnimating = false;
});
}
Then make use of the private property isAnimating.
Finally, to know if an element is under an animation, you can use $el.is(':animated').
Hope this helps. Let me know if anything is unclear.

Related

Running a loop multiple times automatically with vanilla JS

I have succeeded in cobbling together pieces of code that achieve my goal. However, I would like some advice from more advanced vanilla JS programmers on how I can go about reaching my goal in a better way.
To start, I want to introduce my problem. I have a piece of text on my website where a portion is designed to change every so often. For this, I am running through a loop of phrases. To run this loop continuously, I first call the loop, then I call it again with setInterval timed to start when the initial loop ends. Here is the code I've got, which works even if it isn't what could be considered quality code:
function loop(){
for (let i = 0; i < header_phrases.length; i++){
(function (i) {
setTimeout(function(){
header_txt.textContent = header_phrases[i];
}, 3000 * i);
})(i);
};
}
loop();
setInterval(loop, 21000);
Is there a better way to right this code for both performance and quality? Do I need to use async? If so, any material I can see to learn more? Thanks!
You can implement the same logic using recursion.
function recursify(phrases, index = 0) {
header_txt.textContent = phrases[index];
setTimeout(function () {
recursify(phrases, index < phrases.length - 1 ? index + 1 : 0);
}, 300)
}
recursify(header_phrases);
The function 'recursify' will call itself after 300 miliseconds, but everytime this function gets called, the value of index will be different.
If I understand your requirement correctly, you want top populate an element from an array of values.
A simple way to do this is:
doLoop();
function doLoop() {
var phraseNo=0;
setTimeout(next,21000);
next();
function next() {
header_txt.textContent = header_phrases[phraseNo++];
if(phraseNo>=header_phrases.length) phraseNo=0;
}
}
This simply puts the next() function on the queue and waits.
The call to next() before the function simply starts it off without waiting for the timeout.
this is assuming that header_txt and header_phrases are not global vars. using global vars isn't a good idea.
var repeatIn = 3000;
phraseUpdater();
function phraseUpdater() {
var updateCount = 0,
phrasesCount = header_phrases.length;
setHeader();
setTimeout(setHeader, repeatIn);
function setHeader() {
header_txt.textContent = header_phrases[updateCount++ % phrasesCount] || '';
}
}

Trying a infinite loop on JavaScript

I am trying to do a infinite loop, but it only works if I include an 'alert' on it. My code looks like this:
while( tocontinue ){
// Some code
alert('Accept to continue');
}
On this way, the user has to click to hide the alerts (for example, on Chrome), and then the loop continues correctly by itself. I need to implement this without any alert. I also tried this:
while( tocontinue ){
// Some code
tocontinue = false;
setTimeout(function(){tocontinue=true},500);
}
And with "window.setTimeout" too, and without the word "function(){}", but it doesn't work. I tried everything: some implementations on JavaScript of a sleep() function, calling the function each X time with setInterval, answers 1 and 3 on this post... :/
Thank you very much for your time.
I'm trying to implement a genetic algorithm, and I want to stop it when I decide (with a button that puts the global variable "tocontinue" to false). Meanwhile, I want a infinite loop.
Well, you won't be able to combine a true infinite loop with user interaction as they'll both be dependent on the same thread being able to work on them exclusively. But, you can get close with a near-instant interval.
var interval = setInterval(function () {
// some code
}, 10);
Possibly grouping a few iterations together for each round:
var interval = setInterval(function () {
var limit = 5;
while (limit--) {
// some code
}
}, 10);
But, the interval will keep the iteration going as quickly as possible while still giving some idle time for user interactions, like clicking a particular button to clear the interval.
document.getElementById('stopButton').addEventListener('click', function () {
clearInterval(interval);
}, false);
Example: http://jsfiddle.net/coiscir/xZBTF/
setInterval() may be more useful here.
function updateLoop() {
//All the code goes here
}
setInterval(updateLoop,500);
var reader = new XMLHttpRequest() || new ActiveXObject('MSXML2.XMLHTTP');
function loadFile() {
reader.open('get', 'ccc.txt', true);
reader.onreadystatechange = displayContents;
reader.send(null);
}
function displayContents() {
if(reader.readyState==4) {
var el = document.getElementById('main');
el.innerHTML = reader.responseText;
var data = el.innerHTML;
}
}
for(var I = 7; I >1; i+=3);
console.log(i)

how to make sure two setInterval() will not affect each other?

I have 2 setInterval function (okay guys, sorry, i thought the codes inside may be redundant and will make the question become localized :/ but anyway, here it is:
$('#armStatus').click(function(){
armingloop = setInterval(function () {
if ($checkbox.is(':checked ')) {
$.post('/request', {
key_pressed: "arming_status"
}).done(function (reply) {
$arm.empty().append("<h3>The Arming Status is " + reply + "</h3>").show();
$arm.show();
});
} else {
$arm.hide();
}
}, 3000);
});
and
$('#monitor').click(function () {
bigloop = setInterval(function () {
var checked = $('#status_table tr [id^="monitor_"]:checked');
if (checked.index() === -1 || checked.length === 0) {
clearloop(bigloop);
$('#monitor').button('enable');
} else {
//$('#monitor').button('enable'); //enable the monitor button
(function loop(i) {
//monitor element at index i
monitoring($(checked[i]).parents('tr'));
//delay of 3 seconds
setTimeout(function () {
//when incremented i is less than the number of rows, call loop for next index
if (++i < checked.length) loop(i);
}, 3000);
}(0)); //start with 0
}
}, index * 3000); //loop period
});
function clearloop(loopname){
bigloop= window.clearInterval(loopname);
}
Both will be triggered by different selector. I observe that when the bigloop is activated, and armingloop is also activated at a later time, the status update function monitoring in my bigloop is affected (e.g. status reply is captured by wrong element.)
Note that i have a setTimer as well.
My question here is, how can i make sure any 2 setIntervals are isolated and will not affect each other?
You simply can't as they have no guarantee of order. They are added to an event queue together with other events (incl. repaints etc.), and which ever comes first is called.
A better implementation would be in your main loop to throw a CustomEvent which your monitor is listening to.
Simplified:
// global flag for monitoring
var isMonitoring = true,
armingloop;
// the function we use to update monitor.
// This will be called every time we send an event
function updateMonitor(e) {
/* ... update ... */
// ie. e.detail.mydata
}
// Start listening to 'monitor' event. If received, call
// the function above (only reference the function).
window.addEventListener('monitor', updateMonitor, false);
// The main loop. Self-triggering for loop by calling
// setTimeout.
// Do the stuff you need and then, if monitor is
// enabled create an event and dispatch (send) it.
function loop() {
/* ... main stuff ... */
// do we monitor?
if (isMonitoring) {
// something need to be updated on monitor so
// create an event
var myEvent = new CustomEvent('monitor', {
detail: {
/* here you can provide needed data for monitor */
"mydata": data /*, other data */
},
/* If you don't need to send any data in particular,
just leave detail empty like this:
detail: {},
*/
bubbles: false,
cancelable: true
});
// send event to anyone who listens..
window.dispatchEvent(myEvent);
}
//here you can use a use a flag to stop the loop,
//f.ex. if 'isLooping' === true then setTimeout...
armingloop = setTimeout(loop, 3000);
}
function toggleMonitor() {
// Call this from the toggle button, or modify to
// reflect checkbox-status etc...
isMonitoring = !isMonitoring;
}
//start everything:
loop();
I changed the example a bit from setInterval to setTimeout to avoid stacking/blocking. Also keep in mind that Javascript is single-threaded (with a few exceptions that are not relevant here). For this reason setTimeout is a better choice (call it from inside the loop).
How can I make sure any 2 setIntervals are isolated and will not affect each other?
Variable scope
Make sure that all of the variables involved are correctly scoped, and avoid adding any to the global scope unless it's completely unavoidable (this shouldn't be the case). This means you'll want to be using the var keyword whenever you declare any variables.
If your variables are correctly scoped to their respective setInterval calls then there's no danger of one affecting values in the other, even if you've used the same variable names.
Check your logic
If you're querying, and then modifying, the same set of elements on the page in both of them then they can't be independent, since changes in one of them will then be reflected in the next execution of the other one. Any shared logic, any use of global variables, etc. are all potential candidates for issues to be introduced.
Essentially you're looking for any overlap between the two, and then (hopefully) eliminating that. If it can't be eliminated then your two setIntervals can't be isolated, and you either have to accept that the two are linked or find another approach to solving the problem.
How are the intervals triggered?
Maybe you can try to call them in for example a click function:
$('<ELEMENT>').click( function() {
setInterval(function(){
},3000);
});
$('<ELEMENT>').click( function() {
setInterval(function () {
var checked = $('#status_table tr [id^="monitor_"]:checked');
if (checked.index()===-1 ||checked.length===0){
clearloop(bigloop);
$('#monitor').button('enable');
}else{
//$('#monitor').button('enable'); //enable the monitor button
(function loop(i) {
//monitor element at index i
monitoring($(checked[i]).parents('tr'));
//delay of 3 seconds
setTimeout(function () {
//when incremented i is less than the number of rows, call loop for next index
if (++i < checked.length) loop(i);
}, 3000);
}(0)); //start with 0
}
}, index*3000); //loop period
});

Can anyone explain why this JavaScript wont run

Im not very good wit JS and I just dont get why this wont work!
The code uses jquery to apply the pulsate efect to one of my divs and run forever unless I stop it with another function, but I cannot figure our why my first piece of code wont run!
function animate(var x){
// Do pulsate animation
$(x).effect("pulsate", { times:4 }, 5000);
// set timeout and recall after 10secs
setTimeout(animate, 10000);
}
$(document).ready(animate("#mydiv"));
Only way to get it working is for me to do this
function animate(){
// Do pulsate animation
$("#mydiv").effect("pulsate", { times:4 }, 5000);
// set timeout and recall after 10secs
setTimeout(animate, 10000);
}
$(document).ready(animate);
Note that in the first snippet the code uses variables to be more useful and the second piece has the selectors name hardcoded
Don't use var in your function declaration. Just use:
function animate(x){
Also, you probably want something like this for your first example:
function animate(x){
return function () {
function animateInner() {
$(x).effect("pulsate", { times:4 }, 5000);
setTimeout(animateInner, 10000);
}
animateInner();
};
}
$(document).ready(animate("#mydiv"));
DEMO: http://jsfiddle.net/XHKbC/
Otherwise, the original animate("#mydiv") call executes immediately (and $(x) probably won't find anything since the DOM isn't ready yet). $(document).ready() expects a reference to a function. You called a function instead. But that's all a little overkill. Just use:
$(document).ready(function () {
animate("#mydiv");
});
but you'll have to change your function so the setTimeout passes the value of x as well:
function animate(x){
// Do pulsate animation
$(x).effect("pulsate", { times:4 }, 5000);
// set timeout and recall after 10secs
setTimeout(function () {
animate(x);
}, 10000);
}
DEMO: http://jsfiddle.net/XHKbC/2/
Although it's a little more code/complex, my first example doesn't suffer the problem in my second (having to pass x in the setTimeout) by using a closure.
UPDATE:
Being shown how you are using this code, I'd set it up like this:
function Animater(target) {
var self = this;
var animateTO;
var animateElement = target;
function animate() {
animateElement.effect("pulsate", { times:4 }, 5000);
animateTO = setTimeout(animate, 10000);
}
self.start = function () {
animate();
};
self.stop = function () {
animateElement.finish();
clearTimeout(animateTO);
};
}
And create a new one like:
var mydivAnimater = new Animater($("#mydiv"));
You can then call .start() and .stop() on it, and you create any number of these Animater objects on different elements as you want.
DEMO: http://jsfiddle.net/K7bQC/3/
Your code has two issues:
omit the var:
function animate(x){
modify your event handler:
$(document).ready(function(){
animate("#mydiv");
});
You need to hand over a function reference (either animate or function(){}), not run the code right away which you are doing if you pass animate().
Now to not lose the reference to your x you have to modify the animate call in the timeout too:
setTimeout(function () {
animate(x);
}, 10000);
You dont need to type var when specifying a function parameter.

Interrupt jQuery delay function

I am working on a way to autocomplete function to navigate through steps of a form. Here is the code that when 5 characters are entered into an input, it then moves to the next element. My delay is working great, but I don't have a way to stop it from completing if characters get deleted after 5 characters are entered. It just fires off the focus right after that no matter what has changed in the input.
Any thoughts?
var delay = (function(){
var timer = 0;
return function(callback, ms) {
clearTimeout (timer);
timer = setTimeout(callback, ms);
};
})();
$('input').keyup(function(){
if($(this).val().length == 5) {
delay(function(){ $("#saveForm2").focus(); }, 2000 );
}
})
If you're looking for an easy way to associate a timeout instance with an element, consider using jQuery's .data() method.
Something like this.
$('input').keyup(function() {
var $th = $(this);
var data = $th.data();
if(data.timeout === undefined) {
data.timeout = null;
}
if ($th.val().length == 5) {
clearTimeout(data.timeout);
data.timeout = setTimeout(function() {
$("#saveForm2").focus();
}, 2000);
} else {
clearTimeout(data.timeout);
}
});​
I don't think the way you were using the closure was quite right. I think you would need to assign the handler to the element inside the closure as well, so it has a local reference to the instance.
EDIT: Made a little more efficient with earlier stored reference to data().
Each time you call delay(), you clobber your timeout handle, which means that you can't manage it after the fact. It also means that you're going to fire off a request every time you hit 5 characters, if I read that correctly. Try something like this:
var delayTimer;
var nextField = function() { $("#saveForm2").focus(); }
$('input').keyup(function(){
clearTimeout(delayTimer);
if($(this).val().length >= 5) {
delayTimer = setTimeout(nextField, 2000);
}
})
That'll a) fire off no more than 1 request unless you wait more than 2 seconds between keystrokes, and b) will cancel any pending request if you drop back under 5 characters before the timeout expires. As a bonus, it won't create a whole mess of anonymous functions.

Categories

Resources