Let's say we need to create a Web Component that does a very simple task of showing a heading and a paragraph inside it, and the name of the custom element should be <revamped-paragraph>. This is what the definition of this Web Component would look like:
//revampedParagraph.js
export default class RevampedParagraph extends HTMLElement {
constructor() {
super();
// template ref and content
let templateReference = document.querySelector('#revamped-paragraph-template');
let template = templateReference.content;
// adding html from template
this.append(template.cloneNode(true));
}
}
Our index.html file, the file that imports this module, would look like this:
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<title>Revamped Paragraph</title>
<!--
Notice how we use type="module"
-->
<script type="module">
// imports object from the module
// and names it as RevampedParagraph
// You can name it anything you want
// since it is a default export
import RevampedParagraph from './revampedParagraph.js';
// We are now defining the custom element
customElements.define('revamped-paragraph', RevampedParagraph);
</script>
</head>
<body>
<revamped-paragraph></revamped-paragraph>
<!--
Template for
Revamped Paragraph
-->
<template id="revamped-paragraph-template">
<h1>Revamped Paragraph</h1>
<p>This is the default paragraph inside
the revamped-paragraph element</p>
</template>
</body>
</html>
Notice how the template is a part of our HTML, and how it gets used when the module is imported. We will be learning about all the steps that take place from the actual registration of the Web Components to what happens when they are removed from the page in the next chapter, where we will learn about life cycle methods. But for now, we need to look at more examples to understand how to create Web Components.
Let's take a look at another example. In this example, we need to import multiple Web Components in the index.html file. The components are as follows:
- A student attendance table component: A table that shows the index number, student name, and attendance in a checkbox. This data is obtained from a student.json file.
- An information banner component: The purpose of this component is to show a phone number and an address for the school where these students are studying.
- A time slot component: A component that lets the user select a time slot for the class between three sets of timings.
Let us start with the first one, the <student-attendance-table> component. We need to first identify what it needs. In my opinion, these are the things it needs:
- A fetch call to the student.json file
- A template for each row of the string. I will be using template strings here
- A default text that says loading... when it is making the call and another text that says unable to retrieve student list. when the fetch call fails
This is what our student.json file looks like:
[
{
"name": "Robert De Niro",
"lastScore": 75
},
{
"name": "Jack Nicholson",
"lastScore": 87
},
{
"name": "Marlon Brando",
"lastScore": 81
},
{
"name": "Tom Hanks",
"lastScore": 62
},
{
"name": "Leonardo DiCaprio",
"lastScore": 92
}
]
This is what the definition of the Web Component looks like:
// StudentAttendanceTable.js
export default class StudentAttendanceTable extends HTMLElement {
constructor() {
super();
// we simply called another method
// inside the class
this.render();
}
render() {
// let put our loading text first
this.innerText = this.getLoadingText();
// let's start our fetch call
this.getStudentList();
}
getStudentList() {
// lets use fetch api
// https://developer.mozilla.org/en-US/docs/Web
// /API/Fetch_API/Using_Fetch
fetch('./student.json')
.then(response => {
// converts response to json
return response.json();
})
.then(jsonData => {
this.generateTable(jsonData);
})
.catch(e => {
// lets set the error message for
// the user
this.innerText = this.getErrorText();
// lets print out the error
// message for the devs
console.log(e);
});
}
generateTable(names) {
// lets loop through names
// with the help of map
let rows = names.map((data, index) => {
return this.getTableRow(index, data.name);
});
// creating the table
let table = document.createElement('table');
table.innerHTML = rows.join('');
// setting the table as html for this component
this.appendHTMLToShadowDOM(table);
}
getTableRow(index, name) {
let tableRow = `<tr>
<td>${index + 1}</td>
<td>${name}</td>
<td>
<input type="checkbox" name="${index}-attendance"/>
</td>
</tr>`;
return tableRow;
}
appendHTMLToShadowDOM(html) {
// clearing out old html
this.innerHTML = '';
let shadowRoot = this.attachShadow({mode: 'open'});
// add a text node
shadowRoot.append(html);
}
getLoadingText() {
return `loading..`;
}
getErrorText() {
return `unable to retrieve student list.`;
}
}
Notice the functions getLoadingText() and getErrorText(). Their purpose is simply to return a text. Then the render() method first consumes the getLoadingText() method, and then makes the call using getStudentList() to fetch the student list from student.json file.
Once this student list is fetched, it gets passed onto generateTable() method, where every name and its index is passed into the getTableRow() method to generate rows and then gets returned back to be a part of the table. Once the table is formed, it is then passed into the appendHTMLToShadowDOM() method to be added to the shadow DOM for the component.
It's time to look into the <information-banner> component. Since this component simply needs to display a phone number and an address of the school where they are studying, we can use <template> and make it work. This is what it looks like:
//InformationBanner.js
export default class InformationBanner extends HTMLElement {
constructor() {
super();
// we simply called another method
// inside the class
this.render();
}
render() {
// Get the reference to the template
let templateReference = document.querySelector('#information-banner-template');
// Get the content node
let templateContent = templateReference.content;
let shadowRoot = this.attachShadow({mode: 'open'});
// add the template text to the shadow root
shadowRoot.append(templateContent.cloneNode(true));
}
}
Furthermore, information-banner-template looks something like this:
<template id="information-banner-template">
<div>
<a href="tel:1234567890">Call: 1234567890</a>
<div>
<p>Just Some Random Street</p>
<p>Town</p>
<p>State - 123456</p>
</div>
</div>
</template>
As you can see, it is not much different than the custom elements we have already talked about in previous sections.
Let's move on to the last custom element, the <time-slot> component. Since it also involves a preset number of time slots, we can use a <template> tag to do our work.
The template would look something like this:
<template id="time-slot-template">
<div>
<div>
<input type="radio" name="timeslot" value="slot1" checked> 9:00
AM - 11:00 AM
</div>
<div>
<input type="radio" name="timeslot" value="slot2"> 11:00 AM -
1:00 PM
</div>
<div>
<input type="radio" name="timeslot" value="slot3"> 1:00 PM - 3:00
PM
</div>
</div>
</template>
The definition of the <time-slot> component would look like this:
// TimeSlot.js
export default class TimeSlot extends HTMLElement {
constructor() {
super();
// we simply called another method
// inside the class
this.render();
}
render() {
// Get the reference to the template
let templateReference = document.querySelector('#time-slot-
template');
// Get the content node
let templateContent = templateReference.content;
let shadowRoot = this.attachShadow({mode: 'open'});
// add the template text to the shadow root
shadowRoot.append(templateContent.cloneNode(true));
}
}
It is the same as the previous component.
Now that we have written the Web Components, it's time to take a look at the index.html file that includes all of these components together. This is what it looks like:
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<title>Student Page</title>
<!--
Notice how we use type="module"
-->
<script type="module">
// importing the first custom element
import StudentAttendanceTable from './StudentAttendanceTable.js';
// importing the second custom element
import InformationBanner from './InformationBanner.js';
// importing the third custom element
import TimeSlot from './TimeSlot.js';
customElements.define('student-attendance-table',
StudentAttendanceTable);
customElements.define('information-banner', InformationBanner);
customElements.define('time-slot', TimeSlot);
</script>
</head>
<body>
<time-slot></time-slot>
<student-attendance-table></student-attendance-table>
<information-banner></information-banner>
<template id="information-banner-template">
<div>
<a href="tel:1234567890">Call: 1234567890</a>
<div>
<p>Just Some Random Street</p>
<p>Town</p>
<p>State - 123456</p>
</div>
</div>
</template>
<template id="time-slot-template">
<div>
<div>
<input type="radio" name="timeslot" value="slot1" checked>
9:00 AM - 11:00 AM
</div>
<div>
<input type="radio" name="timeslot" value="slot2"> 11:00 AM -
1:00 PM
</div>
<div>
<input type="radio" name="timeslot" value="slot3"> 1:00 PM -
3:00 PM
</div>
</div>
</template>
</body>
</html>
As you can see, one <script> tag of type="module" can import all three of them together, and can register the custom elements, which can be used in the <body> tag.