Loop object method - javascript

i'm trying to call an objects methods at the same time with a loop.
I would create a game with a group of enemies that are move at the same time.
I create an object "Warrior"
function Warrior(x, y) {
// Coordinates x, y
this.x = x;
this.y = y;
this.character = $('<div class="enemy"></div>');
// Inserting the div inside #map
this.character.appendTo('#map');
// assigning x and y parameters
this.character.css({
marginLeft: x + 'px',
marginTop: y + 'px'
});
// walk method that move the div
this.walk = function() {
// Moving div 10px to the right
this.character.css('left', "+=10");
}
}
var enemies = new Array();
enemies[0] = new Warrior(0, 0);
enemies[1] = new Warrior(10, 20);
enemies[2] = new Warrior(60, 80);
for(var i=0;i<enemies.length;i++) {
setInterval(function(){enemies[i].walk()}, 1000);
}
I've created an array "enemies"
and I tried to call walk() methods at the same time
But nothing it happens. I would like that the divs are moving at the same time!
Thanks!

Based on this question
You might want to pass a parameter by value to setInterval:
for(var i=0;i<enemies.length;i++) {
setInterval(
function(index){ //<-- accept parameter here
enemies[index].walk();
}, 1000, i); //<-- pass i here
}
If you do not pass the parameter, x variable changes while loop is running, thus all the functions will be called having x = 2 (because you have 3 elements in your array)

You have two major problems where:
CSS does not allow programmatic modification in the way that javascript does. You modify values in javascript, and then set them in CSS:
this.walk = function() {
var current = parseInt(this.character.css('left')),
stepped = current + 10;
this.character.css('left', stepped);
}
Your loop creates a closure on var i which is only declared once, so the same value will be shared across the functions. You can either have the function take a parameter, and provide it in the setInterval call:
setInterval(function(index) { /* ... */}, 1000, i);
or you can put the loop within the interval function:
setInterval(function() {
for (var i = 0; i < enemies.length; i++) {
enemies[i].walk();
}
});
You might also want to reconsider whether your walk function should modify the CSS directly. You've already forgotten about your x and y coordinate attributes in this code. Perhaps you should have a walk method that modifies these coordinates, and a draw function that updates the sprites' positions based on their current coordinates.

Related

pass a function with its values "locked"

