0

I have written a checkers game model which has a Board object that holds a bunch of Piece objects. The Piece objects have an event (pieceMoved) which they fire each time they are moved.

I am using EaselJS to render this game model in a canvas element.

The first thing I do is create an easeljs shape object to graphically represent each piece.

I wish to handle the pieceMoved event on the pieces' corresponding shape objects so that I can update the shapes location.

The code where I create the shapes and assign the event handlers looks something like this -

for (x=0;x<=(board_.getColCount()-1);x++) {
for (y=0;y<=(board_.getRowCount()-1);y++) {

var piece = board_.getPieceAt(x, y);

    if (!piece) continue;

    var circ = new Shape();

    circ.eventHandler = function(pieceObj) {
        this.x = pieceObj.x*squareSize;
        this.y = pieceObj.y*squareSize;
    }

    piece.addEventHandler(circ.eventHandler);

    ....

The problem is that the 'this' keyword inside the event handler doesn't refer to the right thing. I also tried the following -

piece.addEventHandler(function(px) { return circ.eventHandler; });

But again, 'this' refers to the wrong object.

What is the proper way to do something like this?

5 Answers 5

1

In an event handler, this refers to the object which fired the event. There are ways around it, but my philosophy is that you should embrace the convention rather than fight against it. So, just separate the method from the event handler:

piece.addEventHandler (function (e) {
    circ.eventHandler(e);
});

In your case, since it looks like you have looping issues, create a handler factory:

function getHandler (circ) {
    return function(e) {
        circ.eventHandler(e);
    };
}
for (var i = 0; i < circles.length; i++) {
    piece.addEventHandler(getHandler(circles[i]));
}

Edit: Prettify it with Array.forEach(). I'm guessing that circles is not an Array, so you'll probably have to use.call():

[].forEach.call(circles, function(circ) {
    piece.addEventHandler(function(e) {
        circ.eventHandler(e);
    });
});
Sign up to request clarification or add additional context in comments.

3 Comments

The factory option is the only one that has worked so far. Makes me cringe having this much code for something so simple.. anything I can do to make it prettier?
@NoPyGod - I've added my attempt at prettifying to my answer. Note that for IE8 and earlier, you'll need to add Array.forEach() yourself
Whoops, I just noticed your edit. Array.forEach() won't help here. Looks like I mixed up which item you are iterating, too. Oh, well.
1

Try moving the code to create the circle shape to a method such as,

function addShape(piece) {
    var circ = new Shape();

    piece.addEventHandler(function (pieceObj) {
        circ.x = pieceObj.x * squareSize;
        circ.y = pieceObj.y * squareSize;
    });

    return circ;
}

This will make the loop look like,

for (x = 0; x <= (board_.getColCount() - 1); x++) {
    for (y = 0; y <= (board_.getRowCount() - 1); y++) {
        var piece = board_.getPieceAt(x, y);
        if (!piece) continue;

        var circ = addShape(piece);

        ...
    }
}

I realize that this doesn't actuall use this but, in this case, it make the code more complicate and you really don't need it.

Comments

0
piece.addEventHandler(function(px) {
  circ.eventHandler(px);
});

Functions lose their context when you pass them around directly. This is because it's the dot syntax of the invocation that adds the context. So when you simply pass a reference to that function, it's invoked directly and you lose context.

So for callbacks that depend on context, it usually wise to pass an anonymous function as the callback and then do whatever you need to do inside of that.

Alternatively, if you want to edit how the callback is called you could do this:

// event happened invoke handler!
myEventHandler.call(this)

Which will execute the function object with the context of where you are calling it from (like say, your Piece isntance).

5 Comments

Just tried that, and it doesn't work. Because the code I posted is running in a loop, and the circ object is recreated for every piece on the board, the 'this' variable ends up referring to the very last circle that was created.
Well thats an entirely different issue :) In that case if you can control how the callback in invoked, then try my other suggestion.
The other suggestion isn't viable, because I need the Circle object to be the context, not the Piece object. The Piece object isn't aware of the Circle object, it's only aware of the event handler on the circle object.
A function's this keyword is set by the call (except where ES5 bind is used). It is one parameter among many that make up a function's execution context, this does not in any way represent "context".
Was just trying to use the terminology Squeegy used.
0

Try using bind (no arguments):

piece.addEventHandler(circ.eventHandler.bind(circ));

or use an anonymous function, since this is a closure problem:

piece.addEventHandler((function(circ) {
    return function() {
        circ.eventHandler.apply(circ, arguments);
    };
})(circ));

Comments

0

You must simply make a closure:

var that = this;
circ.eventHandler = function(pieceObj) {
    that.x = pieceObj.x*squareSize; // we will "remember" the correct scope now!
    that.y = pieceObj.y*squareSize;
}

Since the function fires in a different scope than the scope it was created in, it gets confused, in a way. You need to set it straight by closing over the variable. You do this by making a reference to it outside of your function and then using that reference inside your function. This will allow the function to always use the correct "this".

Comments

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.