Backbone.js setTimeout() loop in CoffeeScript - javascript

Seems like every way I try this, it throws some sort of error. Here's what my code looks like now:
runShow: ->
moments = #model.get('moment_stack_items')
if inc == moments.length
inc = 1
pre = 0
$("#" + moments[pre].uid).hide("slide", { direction: "left" }, 1000)
$("#" + moments[inc].uid).show("slide", { direction: "right" }, 1000)
inc += 1
pre += 1
console.log "looping" + inc
t = setTimeout(this.runShow(),2000);
I call the function in my events.
I have inc = 1 and pre = 0 defined outside the Backbone.View.. My current error is "Uncaught TypeError: Object [object DOMWindow] has no method 'runShow'"
BONUS POINTS: how can I reference t from another function (to run my clearTimeout(t))?

You ask the setTimeout function to evaluate "this.runShow()", and setTimeout will do that in the context of window. This means that this is the window object when this code is evaluated.
To avoid this you can create a function and bind it to a the current context, so that everytime the function is called, this is the same as when the function has been created.
In coffee script you can do this with the =>:
func = =>
this.runShow()
setTimeout(func, 2000)
Or on a single line:
setTimeout((=> this.runShow()), 2000)
how can I reference t from another function?
Make t a property of your object:
class Something
t: null
runShow: ->
...
this.t = ...
otherFunction: ->
t = this.t

Related

How do I export this function without its value being undefined? [duplicate]

This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 6 days ago.
I want to export the function manualStrobeTimeout with the values especified within the manualBPM_text.addEventListener("change")'s scope. Right now, the console logs the export as undefined, meaning that the value doesn't change from when the variable was declared. How would I export the function with the values declared within that scope? Keep in mind that I cannot declare the function's values outside of the event listener, as this would interfere with the set intervals.
Here's the relevant module's code:
import { strobeActive } from "./onBtn.js"; // variable to check if ON button is active/pressed
// manual bpm variables definition and event listener
var manualBPM = 0;
var manualBPM_interval = 0;
const body = document.querySelector("body");
var ranTimes = 0;
// strobe duration variable definition and event listener
var duration = 100;
const slider = document.getElementById("MM-duration-slider");
slider.addEventListener("input", function() {
duration = slider.value;
}, false);
var manualBPM_text = document.getElementById("manual-value");
var manualStrobeTimeout;
if (strobeActive == true) {
manualBPM_text.addEventListener("change", function() {
clearInterval(manualStrobeTimeout); // so that old value doesn't interfere with new value
manualBPM = manualBPM_text.value;
manualBPM_interval = (60 / manualBPM) * 1000;
manualStrobeTimeout = function() {
// repeat once the interval expires
setInterval(function() {
// trigger strobe
body.classList.remove("bg-black");
body.classList.add("bg-white");
// kill strobe once the strobe duration expires
setTimeout(function() {
body.classList.remove("bg-white");
body.classList.add("bg-black");
}, duration);
ranTimes++;
console.log("BPM: " + manualBPM + " (source: " + BPMvalueSource + ") | Strobe duration: " + duration + "ms | " + "Times ran: " + ranTimes);
}, manualBPM_interval);
}
}, false);
}
export { manualBPM_text };
export { manualStrobeTimeout };
I want use the imported function in the following statement (on a seperate JS file):
if (BPMvalueSource == "manual") {
manualStrobeTimeout();
console.log(manualStrobeTimeout()); // returns "undefined"
} else if (BPMvalueSource == "tap") {
tapBPM_strobe();
}
I have tried using window. to set the function as global, but to no avail. I have also made sure that I am importing and exporting correctly, and also tried using a dynamic import. This also did not work. Both JS files have the attribute type="module" especified.
The clearInterval was not clearing the interval.
The interval would not even be able to run since the wrapping function was never called
You cannot export non-constat values
let lastManualBPMValue;
if (strobeActive == true) {
manualBPM_text.addEventListener("change", function() {
lastManualBPMValue = manualBPM_text.value;
}, false);
}
export { manualBPM_text };
let manualStrobeTimeout;
export function manualStrobe() {
clearInterval(manualStrobeTimeout); // so that old value doesn't interfere with new value
manualBPM_interval = (60 / lastManualBPMValue) * 1000;
manualStrobeTimeout = setInterval(function() {
...
}, manualBPM_interval);
}
The function that is assigned to the manualStrobeTimeout variable has no return statement in it, and as such will always return undefined when executed.
If you want to check that manualStrobeTimeout is assigned a value, you may want to do console.log(manualStrobeTimeout) instead, which will log the value assigned to the variable.
With console.log(manualStrobeTimeout()) you are executing the manualStrobeTimeout function, and its return value will be fed to console.log. The return value of a JavaScript function is undefined by default.
Keep in mind that manualStrobeTimeout will not be a function unless the "change" event listener has triggered.
You didn't directly ask about this, but you may also want to look at the documentation for clearInterval(). The code is attempting to call clearInterval on a function, not an interval ID.

Javascript recursive call creates range-error

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.

Testing nested object with jasmine

Here is my Test
describe("setTimer", function () {
it("set status timer values from parameters and sets timer.visible to true", function(){
var boxNumber = 1,
time = 15;
myObject.setTimer(boxNumber, time);
expect(anotherObject.status.timer.boxNum).toBe(boxNumber);
expect(anotherObject.status.timer.seconds).toBe(time);
})
});
Here is the code
setTimer: function (boxNum, seconds) {
anotherObject.status.timer.boxNum = boxNum;
anotherObject.status.timer.seconds = seconds;
anotherObject.status.timer.visible = true;
},
Here is the error I am getting
TypeError: Cannot read property 'timer' of undefined
I tried setting the object using anotherObject = {} I tried setting anotherObject.status = {} and lastly tried setting anotherObject.status.timer = {}, however I still get the error. Any ideas, how can I mock the object?
Without knowing how/where 'anotherObject' is constructed I would think that you would need to initialize the 'anotherObject' before you execute the setTimer function in your test.
Do you have an init() or setup() function that exists on 'anotherObject' that would initialize the 'timer' object for you?
Although the method looks like it is just trying to make sure that the method is setting all the corresponding properties.
You could do the following before calling setTimer in your test
describe("setTimer", function () {
it("set status timer values from parameters and sets timer.visible to true", function(){
var boxNumber = 1,
time = 15;
//Initialize the anotherObject
anotherObject.status = { timer : {} }
myObject.setTimer(boxNumber, time);
expect(anotherObject.status.timer.boxNum).toBe(boxNumber);
expect(anotherObject.status.timer.seconds).toBe(time);
})
});
This of course comes with the caveat that you have now defined an 'anotherObject' inside your test using the global scope (since excluding the var on any variable definition in javascript makes it global scope). This could effect other test cases that expect the timer object to be setup a certain way but your test case has now set the timer values to 1 and 15 respectively (could be alot of other values all depending on what the test case is doing).
So to help with this, resetting the 'anotherObject' at the beginning or end of your tests would help with pollution
afterEach(function(){
anotherObject.status = { timer : {} }
})
or
beforeEach(function(){
anotherObject.status = { timer : {} }
})
Of course if you have an init(), create() or setup() function on the 'anotherObject' that could be used it would of course give you more realistic results since the object would be much closer to what it would look like in production.
You are not working on the same "anotherObject" object in both source and test codes.
Each code has it's own object and setting values to one will not set in the other.

Integer returning as NaN when added

Writing some code, and when creating an instance of a class, something strange happens with an integer variable I have:
function Mat(x, y, spawner) {
this.x = x;
this.y = y;
this.val = 1;
this._spawner = spawner;
this.newborn = true;
this.bornTime = 0;
this.spawnTimer = setInterval("this.bornTime++; console.log(this.bornTime);", 1000);
}
Pretty cut and clear code; every second after an instance of the variable is created, it should increment the bornTime variable by 1 and log it.
Mat.prototype.update = function() {
if (this.bornTime >= 5) {
this.bornTime = null;
clearInterval(this.spawnTimer);
this.newborn = false;
console.log("Grown!");
}
}
This additional code would cause this instance to be "grown" after 5 seconds, however when I check the console, it reads that bornTime is not a number(NaN).
Why is this, and is there a solution that I am not seeing?
this inside the setTimeout code is not the same as outside (more info on MDN), so your code is actually calculating undefined++, which is NaN.
You have to create another variable, and pass a function to setTimeout instead of letting it eval a string (by the way, passing a function is supposed to be faster, and looks better):
var that = this;
this.spawnTimer = setInterval(function(){
that.bornTime++;
console.log(that.bornTime);
}, 1000);
I know this is 5 years old question but its 2018 and heres an Es6 syntax solution to avoid extra step of binding key word this.
this.spawnTimer = setInterval(() => {
this.bornTime++;
console.log(this.bornTime);
}, 1000);

Recursion function not defined error

Hi i have a problem with recursion.
i followed this example from wc3 http://www.w3schools.com/jsref/met_win_settimeout.asp
But mine seems to not work at all.
function rotateImages(start)
{
var a = new Array("image1.jpg","image2.jpg","image3.jpg", "image4.jpg");
var c = new Array("url1", "url2", "url3", "url4");
var b = document.getElementById('rotating1');
var d = document.getElementById('imageurl');
if(start>=a.length)
start=0;
b.src = a[start];
d.href = c[start];
window.setTimeout("rotateImages(" + (start+1) + ")",3000);
}
rotateImages(0);
Firebug throws the error :
rotateImages is not defined
[Break On This Error] window.setTimeout('rotateImages('+(start+1)+')',3000);
However if i change the timeOut to :
window.setTimeout(rotateImages(start+1),3000);
It recursives but somehow the delay doesn't work and gives me too much recursion(7000 in a sec)
There are many reasons why eval should be avoided, that it breaks scope is one of them. Passing a string to setTimeout causes it to be evaled when the timer runs out.
You should pass a function instead.
window.setTimeout(rotateImages(start+1),3000);
This calls rotateImages immediately, then passes its return value to setTimeout. This doesn't help since rotateImages doesn't return a function.
You probably want:
window.setTimeout(rotateImages,3000,[start+1]);
Or create an anonymous function that wraps a closure around start and pass that instead:
window.setTimeout(function () { rotateImages(start + 1); },3000);
The latter option has better support among browsers.
Be wary of code from W3Schools.
The other answers give a solution. I'll just add that you're recreating the Arrays and repeating the DOM selection every time the rotateImages function is called. This is unnecessary.
You can change your code like this:
(function() {
var a = ["image1.jpg","image2.jpg","image3.jpg", "image4.jpg"];
var c = ["url1", "url2", "url3", "url4"];
var b = document.getElementById('rotating1');
var d = document.getElementById('imageurl');
function rotateImages(start) {
b.src = a[start];
d.href = c[start];
window.setTimeout(function() {
rotateImages( ++start % a.length );
}, 3000);
}
rotateImages(0);
})();
Try this syntax:
window.setTimeout(function() {
rotateImages(start+1);
},3000);
setTimeout() expects a function reference as the 1st parameter. Simply putting a function call there would give the return value of te function as the parameter, this is why the delay did not work. However your first try with evaluating a string was a good approach, but it is not recommended.

Categories

Resources