Javascript recursive call creates range-error - javascript

I'm trying to get the following script working, but when it falls into the continueAnimation function it isn't updating the cPreloaderTimeout variable and it runs in a 'Uncaught RangeError: Maximum call stack size exceeded'.
var loadingIcon = {
cSpeed : 8,
cWidth : 64,
cHeight : 32,
cTotalFrames : 7,
cFrameWidth : 64,
cImageSrc : 'sprites.png',
cImageTimeout : false,
cIndex : 0,
cXpos : 0,
cPreloaderTimeout : false,
SECONDS_BETWEEN_FRAMES : 0,
startAnimation : function() {
document.getElementById('loaderImage').style.backgroundImage='url('+ this.cImageSrc+')';
document.getElementById('loaderImage').style.width=this.cWidth+'px';
document.getElementById('loaderImage').style.height=this.cHeight+'px';
//FPS = Math.round(100/(maxSpeed+2-speed));
FPS = Math.round(100/this.cSpeed);
SECONDS_BETWEEN_FRAMES = 1 / FPS;
this.cPreloaderTimeout = setTimeout( this.continueAnimation(), SECONDS_BETWEEN_FRAMES/1000);
},
continueAnimation : function() {
this.cXpos += this.cFrameWidth;
//increase the index so we know which frame of our animation we are currently on
this.cIndex += 1;
//if our cIndex is higher than our total number of frames, we're at the end and should restart
if (this.cIndex >= this.cTotalFrames) {
this.cXpos =0;
this.cIndex=0;
}
if(document.getElementById('loaderImage'))
document.getElementById('loaderImage').style.backgroundPosition=(-this.cXpos)+'px 0';
this.cPreloaderTimeout = setTimeout(this.continueAnimation(), SECONDS_BETWEEN_FRAMES*1000);
},
stopAnimation : function(){//stops animation
clearTimeout( this.cPreloaderTimeout );
this.cPreloaderTimeout = false;
}
}
jQuery( document ).ready(function() {
jQuery( document ).on("click", "#test", function(){
var loader = loadingIcon;
loader.startAnimation();
setTimeout( loader.stopAnimation(), 3000);
});
});
It was a plain javascript script at first, but I'm trying to make an object from it so it can be re-used and multiple times at the same time. The problem is now that the cPreloaderTimeout variable isn't set correctly when startAnimation or continueAnimation is triggerd.

You have a couple issues.
First, your cPreloaderTimeout isn't going to be set like you think it is, as you're not creating an object with a prototype, so the scope of this inside that function is going to be the function itself, not the object.
Second, setTimeout takes a function, but you're calling the function when you try and use it, so the value sent to setTimeout will be the results of the function, not the function itself.
Consider instead the format:
function LoadIcon() {
this.cSpeed = 8;
// All your other properties
}
LoadIcon.prototype.startAnimation = function() {
// your startAnimation function, concluding with
this.preloaderTimeout = setTimeout(this.continueAnimation.bind(this), SECONDS_BETWEEN_FRAMES/1000);
}
// the rest of the methods built the same way
//then, your calling code would look like:
var loadIcon = new LoadIcon();
loadIcon.startAnimation();
EDIT
I updated the setTimeout call as I'd forgotten about binding to this for correct scoping when the callback fires.

Related

How can I change this into a loop instead of a recursive function?

So I have a piece of code like
var barlen = $('#SSWEprogressbar').width(),
$elems = $('[data-srcurl]'),
k = 0,
n = $elems.length;
LoadImage();
function LoadImage()
{
var $elem = $($elems[k]);
var img = new Image(),
url = $elem.attr('data-srcurl');
$(img).load(function(){
$('#SSWEloaderfront').attr('src',url);
$('#SSWEloadprogress').width((k+1)/n*barlen + "px");
var srctgt = $elem.attr('data-srctgt');
// change url to src attribute or background image of element
if ( srctgt == "srcattr" ){ $elem.attr('src',url); }
else if ( srctgt == "bgimg" ) { $elem.css('background-image',"url("+url+")"); }
// decide whether to exit the
if ( ++k == n ) { AllyticsSSWEPlayerShow(); }
else { LoadImage(); }
});
img.src = url;
}
and the reason I have it written that way is because load callback needs to be called before the stuff in the function can be executed again. If possible, I'd like to change this from a recursive function to a loop, but I don't know how to do that because there's no way to make a for or while loop "wait" before going on to the next iteration. Or is there?
As I mentioned in the comment you can easily resolve your problem, by using setTimeout(LoadImage, 100); in the else instead of calling the function directly. The 2nd parameter is the delay in ms.
If you understand why setTimeout(LoadImage, 0); is not stupid and not the same as calling the function directly then you understood setTimeout. It puts the function call in the queue, this means other events like clicks or keys that were pressed can be processed before the function is called again and the screen doesn't freeze. It's also impossible to reach max recursion like this, the depth is 1.

Javascript object property check if returned true

Using a library called Velocity JS for animating: http://julian.com/research/velocity/
I am using it as follows:
var velocity = new Velocity(element, {
translateX: 250,
complete: function() {
return true;
}
}, 5);
What I am trying to do is check if complete has returned true on successful completion of the animation so that I can toggle the animation.
What is the best method for checking if it is true
if(velocity.complete() === true)
This returns undefined.
Any help is appreciated thanks.
If you need to store the fact that the animation has completed, in order to read that information later based on user interaction (e.g., clicking a button), simply set a variable in an outer scope:
var animationComplete = false;
var velocity = new Velocity(element, {
translateX: 250,
complete: function() {
animationComplete = true;
}
}, 5);
And read it when the user interaction happens:
$("#someButton").on("click", function() {
if(animationComplete) {
// do something only if the animation has completed
}
});
It does not matter where you store that value (as a local or global variable, as a property on an object, etc.), as long as it's visible to the functions that need to read and set it.
Note that the return value of the complete callback is never used. The complete callback function object is passed into the Velocity constructor, and it is called later from inside of velocity.js's own code somewhere. Velocity.js probably does not expose (or even store) this return value anywhere.
You already pass the complete function, but returning true is not so helpful in this case. Start the second animation when the complete function is called:
var velocity = new Velocity(element, {
translateX: 250,
complete: function() {
// start another animation
}
}, 5);
If you need to toggle animation on click or another event you have to store animation boolean outside somewhere, even in the dom element or using jQuery data method:
var $velocity = $("#velocity");
$velocity.data("animating", false);
$velocity.data("direction", "left");
$velocity.on("click", function () {
if ($velocity.data("animating")) {
return;
}
$velocity.data("animating", true);
if ($velocity.data("direction") === "left") {
var ml = -30;
$velocity.data("direction", "right");
} else {
var ml = 30;
$velocity.data("direction", "left");
}
$velocity.velocity({marginLeft:ml},{
duration:500,
complete: function () {
$velocity.data("animating", false);
}
});
});
JSFIDDLE

Set global variable within anonymous function

I want to query a value from a script and use the value as variable for further functions. This code returns zero despite a value is set.
var total = 0;
var jqxhr = $.getJSON("number.php?jsonp=?", function(data) {
total = data[0]['Total']; // e.g 1234567
});
alert (total); // gives zero
I read that the problem could be with the asynchronous call and that my alert is executed before the anonymous function. I also tried to use some kind of setter function but withous success.
How can I set the global variable total?
Edit:
I now tried to use deferred objects / promises. I still have the problem that I need the value from the AJAX call before I initialise my counter. I cannot create the counter object in the done section because I cannot later change the value of the counter (still no global variable). Currently I have this:
var counter = $('.counter').jOdometer({
counterStart: '0',
numbersImage: '/img/jodometer-numbers-24pt.png',
widthNumber: 32,
heightNumber: 54,
spaceNumbers: 0,
offsetRight:-10,
speed:10000,
delayTime: 300,
maxDigits: 10,
});
function update_odometer() {
var jqxhr = $.getJSON("/number.php?jsonp=?")
.done(function(data){
console.log(data);
total = data['Total'];
counter.goToNumber(total);
})
.fail(function(data){
console.log(data);
});
};
update_odometer();
setInterval(update_odometer, 60000);
If I initialize the counter with zero then there is a strange start animations which jumps up and down. If I could get the real number instead of zero everything would work fine. Or should I completely switch to a synchronous call? How would I do that? Or are callbacks the better solution?
Took me a while to understand your problem. Still I'm not sure whether this would help or not.
var counter = null;
function update_odometer() {
var xhr = $.getJSON("/number.php?jsonp=?").done(function(data) {
if (counter === null) {
var counter = $('.counter').jOdometer({
counterStart: data['Total'],
numbersImage: '/img/jodometer-numbers-24pt.png',
widthNumber: 32,
heightNumber: 54,
spaceNumbers: 0,
offsetRight:-10,
speed:10000,
delayTime: 300,
maxDigits: 10,
});
} else {
counter.goToNumber(data['Total']);
}
});
}
If I were you I would look into library code and rectify the strange animation for zero start value. And I understand that you are not afraid to do that.

Delayed jCanvas drawing of a line

