Home > Software engineering >  Event listener not being removed
Event listener not being removed

Time:12-29

const initialPosition = [{
    position: "a2",
    name: "black_pawn_1",
    color: "black",
    image: "./images/Pawn.png",
  },
  {
    position: "b2",
    name: "black_pawn_2",
    color: "black",
    image: "./images/Pawn.png",
  },
  {
    position: "c2",
    name: "black_pawn_3",
    color: "black",
    image: "./images/Pawn.png",
  },
  {
    position: "d2",
    name: "black_pawn_4",
    color: "black",
    image: "./images/Pawn.png",
  },
  {
    position: "e2",
    name: "black_pawn_5",
    color: "black",
    image: "./images/Pawn.png",
  },
  {
    position: "f2",
    name: "black_pawn_6",
    color: "black",
    image: "./images/Pawn.png",
  },
  {
    position: "g2",
    name: "black_pawn_7",
    color: "black",
    image: "./images/Pawn.png",
  },
  {
    position: "h2",
    name: "black_pawn_8",
    color: "black",
    image: "./images/Pawn.png",
  },
  {
    position: "a1",
    name: "black_rook_1",
    color: "black",
    image: "./images/Pawn.png",
  },
  {
    position: "h1",
    name: "black_rook_2",
    color: "black",
    image: "./images/Pawn.png",
  },
  {
    position: "b1",
    name: "black_knight_2",
    color: "black",
    image: "./images/Pawn.png",
  },
  {
    position: "g1",
    name: "black_kinght_1",
    color: "black",
    image: "./images/Pawn.png",
  },
  {
    position: "c1",
    name: "black_bishop_1",
    color: "black",
    image: "./images/Pawn.png",
  },
  {
    position: "f1",
    name: "black_bishop_2",
    color: "black",
    image: "./images/Pawn.png",
  },
  {
    position: "d1",
    name: "black_queen",
    color: "black",
    image: "./images/Pawn.png",
  },
  {
    position: "e1",
    name: "black_king",
    color: "black",
    image: "./images/Pawn.png",
  },
];


class Piece {
  constructor(position, color, name, image) {
    this.position = position;
    this.image = image;
    this.color = color;
    this.name = name;
  }
  renderPiece() {
    const square = document.getElementById(this.position);
    const imageElement = document.createElement("img");
    imageElement.src = this.image;
    imageElement.id = this.name;

    imageElement.classList.add("piece");

    square.appendChild(imageElement);
    square.classList.remove("emptysqr");
    square.classList.add("occupiedsqr");

    this.handlePieceMovement();
  }

  handlePieceMovement() {
    let newPositionClicked = false;

    let selectedPiece = document.getElementById(this.name);
    selectedPiece.addEventListener("click", () => {
      console.log(selectedPiece.id   " piece clicked");


      selectedPiece.parentElement.classList.add("activeBox");
      document.querySelectorAll(".emptysqr").forEach((box) => {


        // function to remove and append piece

        function handleBoardClick() {
          console.log("trigger per click of empty square");

          setTimeout(() => {
            selectedPiece.parentElement.classList.remove("activeBox");
          }, 500);
          console.log(selectedPiece.id   " piece moved");
          if (!newPositionClicked) {
            console.log("trigger per click of empty square");
            selectedPiece.parentElement.classList.remove("activeBox");
            selectedPiece.parentElement.classList.add("emptysqr");
            selectedPiece.parentElement.classList.remove("occupiedsqr");
            // console.log(event.target);
            selectedPiece.remove();
            box.appendChild(selectedPiece);

            selectedPiece.parentElement.classList.add("activeBox");
            selectedPiece.parentElement.classList.remove("emptysqr");
            selectedPiece.parentElement.classList.add("occupiedsqr");
            setTimeout(() => {
              selectedPiece.parentElement.classList.remove("activeBox");
            }, 500);
            console.log(selectedPiece.id   " piece moved");
            newPositionClicked = true;
          } else {
            console.log(selectedPiece.id   " piece not moved");
          }
          document
            .querySelectorAll(".emptysqr")
            .forEach((item) =>
              item.removeEventListener("click", handleBoardClick)
            );
        }
        // End of function
        box.addEventListener("click", handleBoardClick);
      });
    });
  }
}

