LD
Change your colour scheme

Using WebC for progressively-enhanced UI elements

Published:

Now I’m back in Eleventy-land, I thought I’d give WebC a go. For those unaware, it’s a templating language that generates Web Components, complete with asset bundling.

But unlike regular Web Components, you can build things that aren’t completely reliant on Javascript. Because WebC is server-side rendered initially, you can provide fallback elements that will still render if Javascript fails or is disabled.

To try it out, I created a simple live Markdown editor. It’s simply a text area that lets you input Markdown, and then live-renders it to the side. I’ve got a new 11ty project, where every file is using webc extensions, and global components are configured. I’ve created a new file, _components/rich-textarea.webc.

To start with, I’ll add my markup, which is just a textarea and a div for showing the output.

<template webc:root>
<textarea :id="this.uid" :name="name" @raw="content"></textarea>
<div class="rich-text-root" aria-live="polite">
</div>
</template>

By giving the webc:root attribute to the template, when it renders WebC will just strip out the tags and leave me with the textarea and div inside my Web Component tag.

Now in my index.webc I can use it:

<main>
<rich-textarea name="my-name" content="# This is some raw content"></rich-textarea>
</main>

And that renders, albeit a bit un-inspiring:

A textarea input on a white background. The input says "# This is some raw content".

Styling

Next, up, I want to add some styles to my component. I can include these directly in my webc file and they’ll be bundled at build time:

<style webc:scoped>
:host {
display: flex;
flex: 1;
width: 100%;
align-items: stretch;
justify-items: stretch;
}

:host textarea {
background: white;
box-sizing: border-box;
flex: 1;
font-size: 2rem;
padding: 0.5rem;
resize: none;
}

:host:not(:defined) .rich-text-root {
display: none;
}

:host .rich-text-root {
flex: 1;
padding: 0.5rem;
}
</style>

The interesting parts are the :host and the :defined pseudoclasses. :host refers to the webcomponent itself, I’ve cheated a bit and used it to give some flex styling to help make things fullscreen, but I’m not sure if that’s best practice or not.

The :defined pseudoclass tells us if the Web Component has been defined or not - with Javascript disabled, this will always be false and so we want to hide the render area when that’s the case. It’s also false right now because we haven’t added any javascript.

So, with this CSS, we now get a full-width textarea:

A large textarea on a white background, containing the text "# This is some raw content"

Adding interactivity

This is the final bit! Now, all we need is a bit of Javascript to make things interactive. We can add a script tag to our component, and in that tag use window.customElements.define to define our Web Component:

<script>
window.customElements.define("rich-textarea", class extends HTMLElement {
connectedCallback() {
this.renderer = new markdownit();
this.content = this.attributes.content.value;
this.textarea = this.querySelector('textarea');
this.root = this.querySelector(':scope > .rich-text-root');

this.textarea.addEventListener('change', (e) => this.update(e));
this.textarea.addEventListener('keyup', (e) => this.update(e));
this.root.innerHTML = this.renderer.render(this.content);
}

update(e) {
this.content = this.textarea.value;
this.root.innerHTML = this.renderer.render(this.content);
}
});
</script>

So, when the Javascript has loaded and connectedCallback runs, we’re getting the content we’ve passed as a prop, and using the MarkdownIt library to transform it to HTML and render it within our rich-text-root.

Then, whenever the user triggers a change or keyup event on the textarea, we update that content again, giving us a live reload.

The nice part about this is that because we’re using WebC, the markup it generates already includes a fallback:

<!-- _site/index.html -->
<rich-textarea
name="my-name"
content="# This is some raw content"
class="wa1k3zmq0"
>

<textarea id="webc-hf3zp" name="my-name">
# This is some raw content
</textarea>
<div class="rich-text-root" aria-live="polite"></div>
</rich-textarea>

That means that, if Javascript is enabled then I get the full live-edit functionality:

A page with a textarea on one half of the page, and the rendered output on the other.

And when we disable Javascript, we just get the textarea on it’s own as a fallback:

The same textarea, now taking up the full page size

About the author

My face

I'm Lewis Dale, a software engineer and web developer based in the UK. I write about writing software, silly projects, and cycling. A lot of cycling. Too much, maybe.

Responses