I have already tried my luck and searched a lot but could not find a solution to my issue.
The function in question is supposed to draw a set of lines using jcanvas and pause the drawing according to prerecorded times.
Instead it just draws the entire lines at once.
Here is the jQuery code in question:
$("#start").click(function(){
$("canvas").css("display","block");
var obj = { strokeStyle: "#000", strokeWidth: 6, rounded: true};
for (i=0;i<counter;i++)
{
obj['x'+(i+1)] = arrX[i];
obj['y'+(i+1)] = arrY[i] - 12;
setTimeout(function() {
var interval = setInterval(function() {
$("canvas").drawLine(obj);
}, 0);
}, timeDiffs[i]);
}
});
Because the loop finishes before your setTimeout() callback is run, your callback will always reference the final state of the object (that is, its state after the entire loop has run).
To fix this, you can wrap your setTimeout() callback in a closure. By wrapping the callback in a closure function, I am capturing the state of the obj variable. However, in this case, because objects are passes by reference in JavaScript, I have cloned the object (using $.extend) to ensure that its current state (properties and all) is preserved.
setTimeout((function(obj) {
return function() {
var interval = setInterval(function() {
$("canvas").drawLine(obj);
}, 0);
};
}($.extend({}, obj))), timeDiffs[i]);
FWIW, there is an extensive examination of this issue on StackOverflow.
just a simplification of Caleb531's point
$("#start").click(function(){
$("canvas").css("display","block");
var obj = { strokeStyle: "#000", strokeWidth: 6, rounded: true};
for (i=0;i<counter;i++)
{
(function(){
obj['x'+(i+1)] = arrX[i];
obj['y'+(i+1)] = arrY[i] - 12;
var incremental = $.extend({}, obj);
setTimeout(function() {
var interval = setInterval(function() {
$("canvas").drawLine(incremental);
}, 0);
}, timeDiffs[i]);
})();
}
});

cannot access function within function in javascript

I need to know what I am doing wrong because I cannot call the internal functions show or hide?
(function()
{
var Fresh = {
notify:function()
{
var timeout = 20000;
$("#notify-container div").get(0).id.substr(7,1) == "1" && (show(),setTimeout(hide(),timeout));
var show = function ()
{
$("body").animate({marginTop: "2.5em"}, "fast", "linear");
$("#notify-container div:eq(0)").fadeIn("slow");
},
hide = function()
{
$("#notify-container div").hide();
}
}//END notify
}
window.Fresh = Fresh;
})();
Fresh.notify();
thanks, Richard
UPDATE
If you wanted to be able to do something like: Fresh.notify.showMessage(), all you need to do is assign a property to the function notify:
var Fresh = {notify:function(){return 'notify called';}};
Fresh.notify.showMessage = function () { return this() + ' and showMessage, too!';};
Fresh.notify();//notify called
Fresh.notify.showMessage();//notify called and showMessage, too!
This will point to the function object here, and can be called as such (this() === Fresh.notify();). That's all there is too it.
There's a number of issues with this code. First of all: it's great that you're trying to use closures. But you're not using them to the fullest, if you don't mind my saying. For example: the notify method is packed with function declarations and jQuery selectors. This means that each time the method is invoked, new function objects will be created and the selectors will cause the dom to be searched time and time again. It's better to just keep the functions and the dom elements referenced in the closure scope:
(function()
{
var body = $("body");
var notifyDiv = $("#notify-container div")[0];
var notifyDivEq0 = $("#notify-container div:eq(0)");
var show = function ()
{
body.animate({marginTop: "2.5em"}, "fast", "linear");
notifyDivEq0.fadeIn("slow");
};
var hide = function()
{//notifyDiv is not a jQ object, just pass it to jQ again:
$(notifyDiv).hide();
};
var timeout = 20000;
var Fresh = {
notify:function()
{
//this doesn't really make sense to me...
//notifyDiv.id.substr(7,1) == "1" && (show(),setTimeout(hide,timeout));
//I think this is what you want:
if (notifyDiv.id.charAt(6) === '1')
{
show();
setTimeout(hide,timeout);//pass function reference
//setTimeout(hide(),timeout); calls return value of hide, which is undefined here
}
}//END notify
}
window.Fresh = Fresh;
})();
Fresh.notify();
It's hard to make suggestions in this case, though because, on its own, this code doesn't really make much sense. I'd suggest you set up a fiddle so we can see the code at work (or see the code fail :P)
First, you're trying to use show value when it's not defined yet (though show variable does exist in that scope):
function test() {
show(); // TypeError: show is not a function
var show = function() { console.log(42); };
}
It's easily fixable with moving var show line above the point where it'll be called:
function test() {
var show = function() { console.log(42); };
show();
}
test(); // 42
... or if you define functions in more 'traditional' way (with function show() { ... } notation).
function test() {
show();
function show() { console.log(42); };
}
test(); // 42
Second, you should use this instead:
... && (show(), setTimeout(hide, timeout) );
... as it's the function name, and not the function result, that should be passed to setTimeout as the first argument.
You have to define show and hide before, also change the hide() as they said.
The result will be something like this:
(function()
{
var Fresh = {
notify:function()
{
var show = function()
{
$("body").animate({marginTop: "2.5em"}, "fast", "linear");
$("#notify-container div:eq(0)").fadeIn("slow");
},
hide = function()
{
$("#notify-container div").hide();
},
timeout = 20000;
$("#notify-container div").get(0).id.substr(7,1) == "1" && ( show(), setTimeout(hide,timeout) );
}//END notify
}
window.Fresh = Fresh;
})();
Fresh.notify();
I think order of calling show , hide is the matter . I have modified your code . It works fine . Please visit the link
http://jsfiddle.net/dzZe3/1/
the
(show(),setTimeout(hide(),timeout));
needs to at least be
(show(),setTimeout(function() {hide()},timeout));
or
(show(),setTimeout(hide,timeout));

Categories

Resources