Question

I'm writing a small object-oriented wrapper around a canvas object, and I'd like to set some button callback to actual function members, instead of globally scoped functions (this way I can refer the context object as a member).

function CanvasManager(canvasId) {
    this.canvas = document.getElementById(canvasId);    
    this.ctx = this.canvas.getContext('2d');    

    var imageLoader = document.getElementById('imageLoader');
    imageLoader.addEventListener('change', this.handleImage, false);

    var lineNumberField  = document.getElementById('linenumber');
    lineNumberField.addEventListener('change', this.onLineNumberChange, false);
}

function handleImage = function(e) {
    var reader = new FileReader();    
    var that = this;

    reader.onload = function(event){
        var img = new Image();

        img.onload = function(){
            that.canvas.width = img.width;
            that.canvas.height = img.height;
            that.ctx.drawImage(img,0,0);
        };

        img.src = event.target.result;
    };

    reader.readAsDataURL(e.target.files[0]);     
};

This is called in an inline script in the html:

<div id="canvas-container" style="display:none;">
    <canvas id="imageCanvas"></canvas>
</div>
    ...
<script lang="javascript">new CanvasManager('imageCanvas');</script>

This doesn't work because in the handleImage callback, "this" doesn't refer to the CanvasManager instance, but the imageLoader instance, to my great confusion.

What am I doing wrong here?

Was it helpful?

Solution

sorry for the comment - actually, I had misinterepreted the issue as you described it if the issue was inside the second method.

As the others implied, the event listener is going to call the function with window as the this object. Don't worry, even as a JS guru I agree it doesn't make a lot of sense.

While you can simply put everything in closures and put this into a constant var (ie, me or self) my preference is to customize the call to the function to always have a particular this reference. You can do this like so:

imageLoader.addEventListener('change', this.handleImage.bind(this), false);

bind() is a relatively new JavaScript function that gives you a new method, representing the original, but called with a particular this context. Some JavaScript libraries have equivalents that are compatible with older browsers, like Dojo's "hitch". But, since your code involves a <canvas> anyway, you should be mostly okay for compatibility.

OTHER TIPS

try this

function CanvasManager(canvasId) {
    this.canvas = document.getElementById(canvasId);    
    this.ctx = this.canvas.getContext('2d');    

    var imageLoader = document.getElementById('imageLoader');
    imageLoader.addEventListener('change', this.handleImage, false);

    var lineNumberField  = document.getElementById('linenumber');
    lineNumberField.addEventListener('change', this.onLineNumberChange, false);
    var self = this; // keep CanvasManager instance
    this.onLineNumberChange  = function(){
    };
    this.handleImage  = function(){
       console.log(self); // use CanvasManager instance
    }
}

It looks like your function handleImage is outside your main function (or class) that you are trying to use. I would update the code as below:

function CanvasManager(canvasId) {
    var me = this;

    me.canvas = document.getElementById(canvasId);    
    me.ctx = me.canvas.getContext('2d');    

    var imageLoader = document.getElementById('imageLoader');
    imageLoader.addEventListener('change', me.handleImage, false);

    var lineNumberField  = document.getElementById('linenumber');
    lineNumberField.addEventListener('change', me.onLineNumberChange, false);

    me.handleImage = function(e) {
        var reader = new FileReader();    

        reader.onload = function(event){
            var img = new Image();

            img.onload = function(){
                me.canvas.width = img.width;
                me.canvas.height = img.height;
                me.ctx.drawImage(img, 0, 0);
            };

            img.src = event.target.result;
        };

        reader.readAsDataURL(e.target.files[0]);     
    }
}

By moving the handleImage inside the function it is now in scope of the class. Also by using me = this at the start of the method to always have access to it's class instance as the this will by default reference the objects who's event triggered it.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top