I'm a big fan of Web Components, they're super useful. But have you ever thought of using them as an if statement?
If the user selects the cat radio element, then the "Yep, cats are the best!" message will be displayed. If they select the dog radio element, then the other message will be displayed.
Web Components for authoring content
People tend to think about web development as a way of building apps. But that misses a big category of what people do on the web: making documents! Back in the day people used to write raw HTML straight to their web servers. But now we write in a CMS or a blogging system like this one. In those sorts of systems you tend to use a rich text editor, or markdown.
HTML is still really good for making documents though! If you want to make something really custom, HTML is a great tool. Using Web Components lets you do even more interesting things, things that go beyond just bold, italic and headings. You can create custom logic as well! That's the kind of stuff that hypertext should be used for.
If you're maintaining a website, blog, or CMS with authors who like to do interesting things you should try out Web Components. They're easy to write and they're custom just for your purpose!
What does the HTML look like?
In this example I wanted to create a kind of if-statement that changed which content was displayed based on which option was selected in some radio buttons. I started by writing out the HTML, so I could get a sense of how it should work:
<label>
<input type="radio" name="animal" value="cat">
Cat
</label>
<label>
<input type="radio" name="animal" value="dog">
Dog
</label>
<ben-if name="animal" value="cat">
<p>
Yep, cats are the best!
</p>
<img src="http://placekitten.com/200/100" alt="kitten">
</ben-if>
<ben-if name="animal" value="dog">
<p>
Dogs are pretty good, but have you tried cats?
</p>
</ben-if>
You can see here I'm creating a custom element called ben-if
which has two attributes name
and value
. The idea is that if the matching radio box is ticked, then the if statement will show. Otherwise it will be hidden.
Because they're just HTML, I can put other HTML elements inside them without any issues. If you were using a markdown parser that allowed HTML, you could also put markdown inside the HTML. This makes it super flexible, so I could make lots of different sorts of things with just this one trick.
There's a lot of other benefits here to using web components. You don't need to include any third party libraries, and you don't need to set up a rendering context. It will work across any framework, including React, Vue, Svelte etc. It's part of the way the browser works!
Creating the template
To write my web component, I needed a template. This template is really simple because it doesn't do much. This is the HTML for it:
<template id="ben-if">
<style>
:host {
display: none;
}
</style>
<slot></slot>
</template>
In the styling here the :host
element refers to the web component I'm building. I've made it display: none
so that it is hidden by default. The <slot></slot>
element is where child content will be put inside this element.
Writing the javascript
The logic for this is a little bit more complicated. First I've set up some boilerplate. This renders the template I created into the web component, and keeps track of the name
and value
attributes. It also defines the custom element I've created as ben-if
.
class IfElement extends HTMLElement {
static get observedAttributes() {
return ['name', 'value'];
}
constructor() {
super();
this.attachShadow({mode: 'open'});
const template = document.getElementById('ben-if');
const clone = template.content.cloneNode(true);
this.shadowRoot.appendChild(clone);
}
attributeChangedCallback(name, oldValue, newValue) {
this[name] = newValue;
}
}
// Define this custom element
customElements.define('ben-if', IfElement);
Now that I've got the boilerplate out of the way, it's time to do the logic. I created a checkIf
method on my IfElement
to show or hide my element:
checkIf() {
const radio = document.querySelector(`[name="${this.name}"][value="${this.value}"]:checked`);
if (radio) {
this.style.display = "block";
} else {
this.style.display = "none";
}
}
This will query the document to find a checked element with the matching name
and value
. If there is one, it will set the element to display: block
. If there isn't one it will set the element to display: none
.
Now we just need to wire that call up. I put it in two places:
As an event that gets called any time a change event happens on the page
After the attributes change.
// ...
constructor() {
// ...
document.addEventListener('change', () => {
this.checkIf();
});
}
attributeChangedCallback(name, oldValue, newValue) {
// ...
this.checkIf();
}
And that's everything! Now it should all work together. Here's a codepen:
Interested in Web Components?
I'm speaking about Practical Uses for Web Components at Web Directions: Code on September 17 & 24 2021. If you're interested you can use the voucher bensentme
to get 20% off!
Top comments (2)
Is there a more functional approach to writing the JavaScript as compared to the classes?
The Web Component API is pretty strongly bound to classes. For example, registering a custom element ensures the argument is a class. There are some libraries which will help you compile to Web Components as a target. But there’s nothing like (for example) a React pure function component.