Update or Change or Remove/Reset Javascript event listener

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.

Answers:

Answer

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);
    }
};

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.