5

Ok, so I'm working on a small html5 canvas drawing library and I'm having a slight problem, here's the code (fiddle below):

var drawr = {
init: function (canvas_id, canvasWidth, canvasHeight) { //height & width are optional
    this.canvas_id = document.getElementById(canvas_id);
    this.canvasWidth = canvasWidth;
    this.canvasHeight = canvasHeight;
    this.context = this.canvas_id.getContext('2d');

    if (canvasWidth) {
        this.canvas_id.width = canvasWidth;
    }

    if (canvasHeight) {
        this.canvas_id.height = canvasHeight;
    }

},

//magic line drawing function
ctx: function (a, b, x, y, dLineColor, dLineWidth) { //lineWidth & lineColor are optional; defaults are 1px & 'black'
    this.context.lineJoin = 'round';
    this.context.beginPath();
    this.context.moveTo(a, b);
    this.context.lineTo(x, y);
    this.context.closePath();
    this.context.strokeStyle = dLineColor;
    this.context.lineWidth = dLineWidth;
    this.context.stroke();

},

//destroy event handlers to prevent drawing
destroy: function () {
    //destroy event handlers

},

draw: function (lineColor, lineWidth) {
    //create some utilities for draw function to use
    var localPen = {};
    var drawing = false;
    var canvasPos = {
        x: this.canvas_id.offsetLeft,
        y: this.canvas_id.offsetTop
    }

    //initiate event handlers
    this.canvas_id.addEventListener('mousedown', addDraw, false);

    function addDraw(e) {
        drawing = true;
        console.log(drawing);
        localPen.x = e.pageX - canvasPos.x;
        localPen.y = e.pageY - canvasPos.y;
    };


    this.canvas_id.addEventListener('mousemove', function (e) {
        var drawTo = {
            x: e.pageX - canvasPos.x,
            y: e.pageY - canvasPos.y
        }
        if (drawing) {
            drawr.ctx(localPen.x, localPen.y, drawTo.x, drawTo.y, lineColor, lineWidth);
        }

        localPen.x = drawTo.x;
        localPen.y = drawTo.y;

    });

    this.canvas_id.addEventListener('mouseup', function (e) {
        drawing = false;
    });

    this.canvas_id.addEventListener('mouseleave', function (e) {
        drawing = false;
    });
    }

}

    drawr.init('my_canvas');
    drawr.draw('red', 10);
    drawr.draw('blue', 5);

What I'm trying to accomplish here is this: when I call drawr.draw(); a second (or third, etc) for it to override the previous function. How should I go about this? As you can see in my fiddle each instance simultaneously runs.

Feel free to edit, update, delete, yell at me for bad code, etc.

1 Answer 1

11

A call to addEventListener will override the previous one, or a call to removeEventListener will remove a listener, only when the handler functions specified for that event type are strictly equal. A anonymous function, even if lexically identical, will not be equal to a second anonymous function created during a separate execution of the method.

Here's one idea: define your handler as a separate function in a closure, as follows:

obj = function() {
    function handler() { /* handle the click; "this" is the element */ }

    return {
        draw: function() {
            this.elt.addEventListener('click', handler);
            //draw a bunch of stuff
        },

        undraw: function() {
            this.elt.removeEventListener('click', handler);
            //undraw a bunch of stuff
        }

    };
}();

Now, since handler is always strictly equal to itself, the removeEventListener will successfully remove the handler. Or, a second addEventListener will not do anything (just leave the current handler in place).

However, handler has no access to the this of the object; it will be called with the event's target element as its this. In order to get the object's this into the event handler, you may be tempted to try

this.elt.addEventListener('click', handler.bind(this));

but this will fail to accomplish what you want, because the value of handler.bind(this) is different each time the method is invoked, so you will again end up with redundant event handlers, or removeEventListeners which do not work.

If you really want the object's this in the handler, and can't figure out how to retrieve it from the event, you could initialize a bound version of handler in some init function:

obj = {
    handler: function(event) { /* handle the click; "this" is the object */ },

    draw: function() {
        this.elt.addEventListener('click', this.handler);
        //draw a bunch of stuff
    },

    undraw: function() {
        this.elt.removeEventListener('click', this.handler);
        //undraw a bunch of stuff
    },

    init: function() {
        this.handler = this.handler.bind(this);
        return this;
    }
}.init();

Since this.handler is always identical to itself, this works as expected.

Using EventListener

A somewhat more elegant way to solve this problem is to pass to addEventListener, instead of a function, an object with the EventListener interface, which is any object that implements the specially-named handleEvent method, which could be the 'this' object itself, so you can do:

obj = {
    handleEvent: function(event) {
        // "this" is the  object
        if (event.type === 'click') {
            // do stuff
        }
    },
    draw: function() {
        this.elt.addEventListener('click', this);
    },
    undraw: function() {
        this.elt.removeEventListener('click', this);
    }
};

Note the this being passed to addEventListener. In other words, we are passing the object itself, in its incarnation as an instance of EventListener, by virtue of its having implemented handleEvent. handleEvent is a full-fledged method of the object and as such has full access to all its methods and properties, and because this is identical to itself, the adding, adding again, and removing behavior works as you want.

I don't see this approach used very much, but it can make event handling more streamlined, especially if you add some sugar around it, such as arranging for individual methods to handle each event type:

obj = {
    handleEvent: function(event) {
        return this[event.type](event); // dispatch to method with name of event
    },
    click: function(event) {
        // handle click; "this" is obj
    },

    draw: function() { 
        this.elt.addEventListener('click', this); 
    },
    undraw: function() {
        this.elt.removeEventListener('click', this);
    }
};
Sign up to request clarification or add additional context in comments.

8 Comments

Ok, I'm going to give this a shot. I'll let you know how it goes!
Is the way I'm wanting to do it the most logical? With the event handlers etc? Should I consider a different approach?
Not quite sure what your asking--if you want to handle events, you need event handlers.
I like the first solution under what you titled Event Listener think that I approach was what I was trying to get at. I still haven't had a chance to try it. If all goes well I'll come back and give ya the green check. In the mean time +1 for all your time and effort!!
@torazaburo you should consider copy-pasting this into a tutorial with some little examples on HTML5 and perhaps legacy handling, I haven't found so well explained event handling anywhere here.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.