In this article, we will analyze an example of this issue, how it is traditionally tackled and how we can resolve it by using the Shadow DOM, a standard feature that has been around for a while but is not as well-known by front-end developers.
Using the DOM
Here, we define an object with a single method, renderSearchBox(). This method takes the identifier of an HTML element as its argument and appends to it the elements needed to build up a search textbox like the one shown below:
This search textbox is intended to allow the user to find glossary definitions from an online service. For simplicity, we have embedded the term definitions in the code.
When the user inserts a term in the textbox and clicks the search button, the form.onsubmit() event listener is executed. This function simply shows the definition associated with the inserted term, if any, otherwise it shows an appropriate message.
The following picture shows what the user will see when they submit the HTML term into the search text box:
You can use the myGlossary object in an HTML page:
As you can see, we imported the script from myGlossary.js file and invoked the renderSearchBox() method by passing the divBox string, as the identifier of the only div element in the page.
You can try this code on CodePen.
The problem with the DOM
The search box created by the script shown above works as expected. It is also self-contained enough to be re-used in other projects.
But what happens if you use the search box inside a page with a markup?
In this page, you notice a CSS rule re-defining the colour of the h4 element. Of course, the page designer meant to customize the welcome message. However, this rule also affects the title of the imported search box:
Since the DOM is a global entity, the CSS rule shown above affects any h4 element in the page, even the ones dynamically added from an external script. You have no way of protecting the specific h4 element defined when building the search box. You cannot encapsulate it if you just rely on the DOM.
Your search textbox magically disappears:
Try this code on CodePen.
You cannot prevent the txtTerm id from being used elsewhere on the page. And you cannot control what any external code may do, intentionally or not, with the DOM elements you created for your search box.
Again, the DOM is a global entity: any element you add to it is publicly accessible which means conflicts may happen.
The most common solution to get around this problem is to embed the portion of DOM representing your component in a separate DOM, i.e. in a separate HTML page. This page is then appended to the main page via an iframe. This is the approach used by Google to allow developers to embed its maps and its videos from YouTube and Twitter.
Let’s take a look at how to implement this idea. Rearrange the previous code like this:
The first difference, with respect to the previous version, is the creation of an iframe element:
The second difference is the way you append the portion of DOM representing the search box to the external element. With this approach, you don’t directly append the box element to the external element. You append the iframe element to the external element and then append the box element to the body of the document associated with the iframe. The code would look something like this:
With this approach, you will no longer get the conflicts you experienced with the previous version of the search box.
You can try this on CodePen.
The problem with iframes
In the iframe-based approach, you should have noticed a couple of assignments concerning the style of the iframe. Let’s recall these two assignments in the following:
These assignments define the border thickness and the height of the iframe itself. They are fundamental to provide the illusion that the search box is integrated with the main page.
But while the first instruction fulfills its task well, the second may be insufficient. In fact, you may have a chance that the size of the iframe doesn’t fit the size of the document, so you could have an effect similar to the following:
This is an ugly effect and you should avoid it.
Iframes are not very responsive. In addition, iframes are designed to embed another full document in the current page. This leads to more complexity when we want to pass data from the parent document to the child document, for example, in order to configure some elements in the child document.
Entering the Shadow DOM
A better solution to manage the encapsulation of a portion of the DOM inside the DOM of an HTML page is to use the Shadow DOM. This is a set of standard APIs enabling the encapsulation at the DOM level by allowing you to create a sort of private DOM for an HTML element.
In this case, the only difference with respect to the original version of the script consists of the following statements:
Instead of directly attaching the box element to the external element, we first create a Shadow DOM to the external element via attachShadow(). This method is available for almost any HTML element and it generates the root node for the Shadow DOM of the element.
This root node is then accessible through the shadowRoot property. In the example above, we appended to the shadowRoot of the external element the box element representing the search box. These two simple instructions grant us protection against conflicts and undesired effects when the search box is included in an arbitrary HTML page. You can try this code on CodePen.
You may notice that we passed a literal object as an argument to the attachShadow() method. This object allows us to specify the creation mode of the Shadow DOM. In our example, we set the value open as the Shadow DOM mode. This means that the Shadow DOM will be hidden to standard global DOM manipulation such as CSS rules applications, node selection and the like.
This code replaces the text of the label associated with the search textbox.
If you don’t want this to happen, you may choose to set the closed value:
In this case, the Shadow DOM will not be accessible at all.
Iframes can still be useful when they do the work they were born for: embedding an independent external page in the current page. However, we have now seen how the Shadow DOM can replace the typical encapsulation workaround based on iframes which can have a lot of benefits.
Plug: LogRocket, a DVR for web apps
LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.