1

I'm not sure what's wrong here, but testing in the chromium and firefox, I find that I'm doing it wrong with respect to removing an EventListener from an element in javascript.

The context is a canvas game. At first, there's a splash screen shown where you click to begin the game. After you click to begin, I want to remove the listener.

The main point of interest is the removeEventListener in the startGame function. It doesn't throw an error. And the code executes (I see the game starting message in the console and I can see that "this" is the Game instance). I'm totally confused why if I keep on clicking on the canvas runs startGame each time. The expected behavior is that clicking there does nothing once the EventListener is removed.

Help!

function Game(canvas) {
  this.c = canvas;
  this.ctx = this.c.getContext("2d");
  this.c.width = CANVAS_WIDTH;
  this.c.height = CANVAS_HEIGHT;

  // Background image
  this.bgReady = false;
  this.bgImage = new Image();
  this.bgImage.onload = function () {
    window.g.bgReady = true;
  };
  this.bgImage.src = MAIN_BACKGROUND;
}


Game.prototype.setSplash = function() {
  if (this.bgReady) {
    this.ctx.drawImage(window.g.bgImage, 0, 0);
    this.ctx.font="48px Helvetica";
    this.ctx.textAlign = "center";
    this.ctx.fillStyle="rgb(0,0,255)";
    this.ctx.fillText("Click To Start",310,240);
    document.getElementById("cnvs").addEventListener(
      'click',this.startGame.bind(this),true);
  } else {
    // since setSplash is an early function
    // wait a bit for the background image and then try again
    setTimeout(this.setSplash.bind(this),100);
    console.log("bgImage not ready...");
  }
}

Game.prototype.startGame = function() {
  console.log("game starting ...");
  console.log(this);

  // step 1, remove the click listener for this function

  // why isn't this working?!
  document.getElementById("cnvs").removeEventListener(
    'click',this.startGame,true);
}
...
// other stuff ...
function initialize() {
  // Get the canvas
  var c = document.getElementById("cnvs");

  // Create a game object
  window.g = new Game(c);

  // Set the splash page
  g.setSplash();
}
window.onload=initialize;

Further info:

I also had a version where the non-working removal was written as:

this.c.removeEventListener('click',this.startGame,true);

Same behavior as the code referenced above.


EDIT: in reply to the first answer by mczepiel

I'm trying to implement your answer like this:

Typer.prototype.setSplash = function() {
  if (this.bgReady) {
    this.ctx.drawImage(window.t.bgImage, 0, 0);
    this.ctx.font="48px Helvetica";
    this.ctx.textAlign = "center";
    this.ctx.fillStyle="rgb(0,0,255)";
    this.ctx.fillText("Click To Start",310,240);
    var boundFunction = this.startGame.bind(this);
    document.getElementById("cnvs").addEventListener(
      'click',boundFunction,true,boundFunction);
  } else {
    // since setSplash is an early function 
    // wait a bit for the background image and then try again 
    setTimeout(this.setSplash.bind(this),100);
    console.log("bgImage not ready...");
  }
}

Typer.prototype.startGame = function(boundFunction) {
  console.log("game starting ...");
  console.log(this);  // strangely, now this is an Object rather 
                      // than Game, it still has the properties of 
                      // Game tho

  // step 1, remove the click listener for this function

  // still isn't working... 
  document.getElementById("cnvs").removeEventListener(
    'click',boundFunction,true);
}

I think I understood your suggestion, but perhaps not. The code above still doesn't remove the listener. Any help appreciated.

JawguyChooser
  • 1,816
  • 1
  • 18
  • 32

1 Answers1

5

You'll need to store a reference to the result of calling this.startGame.bind(this) and pass that same value to both addEventListener and removeEventListener

The remove call is expecting to remove the exact same object that was added as a listener.

Likely duplicate of removeEventListener is not working and others if you want to see the same issue in various flavors.

EDIT untested off-the-cuff suggestion:

Typer.prototype.setSplash = function() {
  if (this.bgReady) {
    // draw stuff

    var canvasElement = document.getElementById("cnvs");
    var dismissSplash = function (evt) {
        canvasElement.removeEventListener('click', dismissSplash, true);
        this.startGame();
    }.bind(this);

    canvasElement.addEventListener('click', dismissSplash, true);
  } else {
        // try to show splash later
    }
}

Typer.prototype.startGame = function() {
    // start game
}
Community
  • 1
  • 1
mczepiel
  • 711
  • 3
  • 12
  • Aha, I think I see what you mean now. Something like 'boundFunction = this.startGame.bind(this);' and then use boundFunction as the arg to the adding and removing. I will try it! – JawguyChooser Jan 15 '16 at 22:53
  • I edited the question to show my attempt to implement your answer. I must not have understood some subtlety, because the listener still isn't removed. – JawguyChooser Jan 15 '16 at 23:02
  • `boundFunction` is a local variable that exists only within the scope of the `setSplash` function; it is `undefined` in `startGame`. You'll need to store it as `this.boundFunction` for example, somewhere where you have access to it in both functions where you need it. – mczepiel Jan 15 '16 at 23:08
  • (You could also do the removal in the same closure of `setSplash` and only after you've removed the listener, then call `startGame` – mczepiel Jan 15 '16 at 23:08
  • Aha, yes, using this.boundFunction got me going. I was trying to pass boundFunction but alas, I suppose that the same error as what led to me not being able to remove the listener in the first place. I think you're probably right that it would be more elegant to do the removal in the "closure of setSplash" but I have to admit that I'm not sure what that jargon means. Any clarification of that would be greatly appreciated. – JawguyChooser Jan 15 '16 at 23:14
  • I've updated the answer with what I am suggesting to avoid adding a property to your object you may not want to expose. You can give it a try, but either way should get you going. – mczepiel Jan 15 '16 at 23:16
  • Thanks, mczepiel! Your example is what I had envisioned when you said "within the same closure", but I didn't have the detail correct until you wrote that. I'm up and running! Cheers! – JawguyChooser Jan 15 '16 at 23:31
  • removeEventListener() has recently become the bane of my life. One BIG problem with this function, is that it will NOT work with anonymous functions. You need a NAMED function to be passed to removeEventListener() in order for it to work. Even then, there are other issues to address, as I am now finding out, having rewritten an entire event handler library to purge all anonymous functions from it, but which *still* misbehaves under certain circumstances. – David Edwards Jan 29 '18 at 01:31
  • WARNING: If you specify a useCapture argument in addEventListener(), you need to provide the SAME useCapture argument in removeEventListener() in order for it to work! Which means if you're attaching multiple event listeners to a DOM element, you have to save ALL the function references AND their useCapture values in a persistent object somewhere, and then reference that object when deciding which event listener to remove! – David Edwards Jan 29 '18 at 01:37