Introducing Web Components And Custom Elements Using HTML and Javascript

Introducing Web Components And Custom Elements Using HTML and Javascript

Web components is a new technology that allows to create reusable custom html elements and complex components that has a separate markup, in this post we will look at web components and how to create such components in javascript.

 

 

 

Long time ago creating a custom html elements was not so easy for example when we need to create some widget that consists of a title, description and image, the traditional way is to create a <div> which contains an <h2> that represent the title, <p> element that contains the description, <img> element that contains the image like so:

Now consider that we need to use this widget in multiple places, we will do this by repeat this code over and over in the desired places. As you see this isn’t the optimal solution. Web components comes to overcome those problems by defining this widget once, then we can reuse it. 

 

The Shadow DOM

A new concept comes with web components that some browsers support it which is the Shadow DOM. The Shadow DOM is the same as Real DOM but when rendered in a web page it’s hidden, this is why it’s named Shadow. There are already built in html elements that has Shadow DOM build in their internal structure some examples include <video> and <audio> elements.

For example let’s inspect this code snippet which contains this <video> tag:

Open this code in Chrome, then right click to inspect the element as shown:

viewing shadow dom

The shadow DOM is hidden be default, to display it go to developer settings as shown:

chrome developer settings

 

 

 

 

 

 

 

Next check this button “Show user agent shadow DOM”

display chrome shadow DOM

Reload the page and inspect the video element again you will see this complex html known as the shadow DOM:

shadow DOM

As you see this is the shadow DOM tree for the <video> element, there also other many built in html elements that built with shadow DOM tree. So let’s understand some concepts related to shadow DOM in the next section and how to create a custom shadow DOM element.

Shadow DOM Architecture

The idea behind shadow DOM is encapsulation so to keep the markup, structure, style and behavior hidden and separate from the source code of the page so that other parts don’t crash and source code be kept nice and clean.

To illustrate this is will use the below diagram taken from the above example which i applied it to the <video> element.

shadow dom ilustration

As you see in this diagram shadow DOM is now clear to you but let’s clarify some terminologies in the diagram:

  • Document Tree: This is the entire page dom which begins by opening the <html> tag, <head>, <body>. The document tree can hold other nested elements as well as elements that contain shadow DOM as you see above the <video> element.
  • Shadow Host: This is the element that the shadow DOM attached to in this example the <video> element.
  • Shadow Tree: This is the same as the document tree and contain other nested elements but it’s by default hidden and can be shown by toggling browser settings. The shadow tree also have a root element which all other elements belong to.
  • Shadow Root: The root node of the shadow tree. This is the only item that appears on the page in this case the <video> element is the shadow root and all other elements that descendant from is hidden by default.

 

Keep in mind that the shadow dom contain normal html elements, those elements can contain also nested shadow DOMs.

 

Shadow DOM Usage

You can Element.attachShadow() function to attach a shadow root to any element. The method takes options parameter which specifies the mode of ‘open’ or ‘closed’.

When the mode is set to ‘open’ then we can access the elements of the shadow root from javascript. If the mode set to ‘closed’ we can’t access elements of the shadow root and it return null. This behavior is useful to prevent access to custom elements for security reasons.

Let’s see an example where we set a shadow root to this <article> element below:

Note that when running this example the <article> show on rendered page as this is the default behavior of shadow DOM, but we will use this in the next section when creating custom elements.

When calling elementRef.shadowRoot return a reference to the shadow root object which is #shadow-root(open), now if we update the mode to ‘closed’ like this:

 

Shadow Root Can Be Applied To These Elements

 

  • <article>
  • <aside>
  • <blockquote>
  • <body>
  • <div>
  • <footer>
  • <h1>
  • <h2>
  • <h3>
  • <h4>
  • <h5>
  • <h6>
  • <header>
  • <main>
  • <nav>
  • <p>
  • <section>
  • <span>
  • Any custom element

 

 

What about attaching a shadow root to an element that is already has shadow root. Let’s see this in action in this example:

When running this example it won’t run and an Invalid State Exception occurs to elements already has shadow DOM.

Now after you understand the shadow DOM let’s move on to apply this when creating custom elements.

 

 

Creating Custom Elements

Creation of custom elements become available in latest versions of Ecmascript with the use of special interface CustomElementRegistery interface, where we use CustomElementRegistery.define() method which accept two mandatory parameters:

  • A DOMString representing the tag name of the custom element. Note that custom elements must be written with (kebab-case) with dash to separate tag names.
  • A class object that defines the functionality of that element
  • The function also accept an optional parameter that specifies the extends property in this case the class must extend from the specified html object to implement it.

To see this in action we need to setup some things, first as we will deal with Ecmascript 6 classes we need away to transpile the code into ES5 code that all browsers support it as classes not supported by all major browsers, we can use something like webpack or gulp but for simplicity we will use a special npm package called Rollupjs.

Note that this example need to be run using http:// protocol not file:// protocol in order to work properly, in my case i use apache web server but you can use any server you want.

 

Install Rollup globally using this command:

 

Next prepare a new folder with this structure:

