This weekend, I blogged about a web component experiment wrapping the excellent Reveal.js presentation library. In that post, I created a component to wrap <section>
tags that represented individual slides.
I mentioned that I wanted to follow up on this and create a "child" component to represent slides. Here's what I did - including my first version which failed for a pretty obvious reason.
Alright, so in my initial post, I created the <reveal-preso>
tag. Here's an example of how such a component would work:
<reveal-preso height="700px" theme="dracula">
<section>Slide 1b</section>
<section>
<h2>Plan</h2>
<ul>
<li class="fragment">Phase One - Collect Underpants</li>
<li class="fragment">Phase Two - ?</li>
<li class="fragment">Phase Three - Profit</li>
</ul>
</section>
<section>Slide 3</section>
<section data-background-color="aquamarine">
<h2>🍦</h2>
</section>
<section data-background-image="https://placekitten.com/800/800">
<h2>Image</h2>
</section>
</reveal-preso>
My plan was to simply replace <section>
with a new component, <reveal-slide>
. My initial, flawed implementation, was super simple:
class RevealSlide extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
console.log('RS connected callback called');
let currentHTML = this.innerHTML;
this.innerHTML = `<section>${currentHTML}</section>`;
}
}
if(!customElements.get('reveal-slide')) customElements.define('reveal-slide', RevealSlide);
I then edited my HTML to add one on top:
<reveal-preso height="700px" theme="dracula">
<reveal-slide>Reveal Slide</reveal-slide>
<section>Slide 1b</section>
<!-- more sections... -->
</reveal-preso>
I intentionally just added one because I figured the other slides would continue to work as before. However, the result was broken:
I've been using Reveal for a long time, and typically when this happens, it means I made a typo somewhere in my HTML. So I did what any good web developer should do - run to StackOverflow and open up my dev tools. When I did, I saw this:
In case that's a bit hard to read, it's basically showing this:
<reveal-preso ...>
<reveal-slide>
<section> ... </section>
</reveal-slide>
<section> ... </section>
<section> ... </section>
</reveal-preso>
Reveal expects top-level <section>
tags immediately under the <div>
wrapper with class="slides"
. Since it was "under" the original web component, it wasn't found and properly handled by Reveal.
In case you want to see this broken version, you can find it below.
Fixing it ended up being rather easy, although I'm not entirely sure how "proper" this is for web components, but one of the fun things about building demos on the web is seeing what you can use in the wrong way. I simply replaced my web component instance with a new section
tag:
let currentHTML = this.innerHTML;
let section = document.createElement('section');
section.innerHTML = `${currentHTML}`;
this.parentNode.replaceChild(section, this);
This works, but of course, my web component is basically self-destructing. If I wanted to do things like handle attribute changes and the such, as far as I know, it wouldn't work. But in terms of the presentation, it worked just fine.
I then decided to take it one step further. Reveal slides can be modified with data attributes, so for example:
<section data-background-image="https://placekitten.com/800/800">
<h2>Image</h2>
</section>
"Proper" HTML allows for any custom attribute as long as you prefix it with data
, and then you can get and manipulate those attributes as you see fit.
For my web component, I thought it would be cool to allow you to use all of the attributes Reveal supports, but without the data-
prefix:
<reveal-slide background-color="red">Red Reveal Slide</reveal-slide>
<reveal-slide background-gradient="linear-gradient(to bottom, #283b95, #17b2c3)">
<h2>🐟</h2>
</reveal-slide>
<reveal-slide background-image="https://placekitten.com/800/800">
<h2>Image</h2>
</reveal-slide>
To support this, I checked the attributes
property of my web component instance. For reach, I find, I simply prefix it with data-
.
for(let i=0; i<this.attributes.length; i++) {
section.setAttribute(`data-${this.attributes[i].name}`, this.attributes[i].value);
}
Now, this would be bad for standard attributes like id
, width
, etc. But Reveal doesn't really use those for slides.
All in all, this really "reads" nicely to me:
<reveal-preso height="700px" theme="dracula">
<reveal-slide>Reveal Slide</reveal-slide>
<reveal-slide background-color="red">Red Reveal Slide</reveal-slide>
<reveal-slide background-gradient="linear-gradient(to bottom, #283b95, #17b2c3)">
<h2>🐟</h2>
</reveal-slide>
<reveal-slide>
<h2>Plan</h2>
<ul>
<li class="fragment">Phase One - Collect Underpants</li>
<li class="fragment">Phase Two - ?</li>
<li class="fragment">Phase Three - Profit</li>
</ul>
</reveal-slide>
<reveal-slide background-image="https://placekitten.com/800/800">
<h2>Image</h2>
</reveal-slide>
</reveal-preso>
Feel free to play with, and fork, this version: