"too much recursion" in my rotating banner script - javascript

My rotating banner script is throwing a "Too much recursion" error when the iKeyless.widget.Rotator.rotate(); function is called within init();. I can't figure out why, init(); is only called once and there's a defer() on the second call.... when the page loads it lags before throwing "too much recursion"
return {
init: function () {
ui.container = $("#rotating-banner");
ui.thumbs = $("#rotating-banner .tabs .tab");
ui.banners = $("#rotating-banner .banner");
ui.fadeBox = $(".rotate .bd .fade-box");
ui.thumbs.each(function (idx, el) {
$(el).click(function () {
paused = true;
iKeyless.widget.Rotator.rotate.show(idx);
});
});
iKeyless.widget.Rotator.rotate();
},
show: function (idx) {
ui.fadeBox.css("display", "block");
ui.fadeBox.animate({
opacity: 1
},
.125,
function () {
$('.active', $(ui.banners[idx]).addClass('active').parent()).removeClass('active');
ui.fadeBox.animate({
opacity: 1
},
.125,
function () {
ui.fadeBox.css("display", "none");
});
});
},
rotate: function () {
if (!paused) {
this.show(counter);
counter = counter + 1;
if (counter === ui.thumbs.length) {
counter = 0;
}
iKeyless.widget.Rotator.rotate().defer(5000);
}
}
}

I think the issue is that you need to do this
iKeyless.widget.Rotator.rotate.defer(5000);
instead of this
iKeyless.widget.Rotator.rotate().defer(5000);
Source: When you write rotate(), you execute the function rotate and then execute defer. When you write rotate.defer() you are getting the function rotate, and using the method defer that is defined on Function.
Also, if this is jQuery, how do you have defer defined? I'm not sure jQuery provides a defer function. If this is Prototype, I don't think defer takes any arguments. Perhaps you want delay. Delay takes a time in seconds, so you'd want (assuming 5 seconds, not 5000 seconds):
iKeyless.widget.Rotator.rotate.delay(5);

I didn't know the defer, but what I read is this:
Schedules the function to run as soon as the interpreter is idle.
But that doesn't mean that it doesn't actually do it. The function rotate calls itself, again and again. Not linear, but inside itself, so you'll never end the 'top' calling rotate.
This would mean infinite recursion, and so I'm not surprised at that error.

I'm not familiar with defer() but you're calling rotate() inside your rotate method which is where the recursion is coming from. Perhaps leveraging something like setTimeout or setInterval is a way to work around your issue?

Related

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

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.

Wait for a recursive jQuery function to completely finish

Another one that should be simple, but is giving me trouble. I am trying to learn about jQuery's .Deferred() and .promise() functionality to delay certain actions until a recursive function is completely finished. Currently, my code is similar to the following:
function showText(setTarget, setMessage, setIndex, setInterval) {
var defer = jQuery.Deferred();
var doShowText = function (target, message, index, interval) {
if (index < message.length) {
$(target).append(message[index++]);
setTimeout(function () { doShowText(target, message, index, interval); }, interval);
}
else {
alert("Done!");
defer.resolve();
}
};
doShowText(setTarget, setMessage, setIndex, setInterval);
return defer.promise();
}
function startButtonClick() {
displayElement($("#getElement"));
showText($("#getElement > h1"), "This text will slowly write to the screen.", 0, 50).promise()
.then(alert("Finished."));
}
When this runs, "Finished" alert (which I am trying to defer) runs after the first execution of the recursive script, so it will appear when only one letter has been printed (not the intended result). The "Done" alert, however, appears correctly once all of the letters have been printed and the recursion is finished, so it seems as though my defer variable should not be resolved until then. Can anyone help me discover why the "Finished" alert is being called early here? Any assistance is appreciated!
EDIT: I realized I had accidentally posted a slightly older version of my code. It has been updated with the correct version (the behavior at run time is the same).
That's happening because you are actually executing the alert function right away instead of passing a function reference.
Do this instead:
.then(alert.bind(null, 'finished'));
Or
.then(function () {
alert('finished');
});
In the startButtonClick function you don't need to call .promise() on the result of showText, because you are already doing that inside of showText. Next, the argument to the then callback should be a function, right now you are immediately calling the alert function, not passing it as a function, which is why it is displaying immediately, so just wrap it in a function:
function(){ alert("Finished."); }
Here is a jsfiddle with the code: http://jsfiddle.net/RnLXF/
Here's a fiddle with the whole lot wrapped up in a nice little object: http://jsfiddle.net/YVZKw/3/
As plalx and ctcherry have already stated, the biggest problem was the lack of a function in your .then call.
HTML
<a href='javascript:void(0)'>Start</a>
<div id='sampleElement'>
<h1></h1>
</div>
JavaScript
$('a').on('click', function(){
new ShowText(
$("#sampleElement > h1"),
"I will slowly write text to the screen.",
50
)
.done(function(){
alert("Finished.")
});
});
function ShowText(target, message, speed)
{
me = this;
me.target = target;
me.message = message;
me.index = 0;
me.speed = speed;
me.defer = new $.Deferred();
me.interval = setInterval(function() {
me.target.append(me.message[me.index++]);
if (me.index > me.message.length) {
clearInterval(me.interval);
me.defer.resolve();
}
}, me.speed);
return me.defer;
}

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.

setTimeout not working after hours of research.

I have been looking for answers for hours and it seems that my problem is with closure. I could not figure out how to fix it in my scenario. I want to trigger an animation onload on 6 elements, all 1000ms apart. I can't get the setTimeout method to work properly. Please help!
$(document).ready(function() {
we();
});
var data = ['450px', '300px', '200px', '120px', '250px', '320px'];
var bars = $('.bar');
var i = 0;
function grow(size, elem) {
$(elem).animate({
height: size,
opacity: '1.0'
}, 1000);
}
function we() {
setTimeout (function(){ // if I don't use an annonymous function call here, they all animate at the same time
grow(data[i], bars[i]);
}, 1000);
if (i >= bars.length) {
return;
} else {
i++;
return we();
};
}
You have to put your setTimeout around the rest of your function as well; not just the call to grow.
function we() {
setTimeout (function(){
grow(data[i], bars[i]);
if (i >= bars.length) {
return;
} else {
i++;
return we();
};
}, 1000);
}
I think what you've missed is that setTimeout does not block your program. It doesn't stop execution, wait for 1000 ms and then continue. Instead, it will delay the execution of the anonymous function for 1000 ms, but the rest of the program will continue to execute right away. And that part of your program will call we once again, and schedule a new animation (almost at the same time as the last one). And then another and so on.
By putting the recursive call inside setTimeout, we delay the recursion as well and hence all future animation steps.

Weird infinite loop bug in jquery recursive function

I have a strange issue on the project I'm working with. This changes an image source and a content of a div automatically.
I have coded a function, but it falls into infinite loop and page does not load (page is showing the loading page always).
These are the codes:
$.fn.extend({
changehaber: function(a){
$('#cokokunanlarcontent').fadeOut('slow',function() {
$('.jquerycokokunanlarbutton').attr('src','images/sabah/buton-pasif.png');
$('img[rel="'+a+'"]').attr('src','images/sabah/buton-aktif.png');
}).html($('#'+a).html()).fadeIn('slow');
return this;
}
});
function slidecokokunanlar() {
$('#cokokunanlarcontent').html($('#cokokunanlar1').html()).delay(3000).changehaber('cokokunanlar2').delay(3000).changehaber('cokokunanlar3').delay(3000).changehaber('cokokunanlar4').delay(3000).changehaber('cokokunanlar5').delay(3000);
slidecokokunanlar();
}
slidecokokunanlar();
What's the issue here, when this is executed, I want the function to work infinitely, but the page shows it's always loading. This is the console's output:
Uncaught RangeError: Maximum call stack size exceeded
Thanks in advance
You can't call a function from inside itself without blocking up the whole execution stack. By calling the function from inside itself, you're effectively preventing it from ever returning, and as Javascript is single-threaded, everything will grind to a halt!
Change your function to this:
function slidecokokunanlar() {
$('#cokokunanlarcontent').html($('#cokokunanlar1').html()).delay(3000)...
setTimeout(slidecokokunanlar, 0);
}
This allows for concurrent execution without blocking the UI, thus allowing your page to remain responsive.
See this article on "chunking" for more information on how this works.
This is because JavaScript doesn't have proper tail calls.
Your function calls itself at the end of itself forever. The first one never finishes and returns, nor does the second, nor do any of them until you run out of stack and explode.
You might try using setTimeout instead. See an example on jsFiddle.
EDIT You might not want to use 0 unless you really need it to be running continuously. Even using 100, you'd execute the function 10 times per second.
function foo(){
console.log('foo');
setTimeout(foo, 0);
}
foo();
Here's a cleaner way to do it.
var coko = $('#cokokunanlarcontent'); // cokokunanlarcontent
var cokos = $('[id^="cokokunanlar"]').not(coko); // cokokunanlar1..2..3 etc
var total = cokos.length; // total quantity
var i = 0;
var allow = true;
$('.jquerycokokunanlarbutton').attr('src','images/sabah/buton-pasif.png');
function slidecokokunanlar( isRestart ) {
if( !isRestart ) {
$('img[rel="' + cokos[i].id + '"]').attr('src','images/sabah/buton-aktif.png');
coko.html( cokos.eq(i).html() )
.fadeIn( 'slow' );
}
if( allow ) {
coko.delay( 3000 )
.fadeOut('slow', function() {
i = (++i % total);
slidecokokunanlar(); // recursively call with next index or 0
});
}
}
slidecokokunanlar(); // start it off
function restartSlider() {
allow = true;
slidecokokunanlar( true );
}
function stopSlider() {
allow = false;
}
stopSlider(); // to stop it
restartSlider(); // to restart it

Categories

Resources