I have created my own custom button as below
@Component({
tag: "idv-button",
styleUrl: "idv-button.scss",
shadow: true,
})
export class IdvButton {
@Prop({ reflect: true }) text: string;
@Prop({ reflect: true }) disabled: boolean = false;
render(): any {
return (
<button disabled={this.disabled} onClick={this.onClick}>
<span class="btn-text">{this.text}</span>
</button>
);
}
private onClick(event: Event): void {
if (this.disabled) {
console.log("prevent default", event); // it doesn't come here at all
event.preventDefault();
}
}
}
and used it like this in another component in stenciljs
<idv-button
text="my button"
disabled={true}
onClick={this.startClick.bind(this)}>
</idv-button>
the problem is although the button is disabled
and even I tried to prevent default, onClick still happening and this.startClick
is called
How can I fix this issue?
I think there's some confusion about how the on...
attributes work with custom elements. On a Stencil component, if an attribute starts with on
, then it becomes a handler for the event with the same name as the rest of the attribute (case is converted automatically). E. g. if your component emits an event called myEvent
(using Stencil's @Event
decorator and EventEmitter
type), then you can add a listener for that event to your component using an onMyEvent
attribute. This is explained with an example in the docs here.
In your case, you're adding an onClick
attribute to your component, which handles the click
event for that element, which is a native DOM event that doesn't need to be set up, i. e. any HTML or custom element triggers a click
event when it is clicked (not just buttons).
So even if your component was just like
@Component({ tag: "idv-button" })
export class IdvButton {
render() {
return <p>Hello, World!</p>;
}
}
and you used it like
<idv-button onClick={console.log} />
you'd still receive the click
event every time you click the text.
What I think you're trying to achieve is to pass the onClick
handler through to the underlying button. The easiest way to do so is to pass the handler function as a prop.
import { Component, h, Host, Prop } from '@stencil/core';
@Component({ tag: "idv-button", shadow: true })
export class IdvButton {
@Prop() disabled?: boolean;
@Prop() clickHandler: (e: MouseEvent) => void;
render() {
return (
<button disabled={this.disabled} onClick={this.clickHandler.bind(this)}>
<slot />
</button>
);
}
}
<idv-button clickHandler={console.log}>
My Button
</idv-button>
Not sure you actually need to bind this
, and also changed it to pass the content as a slot but feel free to discard that suggestion.
BTW it's quite tempting to call the prop onClick
but that would clash with the on...
event handling for the native click
event, so Stencil would warn you about that at compile-time.
Another solution is to add pointer-events: none
to the host when the disabled
prop is set. This is what Ionic's ion-button
does (see button.tsx#L220 and button.scss#L70-L74).
In your case it would be something like
import { Component, Host, h, Prop } from '@stencil/core';
@Component({ tag: "idv-button", shadow: true })
export class IdvButton {
@Prop() disabled?: boolean;
render() {
return (
<Host style={{ pointerEvents: this.disabled ? 'none' : undefined }}>
<button disabled={this.disabled}>
<span class="btn-text">
<slot />
</span>
</button>
</Host>
);
}
}
(the onClick
attribute would work automatically on your component because it's a default event)