web components sample project

 

 

 

 

 

  • src: Contains Es6 code. In this case i added a single file main.js, this file where we write our Es6 code.
  • dest: Contain compiled code after we build it with rollup. build.js will be created automatically after each build.
  • rollup.config.js: This is rollup configuration file that defines the input js files and the output file locations.

 

Open rollup.config.js and add this code:

Then inside your project open terminal after each update to main.js compile the file with:

This will compile src/main.js to dest/build.js

index.html

As you see above we added dest/build.js as this the compiled code that browsers can render.

In this example i want to create a custom item that represent a comment item that contains some kind of avatar, username and comment text. Before creating custom element let’s imagine the resulting output so for this example this is i came with:

Our class must produce the above html component, so let’s open main.js and begin by adding this code:

This is the syntax of Ecmascript 6 classes, when creating custom elements the class needs to extend from HTMLElement. In the constructor i invoked super() function to call the super class constructor. Inside the constructor add this code snippet:

At first i created a shadow root using this.attachShadow() function as we saw above, note that ‘this’ refers to the current element in this case CommentItem. The reset of the code is a normal dom manipulation which i created a container and assigned a class of ‘comment-item‘. Then i created an img element and assigned class ‘comment-avatar‘, the other elements created the same way.

As a rule of thumb the class names of custom elements should describe the comment name and should be separated with dash to prevent them from interfere with other page classes.

 

Next let’s bind dynamic values from attributes so add this code right after var comment_text_item=document.createElement(‘p’).

I used html function getAttribute() to get attribute value, so i called it to get avatar, the username and the comment text.

Next we need that our custom component to contain some styling, custom components provides this and can contain an internal or external style so let’s add some internal styles, add this code after comment_text_item.textContent:

The last piece is to append the created element and style to the shadow dom:

To append the container to the shadow dom we use shadow.appendChild() as a normal html element, i do the same thing also for style. Then i append the child elements like avatar_item and comment_bio to the container, now all items connected to their parent container and the parent container connected to shadow root.

 

The Complete Code

main.js

Notice that i called customElements.define() passing the component name and the class. This is important to register the component as an element.

Now run this command to build:

To use the newly created component like so:

index.html

Great now we created a custom component like you see in frameworks like Angular or Reactjs.

 

Extended Example: Comment List

Let’s see another example which we will create a custom web component for comment list, in fact i will make use of the comment item component we just created above, this gives you a deep understanding of how to nest multiple custom components inside each other.

First refactor the comment item component to be in separate file. In the src/ folder create comment-item.js

src/comment-item.js

Next create comment list component, In the same src/ folder create comment-list.js.

src/comment-list.js

As you see in the above code the <comment-list> component contains list of <comment-item> components so at first i created a <div> that hold these child components with css class of ‘comment-list’.

 

Next we need a way to pass list of comments as an attribute to <comment-list> this term called props in frameworks like Vuejs and Reactjs so for the purpose of this example i created two methods in the above class which are strToObject() and parseCommentStr(). Those methods will take string of comments and convert it to array of comment objects which i iterated over them and injected the <comment-item> in every iteration like this:

Finally i registered the component the same way as <comment-item> using customElements.define().

Update src/main.js:

Finally to use this component open index.html and replace <comment-item> with <comment-list>:

In the above template we passed comments attribute using this structure, of course you can use another structure but you need to update the component list component to properly parse those comments.

Don’t forget to run rollup -c to build the main.js.

Using <template> and <slot>

Html5 includes the <template> tag which is well supported in all modern browsers. In fact Vuejs library utilize the <template> elements to render file based component. The <template> can be used alongside shadow DOM to create custom web components very easily.

Inside the <template> tag we can insert any arbitrary html but this html can’t be rendered in the page unless we use javascript to fetch it’s contents and insert into the document so if you run this code you see nothing:

To render the above contents is a matter of creating a class that manipulates shadow DOM the same way as saw earlier so create src/lorem-text.js:

Here i don’t need to create elements programmatically just i obtained a reference to my <template> then i used template.content.cloneNode(true) so that we can append it as a shadow root. As you see now we can rewrite the comment item example using templates approach, but i leave this assignment to you.

Now you can use the component:

 

What if we want to style this template, you can do this easily by inserting <style> directly into the <template> as shown:

Of course the <style> affect the current template contents only.

 

<slot>

<slot> is another helpful elements that can be used in combination with <template> for adding extra flexibility. The slot takes a name attribute so that we can reference that name when adding the slot content, also we can add multiple slots inside the same template to divide the template into multiple sections.

In the next example i will implement slots into template to create a component that displays a page with two sections the content and sidebar.

Create a new component articles.js in src/ directory:

main.js

index.html

In the above example in created a template with two slots. slots act as a placeholder for adding any arbitrary content. So in this case i created a slot with name article-list and another slot with name sidebar, next we need to fill those slots.

To fill the slots this is happen when implementing the component <my-articles> i referenced each slot using slot attribute and passing the slot name like this:

Also i added some styles to style the template content as we learned in the previous section. Note that to style content inside slots you have to add external style as i did in head of the document because styles added inside the templates doesn’t affect slot content.

 

 

 

Share this: