<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jonathan Gros-Dubois</title>
    <description>The latest articles on DEV Community by Jonathan Gros-Dubois (@jondubois).</description>
    <link>https://dev.to/jondubois</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F70488%2Fbe6e5f48-7142-4024-94a3-9161c1df3a1e.jpg</url>
      <title>DEV Community: Jonathan Gros-Dubois</title>
      <link>https://dev.to/jondubois</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jondubois"/>
    <language>en</language>
    <item>
      <title>Web Components  -  The Template-Viewport Pattern  for the Shadow DOM</title>
      <dc:creator>Jonathan Gros-Dubois</dc:creator>
      <pubDate>Tue, 21 Nov 2023 14:18:48 +0000</pubDate>
      <link>https://dev.to/jondubois/web-components-the-template-viewport-pattern-for-the-shadow-dom-2gm4</link>
      <guid>https://dev.to/jondubois/web-components-the-template-viewport-pattern-for-the-shadow-dom-2gm4</guid>
      <description>&lt;p&gt;Over the past couple of months, I've been working on a new no-code (HTML markup only) 'serverless' platform which uses native Web Components on the front end (see &lt;a href="https://saasufy.com/"&gt;https://saasufy.com/&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;One of the problems I ran into early on was related to styling components which make use of the Shadow DOM. For those who don't know, the Shadow DOM is a mechanism which allows a component to support a range of features; most notably, the ability to generate new child elements inside the component at runtime without polluting the main DOM and without conflicting with (or overwriting) child elements slotted in from the outside.&lt;/p&gt;

&lt;p&gt;I had previously built components without using the Shadow DOM but I ran into a situation where I needed to work with slotted content/HTML whilst also generating additional HTML from within the component.&lt;/p&gt;

&lt;h2&gt;
  
  
  The template
&lt;/h2&gt;

&lt;p&gt;The component I wanted to build was a general-purpose &lt;code&gt;collection-browser&lt;/code&gt; component; the goal of this component was to render a customizable, browsable list of elements by filling out an arbitrarily complex HTML template with data from a back end server for each entry in a collection. I realized that this could be achieved using the Shadow DOM with slotted &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; elements like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;slot=&lt;/span&gt;&lt;span class="s"&gt;"item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;Hello {{username}}!&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately, upon rendering the filled-out template inside the Shadow DOM, external CSS definitions would not apply to them. This posed a significant problem because it meant that the &lt;code&gt;collection-browser&lt;/code&gt; component could not be made 'general purpose' across multiple different projects and/or companies as it lacked styling flexibility.&lt;/p&gt;

&lt;p&gt;The reason why external CSS styles do not apply to elements inside the Shadow DOM is because the Shadow DOM fully encapsulates its styling; this means that style definitions from the Shadow DOM cannot affect elements outside of it and it also means that elements which are inside the Shadow DOM cannot be styled with CSS that is defined outside of the component. This poses a significant problem when building general-purpose components.&lt;/p&gt;

&lt;p&gt;While there are ways to 'inject' style information into the Shadow DOM from the outside, the approaches are unfamiliar and add complexity*. To be viable, the HTML generated by the &lt;code&gt;collection-browser&lt;/code&gt; component had to abide by the page's main CSS styles automatically and without any magic.&lt;/p&gt;

&lt;p&gt;This led me to the discovery of a simple pattern which I later re-used for many of other components. I call it the &lt;code&gt;template-viewport pattern&lt;/code&gt;. The idea is that because HTML which is generated inside the Shadow DOM cannot be styled externally, any HTML generated from inside the component had to be injected outside of the Shadow DOM (inside the Light DOM).&lt;/p&gt;

&lt;h2&gt;
  
  
  The viewport
&lt;/h2&gt;

&lt;p&gt;The simplest solution I could find to allow this was to invent the concept of a 'viewport' element - This meant that the &lt;code&gt;collection-browser&lt;/code&gt; component would need to accept two slotted elements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A template to use as input to generate some 'filled out' output HTML.&lt;/li&gt;
&lt;li&gt;A viewport element to act as a container for the  output HTML.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The idea behind this is that if a viewport element is slotted into my &lt;code&gt;collection-browser&lt;/code&gt; component from the outside, any elements which are injected inside it (including those generated internally by the component) will abide by the page's main CSS styles as they will be part of the Light DOM and not the Shadow DOM.&lt;/p&gt;

&lt;p&gt;Working with slotted elements with Web Components is simple. I defined a &lt;code&gt;render()&lt;/code&gt; method for my component which generates placeholder tags for the slotted elements like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
  &amp;lt;slot name="item"&amp;gt;&amp;lt;/slot&amp;gt;
  &amp;lt;slot name="viewport"&amp;gt;&amp;lt;/slot&amp;gt;
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, the slot with &lt;code&gt;name="item"&lt;/code&gt; will hold a &lt;code&gt;&amp;lt;template slot="item"&amp;gt;&lt;/code&gt; element slotted in from the outside and the slot with &lt;code&gt;name="viewport"&lt;/code&gt; will hold a slotted &lt;code&gt;&amp;lt;div slot="viewport"&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt; element.&lt;/p&gt;

