scriptify.js
scriptify.js

Reputation: 37

Javascript Custom HTMLElement issue

I'm creating a custom 'Counter' component with HTMLElement

Here is the HTML for this component:

<template id="counter-template">
  <div class='counter__wrapper'>
    <h2 id='counter__status'></h2>
    <button id='continue'>Continue</button>
    <button id='stop'>Stop</button>
  </div>
</template>
<script src="Counter.js"></script>

and the Counter.js code:

const currentDocument = document.currentScript.ownerDocument;

class CustomCounter extends HTMLElement {
  constructor() {
    super();

    this.count = 0;
    this.enableCount = true;
  }

  static get observedAttributes() { 
    return ["count"]; 
  }

  attributeChangedCallback(name, oldValue, newValue) {
    if (this.enableCount) {
      this.count = newValue;
      this.shadowRoot.querySelector('#counter__status').innerHTML = this.count;
    }
  }


  connectedCallback() {
    const shadowRoot = this.attachShadow({mode: 'open'});
    const template = currentDocument.querySelector('#counter-template');
    const instance = template.content.cloneNode(true);
    shadowRoot.appendChild(instance);

    this.setAttribute('count', this.count);
    this.shadowRoot.querySelector('#stop').addEventListener('click', function() {
      this.enableCount = false;
    })
  }

}

customElements.define('custom-counter', CustomCounter);

The component is working fine, but I'm using it in a setInterval function, where I am setting the 'count' attribute to the interval's iterator, like this:

  var counter = document.querySelector('custom-counter');
  var i = 1;
  var completedAt = 10;

  var interval = setInterval(() => {
    counter.setAttribute('count', i);
    if (i == completedAt) {
      clearInterval(interval);
    }
    i++;
}, 1000);

and it's working fine, the 'count' attribute is changing and being displayed in the html.

Now, the problem is that I need to stop the counter when I click the 'stop' button, and as you can see I've added a click listener in the connectedCallback() that will set the value of this.enableCount to false,

and despite having an if statement to check this.enableCount before updating the html in attributeChangedCallback()

  attributeChangedCallback(name, oldValue, newValue) {
    if (this.enableCount) {  // <==== THIS ONE
      this.count = newValue;
      this.shadowRoot.querySelector('#counter__status').innerHTML = this.count;
    }
  }

The count does not stop when I click the stop button. (If I console.log the value of this.enableCount it's giving me false, but the count does not stop updating)

Upvotes: 0

Views: 58

Answers (1)

connexo
connexo

Reputation: 56753

You have a this problem.

Inside your anonymous click handler function

this.shadowRoot.querySelector('#stop').addEventListener('click', function() {
  this.enableCount = false;
})

this is not your webcomponent, but the button being clicked. You can fix that in three ways:

a) Use an arrow function which does not have a this of its own:

this.shadowRoot.querySelector('#stop').addEventListener('click', () => {
  this.enableCount = false;
})

b) Explicitly state what this inside your handler function should be by using Function.prototype.bind():

this.shadowRoot.querySelector('#stop').addEventListener('click', function() {
  this.enableCount = false;
}.bind(this))

c) Use a closure and define a correctly scoped variable holding a reference to this:

const self = this;
this.shadowRoot.querySelector('#stop').addEventListener('click', function() {
  self.enableCount = false;
})

Upvotes: 1

Related Questions