Web Components (Part 3) – Shadow DOM (Part 2)

shadow dom web components
Tue Aug 26 2014

In the previous Shadow DOM post we went over the “hidden” encapsulated tree located in our page’s elements. In this post we’ll dive a bit deeper and create our own Shadow DOM.

Shadow DOM Scope

Checkout the following snippet. We create a Shadow Root with Javascript in our DOM and then we can treat that new root separately. However, anything we do in that root will be encapsulated and out of the original document’s scope. This means that the elements of the tree are not inaccessible but not ‘immediately’ accessible. http://bstavroulakis.com/demos/webcomponents/shadow-dom/demo1.html

<!DOCTYPE HTML>
<html>
 <head>
 </head>
 <body>
 <div id="test"></div>
 <script>
 var root1 = document.querySelector("#test").createShadowRoot();
 root1.innerHTML = "<div id='spooky'><span>Boooo, I'm a spooky Shadow DOM</span></div>";
 </script>
 </body>
</html>

We can access the ‘spooky’ span with the following line of code.

document.querySelector('#test').shadowRoot.querySelector('span')

shadowDom1

CSS Scope

To access the Shadow DOM’s elements with CSS we can either add styles in the Shadow Root to style elements in the root.

Style Tag in Shadow DOM

root1.innerHTML = "<style>span{color:blue}</style><div id='spooky'><span>Boooo, I'm a spooky Shadow DOM</span></div>";

Or by adding the :host selector you can style the element that contains the Shadow Root.

The :host and :host-context Selector

root1.innerHTML = "<style>:host{border:1px solid #000;padding:10px;}span{color:blue}</style><div id='spooky'><span>Boooo, I'm a spooky Shadow DOM</span></div>";

The /deep/ Combinator and ::shadow pseudo-element

To access the elements of the Shadow DOM outside of the root you can add the /deep/ combinator. Or the ::shadow pseudo-element to access the shadow root.

<style<
 #test /deep/ span{
 font-size:22px;
 }
</style>

You can checkout the CSS examples here => http://bstavroulakis.com/demos/webcomponents/shadow-dom/demo2.html The ::content pseudo element is the last selector we’ll see. This accesses the content insertion point we’ll go over in the next section.

Insertion Points

The last section concerning the Shadom DOM will go over a concept called “Insertion Point”. We’ll go over an example to check it out. Let us say we would like to create a nice little component for our site which is a business card div. You can checkout the demo at the following link http://bstavroulakis.com/demos/webcomponents/shadow-dom/demo3.html

sd01

where we click on the business card to see the back of it. It’s HTML/CSS/JS is pretty basic

<!DOCTYPE HTML>
<html>
<head>
<style>
CSS Magic here...
</style>
</head>
<body>
<div class="flip">
 <div class="card">
 <div class="face front padding">
 <h1>Bill Stavroulakis</h1>
 </div>
 <div class="face back padding">
 <div class="back-inner">
 <img class="bio-img" src="https://api.fullstackweekly.com/wp-content/themes/bstavrou/images/200x200.jpg"/>
 <ul class="bio-details padding-left padding-top">
 <li>
 <span>Email:</span>
 <span>bstavroulakis@gmail.com</span>
 </li>
 <li>
 <span>Phone:</span>
 <span>+30 000 000 0000</span>
 </li>
 <li>
 <span>Blog:</span>
 <span>https://www.fullstackweekly.com/</span>
 </li>
 </ul>
 <div class="clearfix"></div>
 </div>
 </div>
 </div>
</div>
<script>
 document.querySelector('.front').onclick = function(){
 document.querySelector('.card').className = 'card flipped';
 };
 document.querySelector('.back').onmouseleave = function(){
 document.querySelector('.card').className = 'card';
 };
 document.querySelector('.back').onclick = function(){
 document.querySelector('.card').className = 'card';
 };
</script></body></html>

Now, how can we wrap up the HTML to make it more readable? Instead of adding all of the bells and whistles needed to make it work I want my HTML to look like the following.

<div id="card">
 <span class="name">Bill Stavroulakis</span>
 <ul class="data">
 <li>
 <span>Email:</span>
 <span>bstavroulakis@gmail.com</span>
 </li>
 <li>
 <span>Phone:</span>
 <span>+30 000 000 0000</span>
 </li>
 <li>
 <span>Blog:</span>
 <span>https://www.fullstackweekly.com/</span>
 </li>
 </ul>
</div>

I’ll use the Shadom DOM of the following and attach it to the “card” div. Can you find in the code below the <content> tags? These are called insertion points where the browser first renders the HTML till that part and then when it finds <content select=”.name”></content> it will find the div with the class “name” in the default dom tree (with the id “card” which we’ve attached this shadow dom to) and render that part at that point. Then it will continue rendering the rest of the shadow tree until it finds <content select=”.data”></content>

<div class="flip">
 <div class="card">
 <div class="face front padding">
 <h1><content select=".name"></content></h1>
 </div>
 <div class="face back padding">
 <div class="back-inner">
 <img class="bio-img" src="https://api.fullstackweekly.com/wp-content/themes/bstavrou/images/200x200.jpg"/>
 <div class="bio-details padding-left padding-top">
 <content select=".data"></content>
 </div>
 <div class="clearfix"></div>
 </div>
 </div>
 </div>
</div>

In the last demo we can see that we have the same functionality as before but have separated the descriptive HTML part with the part that adds the bells and whistles. I select the divs with the id “card” and “card2” create a shadow root and attach some HTML found in a template element. Last, I attach the same events and much more clean and declarative HTML. The sky is your limit with this method were you can have very clean and declarative HTML on one hand and on the other wrap those around with cool features and functionality hidden in those divs in an encapsulated manner. Imagine this at work with very advanced functionality hidden in nicely written HTML. http://bstavroulakis.com/demos/webcomponents/shadow-dom/demo4.html

sd02

<!DOCTYPE HTML>
 <html>
 <head>
 <style>
 CSS Magic Here...
 </style>
 <div class="flip">
 <div class="card">
 <div class="face front padding">
 <h1><content select=".name"></content></h1>
 </div>
 <div class="face back padding">
 <div class="back-inner">
 <img class="bio-img" src="https://api.fullstackweekly.com/wp-content/themes/bstavrou/images/200x200.jpg"/>
 <div class="bio-details padding-left padding-top">
 <content select=".data"></content>
 </div>
 <div class="clearfix"></div>
 </div>
 </div>
 </div>
 </div>
 </template>
<div id="card">
 <span class="name">Bill Stavroulakis</span>
 <ul class="data">
 <li>
 <span>Email:</span>
 <span>bstavroulakis@gmail.com</span>
 </li>
 <li>
 <span>Phone:</span>
 <span>+30 000 000 0000</span>
 </li>
 <li>
 <span>Blog:</span>
 <span>https://www.fullstackweekly.com/</span>
 </li>
 </ul>
 </div>
<div id="card2">
 <span class="name">John Papadakis</span>
 <ul class="data">
 <li>
 <span>Email:</span>
 <span>...</span>
 </li>
 <li>
 <span>Phone:</span>
 <span>+30 000 000 0000</span>
 </li>
 <li>
 <span>Blog:</span>
 <span>...</span>
 </li>
 </ul>
 </div>
<script>
var cards = ["card", "card2"];
function addRoot(domId){
 document.querySelector('#' + domId).createShadowRoot();
 document.querySelector('#' + domId).shadowRoot.innerHTML = document.querySelector('#card-template').cloneNode(true).innerHTML;
document.querySelector('#' + domId).shadowRoot.querySelector('.front').onclick = function(e){
 document.querySelector('#' + domId).shadowRoot.querySelector('.card').className = 'card flipped';
 };
 document.querySelector('#' + domId).shadowRoot.querySelector('.back').onmouseleave = function(e){
 document.querySelector('#' + domId).shadowRoot.querySelector('.card').className = 'card';
 };
 document.querySelector('#' + domId).shadowRoot.querySelector('.back').onclick = function(e){
 document.querySelector('#' + domId).shadowRoot.querySelector('.card').className = 'card';
 };
 }
for(var key in cards){
 addRoot(cards[key]);
 }
 </script>
</body>
</html>

Full Stack Weekly Newsletter

A free weekly newsletter for full stack web developers!