class Board {
  constructor() {
    this.sqrArray = [];
    this.numbering = [8, 7, 6, 5, 4, 3, 2, 1];
    this.letters = ["a", "b", "c", "d", "e", "f", "g", "h"];
    this.coloredBox = [
      "b8",
      "d8",
      "f8",
      "h8",
      "a7",
      "c7",
      "e7",
      "g7",
      "b6",
      "d6",
      "f6",
      "h6",
      "a5",
      "c5",
      "e5",
      "g5",
      "b4",
      "d4",
      "f4",
      "h4",
      "a3",
      "c3",
      "e3",
      "g3",
      "b2",
      "d2",
      "f2",
      "h2",
      "a1",
      "c1",
      "e1",
      "g1",
    ];
  }

  renderChessBoard() {
    const board = document.getElementById("board");
    for (let index = 0; index < this.numbering.length; index  ) {
      for (let j = 0; j < this.letters.length; j  ) {
        this.sqrArray.push(`${this.letters[j]}${this.numbering[index]}`);
      }
    }
    this.sqrArray.map((item, index) => {
      let sqr = document.createElement("div");
      board.appendChild(sqr);
      sqr.classList.add(`box`);
      sqr.classList.add("emptysqr");
      sqr.setAttribute("id", item);
      this.coloredBox.includes(item) ?
        sqr.classList.add(`blackbox`) :
        sqr.classList.add(`whitebox`);
    });
  }
}

class Engine {
  constructor() {
    this.board = new Board();
    this.initialPosition = initialPosition;
  }
  runGame() {
    this.board.renderChessBoard();
    this.renderAllPiece();
  }
  renderAllPiece() {
    this.initialPosition.map((profile) => {
      const {
        color,
        name,
        image,
        position
      } = profile;
      new Piece(position, color, name, image).renderPiece();
    });
  }
  movePiece() {
    document.querySelectorAll(".piece").forEach((piece) => {
      if (piece.parentElement.id === "selectedPiece") {
        piece.addEventListener("click", (e) => {
          console.log(piece);
        });
      }
    });
  }
}

let newGame = new Engine();

newGame.runGame();
body {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
  background-color: green;
}

#board {
  display: grid;
  grid-template-columns: repeat(8, auto);
  grid-template-rows: repeat(8, auto);
  grid-column-gap: 0px;
  grid-row-gap: 0px;
  place-content: center;
  margin: 100px auto;
  width: 411px;
  height: 411px;
  background-color: white;
}

.piece {
  position: absolute;
  cursor: pointer;
  z-index: 100;
  background-color: transparent;
}

.box {
  position: relative;
  z-index: 1;
  width: 50px;
  height: 50px;
  border: 1px solid black;
}

.blackbox {
  background-color: grey;
}

.whitebox {
  background-color: white;
}

