Passing Variable to Asynchronous JavaScript Function Invoked in Loop (React) - javascript

I'm writing some JavaScript code for my React project. The code loads a series of images, then updates the state with the dimensions of each image. The problem is that when I invoke the onload function, the this keyword refers to the object attached to the onload. This means I can no longer access props through this.props. Is there a way to pass the props into the function?
Here's my code:
for (var i = 0; i < a; i++) {
var path = i + ".jpg";
imgArray[i].index = i;
imgArray[i].onload = function() {
this.props.actions.updateImage(this.index, this.width, this.height);
}
imgArray[i].src = path;
}
I currently get an error, as this.props is undefined, since this refers to imgArray[i] in the function, not the global context.

A simple solution might just be to save the context or props into a variable and use them:
const { props } = this;
// ...
imgArray[i].onload = function() {
props.actions.updateImage(this.index, this.width, this.height);
}
You can also save the other context if you find that more readable:
const ctx = this;
// ...
imgArray[i].onload = function() {
ctx.props.actions.updateImage(this.index, this.width, this.height);
}

Your best bet is to capture the props by using a variable that holds a reference to the outer 'this' that you access via the closure:
// This line here, now inside the function, use 'self' to refer to outer context
let self = this;
for (var i = 0; i < a; i++) {
var path = i + ".jpg";
imgArray[i].index = i;
imgArray[i].onload = function() {
// note call to self.props instead of this.props:
self.props.actions.updateImage(this.index, this.width, this.height);
}
imgArray[i].src = path;
}

Related

p5js instance mode and object orientation

I have a "sketch.js" where i want to instance multiple canvases und display different objects of one class in them.
sketch.js:
var myObjects = [];
var sketch1 = function (p) {
p.setup = function () {
p.createCanvas(600, 400);
}
p.draw = function () {
p.background(51);
p.rect(p.width/2, p.height/2, 200, 200);
}
};
new p5(sketch1, "canvas_container");
var sketch2 = function (p) {
p.setup = function () {
p.createCanvas(600, 400);
myObjects.push(new myObject(p, 1, 2));
myObjects.push(new myObject(p, 3, 4));
}
p.draw = function () {
p.background();
for (var i=0; i<myObjects.length; i++) {
p.line(0, 0, myObjects[i].x, myObjects[i].y);
myObjects[i].doSomething(Math.random()*10);
}
}
};
new p5(sketch2, "canvas_container");
When do i use "this." and when "p." in this case?
Furthermore I would like to use some "other" methods from the p5 library in my sketch.js file, outside of the instaces, like:
select('..') ...
but I get the error:
select is not defined
I found myself a dirt workaround:
new p5().select('...') ...
What is the clean way to do this?
myObjects.js
function myObject(canvas, x, y) {
this.canvas = canvas;
this.x = x;
this.y = y;
this.doSomething = function(rad) {
this.canvas.ellipse(this.x, this.y, rad, rad);
}
}
Has anybody an example for handeling multiple instances of canvases?
Note that right now, you aren't ever creating a new instance of myObject. Your array is named myObjects, but you're only ever adding p (which is an instance of p5) to it. So you can't magically call your doSomething() function on the objects you've added to your myObjects array, because they aren't myObject objects. They're p5 objects.
I think what you would want to do is take the p variable passed into each of your sketches and pass that into your "class" functions. Something like this:
p.setup = function () {
p.createCanvas(600, 400);
var myObjectOne = new myObject(p, 1, 2);
var myObjectTwo = new myObject(p, 3, 4);
}
Then it should work how you're expecting it to.

Pass additional parameters to load event JavaScript [duplicate]

