Event handling
The core aspects of event handling are:
- 
Component Communication. You need to enable a component (such as <my-button>) to notify or interact with parent components or other parts of the application. Here, we achieve this by emitting a custom event from<my-button>when it’s clicked.
- 
Custom Events. You can create your own custom events (such as button-clickedin the example below), which communicate specific actions or states. You can use the$emithelper to create and dispatch a custom event.
- 
Event Data Passing. When emitting custom events, you can send additional data along with the event. In the example below, event.detailpasses contextual information (labelandcount) to any listener that handles the event.
These aspects are all implemented in the example below:
import {
  GenesisElement,
  customElement,
  html,
  observable,
} from "@genesislcap/web-core";
@customElement({
  name: "my-button",
  template: html<MyButton>`
    <button @click="${(x, c) => x.handleClick(c.event)}">
      ${(x) => `${x.label}: ${x.count}`}
    </button>
  `,
  styles,
})
export class MyButton extends GenesisElement {
  @observable label = "Clicks"; // Label text for the button
  @observable count = 0; // Tracks the number of clicks
  handleClick(event: Event) {
    this.count += 1; // Increment the click count
    console.log("Event Target:", event.target); // Log the event target element
    console.log("Component Label:", this.label); // Log the current label
    // Emit a custom "button-clicked" event with the current label and count
    this.$emit("button-clicked", {
      label: this.label,
      count: this.count,
    });
  }
}
Component communication
The example below creates a new component that listens to <my-button>.
import { GenesisElement, customElement, html } from "@genesislcap/web-core";
import MyButton from "./my-button"; // Import my-button component
MyButton;
@customElement({
  name: "parent-component",
  template: html<ParentComponent>`
    <button
      @button-clicked="${(x, c) =>
        x.handleButtonClicked(
          c.event
        )}"  // custom button-clicked event from my-button
    ></button>
  `,
})
export class ParentComponent extends GenesisElement {
  handleButtonClicked(event: CustomEvent) {
    console.log("Custom event received in ParentComponent:", event.detail);
    // Output: { label: "Clicks", count: X }
  }
}
So, <my-button> is able to communicate with its parent component (or other parts of the application) by notifying them whenever it’s clicked. The $emit helper method in the configuration of <my-button> emits a button-clicked event that any parent component or listener can handle.
Let's focus on the button-clicked custom event and the $emit method.
- 
The button-clickedevent is unique to<my-button>. It indicates that the button has been clicked, providing a specific trigger for event listeners.
- 
The $emitmethod simplifies the creation and dispatch of the button-clicked event with{bubbles: true} and {composed: true}. This makes it available to parent components and global listeners.
- 
The button-clickedevent is able to pass data (thelabelandcountproperties) to provide context on the button’s state when the event was emitted. Specifically, the$emitmethod’s second parameter passes{ label: this.label, count: this.count }asevent.detail. Any event listener can access this information for further processing.
Event bubbling
Event bubbling enables an event to travel up the DOM from the component that triggered it, and through all components to the root (yes, the root is at the top of the structure). This enables all parent components to handle the event.
- Components higher up in the DOM tree can listen for and react to events emitted by child components.
- This reduces the need for coupling child and parent components tightly.
When a custom event is emitted with $emit, it bubbles up the DOM by default unless bubbles is explicitly set to false.
import {
  GenesisElement,
  customElement,
  html,
  observable,
} from "@genesislcap/web-core";
@customElement({
  name: "my-button",
  template: html<MyButton>`
    <button @click="${(x) => x.emitCustomEvent()}">${(x) => x.label}</button>
  `,
})
export class MyButton extends GenesisElement {
  @observable label: string = "Click Me";
  @observable count: number = 0;
  emitCustomEvent() {
    this.count += 1;
    this.$emit("button-clicked", {
      label: this.label,
      count: this.count,
    }); // By default, this event bubbles up the DOM.
  }
}
Listening for events on a parent component:
@customElement({
  name: "parent-container",
  template: html<ParentContainer>`
    <div @button-clicked="${(x, c) => x.handleButtonClick(c.event)}">
      <my-button></my-button>
    </div>
  `,
})
export class ParentContainer extends GenesisElement {
  handleButtonClick(event: CustomEvent) {
    console.log("Button clicked event bubbled to parent:", event.detail);
    // Logs: { label: "Click Me", count: <number> }
  }
}
In the example above, we didn’t explicitly set bubbles and composed because they are set to true by default.
However, if you want to customize this behaviour, you can pass an 'options object' as the third parameter to $emit.
Here's how to modify the emitCustomEvent method to include bubbles and composed settings:
emitCustomEvent() {
  this.count += 1;
  this.$emit(
    "button-clicked",
    {
      label: this.label,
      count: this.count,
    },
    {
      bubbles: true,  // Event will bubble up the DOM.
      composed: true, // Event will cross the shadow DOM boundary.
    }
  );
}
In this modified event, this is how bubbling works:
- When bubblesis true, the event propagates upward through the DOM. (If you want the event to be confined to the component emitting it and not propagate up the DOM tree, setbubblesto false.)
- When bubblesis false, the event does not bubble.
This is how the `compose setting behaves:
- When composedis true, the event crosses the shadow DOM boundary and can be listened to outside the shadow DOM. (If you want the event to stay within the shadow DOM of the component, setcomposedto false.)
- When composedis false, the event is confined within the shadow DOM.
You don't need to make any changes to the parent component if you keep bubbles: true. It will still handle the event the same way.
If you set bubbles: false or composed: false, make sure you either attach the event listener directly to the emitting component or you adjust the shadow DOM handling to handle this.