onclick not picked up by custom element

1.3k Views Asked by At

I'm defining a custom element

customElements.define("my-button", class extends HTMLButtonElement {
  onclick() {
    console.log("bar");
  }
}, { extends: "button" })

https://jsfiddle.net/tuxkjp3q/

But when I click it nothing happens.

Why is that and how can I attach event handlers to each instance of the class without overriding the constructor?

Edit:

When I inspect the DOM object there is an onclick handler, but it's not functioning. This is confusing

enter image description here

Edit 2:

Further findings, when omitted from the prototype, defining a handler works

customElements.define("my-button", class extends HTMLButtonElement {
  constructor() {
    super();
    this.onclick = () => console.log("bar");
  }
}, { extends: "button" })

But when the prototype contains onclick, the same handler no longer functions

customElements.define("my-button", class extends HTMLButtonElement {
  onclick() {
    console.log("bar");
  }

  constructor() {
    super();
    this.onclick = () => console.log("bar");
  }
}, { extends: "button" })

I would be really happy if we can get an explanation of this phenomenon.

EDIT 3:

I've come up with a work around, if anyone faces the same problem. It's probably worth noting that only handlers defined in the direct prototype of the instantiated class will get attached, which is good for performance but probably flawed logic. Anyhow it could be tuned to your requirements.

// Only in the base class
constructor() {
  super();

  for (let prop of Object.getOwnPropertyNames(this.constructor.prototype)) {
    if (prop.indexOf("on") === 0) {
      this.addEventListener(prop.substring(2).toLowerCase(), this.constructor.prototype[prop]);
    }
  }
}

I am still very interested in learning the reason for this.

4

There are 4 best solutions below

1
On

The problem is you need to define the listener of click... not the function onClick

class CustomButton extends HTMLButtonElement  {
  constructor() {
    super();

    this.addEventListener('click', () => {
      console.log('clicked');
    });
  }
}

customElements.define('custom-button', CustomButton, { extends: "button" });

html

<button is="custom-button">
  Hi
</button>
1
On

All you've really done is create an onclick property for your custom element and assigned a function to it. Just because it's named onclick does not inherently mean that the system knows it's a click event because you are making your own element. You will need to create a custom event for your custom element.

0
On

For an EventTarget, it is the setter of the onXYZ event handler IDL attribute that adds the listener to the XYZ event. IDL attributes are analogous to JavaScript properties.

Method definitions (and public class fields) invoke the [[DefineOwnProperty]] internal method, not the [[Set]] internal method. (See DefinePropertyOrThrow in the ecma262 standard link.) Thus these do not add the event listener.

On the other hand, the assignment this.onclick = () => console.log("bar") inside the constructor does actually invoke the [[Set]] internal method (See PutValue in the link). Therefore it does add the event listener.

When you both define a onXYZ property in the prototype and set this.onXYZ, the setter EventTarget.prototype.onXYZ is overridden by this.prototype.onXYZ, and the event handler is not added.

1
On

Your are very blunt in attaching all handlers that start with on

Guess how many there could be:

document.body.append(
  ...Object.getOwnPropertyNames(window).filter(prop => prop.indexOf("on") === 0)
  .map((prop, idx, arr) => {
    let div = document.createElement("div");
    div.innerHTML = `${arr.length-idx} &nbsp; ${prop}`;
    return div;
  }));

You don't have to use addEventListener;

Using the (old) this.onclick= handler in Components can be more flexible,
because the user of the Component can easily overwrite it.
With addEventListener the author of the Component has to provide an removeListener method; otherwise the user can not remove a listener.

Information on the different JavaScript Event Models: