(a collection of notes)

Generating Web Components with JavaScript, from HTML Templates

The Problem

In the web application I’m working on, I’m choosing to take as much care as I can for rending performance. A major part of this decision is to write everything I can from pure JavaScript. This has been challenging, given that I don’t really consider myself a JavaScript developer.

I’ve spent a good amount of the last few weeks trying to understand the “right way” to build interfaces from JavaScript. I spent some time developing a draft application with Vue (because it outperforms React) and, in the process, got a sense of the core functionality I would want and need when building the application. This isn’t the first web application I’ve built, so I wasn’t completely unfamiliar with some things. But this is, by far, the most complex.

In the process, it became clear that the fastest way to render repeated elements on a page (eg: search results) would most likely be to create Web Components from HTML Templates…likely using Slots.Here’s more on Templates and Slots from Mozilla’s Developer Network site.

However… When looking around online on YouTube, various web searches, GitHub, and GitHub Gists, I wasn’t finding anything that showed how to create Web Components, using HTML Templates. What I found instead, was that almsot everyone was generating their templates from HTML enclosed in JavaScript literals. In other words, they weren’t referencing actual HTML templates written directly in HTML code.

For a lot of reasons, I love the work done by Brad at Traversy Media. I can honestly say I wouldn’t be able to do what I’m able to do today without him and his Programming Tutorials on YouTube. Since his is the easiest example for me to find again, here’s an example of what I mean by using `innerHtml` for the Web Component html. Notice the third line of the JavaScript, using “template.innerHTML” :

See the Pen Web Components Example by Brad Traversy (@bradtraversy) on CodePen.

This is how virtually every tutorial I came across does it. But, knowing a thing or two about parsing, I knew that had to be less efficient than generating dom nodes. After various bits of research, I confirmed this, but still couldn’t find an example of how to use a WebComponent, but generate it from JavaScript. If I was better with JavaScript, maybe I’d just “get it,” and maybe this problem is pretty basic for a relatively-decent JavaScript developer. But I wasn’t getting it, and I couldn’t find examples of this.

The Solution

I wanted to be able to use the Web Components directly embedded in HTML, but also generate them via JavaScript by importing the HTML templates (and, thus, avoiding the cost of parsing the template from things like `template.innerHTML`).

Without going through the details of my process, I’ll say that I seem to have stumbled through into a solution that works. Since I can’t find this anywhere else online, I’ve included it below and on GitHub.

Finally, for those of you who have trawled the MDN about Web Components, you may notice the code is adapted from their section on using Templates and Slots.Demo
Html
JavaScript

The Code

HTML: Here’s the HTML for our Web Component as a <template> Go to this method in the GitHub repo


<template id="element-details-template">
	<style>
	details {font-family: "Open Sans Light",Helvetica,Arial}
	.name {font-weight: bold; color: #217ac0; font-size: 120%}
	h4 { margin: 10px 0 -8px 0; }
	h4 span { background: #217ac0; padding: 2px 6px 2px 6px }
	h4 span { border: 1px solid #cee9f9; border-radius: 4px }
	h4 span { color: white }
	.attributes { margin-left: 22px; font-size: 90% }
	.attributes p { margin-left: 16px; font-style: italic }
	</style>
	<details>
		<summary>
			<span>
				<code class="name"><<slot name="element-name">NEED NAME</slot>></code>
				<i class="desc"><slot name="description">NEED DESCRIPTION</slot></i>
			</span>
		</summary>
		<div class="attributes">
			<h4><span>Attributes</span></h4>
			<slot name="attributes"><p>None</p></slot>
		</div>
	</details>
	<hr>
</template>


HTML: An example of embedding a rendered template directly into the html. Go to this method in the GitHub repo


<element-details>
	<span slot="element-name">slot</span>
	<span slot="description">A placeholder inside a web
		component that users can fill with their own markup,
		with the effect of composing different DOM trees
		together.</span>
	<dl slot="attributes">
		<dt>name</dt>
		<dd>The name of the slot.</dd>
	</dl>
</element-details>


JavaScript: Note the constructor’s default argument being assumed as true. This is to allow embedding directly into HTML, by assuming it’s generated from embedded HTML.Go to this method in the GitHub repo


constructor(isEmbeddedHtml = true)
{
	super();

	// laod the element automatically when defined explictly in HTML
	// this is the only code that will be executed when embedded in the html
	if (isEmbeddedHtml) {
		const tpl = document
			.getElementById('element-details-template')
			.content;
		this.attachShadow({mode: 'open'})
			.appendChild(tpl.cloneNode(true));
		return;
	}

	//
	// everything from here on out executes when the element is
	// managed by your own custom javascript.
	// ie: it is not generated by embedded html
	//
	this.el = document
		.getElementById('element-details-template')
		.content.cloneNode(true);
};


JavaScript: Our `Attach` method is only run if we trigger it from JavaScript. For the sake of this example, I’ve included an argument to use (or not) the Shadow Root.Go to this method in the GitHub repo


Attach(id, withShadowRoot = true)
{
	const elContainer = document.createElement("div");
	elContainer.appendChild(this.el);
	if (withShadowRoot) {
		elContainer
			.attachShadow({mode: 'open'})
			.appendChild(elContainer.cloneNode(true));
	}
	document.getElementById(id).appendChild(elContainer);
};


JavaScript: Our `SetName` method to modify the component’s “element-name” slot. Notice `replaceWith()`, which could be avoided with a different Web Component structure for the sake of using `.innerHTML` and possibly better performance.Go to this method in the GitHub repo


SetSlotName(name) {
	const slot = document.createElement("span");
	slot.innerHTML = name;
	this.el.querySelector('slot[name="element-name"]').replaceWith(slot);
};


JavaScript: This line is critical, as it assigns our `wcElementDetails` class to our custom `<element-details>` html tags.Go to this method in the GitHub repo


customElements.define('element-details', wcElementDetails);


JavaScript: An example of how to instantiate the wcElementDetails class, modify it, then attach it to some part of your HTML. Go to this method in the GitHub repo


function genWithShadowDom(attachToId, name, description, attributes) {
	const el = new wcElementDetails(false);

	el.SetSlotName(name);
	el.SetSlotDescription(description);
	el.SetSlotAttributes(attributes);

	el.Attach(attachToId, true);
}


HTML: And, finally, how we can generate and append a component to our `attach-here` element. Go to this method in the GitHub repo


<button onclick="genWithShadowDom('attach-here', 'testName', 'testDescription', 'testAttributes')">with ShadowDom</button>

<button onclick="genWithOutShadowDom('attach-here', 'testName', 'testDescription', 'testAttributes')">withOut ShadowDom</button>

<br><br>

<div id="attach-here"></div>