I need to call a function every 100ms, which is easy enough, but what if I need that function to accept a parameter?
The problem is that I create an object, and periodically need to update it. I have tried setting the object reference to a global, that didn't work. I tried setting it to a function variable, still no luck. Apparently I need to pass in the object, which I cant't figure out how to do using setInterval. There has to be a trick to this?
The code below works on forst call, but after that it fails at:
setCounterText.segment.DisplayText("AAABBB");
And complains that setCounterText.segment.DisplayText() is not a function...
Thanks...
window.onload = function ()
{
setInterval(setCounterText, 1000);
}
function setCounterText()
{
//"use strict";
var num;
if(!setCounterText.isInit)
{
num = 0;
setCounterText.isInit=true;
var canvas = document.getElementById('c');
var container = document.getElementById('container');
canvas.width = container.clientWidth;
canvas.height = container.clientHeight;
// Create a new sixteen segment display
setCounterText.segment = new SixteenSegment(1, canvas);
update(setCounterText.segment);
setCounterText.segment.DispayText("T-000:00:00.0");
}
num++;
setCounterText.segment.DisplayText("AAABBB");
}
You can create another function to act as a clojure for the setCounterText function and pass that as a parameter to setInterval.
setInterval(function() {
setCounterText(anotherParameter);
}, 1000);
That will capture your parameter and call the setCounterText function whenever the interval triggers.
Regarding the error you are getting, it's impossible to say without knowing the code in the SixteenSegment function but it should have a property set on it called DisplayText.
Related
Background
Basically, this code takes all of the audio tags on the page, and when one finishes it starts the next one in the DOM.
The Issue
When fnPlay is called I receive an Illegal Invocation error.
//THIS CODE FAILS
var lastAudio = null;
$('audio').each(function(index) {
var fnPlay = $(this)[0].play;
if (lastAudio != null) {
lastAudio.bind("ended", function() {
fnPlay();
});
}
lastAudio = $(this);
});
Now I am sure that the rest of the code is fine, because the following worked.
//WORKS GREAT!
var lastAudio = null;
$('audio').each(function(index) {
var lastAudioObj = $(this)[0];
if (lastAudio != null) {
lastAudio.bind("ended", function() {
lastAudioObj.play();
});
}
lastAudio = $(this);
});
Question
Can anybody explain why I couldn't store the play() function inside my variable fnPlay and call fnPlay(), but I could store the object and call the play() function on the object?
This is because of how the context of JavaScript functions work. The context (or this) inside a function is set when it's ran, not when it's set.
When you call lastAudioObj.play();, the play() function is called in the context of lastAudioObj. Inside play(), this is lastAudioObj, so everything works.
When you do fnPlay() however, it has no context. this inside the function will be null (or window). play() doesn't like that, so it throws an exception.
There are a few ways to fix this.
One is to call the function with .call() to manually set the context.
Set the variables like:
var lastAudioObj = $(this)[0];
var fnPlay = lastAudioObj.play;
Then call:
fnPlay.call(lastAudioObj);
You can also use .bind() to set the context when setting the variable.
var lastAudioObj = $(this)[0];
var fnPlay = lastAudioObj.play.bind(lastAudioObj);
Then you can just call it like:
fnPlay();
I'm very new on my quest for learning JavaScript(only two weeks) so be nice and surprised how far I got.
What I’m trying to do is hit a button and the button will evoke a random image or div to come to the front. I as you see I use the z-index by moving elements from back to front.
I have got far enough to create an alert that tells me it does find the random function just cant get it to activate this function (the changeCombined functions do work fine when I assign it to a button but just can’t get the getImage to run).
I’m unsure if it is possible, and I know there might be a hundred better ways to do this but one step at a time.
function changeZIndex(i,id) {
document.getElementById(id).style.zIndex=i;
};
var changeCombined1 = function() {
changeZIndex(-5,"scene1");
changeZIndex(5,"scene2");
};
var changeCombined2 = function() {
changeZIndex(-5,"scene2");
changeZIndex(5,"scene1");
};
function get_random(){
var ranNum= Math.floor(Math.random()*2);
return ranNum;
}
function getImage(){
var whichImage=get_random();
var image=new Array()
image[0]=changeCombined2;
image[1]=changeCombined1;
alert(quote[whichImage]);
}
Try this:
function changeZIndex(i,id) {
document.getElementById(id).style.zIndex=i;
};
function getImage(){
var whichImage=Math.floor(Math.random()*2);
var image=new Array()
image[0]=function() {
changeZIndex(-5,"scene2");
changeZIndex(5,"scene1");
};
image[1]=function() {
changeZIndex(-5,"scene1");
changeZIndex(5,"scene2");
};
image[whichimage]();
alert(quote[whichImage]);
}
To invoke a function you should use either apply or call methods.
Have you tried image[whichImage].apply(undefined) instead of your alert ?
You are not calling the changeCombined() functions at all.
If you are trying to call the functions, you have to use ()
image[0]=changeCombined2();
image[1]=changeCombined1();
What you have just the functions themselves to image[0] and image[1].
So after
x = changeCombined2;
x will hold a reference to the changeCombined2 function itself. So now if you say x() (or in your case image[0]()), it will call changeCombined2.
() will call the functions and will put the return value of the function into the array elements.
Note: since the functions do not explicitly return anything, image[0] and image[1] will hold undefined.
Thanks for all the help to get past my road block, the rookie mistake of not calling my funcions was the biggest issue. I was able montage the comments to create this version that works as expected.
function changeZIndex(i,id) {
document.getElementById(id).style.zIndex=i;};
function getImage(){
var whichImage=Math.floor(Math.random()*2);
var image=new Array()
image[0]=function() {
changeZIndex(-5,"scene2");
changeZIndex(5,"scene1");
};
image[1]=function() {
changeZIndex(-5,"scene1");
changeZIndex(5,"scene2");
};
image[whichImage].apply(undefined);};
Background: I am trying to edit a zen cart horizontal pop out menu to make the popout open inline within the menu. The problem I am having is that I am struggling to get my head around the javascript/jquery that came with it.
Without posting the whole thing the structure of the code is something like this:
(declare some vars)
//some functions like this:
function funcname(obj) {
//do something
}
//then one big master function like this:
function bigfunc(arg1, arg2, arg3, arg4, arg5) {
//declare some vars based on this
this.varname1=varname1;
this.varname2=varname2;
//declare some functions inside the big function
this.innerfunc1= function() {
//do stuff
}
this.innerfunc2= function() {
//do stuff
}
}//end of big function
//then goes on to declare init function
function initfunc(){
//this creates new bigfunc(arg1 arg2 arg3...) for each main menu item
}
//finally calls init function with
window.onload = initfunc();
Now on to my confusion -
1) firstly for clarification, am I correct in thinking based on all the this's floating about in bigfunc() and the fact that it is called with new bigfunc() that this is creating an object?
2)My current problem is with one of the functions inside bigfunc() which looks like this:
this.slideChildMenu = function() {
var divref = this.children[0].div;
var ulref = this.children[0].ul;
var maxwidth = this.children[0].width;
var nextWidth;
if (this.isMouseOnMe || this.isMouseOnChild()) {
nextWidth = divref.offsetWidth + slideSpeed_out;
if (nextWidth >= maxwidth) {
this.finishOpeningChild(divref, ulref, maxwidth);
} else {
ulref.style.left = nextWidth - maxwidth + "px";
divref.style.width = nextWidth + "px";
setTimeout("slideChildMenu('" + this.getId() + "')", slideTimeout_out);
}
}
Now my plan is to alter this to use jquery show to open the element so I tried this:
this.slideChildMenu = function() {
var divref = this.children[0].div;
var ulref = this.children[0].ul;
if (this.isMouseOnMe || this.isMouseOnChild()) {
$(divref).show(function(){
this.finishOpeningChild(divref, ulref);
});
}
}
But I am getting this-> TypeError: this.finishOpeningChild is not a function
Now, there is a lot of other stuff going on in this js so I wouldnt dream of asking someone on here to do my work for me, but I am hoping that if someone can explain to me why this function is not a function I may be able to work the rest out.
NOTE: I thought this was to do with the scope of "this" but the value of this appears to be exactly the same in both versions of the code.
I know this is a long one but your help is greatly appreciated.
The value of this in a function is called the "context" in which the function runs. In general, whenever you pass a callback function as an argument (as you do with $(divref).show(function() {...})), the function can run the callback in whatever context it wants. In this case, the jQuery show function chooses to run its callback in the context of the element being animated.
However, you want access to the value of this at the time the anonymous callback function is defined, rather than when it is run. The solution here is to store the outer value of this in a variable (traditionally called self) which is included in the scope of the newly-defined function:
this.slideChildMenu = function() {
//...
var self = this;
$(divref).show(function(){
self.finishOpeningChild(divref, ulref);
});
}
I am thinking that the jQuery selector has changed the scope of this.
In your example $(this); would refer to object being animated per jQuery api docs:
If supplied, the callback is fired once the animation is complete. This can be useful for stringing different animations together in sequence. The callback is not sent any arguments, but this is set to the DOM element being animated. If multiple elements are animated, it is important to note that the callback is executed once per matched element, not once for the animation as a whole.
If the object in question is instantiated you can call it with dot notation without using this like bigFunc.finishOpeningChild(divref, ulref);
You're probably a little confused about scope, it's not always easy keeping track, but doing something more like this:
var site = {
init: function(elm) {
self=site;
self.master.funcname2(self.varname1, elm); //call function in master
},
funcname: function(obj) {
//do something
},
varname1: 'some string',
varname2: 3+4,
master: function() {
this.varname3 = sin(30);
this.funcname2 = function(stuff, element) {
site.funcname(element); //call function in 'site'
var sinus = site.master.varname3; //get variable
}
}
}
window.onload = function() {
var elm = document.getElementById('elementID');
site.init(elm); //call init function
}
usually makes it a little easier to keep track.
I wanted to call a function when all required images are loaded. The number of images is known in advance, so I tried attaching a function call to the onload event of each image and count the number of times it was called.
<html>
<head>
<script>
var tractor;
function Tractor()
{
this.init_graphics();
}
Tractor.prototype.init_graphics = function()
{
this.gr_max = 3;
this.load_count = 0;
this.loading_complete(); // #1 test call, works OK
this.img1 = new Image();
this.img1.onload = this.loading_complete; // #2 gets called, but gr_max = undefined, load_count = NaN
this.img1.src = "http://dl.dropbox.com/u/217824/tmp/rearwheel.gif"; //just a test image
}
Tractor.prototype.loading_complete = function()
{
this.load_count += 1;
alert("this.loading_complete, load_count = " + this.load_count + ", gr_max = " + this.gr_max);
if(this.load_count >= this.gr_max) {this.proceed();}
};
function start()
{
tractor = new Tractor();
}
</script>
</head>
<body onload="start();">
</body>
</html>
When it's just called from another function of the object (see #1), it works just as I expected. When, however, it's called from onload event (see #2), the variables become "undefined" or "NaN" or something. What's happening? What am I doing wrong? How do I make it work?
I don't remember ever creating my own objects in Javascript before, so I certainly deeply apologize for this "what's wrong with my code" kind of question. I used this article as a reference, section 1.2, mainly.
Just in case, I put the same code on http://jsfiddle.net/ffJLn/
bind the context to the callback:
this.img1.onload = this.loading_complete.bind(this);
See: http://jsfiddle.net/ffJLn/1/ (same as yours but with this addition)
Here's an explanation of how bind works in detail: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
The basic idea is that it makes this in the bound function equal to whatever you pass as the parameter to bind.
Another option is to create a closure:
var self = this;
this.img1.onload = function() { self.loading_complete() };
Closures are functions that keep references to their context (in fact, all functions in javascript work this way). So here you are creating an anonymous function that keeps a reference to self. So this is another way to maintain context and for loading_complete to have the right this.
See: http://jsfiddle.net/ffJLn/2/ (same as yours but with the second possibility)
When #2 gets called, your this has changed. this now refers to the new Image() rather than the Tractor object.
Try changing...
this.img1.onload = this.loading_complete;
to
var that = this;
this.img1.onload = function() { that.loading_complete(); };
You can now use es6 arrow functions which provide lexical binding:
this.img1.onload = () => { this.loading_complete(); };
In my application I have an object with several properties that get set in various places in the application.
In one of my prototype functions I have a function that runs in intervals to update a timer, and in that function the property (this.)theTime should be set. The problem is that this doesn't happen, and I guess the reason is that this.theTime points to the function itself instead of the object.
Below is two versions of my code, and neither of them works. Any tips for me?
// 1.
function changeTime() {
this.theTime = setTime(time);
time.setSeconds(time.getSeconds()+1);
p1.html(this.theTime);
}
interval = setInterval(changeTime(), 1000 );
// 2.
function changeTime(theTime) {
theTime = setTime(time);
time.setSeconds(time.getSeconds()+1);
p1.html(theTime);
}
interval = setInterval( function() { changeTime(this.theTime); }, 1000 );
...
Too make it more clear, the function above updates a timer (eg. 00:00:01 -> 00:00:02) every second, and I want this.theTime to be updated with the time.
When the timer stops (which happens in another prototype function) I want to be able to see what time the timer stopped on, but as it is now this.theTime is the default value, which means that the function above doesn't update the objects property. Instead this.theTime in the function above must be a local variable.
NOTE: setTime() is another function that exists in the same prototype function as the function above.
Well when you use this in some function this is referencing to the object which actually the function is. Here:
function myF() {
this.var = 'hey';
}
You can reach var using this (myF as a constructor function):
var obj = new myF();
alert(obj.var);
Or here:
function myF2() {
if (typeof this.var === 'undefined') {
this.var = 0;
} else {
this.var += 1;
}
alert(this.var);
}
Here var again is a property of myF2 (which as I said is not just a function because in JavaScript functions are objects).
Each time you call myF2 this.var is going to be incremented and alerted (just in the first call it's going to be initialized).
In the second function (anonymous function using in the second setInterval) you're doing the same.
One solution is to make theTime global in both cases so you don't need to use:
this.theTime
So the result can be something like this:
var theTime = 0, interval;
function changeTime() {
theTime += 1;
document.body.innerHTML = theTime;
setInterval
}
interval = setInterval(changeTime, 1000 );
http://jsfiddle.net/u3EuC/
You can verify easily by writting a
debugger;
to set a breakpoint in your functions. Then it may be pretty easy to find your problem.
You are correct in your assumption that there's something wrong with your this keyword. this in JavaScript is a bit tricky, so using it in functions (especially with setTimeout or setInterval is risky.
What you want to do is save the value of this when you create the function.
Here's more information: http://justin.harmonize.fm/index.php/2009/09/an-introduction-to-javascripts-this/
Maybe these comments will direct you to the right way
var theTime; // global variable
function changeTime() {
theTime = setTime(time); // theTime is global variable declared above (accesible from anywhere)
// var myTime = setTime(time); // myTime is local variable
time.setSeconds(time.getSeconds()+1);
p1.html(theTime);
}
interval = setInterval(changeTime, 1000 ); // no braces
Jason, after your clarification, I believe it is better to provide you whole new answer trying to explain this statement in JS as good (and simple) as possible. I hope it helps.
<html>
<body>
<div id="output1"></div>
<div id="output2"></div>
<script>
// theTime is undefined in global scope
function obj(target) {
var theTime = 0;
var that = this; // var means "private"
this.changeTime = function() { // here "this" points to obj and means "public"
theTime++; // no var => outer scope = obj scope
// here "this" points to changeTime function, not to obj!
// "that" points to obj, you may use that.theTime
document.getElementById(target).innerHTML = theTime;
}
}
var o1 = new obj("output1");
var o2 = new obj("output2");
setInterval(o1.changeTime,1000); // update output1 content every second
setInterval(o2.changeTime,500); // update output2 content twice a second
</script>
</body>
</html>