If you are familiar with Web Components or Custom Elements then you might have heard that you can not only extend from HTMLElement but also from other native elements such as HTMLInputElement.
class myInput extends HTMLInputElement {
...
}
customElements.define("my-input", myInput, { extends: 'input' });
and used like this:
<input type="text" is="my-input">
The benefits of extending an existing native element are many:
- Your customers will be familiar with the properties, attributes and events of that new component
- You will have a very clear set of rules to follow
- Frameworks such as Angular or Vue.js will know how to deal with this component
Not on the list of benefits, but is definitely a pro… if is an old browser such as Internet Explorer 11, then the element like the input will still render in its usual way. If is a text input it will look like a text input. So it can only look better as long as you load the module after checking the type of browser.
The implementation is a bit different than a normal Custom Element and the reason for that is that we can’t draw the current element (this) as it is already rendered. We can’t also remove it from the DOM as this is the communication channel between the browser and our component. We can’t also add child elements to it because elements like input don’t accept children. So what do we do?
You can add a sibling element and if is necessary you can hide the current element.
render(){
this.style.display = "none";
this.wrapperEl = document.createElement("div");
this.wrapperEl.innerHTML = `<div>My Input</div>`;
this.after(this.wrapperEl);
}
You need to remember to remove the sibling element when the component is removed from the DOM:
disconnectedCallback() {
this.wrapperEl.remove();
}
Regarding accessibility, if you are making an input, I suggest making sure the main element you are drawing is focusable (add tabIndex = 0) to make sure the element will be in focus when navigating between inputs but make sure the internal navigation if you have, is not focusable. The idea in a form is that by pressing TAB you can jump from one field to another. Internally (when the user is focused on the actual component) you can enable the keys to navigate internally like up/down. Like the Select.
One of the components I’ve implemented, I made an image browser, so the value would be the image ID and it extends input.
It looks pretty similar to a text input except for the fact that it has a browse button and that opens a modal window that allows browsing. The component depends on Bootstrap to open the modal. In this case, also the modal needs to be removed on disconnected if needed.
You need to be aware of the events you should be throwing when the data is updated like change, input, etc. When you update the value property you also need to dispatch the relevant event as it won’t be dispatched automatically:
this.dispatchEvent(new Event("input"));
I personally don’t like using frameworks for components. I think if the code is too complicated it means that something is wrong and it should be split into smaller components or classes. But that’s me, the important thing is that no matter your implementation, the outcome will be a simple way for your customers to use a component. What’s behind the black box is up to you and not up to the customer.
I hope it helped you and I would love to read your comments and suggestions.
Best of luck ;-)
This article is based on the one I wrote in https://smartcodehub.com/extending-native-elements-f151584699a2
Top comments (1)
This will not work in Safari without a polyfill and it's unlikely to do so.
bugs.webkit.org/show_bug.cgi?id=18... (marked as won't fix)
Unless you can guarantee a performant polyfill this is not necessarily a safe bet to use in Safari. We don't know if the semantics of a polyfill would necessarily match the way it works on native implementations