How to create web components in vanilla JavaScript ?
In modern “Coding Standards”, Code Re-usability ( like, using Web Components ) is something for which developers yearn for, to save time and key-hits and to make the coding style effective. Reusing code here, doesn’t mean to use the clone and copying/pasting it multiple times but instead it means using some existing chunk of code in some altered form again and again in your app.
In Web Development, the Web Components are a boon for code re-usability. Each HTML element that we use is kind of a web component native to the web browser. Along with these, you can also create your own custom web components to rationalise your code and the process of building web applications.
What are Web Components ?
Web Components are a set of Web Platform APIs that lets you create your own custom elements or widgets with their functionality hidden from rest of the code. These custom components or you may say widgets ensures re-usability and interoperability of the code inside the components and creating a functionality encapsulated from the rest of the code.
Web Component inhere three main Web APIs to create a custom component and utilize them in your Web Apps :-
- Custom Elements : An API to create a new HTML Tags / Elements and define their behavior.
- Shadow DOM : It allows you to define an encapsulated shadow DOM tree to an element having functionality separated from the outside DOM. This can have its own style which will not affect the DOM tree.
- Templates : In <template> tags, you can write desired markup which will not be rendered in the DOM unless you instantiate them with Javascript allowing you to reuse them as required.
If you are unfamiliar about Shadow DOM and Template tags you can always read about them ( Using Shadow DOM ) and then continue from here.
Creating a Web Component
Alright, so in this article, we will be creating a custom component and learn how you can extend the capabilities of above APIs to create a custom DOM element having its own markup, its own stylesheet and we will try adding Events too to them which will be fully independent of the outside DOM spec.
In simple words, we will create a tag for showing a notification bar as <notification-bar></notification-bar> with text which you can set in an attribute “text” of this element. So lets get started –
Firstly, we’ll be creating a Template for our Notification Bar defined with its own markup and stylesheet which will be injected to the shadow Dom of the <notification-bar>.
<template id="notification-bar-tmpl"> <div> <p>Hello! I am Mr. Component. Use me anywhere, anytime :)</p> </div> <style> div{ box-shadow: 0 5px 32px 0 rgba(0,0,0,.15); border-radius: 5px; border-left: solid 5px #2149f3; margin-bottom: 30px; padding: 20px; font-family: sans-serif; position: relative; } </style> </template>
So, this is the markup which we’ll be using for our custom element or you may say <notification> will have this markup. Now, actually moving towards the juice of this element, we need to register a custom element. For this, basic procedure is to create a class using ECMAScript 2015 syntax in which we’ll create the functionality of <notification-bar> element.
CustomElementRegistry.define( 'name', 'constructor' )
:- This will be used to register a custom element, where ‘name‘ is the name of the new custom element in which our case is ‘notification-bar‘ and ‘constructor‘ is the constructor of the new element. There is one more parameter ‘extends’ options, a string of a HTML element from which new custom element extends the properties from.
Types of Custom Elements
Basically, there are two types of custom elements,
- Customized Built-in elements :- Those, which inherit properties from default ( or Native ) Elements or extends from. For example, if we specify, ‘p’ in ‘extends’ parameter, our new custom element will inherit properties of a <p> tag.
- Autonomous :- Elements which do not inherit properties from native HTML elements.
So, you may infer that we’ll be creating an Autonomous Custom Element. Pretty right? Lets jump on the syntactic sugar.
class notiBarConstruct extends HTMLElement{ constructor(){ super(); const notiBarTemplate = document.getElementById( 'notification-bar-tmpl' ); const notiBarTemplateContent = notiBarTemplate.content; const notiBarShadowRoot = this.attachShadow( { mode : 'closed' } ); notiBarShadowRoot.appendChild( notiBarTemplateContent.cloneNode( true ) ); } }; customElements.define( 'notification-bar', notiBarConstruct );
As you can see, here we are taking the markup from our defined template ‘notification-bar-tmpl’ and attaching the markup into our component’s shadow DOM using this.attachShadow( { mode : 'closed' } )
, where mode ‘closed’ means that this shadow DOM tree is inaccessible using JavaScript outer DOM scripts and ‘this‘ refers to the new <notification-bar> element.
That’s it, you have created your own Custom Element/Tag which you can use anywhere in your HTML as <notification-bar></notification-bar>. Refer the images below :-
This markup outputs into :-
Dynamically defining properties
Re-using a chunk of code becomes more serviceable and nifty when you can alter the existing process of the code by extending the properties and defining it as per our requirement. Like for an example, we want the message to be passed in the <notification-bar> tag to be defined whenever we call the component.
So, we’ll add two attributes namely ‘color’ and ‘text’ and define the custom values to them. So, our calling of custom element would look like –
<notification-bar color="#00d000" text="Viola! its success! \m/"></notification-bar>
<notification-bar color="#ff6a6a" text="Booo! something's wrong :-("></notification-bar>
We need to update our component snippet to be used to use our passed attributes into the custom element. Nothing fancy here, just using this.getAttribute( 'attribute_name' )
and we are set. So, the code would look like this-
class notiBarConstruct extends HTMLElement{ constructor(){ super(); const notiBarTemplate = document.getElementById( 'notification-bar-tmpl' ); const notiBarTemplateContent = notiBarTemplate.content; const notiBarShadowRoot = this.attachShadow( { mode : 'closed' } ); notiBarShadowRoot.appendChild( notiBarTemplateContent.cloneNode( true ) ); //If 'text' is missing, fallback to default text content notiBarShadowRoot.querySelector( 'p' ).textContent = ( this.getAttribute( 'text' ) ) ? this.getAttribute( 'text' ) : notiBarShadowRoot.querySelector( 'p' ).textContent; notiBarShadowRoot.querySelector( 'div' ).style.borderColor = this.getAttribute( 'color' ); } }; customElements.define( 'notification-bar', notiBarConstruct );
Events in Web Components
Adding Event listeners to these custom elements is a piece of cake! By using these techniques, we can create much dense and beautiful components which can be used anywhere, anytime.
For an eg, we can create a slideshow window having buttons to toggle the slides and much more just with a simple custom element. Infer here that, it resembles how a HTML5 <video> is used and options like fullscreen, play and stop buttons are visible, although you don’t have to define them in your markup or write any script.
Let’s just create a simple ‘close’ notification button in <notification-bar> element, on which we’ll be adding a click event listener which will destroy that particular notification bar. For that, update the template, add a close element and attach some style to it.
<template id="notification-bar-tmpl"> <div> <span>X</span> <p>Hello! I am Mr. Component. Use me anywhere, anytime :)</p> </div> <style> div{ box-shadow: 0 5px 32px 0 rgba(0,0,0,.15); border-radius: 5px; border-left: solid 5px #2149f3; margin-bottom: 30px; padding: 20px; font-family: sans-serif; position: relative; } span{ position: absolute; font-size: 16px; font-weight: bolder; color: #555; cursor: pointer; right: 10px; top: 10px; } span:hover{ color: #333; } </style>
Now, add this code in the constructor of the component which will add a click event to the <span>
//Click event notiBarShadowRoot.querySelector( 'span' ).addEventListener( 'click', () => { this.parentNode.removeChild( this ); });
Note :- In the above snippet, ‘this‘ points to the constructor of the component and not the current scope.
Now, we have added a close button to the notification being displayed whenever the <notification-bar> is used. The close button should be working now. The output is shown in the image –
You can view the live example of creating and using a web component in the below pen and edit/try it on freely to understand the flow and working of this Custom Web Component.
See the Pen Custom Web Component by Sachin Yadav (@sachinyadav04) on CodePen.
By reading this article and trying a followed small example of a Web Component, you might have realized how much powerful these custom web components are and to what extent these components could be extended to facilitate a perfect code re-usability technique while encapsulating the inner functionality in our Web Apps.
Usage :-
This convenient approach let us create create slideshows, notifications, pop-ups, animated elements and etc., the components which could be re-used in the Web Apps and just use them as a plain HTML Native Elements. You can even create fully component based modular web app in which data model changes dynamically while view and controller remains static. And that’s what I call engaging dynamics in Web Apps, in one way. 🙂 🙂
Thanks for reading this article, hope it helps.
3 comments