jbuddy_13
jbuddy_13

Reputation: 1286

JavaScript classes and EventListeners

I'm trying to use JavaScript class architecture in combination with addEventListener such that I can define a series of class methods once, which will be applied to all appropriate HTML and/or CSS objects. Below is an example with ONLY one circle, which should toggles red or blue when clicked. Unfortunately, as is, the class does architecture is unable to properly communicate with the DOM.

I've chosen to write the script in-line just to explore the basic concept more easily rather than juggling around multiple files for purposes of question cohesion (I do realize that in actuality, I'd likely use separate HTML and JS files.)

I'd like to code as is to be edited such that the circle changes color when clicked. (Also, the code DID function when class architecture was not used. I can add that if it's useful.)

<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.circle {
  height: 50px;
  width: 50px;
  background-color: #555;
  border-radius: 50%;
}
</style>
</head>
<body>

<h2>Circle CSS</h2>
<div class="circle"></div>
<script>

class GameObject{
  constructor(shape){
    this.shape = shape;
    this.clicks = 0;
  };
  clickEvent(){
    this.shape.addEventListener('click',function(){
      this.clicks += 1
      if (this.clicks % 2 == 1){
        this.shape.style.backgroundColor = 'red';
      }else{
        this.shape.style.backgroundColor = 'blue';
      }
    })
  };
};

let shape = document.querySelector('.circle')
let s = new GameObject(shape);

console.log(s);
</script>

</body>
</html>

Also, the following questions/answers went above my head, though they are related: Javascript Class & EventListener Dealing with Scope in Object methods containing 'this' keyword called by Event Listeners

Edit: I took advice from comment and added clickEvent() into this constructor. However, the following error was triggered:

Uncaught TypeError: Cannot read property 'style' of undefined
    at HTMLDivElement.<anonymous> (circle.html:31)

Upvotes: 0

Views: 377

Answers (3)

user12407908
user12407908

Reputation:

Change the name clickEvent to handleEvent, and move the .addEventListener call outside the class.

Then instead of a function, pass your GameObject instance as the event handler, and it'll work.

You don't event need to pass the element to the constructor that way. It's available from the event object defined on the handleEvent method.

class GameObject {
  constructor() {
    this.clicks = 0;
  };

  // Implement the EventListener interface
  handleEvent(event) {
    const shape = event.currentTarget;
    this.clicks += 1

    shape.style.backgroundColor = this.clicks % 2 == 1 ? 'red' : 'blue';

    console.log(this); // `this` is your class instance
  };
};

let g = new GameObject();

document.querySelector('.circle')
        .addEventListener("click", g); // use the object as the handler

console.log(g);
.circle {
  height: 50px;
  width: 50px;
  background-color: #555;
  border-radius: 50%;
}
<h2>Circle CSS</h2>
<div class="circle"></div>

What's happening here is that by naming the method handleEvent, you're causing your GameObject class to implement the EventListener interface. Therefore instances of that class are eligible to be used as an event handler instead of a function.

Upvotes: 1

Ele
Ele

Reputation: 33726

  1. You never call the function clickEvent.
  2. The handler of addEventListener uses its own context, to solve this you can either declare a variable let that = this and then use it inside of the handler, or you can use an arrow function in order to use the right context (instance of GameObject).

This approach uses an arrow function

<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.circle {
  height: 50px;
  width: 50px;
  background-color: #555;
  border-radius: 50%;
}
</style>
</head>
<body>

<h2>Circle CSS</h2>
<div class="circle"></div>
<script>

class GameObject{
  constructor(shape){
    this.shape = shape;
    this.clicks = 0;
  };
  clickEvent(){
    this.shape.addEventListener('click',() => {
      this.clicks += 1
      if (this.clicks % 2 == 1){
        this.shape.style.backgroundColor = 'red';
      }else{
        this.shape.style.backgroundColor = 'blue';
      }
    })
  };
};

let shape = document.querySelector('.circle')
let s = new GameObject(shape);
s.clickEvent();

console.log(s);
</script>

</body>
</html>

Upvotes: 0

Đinh Carabus
Đinh Carabus

Reputation: 3494

<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.circle {
  height: 50px;
  width: 50px;
  background-color: #555;
  border-radius: 50%;
}
</style>
</head>
<body>

<h2>Circle CSS</h2>
<div class="circle"></div>
<script>

class GameObject{
  constructor(shape){
    this.shape = shape;
    this.clicks = 0;
    this.clickEvent(); // call the method to add the listener
  };
  clickEvent(){
    this.shape.addEventListener('click',function(){
      this.clicks += 1
      if (this.clicks % 2 == 1){
        this.shape.style.backgroundColor = 'red';
      }else{
        this.shape.style.backgroundColor = 'blue';
      }
    }.bind(this)) // bind callback function to the the correct 'this' context
  };
};

let shape = document.querySelector('.circle')
let s = new GameObject(shape);

console.log(s);
</script>

</body>
</html>

Upvotes: 1

Related Questions