Stobber
Stobber

Reputation: 164

How to provide callbacks to a Custom Element

I decided to try my hand at creating a native web component by writing a Custom Element.

export default class DropDown extends HTMLElement {
...

Once I got everything in place, it seemed to be working fine. But then I decided to add some callback hooks so the application could respond to events on the component.

The logical place to add them is in the constructor, right?

 /**
   * 
   * @param {Object} callbacks
   * @param {Function} callbacks.itemSelected 
   * @param {Function} callbacks.dropDownClicked
   */
  constructor(callbacks = {}) {
    super();
    if (callbacks) this.#callbacks = callbacks;
  }

  #handleItemSelected(selection) {
    console.log('handleItemSelected()');
    if (this.#callbacks.itemSelected) this.#callbacks.itemSelected(this, selection);
    if (this.#displaySelection) this.#valuePara.textContent = selection;
  }

And then in my application code:

function demoHandler(thisVal, selection) {
    console.log('demoHandler ' + thisVal + ' ' + selection);
}

new DropDown({ itemSelected: demoHandler });

But the callback wasn't getting executed. I figured out that this.#callbacks was empty. So I logged the various method/function calls. Here's the order they're executed in:

constructor()
attributeChangedCallback()
connectedCallback()
constructor()
<user click>
handleItemSelected()

Wow. constructor() is called by the Custom Element framework. When my app code calls it again later, it's pointless because the object my app constructs isn't the object being used by the framework. Moreover, there's no way for the application to get the object ref from the framework's instantiation. Without that object reference, I don't know how I can pass in callback functions for the object to execute.

How can I add these callbacks to my object?

Upvotes: 0

Views: 552

Answers (1)

Josh
Josh

Reputation: 4322

The way that I approached this issue is by creating public methods on the class. You can then query the element and call the custom method directly.

Here's a simplified example of what I mean. The following class definition has a changeToRed function that can be called directly, because it's just an object property like any other.

class CustomElement extends HTMLElement {
  
  constructor() {
    super();
  }
  
  changeToRed() {
    this.style.color = 'red';
  }

  connectedCallback() {
    this.innerHTML = `<p>I'm a custom element.</p>`
  }
}

customElements.define("custom-element", CustomElement);

To access this method, let's first create an instance of the custom element in HTML and assign it an id.

<custom-element id="custom-element"></custom-element>

Now, we can access that custom element via the DOM and use the public method.

const customEl = document.getElementById('custom-element');
customEl.changeToRed();

See the working example on CodePen.

Also, FWIW, I really think it's better to use a framework with Custom Elements. You certainly don't have to, but it's going to make your life much easier if you're building anything of any complexity. I started out with vanilla JS/TS, then moved onto using Lit, Vue, Svelte, and React. I eventually landed on Stencil.js. My experience was that vanilla web components became very unwieldy very quickly. It took an unreasonable amount of code to do things. JSX is your friend. It will save you time and result in smaller app payloads.

Upvotes: 1

Related Questions