Say I have a queue class that's executing a series of functions I've already declared:
class DrawQueue{
constructor(interval){
this.sequence = [];
this.interval=interval?interval:50;
}
addFunction=(fn)=>{
this.sequence.push(fn);
//throw exception here if not a function
};
execFunctions = ()=>{
let intvl = setInterval(
()=>{
const fn = this.sequence.shift();
//clear interval & return here if not a function
fn.call();
},
this.interval
)
}
}
Now I want to pass it a series of functions that have some values calculated inside them:
//I have a single count variable here but the code I'm running is being generated by a user who might have any number of variables that are being updated
let count = 0;
let counterDiv = document.querySelector('#counter')
let dq = new DrawQueue(1000);
function startCount(){ //call when window's loaded
let countFn=(()=>
{
let innerFn= function(){
let str = (function(){
return count.toString()
})();
counterDiv.innerHTML=str;
}
//imagine that any number of count variables might be being updated somewhere in the function
count++;
dq.addFunction(innerFn);
})
while(count<10){
countFn();
}
dq.execFunctions();
}
Right now this immediately sets the counter div to 10, and then keeps setting it to 10 ten more times. But I want to assign the str variable's value before I pass the functions. So the first function I pass sets the counter to 1, then 2 and so on.
I was trying to set the let str= function(... using an iife, but that didn't work.
One solution that I know would work is to make the whole function a string and then run it with eval but I really do not want to use eval unless I absolutely have to.
Is there any other way to pass these functions with certain variables already "locked in", meaning they're assigned before the function is placed in the queue?
UPDATE: To clarify, this is just a simplified version of a more complex example. In the actual example, the code is dynamically generated by another user, so in addition to 'count' any number of other values might need to be evaluated. So passing the count variable, as several good answers have suggested, is not going to work.
FURTHER CLARIFICATION: What I'm saying above is that because the user could be generating any number of variables that will be updated as the code runs, I can't pass those variables as arguments. (imagine there might be a count2, count3...countn and I don't know how many or where they'll be used or updated in the code.
FURTHER UPDATE: so a commenter wants to see the code in which this applies so here goes. It is an application using Blockly and P5 play, where users will be making code with blocks to move a sprite. So the code for the blocks might be something like this (yes this code is really ugly because it's just a test, but you asked to see it):
function moveForward(sprite){
let dir = ship.rotation* (Math.PI / 180);
let deltaX = Math.cos(dir)*5;
let deltaY = Math.sin(dir)*5;
let newX = ship.position.x + deltaX;
let newY = ship.position.y + deltaY;
ship.position.x=newX;
ship.position.y=newY;
redraw();
}
function moveBackward(sprite){
let dir = ship.rotation* (Math.PI / 180);
let deltaX = Math.cos(dir)*5;
let deltaY = Math.sin(dir)*5;
let newX = ship.position.x - deltaX;
let newY = ship.position.y - deltaY;
ship.position.x=newX;
ship.position.y=newY;
redraw();
}
function turnLeft(sprite){
let newDir=ship.rotation-90;
ship.rotation=newDir;
redraw();
}
function turnRight(sprite){
let newDir=ship.rotation+90;
ship.rotation=newDir;
redraw();
}
There will be any number of other sprites, each with 20 or so properties that could be updated.
Now if I just put all these functions in a row, the sprite will just immediately jump to where the code would put it. Because, you know, normally we want computers to do things as fast as they can.
But since this is made for teaching, I want the user to see the canvas updating step by step, with a delay between each redraw. That means every sprite will have its x and y coordinates, along with color and rotation and a bunch of other things, change slowly.
So the purpose of the DrawQueue to execute the drawing update steps slowly with a setInterval and update the canvas at any interval I want. I can't just run every single command with a setInterval because there could be logic or loops in there. The only thing I want to go in the interval is the updates to the canvas, anything else can happen as fast as it wants.
So imagine the four functions I provided above, along with any number of other functions and modifications to the properties of any number of other sprites or properties of the canvas.
The problem you have is the value is not stored at the time you make the function. It is just a reference to a variable that you are updating. So when it calls, it is reading that variable.
You would need to pass it into the method so you can store the state of the variable at that moment in time.
let count = 0;
let counterDiv = document.querySelector('#counter')
let dq = new DrawQueue(1000);
function startCount(){ //call when window's loaded
let countFn=((count)=> . // <-- reference it here
{
let innerFn= function(){
let str = (function(){
return count.toString()
})();
counterDiv.innerHTML=str;
}
dq.addFunction(innerFn);
})
while(count<10){
countFn(count++); // <-- update it here
}
dq.execFunctions();
}
By the time the innerFn is actually called, the count variable has already increased to its final value.
To give each innerFn instance its own value for count, you could bind it as function argument:
let innerFn = function(count) { //<--- argument
let str = (function(){
return count.toString()
})();
counterDiv.innerHTML=str;
}.bind(null, count); // pass the global count into a bound argument
NB: make sure to check in your class that fn is defined (as the array will become empty at some point).
class DrawQueue{
constructor(interval){
this.sequence = [];
this.interval=interval?interval:50;
}
addFunction(fn){
this.sequence.push(fn);
//throw exception here if not a function
};
execFunctions(){
let intvl = setInterval(
()=>{
const fn = this.sequence.shift();
//clear interval & return here if not a function
if (fn) fn.call();
},
this.interval
)
}
}
let count = 0;
let counterDiv = document.querySelector('#counter')
let dq = new DrawQueue(1000);
function startCount(){ //call when window's loaded
let countFn=(()=>
{
let innerFn= function(count){
let str = (function(){
return count.toString()
})();
counterDiv.innerHTML=str;
}.bind(null, count);
count++;
dq.addFunction(innerFn);
})
while(count<10){
countFn();
}
dq.execFunctions();
}
window.onload = startCount;
<div id="counter"></div>
Even better would be to avoid a reference to a global variable, and pass count to the countFn function as parameter:
let counterDiv = document.querySelector('#counter')
let dq = new DrawQueue(1000);
function startCount(){ //call when window's loaded
let countFn=((count)=> // local variable
{
let innerFn= function(){
let str = (function(){
return count.toString()
})();
counterDiv.innerHTML=str;
}
dq.addFunction(innerFn);
})
for(let count = 0; count<10; count++){ // local variable
countFn(count); // pass it
}
dq.execFunctions();
}
Addendum
In your question's update you speak of more variables. In that case, pass an object around, which can have many properties, possibly even managed completely by the user-provided code:
let counterDiv = document.querySelector('#counter')
let dq = new DrawQueue(1000);
function startCount(){ //call when window's loaded
let countFn=((state)=> // local variable with count property
{
let innerFn= function(){
let str = (function(){
return state.count.toString()
})();
counterDiv.innerHTML=str;
}
dq.addFunction(innerFn);
})
for(let count = 0; count<10; count++){ // local variable
const state = {};
state.count = count;
countFn(state); // pass it
}
dq.execFunctions();
}
Depending on your expectations you should either use the same state object or create new state variables (within the loop, or even deeper in the execution context). This all depends on how you want the system to behave.

Javascript - element.addEventListener() returns "Uncaught TypeError"

For context, I am trying to code a memory game where you have to pair two of the same colored circles until the whole board is complete. I've called it Match-Two. Here is the code that I'll reference from:
class Circle {
constructor(element, circleColor){
this.elem = element;
this.color = circleColor;
}
}
var frequency = [0, 0, 0, 0, 0, 0, 0, 0];
var num;
var hue = new Array(8);
var circle = new Array(16);
hue[0] = "#0039ff";
hue[1] = "#ff0000";
hue[2] = "#43ff00";
hue[3] = "#fffa00";
hue[4] = "#7405b5";
hue[5] = "#ff9d00";
hue[6] = "#ff00c3";
hue[7] = "#00fff6";
onload = function() {
for(var i = 0; i < 16; i++){
circle[i] = new Circle(document.getElementById("circle" + i));
while(circle[i].color === undefined){
num = Math.floor(Math.random() * 8);
if(frequency[num] != 2){
frequency[num]++;
circle[i].color = hue[num];
circle[i].elem.addEventListener('click', function(){
main(circle[i])
});
}
}
}
}
function main(circle){
circle.elem.style.backgroundColor = circle.color;
}
So in this code I create a class of Circle and I create an array of Circle objects which is identified as 'circle'. When the page is loaded, I give each circle object an element reference from my html document (There are 16 circles and they each have an id of circle0, circle1, circle2.. etc. Then there's a small algorithm to ensure there are only two of each color in the matrix so they all have a matching pair. In each iteration of the for loop, I add an event listener to each circle. If the circle is clicked, I want it to change to its color which is stored in color[i].color. However, when I click the circles all it returns is:
Uncaught TypeError: Cannot read property 'elem' of undefined
at main (script.js:39)
at HTMLDivElement.<anonymous> (script.js:31)
Which is referencing:
circle.elem.style.backgroundColor = circle.color;
So I put some console.log() functions in to see what was going on:
if(frequency[num] != 2){
frequency[num]++;
circle[i].color = hue[num];
console.log(circle[i].elem);
console.log(circle[i].color);
circle[i].elem.addEventListener('click', function(){
main(circle[i])
});
}
And this spits out exactly what I expect:
script.js:31 #ff9d00
script.js:30 div data-brackets-id=​"11" class=​"circle" id=​"circle1" /div
script.js:31 #ff9d00
script.js:30 div data-brackets-id=​"12" class=​"circle" id=​"circle2" /div
script.js:31 #0039ff
script.js:30 div data-brackets-id=​"13" class=​"circle" id=​"circle3" /div
script.js:31 #0039ff
So it returns the element reference and the color of the circle. So then I try putting the "circle[i].elem.style.backgroundColor = circle[i].color" into the event listener and I get the same issue as before...
if(frequency[num] != 2){
frequency[num]++;
circle[i].color = hue[num];
console.log(circle[i].elem);
console.log(circle[i].color);
circle[i].elem.addEventListener('click', function(){
circle[i].elem.style.backgroundColor = circle[i].color
});
}
Circles without their colors. The console log statements are on the right-hand side with their specific colors as well...
So I gave up and decided to write that exact line of code outside the event listener to see if that works, and it changed all the circle's colors to their specific color...
if(frequency[num] != 2){
frequency[num]++;
circle[i].color = hue[num];
console.log(circle[i].elem);
console.log(circle[i].color);
circle[i].elem.style.backgroundColor = circle[i].color;
circle[i].elem.addEventListener('click', function(){
circle[i].elem.style.backgroundColor = circle[i].color
});
}
The circles with their specific colors...
There is some problem the event listener not being able to pass the object of a Circle or something... I don't know please help :(
Your problem boils down to the way JS treats var variables - they sort of "leak" into the global scope.
Consider the event listener that you've attached:
circle[i].elem.addEventListener('click', function(){
main(circle[i])
});
So, whenever the listener gets triggered, it calls main() function and passes circle[i] into it. But since i is the variable that's leaked outside of the supposed scope, it always has the value of 16 - the value assigned to it during the last iteration of the for loop. That's why the main() function tries to access a style property of undefined - it's the value of circle[16] that was passed into it.
Here's a couple of ways to fix it:
If you can use ES6 let variables:
Use let i instead of var i in your for loop:
for (let i = 0; i < 16; i++) {
//...
}
If not, a classic way with function closure:
function createListener(j) {
return function () {
main(circle[j])
}
}
// and use it in your 'for' loop later:
circle[i].elem.addEventListener('click', createListener(i));
Here's a useful topic that provides more techniques to avoid this: JavaScript closure inside loops – simple practical example

Javascript: using one function on multiple elements

I'm new to JavaScript and I'm having trouble figuring out how to resize multiple elements with one function for my rhythm game. This is for my CSP class and theres no use of jQuery sadly. I'm also limited to the commands that the program (AppLab) I'm using has provided. My goal right now is to make an "animation" of a circle growing to its desired size to indicate that it should be clicked. I need these elements to appear while another one is in the process growing and so on.
I'm aware that my code probably sucks so if there is also a way to simplify or improve it I would love to know.
This is my current program code and the hitIndicator function is the one I'm having the most trouble with:
var circleSizeW = 0;
var circleSizeL = 0;
var score = 0;
hitCircle("hitcircle", 300, 6, 206);
hitCircle("image2", 300, 6, 682);
function circleEffects(circleid, whentohit) {
setTimeout(function() {
onEvent(circleid, "click", function() {
playSound("47 (1).mp3", false);
hideElement(circleid);
});
}, whentohit);
}
function hitIndicator(circleid, growthRate) {
var xPos = getXPosition(circleid);
var yPos = getYPosition(circleid);
var t = setInterval(function() {
circleSizeW = circleSizeW + growthRate;
circleSizeL = circleSizeL + growthRate;
xPos = xPos - (growthRate/2);
yPos = yPos - (growthRate/2);
showElement(circleid);
setSize(circleid, circleSizeW, circleSizeL);
setPosition(circleid, xPos, yPos);
if (circleSizeW >= 60) {
clearInterval(t);
circleSizeW = 0;
circleSizeL = 0;
}
}, 50);
}
function scoreSystem(circleid, whentohit) {
setTimeout(function() {
onEvent(circleid, "click", function() {
score = score + 100;
setText("scoreTrack", score);
});
}, whentohit);
}
function hitCircle(circleid, whentohit, growthRate, appearancetime) {
setTimeout(function() {
console.log("hitcircle");
circleEffects(circleid, whentohit);
hitIndicator(circleid, growthRate);
scoreSystem(circleid, whentohit);
}, appearancetime);
My code is nowhere near completion either so there are still many things that needs to be done.
I'm not sure how to have multiple circles running that function at similar times because when I try to fix the errors/change the values of the functions' parameters they sometimes loop twice, infinitely loop, or receive the changed values of the previous circle while the previous circle is still growing.
I am also fairly new with Javascript, but I have a fairly good idea of what should be done here.
I would write an object constructor for instantiating circles.
Scroll down this page to see how to make object constructors.
Then for your circle object add a hitindicator method.
This page covers methods.
Then set up a function that will retrieve and loop through every instantiated circle object and run .hitindicator on each circle. Best way to do this, might be to add every instantiated circle to a circle array, then just loop through the array?
Then have an update() function that calls the function in step 3, and have update() called every "frame" with setInterval.
The pages linked should give you enough information to figure it out from here.

HTML5 Canvas, Having Trouble Running Multiple Animations At Once

I have written code that takes two arrays, both of which contain co-ordinates for a four-cornered shape (effectively a start frame and an end frame), a canvas ID and a time value. The function then calculates dX and dY of each corner and uses window.performance.now() to create a timestamp. Then, on every requestAnimationFrame(), it calculates what the co-ordinates should be by using dX, dY, the old timestamp, a new timestamp and the time value from the function call. It looks like this:
function doAnim(cv, startFrame, endFrame, animTime)
{
this.canvas = document.getElementById(cv);
this.ctx = this.canvas.getContext('2d');
if(startFrame.length != endFrame.length)
{
return('Error: Keyframe arrays do not match in length');
};
this.animChange = new Array();
for(i=1;i<=startFrame.length;i++)
{
var a = startFrame[i];
var b = endFrame[i]
var c = b - a;
this.animChange[i] = c;
}
this.timerStart = window.performance.now();
function draw()
{
this.requestAnimationFrame(draw, cv);
this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height);
this.currentFrame = new Array();
for(i=1;i<=startFrame.length;i++)
{
this.currentFrame[i] = startFrame[i]+(this.animChange[i]*((window.performance.now()-this.timerStart)/animTime));
}
if((window.performance.now()-this.timerStart)>=animTime)
{
this.ctx.beginPath()
this.ctx.moveTo(endFrame[1], endFrame[2]);
this.ctx.lineTo(endFrame[3], endFrame[4]);
this.ctx.lineTo(endFrame[5], endFrame[6]);
this.ctx.lineTo(endFrame[7], endFrame[8]);
this.ctx.fill();
return;
}
else
{
this.ctx.beginPath()
this.ctx.moveTo(this.currentFrame[1], this.currentFrame[2]);
this.ctx.lineTo(this.currentFrame[3], this.currentFrame[4]);
this.ctx.lineTo(this.currentFrame[5], this.currentFrame[6]);
this.ctx.lineTo(this.currentFrame[7], this.currentFrame[8]);
this.ctx.fill();
}
}
draw();
}
The goal is to have multiple animations of objects happening at once. I took the whole co-ordinate approach because I want the objects to appear as if they are coming from the horizon, creating a fake 3D perspective effect (all objects' starting frames would be a single point at the center of the canvas), and I do not want to warp the objects' textures.
Well, it works great for a single animation, but if I try to start a new animation on a completely different canvas while the first one is running, then the first animation stops dead in its tracks.
As you can see from my JS, I've tried getting around this with gratuitous use of this (I do not fully understand how this works yet, and every explanation I've read has left me even more confused), but it has not worked. I also tried a horrific approach which stored all the functions' own variables in one global array (the first time the function runs, all the variables are put in entries 1-30, the second time they're put in 31-60, etc). Unsurprisingly, that did not work either.
Here is a JSFiddle so you can see this scenario for yourself and play with my code. I am officially out of ideas. Any help will be greatly appreciated.
Like markE linked too, trying to call requestAnimationFrame multiple times won't work.
Instead you make multiple objects and then call some sort of function on them each frame.
I have created an example using your code:
https://jsfiddle.net/samcarlin/2bxn1r79/7/
var anim0frame1 = new Array();
anim0frame1[1] = 0;
anim0frame1[2] = 0;
anim0frame1[3] = 50;
anim0frame1[4] = 0;
anim0frame1[5] = 50;
anim0frame1[6] = 150;
anim0frame1[7] = 0;
anim0frame1[8] = 150;
var anim0frame2 = new Array();
anim0frame2[1] = 200;
anim0frame2[2] = 200;
anim0frame2[3] = 300;
anim0frame2[4] = 250;
anim0frame2[5] = 300;
anim0frame2[6] = 300;
anim0frame2[7] = 200;
anim0frame2[8] = 250;
//Call global
animations = [];
requestAnimationFrame( GlobalStep );
function GlobalStep(delta){
//Functions called by request animation frame have the new time as an argument
//so delta should be approximately the same as window.performance.now()
//especially in realtime applications, which this is
//Check if we have any animation objects
if(animations.length > 0){
//Iterate through and call draw on all animations
for(var i=0; i<animations.length; i++){
if(animations[i].draw(delta)){
//Basically we have it so if the draw function returns true we stop animating the object
//And remove it from the array, so have the draw function return true when animation is complete
animations[i].splice(i, 0);
//We removed an object from the array, so we decrement i
i--;
}
}
}
//And of course call requestAnimationFrame
requestAnimationFrame( GlobalStep );
}
function AnimationObject(cv, startFrame, endFrame, animTime){
//Add this object to the objects arrays
animations.push(this);
//We need to store start and end frame
this.startFrame = startFrame;
this.endFrame = endFrame;
this.animTime = animTime;
//Your code
this.canvas = document.getElementById(cv);
this.ctx = this.canvas.getContext('2d');
if (startFrame.length != endFrame.length) {
return ('Error: Keyframe arrays do not match in length');
};
this.animChange = new Array();
for (i = 1; i <= startFrame.length; i++) {
var a = startFrame[i];
var b = endFrame[i]
var c = b - a;
this.animChange[i] = c;
}
this.timerStart = window.performance.now();
}
//This adds a function to an object, but in such a way that every object shares the same function
//Imagine a kitchen, each object is a person, and this function is a spoon
//by defining this function in this manner Object.prototype.function_name = function(arguments){}
//We make it so one function definition is needed, essentially allowing all the people to share one spoon,
//the 'this' variable still refers to whichever object we call this method, and we save memory etc.
AnimationObject.prototype.draw = function(newTime){
//I added this to start frame so we get what we stored earlier
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.currentFrame = new Array();
for (i = 1; i <= this.startFrame.length; i++) {
this.currentFrame[i] = this.startFrame[i] + (this.animChange[i] * ((newTime - this.timerStart) / this.animTime));
}
if ((newTime - this.timerStart) >= this.animTime) {
this.ctx.beginPath()
this.ctx.moveTo(this.endFrame[1], this.endFrame[2]);
this.ctx.lineTo(this.endFrame[3], this.endFrame[4]);
this.ctx.lineTo(this.endFrame[5], this.endFrame[6]);
this.ctx.lineTo(this.endFrame[7], this.endFrame[8]);
this.ctx.fill();
return;
} else {
this.ctx.beginPath()
this.ctx.moveTo(this.currentFrame[1], this.currentFrame[2]);
this.ctx.lineTo(this.currentFrame[3], this.currentFrame[4]);
this.ctx.lineTo(this.currentFrame[5], this.currentFrame[6]);
this.ctx.lineTo(this.currentFrame[7], this.currentFrame[8]);
this.ctx.fill();
}
}
Notes:
Everytime you press the button a new object is added and simply overwrites previous ones for each frame, you should implement your program so that it checks if a specific animation has already started, you could also use the builtin mechanism to stop animation when complete (read the comments in the code)
You also need to change the on button click code
<button onclick="new AnimationObject('canvas1', anim0frame1, anim0frame2, 3000);">
Lastly if you have further questions feel free to contact me

this getting lost again

I thought I fixed it but looks like not. Heres whats happening:
The canvas.mousemove event is handled by viewport.onMouseMove.bind(viewport) function (viewport is an instance of a class).
At the end of the onMouseMove function it calls this.Draw() (referring to viewport.Draw() function).
viewport.Draw() loops through all the items and calls Items[i].Draw(ctx) on each of them where ctx is a back buffer canvas context.
Now if If the item that is being drawn goes ahead and uses the ctx to draw something right there and then (in its Draw function), using this to refer to itself, everything works fine. For example
this.Draw = function(ctx) {
ctx.beginPath();
ctx.moveTo(this.x1, this.y1);
ctx.lineTo(this.x2, this.y2);
ctx.lineWidth = 1;
ctx.strokeStyle = "#000000";
ctx.stroke();
};
However, if the object is a container that has items in itself and tries to loop and draw them like this
this.Draw = function(ctx) {
for (j = 0; j < this.Items.length; j++) {
this.Items[j].Draw(ctx);
}
};
When it gets into the Items[j].Draw, "this" loses all meaning. alert(this) produces "object object" and I cant figure out what its referring to (it's not the viewport nor the container nor the item it needs to be). Also another weird thing - I had to change the container object loop to use j instead of i because otherwise it would create a perpetual loop (like the i's of the viewport.draw and item[i].draw were the same).
Your question is somewhat unclear. Is this.Items an array of objects with the same prototype as this? ie. nested? Also, is the j counter intended to be shared?
Regardless, function contexts' this values can be changed rather easily to whatever you need them to be with the .apply and .call functions:
this.Draw = function(ctx) {
for (var j = 0; j < this.Items.length; j++) {
// These two are the same as what you have in the question
this.Draw.call(this.Items[j], ctx);
this.Draw.apply(this.Items[j], [ctx]);
// This is what you had in the question if Draw is different for Items:
this.Items[j].Draw(ctx);
this.Items[j].Draw.call(this.Items[j], ctx);
// Will preserve the this reference within the nested call
this.Items[j].Draw.call(this, ctx);
}
};
Not sure what the problem is but as my comment suggest this is the invoking object:
//this in someFunction is window
setTimeout(myObject.someFunction, 200);
//this in someFunction is button
button.onClick=myObject.someFunction;
Not sure what you would like this to be when it's called but if it has to be Items[j] then your code is fine and maybe something else is causing you problems. I suggest console.log objects in Chrome or Firefox with firebug, use F12 to open the console and inspect the logged objects.
Here is sample code of items that can be Square or Circle;
var Shape = function Shape(args){
//args.x1 or y1 can be 0, defaults to 2
this.x1 = (args.x1 === undefined)? 2:args.x1;
this.y1 = (args.y1 === undefined)? 2:args.y1;
this.name = args.name||"unnamed";
}
//in this example Square and Cirle draw does the same
// so they can inherit it from Shape
Shape.prototype.draw=function(){
console.log("this x1:",this.x1,"this y1:",this.y1,"name",this.name);
//you can log complex values as well and click on them in the console
// to inspect the details of the complex values (objects)
// the above can be done in the following log
console.log("in draw, this is:",this);
}
var Square = function Square(args){
//re use parent constructor (parent is Shape)
Shape.call(this,args);
}
//set prototype part of inheritance and repair constructor
Square.prototype=Object.create(Shape.prototype);
Square.prototype.constructor=Square;
var Circle = function Circle(args){
//re use parent constructor (parent is Shape)
Shape.call(this,args);
}
//set prototype part of inheritance
Circle.prototype=Object.create(Shape.prototype);
Circle.prototype.constructor=Circle;
//there is only one app so will define it as object literal
var app = {
items:[],
init:function(){
var i = -1;
while(++i<10){
this.items.push(new Circle({x1:i,y1:i,name:"circle"+i}));
}
while(++i<20){
this.items.push(new Square({x1:i,y1:i,name:"square"+i}));
}
},
draw:function(){
var i = -1;len=this.items.length;
while(++i<len){
this.items[i].draw();
}
}
}
app.init();
app.draw();//causes console.logs

Categories

Resources