Learn to build web components with this short, snappy tutorial

来源:互联网 时间:1970-01-01


In aprevious article I talked about why web components are important and since then I’ve been playing around with them quite a bit. In this tutorial I will show you how to build web components using best practices that I’ve discovered over the last few months.

What you will learn

The web component we will build in this tutorial is a simple date widget with changeable themes.

Understand the basic skeleton of a web component which you can use as the boilerplate for all of your future web components Learn how to utilize the shadow DOM within a web component See how to create a HTML template to be used within the shadow DOM Using CSS you will be able to add unique styling to your component Know how to detect attribute changes making your component reactive Be able to make use of the webcomponents.js polyfill to ensure your components work across platform Getting ready

Without further ado lets jump right into to creating our date widget web component.

Project structure

Before we can start writing some code we need to create a simple project with a few files to get us going. In your IDE of choice create a blank index.html file in the root of your project. Then within a components directory create a date-widget.comp.html . You should have a project that now looks similar to the image below.

Create a web component project structure

Notice the naming convention of our component, I always add comp before the file extension so I know at a glance what type of code that particular HTML file holds. This is very useful when you have a project with multiple component types such as Polymer , es2015 , etc.

Importing our component

If you read myprevious article you will know that we can include our web components into our project by using the import link type. Add the following code to your index.html file.

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Web Component Tutorial</title><link rel="import" href="components/date-widget.comp.html"></head><body></body></html> Making our component cross platform compatible

As web components are still in their infancy they are not widely supported in all browsers particularly Apple’s Safari and therefore any iOS based devices. Thanks to webcomponents.org we can get hold of the webcomponents.js library which will fill the gaps for the browsers which don’t support the required features we need. (If you don’t know what makes up the web component standard, check out myprevious article)

You’ll need to download the webcomponents.min.js file from webcomponents.org and add it to the root of your project and then include it into your index.html file as shown below, ensuring that it is included before our component is imported:

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Web Component Tutorial</title><script src="webcomponents.min.js"></script><link rel="import" href="components/date-widget.comp.html"></head><body></body></html> Writing a reusable web component skeleton

Before we write the code specific to our date widget lets take a look at a good starting point for all web components. This way you will get a good understanding of what is available to you and also have a place to start every time you want to build a new web component.

Add the following code to the date-widget.comp.html file you created earlier.