&lt;p&gt;Getting a reference to the Light DOM viewport element from inside the collection-browser via the Shadow DOM's &lt;code&gt;&amp;lt;slot name="viewport"&amp;gt;&lt;/code&gt; element is done like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;viewportNode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shadowRoot&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;slot[name="viewport"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;assignedNodes&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rendering some HTML inside it is just a matter of setting its innerHTML property like &lt;code&gt;viewportNode.innerHTML = ...&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Components in action
&lt;/h2&gt;

&lt;p&gt;Here's what the component looks like from outside:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!--
  Loads some chat messages from the server and renders
  each message based on the template with
  slot="item" into the slot="viewport" element near
  the bottom.
--&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;collection-browser&lt;/span&gt;
  &lt;span class="na"&gt;collection-type=&lt;/span&gt;&lt;span class="s"&gt;"Chat"&lt;/span&gt;
  &lt;span class="na"&gt;collection-fields=&lt;/span&gt;&lt;span class="s"&gt;"username,message,createdAt"&lt;/span&gt;
  &lt;span class="na"&gt;collection-view=&lt;/span&gt;&lt;span class="s"&gt;"recentView"&lt;/span&gt;
  &lt;span class="na"&gt;collection-view-params=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;
  &lt;span class="na"&gt;collection-page-size=&lt;/span&gt;&lt;span class="s"&gt;"50"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;slot=&lt;/span&gt;&lt;span class="s"&gt;"item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"chat-username"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;b&amp;gt;&lt;/span&gt;{{Chat.username}}&lt;span class="nt"&gt;&amp;lt;/b&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"chat-message"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{Chat.message}}&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"chat-created-at"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{date(Chat.createdAt)}}&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;slot=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"chat-viewport"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/collection-browser&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This construct has proven itself to be robust and works well with any amount of nesting. So for example you can have a &lt;code&gt;collection-browser&lt;/code&gt; rendering a template which contains another &lt;code&gt;collection-browser&lt;/code&gt; with its own template so it generates lists within a list.&lt;/p&gt;

&lt;p&gt;Another good use case for this approach has been to build an &lt;code&gt;app-router&lt;/code&gt; component which generates a different template based on the current URL's &lt;code&gt;location.hash&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;app-router&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;slot=&lt;/span&gt;&lt;span class="s"&gt;"page"&lt;/span&gt; &lt;span class="na"&gt;route-path=&lt;/span&gt;&lt;span class="s"&gt;"/home"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;This is the home page&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;slot=&lt;/span&gt;&lt;span class="s"&gt;"page"&lt;/span&gt; &lt;span class="na"&gt;route-path=&lt;/span&gt;&lt;span class="s"&gt;"/about-us"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;This is the about-us page&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;slot=&lt;/span&gt;&lt;span class="s"&gt;"page"&lt;/span&gt; &lt;span class="na"&gt;route-path=&lt;/span&gt;&lt;span class="s"&gt;"/products"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;This is the products page&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;slot=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/app-router&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, this works well with any level of nesting and you can mix and match different components.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EDIT&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you want to see this &lt;code&gt;collection-browser&lt;/code&gt; and &lt;code&gt;app-router&lt;/code&gt; code used in a working app, check out this GitHub repo: &lt;a href="https://github.com/Saasufy/product-browser-demo/blob/main/index.html#L47-L80"&gt;https://github.com/Saasufy/product-browser-demo/blob/main/index.html#L47-L80&lt;/a&gt; - It can run anywhere (just Git clone) but if you're as lazy as me, you'll want to try the hosted version here: &lt;a href="https://saasufy.github.io/product-browser-demo/index.html#/sport"&gt;https://saasufy.github.io/product-browser-demo/index.html#/sport&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The repo for the chat example I alluded to in this guide can be found here: &lt;a href="https://github.com/Saasufy/chat-app"&gt;https://github.com/Saasufy/chat-app&lt;/a&gt; and is hosted here: &lt;a href="https://saasufy.github.io/chat-app/"&gt;https://saasufy.github.io/chat-app/&lt;/a&gt; (you can log in with GitHub by clicking on the link at the bottom of the log in form).&lt;/p&gt;

&lt;p&gt;Anyway, that's it. I hope you find a use for this pattern in your own projects. Don't hesitate to hit the like button if you found this guide useful.&lt;/p&gt;

&lt;p&gt;* An approach to explicitly style elements inside a component's shadow DOM is by using the CSS parts API. See &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/::part"&gt;https://developer.mozilla.org/en-US/docs/Web/CSS/::part&lt;/a&gt; - This approach is ideal if you want to style inner elements but don't want them to abide by your page's style definitions.&lt;/p&gt;

</description>
      <category>webcomponents</category>
      <category>html</category>
      <category>template</category>
      <category>htmlelement</category>
    </item>
  </channel>
</rss>