.activeBox {
  background-color: orange;
  transition: all 0.5s linear;
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
  <link rel="stylesheet" href="style.css" />
</head>

<body>
  <main id="board"></main>

  <script src="script.js" type="module"></script>
</body>

</html>

I have two event listeners, one nested inside the other. After the second event listener is triggered i want the event listener on each of the box to be removed as i only want this sequence to happen once and the only way to do that is to remove the event listener after the function is complete. What happens instead is that the event listener on only one box i removed (the box that is clicked).

let selectedPiece = document.getElementById(this.name)
 selectedPiece.addEventListener("click", () => {

  selectedPiece.parentElement.classList.add("activeBox");
  document.querySelectorAll(".emptysqr").forEach((box) => {
    
    // function to remove and append piece
    
    function handleBoardClick() {
      selectedPiece.remove();
      box.appendChild(selectedPiece);
      document
        .querySelectorAll(".emptysqr")
        .forEach((item) =>
          item.removeEventListener("click", handleBoardClick)
        );
    }
    // End of function
    box.addEventListener("click", handleBoardClick);
    }
   }

I have tried creating a new function to hold the the remove event listener line of code and just trigger it at the end of the handleBoardClick function but its still the same thing.

I used querySelectorAll but even at that the event listener is clicked for only the square that is being clicked which is not what i want.

I have seen some suggestions about using JQuery, its a vanilla javascript challenge so nothing extra can be aadded.

CodePudding user response:

I played a little with your chessboard.

I think adding/removing a bunch of eventListeners is a wrong approach.
I never seen a situation where it's a good one. ;)
Using a flag to enable/disable movement is better.

Since the piece movement starts from a Piece and ends on a Board square, it is way simplier to have event handlers in both the Piece and Board classes.

So that global flag (an element or null) will hold the piece element that will be set in handlePieceMovementStart and used in handlePieceMovementEnd.

Notice that I used event.stopPropagation(); in handlePieceMovementStart to prevent the click event from bubbling up to the square.

CodePen

const initialPosition = [
  {
    position: "a2",
    name: "black_pawn_1",
    color: "black",
    image: "./images/Pawn.png"
  },
  {
    position: "b2",
    name: "black_pawn_2",
    color: "black",
    image: "./images/Pawn.png"
  },
  {
    position: "c2",
    name: "black_pawn_3",
    color: "black",
    image: "./images/Pawn.png"
  },
  {
    position: "d2",
    name: "black_pawn_4",
    color: "black",
    image: "./images/Pawn.png"
  },
  {
    position: "e2",
    name: "black_pawn_5",
    color: "black",
    image: "./images/Pawn.png"
  },
  {
    position: "f2",
    name: "black_pawn_6",
    color: "black",
    image: "./images/Pawn.png"
  },
  {
    position: "g2",
    name: "black_pawn_7",
    color: "black",
    image: "./images/Pawn.png"
  },
  {
    position: "h2",
    name: "black_pawn_8",
    color: "black",
    image: "./images/Pawn.png"
  },
  {
    position: "a1",
    name: "black_rook_1",
    color: "black",
    image: "./images/Pawn.png"
  },
  {
    position: "h1",
    name: "black_rook_2",
    color: "black",
    image: "./images/Pawn.png"
  },
  {
    position: "b1",
    name: "black_knight_2",
    color: "black",
    image: "./images/Pawn.png"
  },
  {
    position: "g1",
    name: "black_kinght_1",
    color: "black",
    image: "./images/Pawn.png"
  },
  {
    position: "c1",
    name: "black_bishop_1",
    color: "black",
    image: "./images/Pawn.png"
  },
  {
    position: "f1",
    name: "black_bishop_2",
    color: "black",
    image: "./images/Pawn.png"
  },
  {
    position: "d1",
    name: "black_queen",
    color: "black",
    image: "./images/Pawn.png"
  },
  {
    position: "e1",
    name: "black_king",
    color: "black",
    image: "./images/Pawn.png"
  }
];

// Used to hold the element of the piece in movement
// and also used as a flag
let pieceInMovement = null;

class Piece {
  constructor(position, color, name, image) {
    this.position = position;
    this.image = image;
    this.color = color;
    this.name = name;
  }
  renderPiece() {
    const square = document.getElementById(this.position);
    const imageElement = document.createElement("img");
    imageElement.src = this.image;
    imageElement.id = this.name;

    imageElement.classList.add("piece");

    square.appendChild(imageElement);
    square.classList.remove("emptysqr");
    square.classList.add("occupiedsqr");

    this.handlePieceMovementStart();
  }

  handlePieceMovementStart() {
    let selectedPiece = document.getElementById(this.name);
    selectedPiece.addEventListener("click", (event) => {
      event.stopPropagation();
      console.log(selectedPiece.id   " piece clicked");
      selectedPiece.parentElement.classList.add("activeBox");

      // Setting the globals for the next user click
      pieceInMovement = selectedPiece;
    });
  }
}

class Board {
  constructor() {
    this.sqrArray = [];
    this.numbering = [8, 7, 6, 5, 4, 3, 2, 1];
    this.letters = ["a", "b", "c", "d", "e", "f", "g", "h"];
    this.coloredBox = [
      "b8",
      "d8",
      "f8",
      "h8",
      "a7",
      "c7",
      "e7",
      "g7",
      "b6",
      "d6",
      "f6",
      "h6",
      "a5",
      "c5",
      "e5",
      "g5",
      "b4",
      "d4",
      "f4",
      "h4",
      "a3",
      "c3",
      "e3",
      "g3",
      "b2",
      "d2",
      "f2",
      "h2",
      "a1",
      "c1",
      "e1",
      "g1"
    ];
  }

  renderChessBoard() {
    const board = document.getElementById("board");
    for (let index = 0; index < this.numbering.length; index  ) {
      for (let j = 0; j < this.letters.length; j  ) {
        this.sqrArray.push(`${this.letters[j]}${this.numbering[index]}`);
      }
    }

    this.sqrArray.map((item, index) => {
      let sqr = document.createElement("div");
      board.appendChild(sqr);
      sqr.classList.add(`box`);
      sqr.classList.add("emptysqr");
      sqr.setAttribute("id", item);
      sqr.addEventListener("click", this.handlePieceMovementEnd);
      this.coloredBox.includes(item)
        ? sqr.classList.add(`blackbox`)
        : sqr.classList.add(`whitebox`);
    });
  }

  handlePieceMovementEnd(event) {
    console.log(
      "Square clicked!",
      pieceInMovement ? "Moving..." : "Nothing to do."
    );

    if (pieceInMovement) {
      const classes = pieceInMovement.parentElement.classList;
      classes.remove("activeBox");
      classes.remove("occupiedsqr");
      classes.add("emptysqr");

      event.target.appendChild(pieceInMovement);
      console.log(pieceInMovement.id   " piece moved");
      pieceInMovement = null;
    }
  }
}

class Engine {
  constructor() {
    this.board = new Board();
    this.initialPosition = initialPosition;
  }
  runGame() {
    this.board.renderChessBoard();
    this.renderAllPiece();
  }
  renderAllPiece() {
    this.initialPosition.map((profile) => {
      const { color, name, image, position } = profile;
      new Piece(position, color, name, image).renderPiece();
    });
  }
  movePiece() {
    document.querySelectorAll(".piece").forEach((piece) => {
      if (piece.parentElement.id === "selectedPiece") {
        piece.addEventListener("click", (e) => {
          console.log(piece);
        });
      }
    });
  }
}

let newGame = new Engine();

newGame.runGame();
body {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
  background-color: green;
}

#board {
  display: grid;
  grid-template-columns: repeat(8, auto);
  grid-template-rows: repeat(8, auto);
  grid-column-gap: 0px;
  grid-row-gap: 0px;
  place-content: center;
  margin: 100px auto;
  width: 411px;
  height: 411px;
  background-color: white;
}

.piece {
  position: absolute;
  cursor: pointer;
  z-index: 100;
  background-color: transparent;
}

.box {
  position: relative;
  z-index: 1;
  width: 50px;
  height: 50px;
  border: 1px solid black;
}

.blackbox {
  background-color: grey;
}

.whitebox {
  background-color: white;
}

.activeBox {
  background-color: orange;
  transition: all 0.5s linear;
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
  <link rel="stylesheet" href="style.css" />
</head>

<body>
  <main id="board"></main>

  <script src="script.js" type="module"></script>
</body>

</html>

  • Related