When I set the src of an image object, it will trigger an onload function. How can I add parameters to it?
x = 1;
y = 2;
imageObj = new Image();
imageObj.src = ".....";
imageObj.onload = function() {
context.drawImage(imageObj, x, y);
};
x = 3;
y = 4;
In here, I want to use the x and y values that were set at the time I set the src of the image (i.e. 1 and 2). In the code above, by the time the onload function would finish, x and y could be 3 and 4.
Is there a way I can pass values into the onload function, or will it automatically use 1, and 2?
Thanks
All the other answers are some version of "make a closure". OK, that works. I think closures are cool, and languages that support them are cool...
However: there is a much cleaner way to do this, IMO. Simply use the image object to store what you need, and access it in the load handler via "this":
imageObj = new Image();
imageObj.x = 1;
imageObj.y = 2;
imageObj.onload = function() {
context.drawImage(this, this.x, this.y);
};
imageObj.src = ".....";
This is a very general technique, and I use it all the time in many objects in the DOM. (I especially use it when I have, say, four buttons and I want them to all share an "onclick" handler; I have the handler pull a bit of custom data out of the button to do THAT button's particular action.)
One warning: you have to be careful not to use a property of the object that the object class itself has a special meaning or use. (For example: you can't use imageObj.src for any old custom use; you have to leave it for the source URL.) But, in the general case, how are you to know how a given object uses all its properties? Strictly speaking, you can't. So to make this approach as safe as possible:
Wrap up all your custom data in a single object
Assign that object to a property that is unusual/unlikely to be used by the object itself.
In that regard, using "x" and "y" are a little risky as some Javascript implementation in some browser may use those properties when dealing with the Image object. But this is probably safe:
imageObj = new Image();
imageObj.myCustomData = {x: 1, y: 2};
imageObj.onload = function() {
context.drawImage(this, this.myCustomData.x, this.myCustomData.y);
};
imageObj.src = ".....";
Another advantage to this approach: it can save a lot of memory if you are creating a lot of a given object -- because you can now share a single instance of the onload handler. Consider this, using closures:
// closure based solution -- creates 1000 anonymous functions for "onload"
for (var i=0; i<1000; i++) {
var imageObj = new Image();
var x = i*20;
var y = i*10;
imageObj.onload = function() {
context.drawImage(imageObj, x, y);
};
imageObj.src = ".....";
}
Compare to shared-onload function, with your custom data tucked away in the Image object:
// custom data in the object -- creates A SINGLE "onload" function
function myImageOnload () {
context.drawImage(this, this.myCustomData.x, this.myCustomData.y);
}
for (var i=0; i<1000; i++) {
imageObj = new Image();
imageObj.myCustomData = {x: i*20, y: i*10};
imageObj.onload = myImageOnload;
imageObj.src = ".....";
}
Much memory saved and may run a skosh faster since you aren't creating all those anonymous functions. (In this example, the onload function is a one-liner.... but I've had 100-line onload functions, and a 1000 of them would surely be considered spending a lot of memory for no good reason.)
UPDATE: See use of 'data-*' attribute for a standard (and "standards approved") way to do this, in lieu of my ad-hoc suggestion to use myCustomData.
Make a private scope closure that will store x & y values:
imageObj.onload = (function(x,y){
return function() {
context.drawImage(imageObj, x, y);
};
})(x,y);
Make a small function that handles it. Local variables will hold the correct scope.
function loadImage( src, x, y) {
var imageObj = new Image();
imageObj.src = src;
imageObj.onload = function() {
context.drawImage(imageObj, x, y);
};
}
var x = 1,
y = 2;
loadImage("foo.png", x, y);
x = 3;
y = 4;
You could use an anonymous function
x = 1;
y = 2;
(function(xValue, yValue){
imageObj = new Image();
imageObj.src = ".....";
imageObj.onload = function() {
context.drawImage(imageObj, xValue, yValue);
};
})(x,y);
x = 3;
y = 4;

Trying to count points with Easeljs in javascript

Hi I'm trying to count my points in a game. I just started with javascript and working with CreateJS. My problem is that I don't know how I can use a Ticker and Click Event at the same time. It doesn't work...
function init(){
var stage = new createjs.Stage("myCanvas");
stage.mouseEventsEnabled = true;
createjs.Ticker.interval = 1500;
createjs.Ticker.addEventListener("tick", handleTick);
var statPoint = new createjs.Text("Punkte:", "bold 20px Arial", "#000000");
statPoint.x = 750;
var currentPoints = new createjs.Text("0", "20px Arial", "#000000");
currentPoints.x= 850;
var victim = new createjs.Bitmap("Opfer.png");
victim.scaleX = 0.4;
victim.scaleY = 0.4;
stage.addChild(statPoint);
stage.addChild(currentPoints);
stage.addChild(victim);
victim.addEventListener("click", handleClick);
function handleTick(event){
victim.x = 850*Math.random();
victim.y = 550*Math.random();
stage.update();
}
function handleClick(event){
currentPoints.text = parseInt(currentPoints.text + 1);
}
}
You are probably dealing with scope issues. You have defined your victim and currentPoints variables using var inside the init method, so it is only available there. This means your handleTick and handleClick methods can not access those variables. You likely have undefined errors in your console.
Change the variables to be declared outside of the init method, and they will be accessible in the handler methods.
var currentPoints, victim;
function init() {
// Other code
currentPoints = values;
victim = value;
}

Javascript constructor not working

I posted this on gamedev.stackexchange but was referred here so I'll try. I've got this simple menu that is a function, with a mainmenu.prototype.Render to draw it to the screen. Inside the mainmenu function i would like to make an array of objects containing the buttons x, y positions and the .src.
This is my current code that works, so no problem with the function itself:
this.Mainmenu = function() {
}
this.Mainmenu.prototype.Render = function() {
imgPause = new Image();
imgPause.src = 'img/pause.png';
c.drawImage(imgPause, canvas.width - 42, 10);
}
var mainmenu = new self.Mainmenu();
What I would like the final result to look like, but can't get to work (I've included the error in a comment):
this.Mainmenu = function() {
this.button = function(src, X, Y) {
this = new Image(); // Gives error "Invalid left-hand side in assignement"
this.src = src;
this.X = X;
this.Y = Y;
}
this.buttons = [pause = new this.button(src, X, Y)];
}
this.Mainmenu.prototype.Render = function() {
for (i = 0; i < this.buttons.length; i++) {
c.drawImage(this.src, this.X, this.Y);
}
}
var mainmenu = new self.Mainmenu();
But it doesn't work, if anyone can identify where my mistake is it would be appreciated, my patience is about to run out.
Well, your mistake is exactly what your js interpreter says it is - the left side of your assignment is invalid. Namely, you cannot assign this to anything, that's a rule of thumb in all languages that have the this word. The reasoning behind that is obvious - this denotes the current context of the function, the hidden argument of its. If you could overwrite it dynamically, you could alter the behaviour of every single function that is using yours thus the whole program.
How not to use this in this broken way:
this.MainMenu = function() {
this.Button = function(src, X, Y) {
var image = new Image();
image.src = src;
image.X = X;
image.Y = Y;
return image;
}
this.buttons = [pause = new this.Button(src, X, Y)];
}
Also, name your classes with PascalCase (Button, not button) and your variables with camelCase EVERYWHERE (x, not X).
You cannot do this
this.button = function(src, X, Y) {
this = new Image(); // Gives error "Invalid left-hand side in assignement"
}
this represents the current instance of Mainmenu. You cannot override an instance by another
instance.
No sense.

