mhulse
mhulse

Reputation: 4072

StencilJS Web Component: How to allow end-user to prevent default via custom click event?

Example Stencil.js web component:

import { Component, ComponentInterface, Event, EventEmitter, h, Host } from "@stencil/core";

@Component({
    tag: 'foo-testwebcomponent'
})
export class TestWebComponent implements ComponentInterface {
    @Event({
        eventName: 'foo-click',
        cancelable: true
    }) fooClick: EventEmitter;
    fooClickHandler() {
        this.fooClick.emit();
    }

    render() {
        return(
            <Host>
                <a href="#"
                   onClick={this.fooClickHandler.bind(this)}
                >Testing</a>
            </Host>
        )
    }
}

HTML:

<foo-testwebcomponent id="test"></foo-testwebcomponent>

<script>
  document.addEventListener('DOMContentLoaded', () => {
    document.getElementById('test')
            .addEventListener('foo-click', event => {
              event.preventDefault();
              console.log(`Foo Test Web Component clicked!`);
            });
  });
</script>

Problem:

In the HTML implementation, the prevent default does not stop the link from working.

Question:

How can I allow the end-user of my web component to prevent default, and stop the link from working?

I know that I can add preventDefault() in the fooClickHandler() (see below), but that seems odd to me. I'd like to give the control to the end user of the web component.

@Event({
  eventName: 'foo-click',
  cancelable: true
}) fooClick: EventEmitter<MouseEvent>;
fooClickHandler(event: MouseEvent) {
  event.preventDefault();
  this.fooClick.emit();
}

Upvotes: 0

Views: 921

Answers (2)

Thomas
Thomas

Reputation: 8849

There are two separate events:

  1. The user-initiated click event
  2. Your fooClick custom event

In your example you call preventDefault() on the custom event but you need to call it on the original click event to prevent the link from navigating.

I know of two ways to achieve this:

1: Track whether your custom event is canceled

You can check whether the user called preventDefault() on your custom event using the defaultPrevented property. The fooClick event handler can stay the same.

fooClickHandler(clickEvent: MouseEvent) {
  const customEvent = this.fooClick.emit();
  if (customEvent.defaultPrevented) {
    clickEvent.preventDefault();
  }
}

Check out this online demo.

2: Pass the click event

Pass the click event to the fooClick event handler so the user can cancel it.

fooClickHandler(clickEvent: MouseEvent) {
  this.fooClick.emit({ originalEvent: clickEvent });
}

And in the handler:

element.addEventListener('foo-click', event => {
  event.detail.originalEvent.preventDefault();
  console.log(`Foo Test Web Component clicked!`);
});

Upvotes: 1

One way would be to overload the addEventListener function and capture the function reference

(needs some more work to make it work with nested elements, you get drift)

Or use a custom method addClick(name,func) so the user can still add any listener

<script>
  customElements.define(
    "my-element",
    class extends HTMLElement {
      connectedCallback() {
        this.clicked = (evt)=>{
          document.body.append("component handler")
        }
        this.onclick = (evt) => {
            this.clicked(evt);
        }
      }
      addEventListener(name, func) {
        this.clicked = func;
      }
    }
  );
  document.addEventListener('DOMContentLoaded', () => {
    document.querySelector('my-element')
      .addEventListener('click', event => {
        document.body.append(`user handler`);
      });
  });
</script>
<my-element>Hello Web Components World!</my-element>

You could also use good old onevent handlers:

<script>
  customElements.define(
    "my-element",
    class extends HTMLElement {
      connectedCallback() {
        this.onclick = (evt) => console.log("component handler")
      }
    }
  );
  document.addEventListener('DOMContentLoaded', () => {
    let el = document.querySelector('my-element');
    el.onclick = event => console.log(`user handler`, el.onclick);
  });
</script>
<my-element onclick="console.log('inline')">Hello Web Components World!</my-element>

Upvotes: 1

Related Questions