<template> <style> </style> <div class="container"> </div></template><script> (function() {//Get the contents of the template (_currentScript is available with webcomponents.js, use currentScript if you don't use this Polyfill)var template = document._currentScript.ownerDocument.querySelector('template');//Create a prototype for this componentvar proto = Object.create(HTMLElement.prototype);//Register a createdCallbackproto.createdCallback = function() { //Grab the contents of the template var clone = document.importNode(template.content, true); //Add the template contents to the shadow DOM this.createShadowRoot().appendChild(clone); };//Called when the component instance is attached to the DOMproto.attachedCallback = function() {};//Called when one of this components attributes changeproto.attributeChangedCallback = function(attrName, oldVal, newVal) {};//Register the element with the documentdocument.registerElement('date-widget', {prototype: proto}); }());</script>

I’ve added plenty of comments to the code so it should be easy to understand. This code represents a good boiler plate for starting you web components. It includes the creation of the shadow DOM and the copying of a template, which holds the components HTML and CSS. The template content is then placed within the components shadow DOM. The most used callbacks are also present and finally the all important registerElement function which is used to make the <date-widget></date-widget> custom DOM element available for use in our project. You should easily be able to reuse this again and again when creating your own web components.

Building the date widget

Now we have the starting point for a web component we can fill in the gaps to build our date widget. First of all we will complete the template including the CSS to style our date widget along with the CSS for the different themes available. Update the template within date-widget.comp.html as follows:

<template><style>@import url(http://fonts.googleapis.com/css?family=Roboto+Condensed:400,300,700);.container {background-color: #FFF;border-radius: 5px;box-shadow: 0 0 5px #dadada;position: relative;min-height: 100px;font-family: 'Roboto Condensed', sans-serif;margin: 10px 0;}.container .left {position: absolute;left: 0;top: 0;bottom: 0;width: 30%;color: #FFF;border-radius: 5px 0 0 5px;text-align: center;padding: 18px 0 0 0;}.container .left .month {line-height: 20px;font-weight: 300;}.container .left .day {font-size: 40px}.container .right {margin-left: 30%;padding: 10px 10px 10px 15px;color: #333;}.container .right .day-long {font-weight: 300;font-size: 18px;line-height: 35px;}.container .right .time {font-weight: bold;font-size: 35px;line-height: 40px;}/* THEME CODE */.container.green .left {background-color: #37bc9b;}.container.green .day-long {color: #278b70;}.container.red .left {background-color: #bc2751;}.container.red .day-long {color: #922146;}.container.blue .left {background-color: #356dbc;}.container.blue .day-long {color: #2d5ea3;}.container.gold .left {background-color: #bc9600;}.container.gold .day-long {color: #9a7b00;}</style><div class="container green"><div class="left"><div class="month"></div><div class="day"></div></div><div class="right"><div class="day-long"></div><div class="time"></div></div></div></template>

Nothing special within our HTML or CSS, we are going to simply display the calendar date on the left and the day of the week plus the time on the right. With the template in place we can move on to the JavaScript.

The first thing we need to do is add some constant variables which will hold the string values for the months and week days and also grab the elements from the template we want to work with. Update the script part of date-widget.comp.html as shown below:

<script> (function() {//Get the contents of the template (_currentScript is available with webcomponents.js, use currentScript if you don't use this Polyfill)var template = document._currentScript.ownerDocument.querySelector('template');//Create a prototype for this componentvar proto = Object.create(HTMLElement.prototype);//Set some constantsvar months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];var days = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];//Register a createdCallbackproto.createdCallback = function() { var clone = document.importNode(template.content, true); this.createShadowRoot().appendChild(clone); //Grab the elements from the shadow root this.$container = this.shadowRoot.querySelector('.container'); this.$month = this.shadowRoot.querySelector('.month'); this.$day = this.shadowRoot.querySelector('.day'); this.$dayLong = this.shadowRoot.querySelector('.day-long'); this.$time = this.shadowRoot.querySelector('.time');};//When the element is attached to the DOM populate the selectproto.attachedCallback = function() {};//When the options are updated, re-populate the selectproto.attributeChangedCallback = function(attrName, oldVal, newVal) {};//Register the element with the documentdocument.registerElement('date-widget', {prototype: proto}); }());</script>

Now we have everything we need to add the functionality and logic. Underneath the proto.createCallback function we will create a new function called draw which will update the web components DOM based on the current date.

proto.draw = function() {this.date = new Date();this.$month.innerHTML = months[this.date.getMonth()];this.$day.innerHTML = this.date.getDate();this.$dayLong.innerHTML = days[this.date.getDay()].toUpperCase();this.$time.innerHTML = this.date.toLocaleTimeString();};

When called this function will update the elements within the template with the associated dates, days and times based on the current time. As we want to create a live clock we are going to need to call this function when the component loads and then every second after. To do this we simple used a setTimeout within the createdCallback function. Add the following code at the bottom of the proto.createdCallback function.

//Call the draw function initiallythis.draw();var that = this;//Call the draw function every section to update the timesetInterval(function(){ that.draw();}, 1000);

With this code added if you open your index.html page within a browser you should now see a functioning date widget what will look something like this!

Functional web component

Reacting to attribute changes

Web components are designed to be interactive with based on the element attributes, just like other DOM elements. To make these interactions possible though, we need to be able to detect when the attributes have changed and then perform the appropriate action. Using the attributeChangedCallback function we can listen for changes of particular attributes and then run the desired code.

For our date widget we want to be able to change the theme when the theme attribute is changed and also set the initial theme when the component is first loaded. First off lets create another function called updateTheme :

proto.updateTheme = function(theme) { var val = "green"; if (["green", "red", "blue", "gold"].indexOf(theme) > -1) { val = theme; } this.$container.className = "container " + val;};

This function simply takes a theme and adds the class to the container element providing it is one of the available themes. Don’t forget to remove the green class from the <div class="container green"> element in the template too!

Then to update the theme when the theme attribute changes after the component has loaded, add the following code to the proto.attributeChangedCallback function.

//When the options are updated, re-populate the select//When the options are updated, re-populate the selectproto.attributeChangedCallback = function(attrName, oldVal, newVal) { switch (attrName) { case "theme": this.updateTheme(newVal); break; }};

Now, with index.html open in a browser run the following code in the JavaScript console and see the theme change from green to red:

document.getElementsByTagName("date-widget")[0].setAttribute("theme", "red");

In a typical application this code would usual be run by another function or action from the user, but this proves the concept.


And that’s all there is to creating a fully functional, reusable web component using nothing but vanilla JS, HTML and CSS. I hope you found this short tutorial useful and please let me know on twitter via @RevillWeb of any components you come up with. In my next post about web components I will be showing you how to write this web component using es2015, so be sure to subscribe to my newsletter below.