Tell me more ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

I've been trying to create a small HTML5-based example, but I've ran into some problems. I want to render a rock on a <canvas> but this Javascript object won't work properly.

function ImageData() {
    this.image_names = ["rock1"];
    this.images = new Array();
    this.loadResources = function () {
        for (var i = 0; i < this.image_names.length; i++) {
            var img = new Image();
            img.onload = (function (a) {
                a.images[a.image_names[i]] = img;
                console.log(a.images); //log is successful, image is in array
            })(this);
            img.src = "images/" + this.image_names[i] + ".png";
        }
    }
}

It's use is to be as follows:

var a = new ImageData();
a.loadResources();
var rock1_image = a.images["rock1"]; //the "rock1.png" image

If you try accessing the object via console, you'll see that there is no image in the image array, after being certain the image loaded. I can't figure out why it's not working. I've looked over it multiple times.

EDIT: Here is my final, working result:

function ImageData() {
    this.image_names = ["rock1"];
    this.images = new Array();
    this.loadResources = function (callback) {
        for (var i = 0; i < this.image_names.length; i++) {
            var img = new Image();
            img.onload = (function (a, i) {
                return function() {
                    a.images[a.image_names[i]] = img;
                    if (i == (a.image_names.length - 1)) callback();
                };
            })(this, i);
            img.src = "images/" + this.image_names[i] + ".png";
        }
    }
}
share|improve this question
The function on the RHS of the assignment to the onload property is run immediately, so the result is assigned. It doesn't return anything, so undefined is assigned. – RobG 10 hours ago
1  
Also why is images an array if you're using non-numeric keys. – Musa 10 hours ago
It's easier to pull out images in an array, regardless of how they're organized, when processing multiple objects in multiple frames being generated instantly. One image gets used a few times. – dylanweber 10 hours ago

4 Answers

up vote 2 down vote accepted

I would venture to say that you are attempting to access it before it is added to your array. I would suggest adding a callback or something to your loadResources method to make sure that it's there before you attempt to access it.

this.loadResources = function (callback) {
    for (var i = 0; i < this.image_names.length; i++) {
        var img = new Image();
        img.onload = (function (a, i) {
            return function() {
                a.images[a.image_names[i]] = img;
                console.log(a.images); //log is successful, image is in array
                callback();
            };
        })(this, i);
        img.src = "images/" + this.image_names[i] + ".png";
    }
} 

then

var a = new ImageData();
var rock1_image;
a.loadResources(function() {
    rock1_image = a.images["rock1"];
    // do whatever you need to do with the image in here.
});

Also, what @Igor mentions. I totally missed that. I adjusted the code above so you could keep your this scope without having to set it elsewhere.

Updated to take care of the i issue brought up by @RobD.

share|improve this answer
Ugh, yes, thank you. I'm too stupid to have noticed. – dylanweber 10 hours ago
Also updated it because @Igor is also correct. – kalley 10 hours ago
1  
this has nothing to do with scope, it's just a variable that is set by the call and is always resolved in the current execution context. – RobG 10 hours ago
1  
Also, there is a closure with i from the onload function to the outer loadResources function that will cause issues later. – RobG 9 hours ago

Remove (this) after onload handler definition. You are actually calling this function right away, assigning undefined (since it returns nothing) as img.onload value.

share|improve this answer
1  
this is required, it references the instance (a) inside the IIFE. – RobG 10 hours ago
@RobG then maybe the IIFE should return a function? – Jan Dvorak 10 hours ago
@JanDvorak—dunno, it might as well just be run the body as statements in the for block, removing the need to pass this to the function. The OP doesn't seem to require anything to be assigned to onload. Kalley has allowed for a callback, so job done. – RobG 9 hours ago

I haven't tested your code yet, but I guess this is the problem: You are trying to access your image before it was loaded. You can use callback event handler after it was load like:

var data = new ImageData();

data.loadResources(function(imgObj){
 // imgObj is the newly load image here. Have fun with it now!
})
share|improve this answer

In the code:

> function ImageData() {
>     this.image_names = ["rock1"];
>     this.images = new Array();
>     this.loadResources = function () {
>         for (var i = 0; i < this.image_names.length; i++) {
>             var img = new Image();
>             img.onload = (function (a) {

This function will be run immediately and does not return a value, so undefined will be assigned. So no point in the assignment, just run the code.

>                 a.images[a.image_names[i]] = img;

Images is an array that is used as a plain object. Better to use a plain object then.

>                 console.log(a.images); //log is successful, image is in array
>             })(this);

The outer this must be passed in because of the IIFE. Remove the IIFE and there is no requirement to pass this. And if a reference to the instance is stored in the function, it won't matter how the function is called (i.e. you won't have to set this in the call).

>             img.src = "images/" + this.image_names[i] + ".png";
>         }
>     }
> }

You might want something like:

function ImageData() {
    this.image_names = ['rock1'];
    this.images = {};
    imageData = this;
    this.loadResources = function (callback) {

        for (var i = 0; i < imageData.image_names.length; i++) {
            var img = new Image();
            imageData.images[a.image_names[i]] = img;
            img.src = 'images/' + this.image_names[i] + '.png';

            if (callback) {
                img.onload = callback;
            }
        }
    }
}

var a = new ImageData();
a.loadResources(function(){console.log('loaded: ' + this.src);}); // loaded: …

window.onload = function() {
    document.body.appendChild(a.images.rock1); // image appears in document
}
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.