Object-oriented Javascript

this is my first attempt at oo javascript:
function GuiObject() {
this.x = 0;
this.y = 0;
this.width = 0;
this.height = 0;
this.parent = null;
this.children = [];
this.getWidth = function()
{
return this.width;
};
this.getHeight = function()
{
return this.height;
};
this.paint = function(ctx)
{
};
};
function paintGui(ctx, root)
{
ctx.save();
ctx.translate(root.x, root.y);
root.paint(ctx);
for (int i=0; i<root.children.length; ++i)
{
paintGui(ctx, root.children[i]);
}
ctx.restore();
};
Now in the paintGUI function, the line root.children.lengths throws an error:
Uncaught SyntaxError: Unexpected identifier.
What did i do wrong?
Thanks!
It's hard to say what your actual problem is without looking at the code that actually constructs a GuiObject but for what it's worth, here's a better way to write that 'class'.
function GuiObject() {
this.x = 0;
this.y = 0;
this.width = 0;
this.height = 0;
this.parent = null;
this.children = [];
}
GuiObject.prototype.getWidth = function()
{
return this.width;
};
GuiObject.prototype.getHeight = function()
{
return this.height;
};
GuiObject.prototype.paint = function(ctx)
{
};
Doing it this way, every instance can share the same methods. The other way, you would be creating new function objects for every instance you created. The only reason to ever define the methods in the constructor instead of attaching them to the prototypes is if they need to have access to private members that don't ever get attached to this.
int i? What's that supposed to mean in Javascript then? I think you meant var i.
BTW, In common with all the other people who responded, I looked at your code and didn't spot it immediately. What I then did was copy/paste your function into the Javascript Console and gradually removed lines until it stopped complaining. It's a useful technique to try out little bits of javascript.

Categories