Not Another To Do App

Getting your hands dirty and feet wet with Open Web Component Recommendations…sort of: Part 5

westbrook
7 min readFeb 27, 2019
May all your to do lists be short, if not empty! How better to give you time to think of the next great thing to do?

Welcome to “Not Another To Do App”, an overly lengthy review of making one of the smallest application every developer ends up writing at some point or other. If you’re here to read up on a specific technique to writing apps or have made your way from a previous installation, then likely your in the right place and should read on! If not, it’s possible you want to start from the beginning so you to can know all of our characters’ back stories

If you’ve made it this far, why quit now?

Make it a Component

Photo by Mark Seletcky on Unsplash

Ok, sure, this one seems like a no brainer, I wanted web component based UI, I chose open-wc’s generator in agreement with its choice of LitElement as a base class for building high quality, performant web components, so everything should be a component, right?

Wrong!

Even when working in web components, not everything has to be a component, sometimes it’s enough just to make it a template part (which we’ll discuss more thoroughly on the next episode of “Not Another To Do App”). What’s more, it’s just as easy to say “that doesn’t need to be a component” even when it does. It’s important to constantly police yourself so as to make reading and understanding your code as easy as possible for future you. When it comes to making components, that means preparing code to be factored down into its own component, factored up into the parent component, or factored completely out of a project as an external dependency, as easy as possible. I found myself running into this when thinking about the input field for the new to do UI.

Pretty simple at first look… right?

At first glance, this is very clearly an input element next to a button element to most people, right? Same here. That is, until I was messing around with my app (some might call it QA [quality assurance testing]) mid development and ran into this:

Where’s the rest of my to do at?

Sure, it’s just a To Do app, why worry about this seemingly small piece of UI not being 100%? My argumentative answer to that is, “why worry about anything?” But, in reality, this is just a conversation, we’re just talking about the possibilities. Taking some knowledge I’d acquired around similar UIs in the past I started writing the code that I felt corrected this experience. Turn the input into a textarea, drop it in a container element, giving a sibling to mirror its content, hide the siblings behind it, and before long you have a growing textarea. What you also have is a lot of code that has nothing to do with writing a to do living inside of src/to-do-write.js. Enter some self policing…

Is this directly related to <to-do-write></to-do-write>? No. Would it make the code flow of <to-do-write></to-do-write> easier to parse by its absence? Yes. Am I using this elsewhere in my project? No. Could I see myself possibly wanting this in another project in the future? Yes. There are no definite answers in code, only what’s right for the context you’re working in at the time, and for me the answer to these questions at that time was “make it a web component”. So, I did.

There it is!

Skipping right to the final version of its delivery, implementation of this new custom element starts in the src/to-do-write.js code where we update the render() method to include my new custom element, like:

<growing-textarea>
<textarea
aria-label="Write the next thing you need to get done."
id="todo"
name="todo"
placeholder="What needs to get done?"
></textarea>
</growing-textarea>

Seems a lot like a pretty normal textarea, right? The growing-textarea custom element uses the decorator pattern to upgrade that normal textarea to have super powers. (Plug: the Decorator Pattern Plus can give it even more!)

But, how?

Let’s dive into src/growing-textarea.js to find out.

class GrowingTextarea extends LitElement {
static get properties() {
return {
value: { type: String }
};
}
constructor() {
super();
this.value = '';
this.setValue = this.setValue.bind(this);
}
setValue(e) {
this.value = e.target.value;
}
listenOnSlottedTextarea(e) {
if (this.textarea) {
this.textarea.removeEventListener(
'input',
this.setValue
);
}
const nodes = e.target.assignedNodes();
const [textarea] = nodes.filter(
node => node.tagName === 'TEXTAREA'
);
if (!textarea) return;
this.textarea = textarea;
this.textarea.addEventListener('input', this.setValue);
}
static get styles() {
return [
styles,
];
}
render() {
return html`
<slot
@slotchange=${this.listenOnSlottedTextarea}
></slot>
<span aria-hidden="true">${this.value}</span>
`;
}
}

But, what’s really going on there?

It all starts with this:

<slot
@slotchange=${this.listenOnSlottedTextarea}
></slot>

Check the lit-element based event listening on the slot element for the slotchange event. That means that any time the content for the default slot in the template of growing-textarea changes, or in other words:

<growing-textarea>
<!--
any changes here that don't have
a specific [slot="..."] attribute
-->
</growing-textarea>

That change triggers a call to listenOnSlottedTextarea. Once you get into that listener you have access to event.target.assignedNodes() which will give you an array of the nodes assigned to the slot in question. There’s a little bit of administrative work going on in there, but the net effect is being able to capture the value of the slotted textarea as it is input. That value is then applied to a mirror element that expands the height of the growing-textarea element, who’s height is now managing the height of the textarea via the CSS like the following:

:host {
display: block;
position: relative;
min-height: 20px;
width: 100%;
}
span,
::slotted(textarea) {
min-height: 20px;
padding: 2px 6px;
font-size: 14px;
line-height: 16px;
box-sizing: border-box;
}
span {
border: 1px solid;
display: block;
white-space: pre-wrap;
}
::slotted(textarea) {
position: absolute;
top: 0;
width: 100%;
height: 100%;
border: 1px solid black;
resize: none;
font-family: inherit;
z-index: 2;
}

What’s more, this element is now factored down into a format that will make publishing it into its own standalone package a snap. When you choose to do just that, don’t forget the rest of the open-wc recommendations for making your new package bulletproof when distributing it across your various project, your team, or hopefully the JS community at large. After you’re done, let me know in the comments below what sort of custom elements you’ve been making.

Disclaimer: no, the slotchange event is not currently available x-browser, and webcomponents.js does not actively add this event to non-supporting browsers. In that we’re merely decorating the textarea with the growing-textarea custom element, this lack of support won’t actually break our application, users in those browsers will simply get a little different UX than more modern browser users. If you are not comfortable with delivering the growing text area via progressive enhancement this could put a damper on the whole approach I’ve just outlined. However, you can apply an x-browser compliant version of this code when using FlattenedNodesObserver as vended by the Polymer.js library if you’d like to opt-into broader browser coverage for this feature. You get to choose your own adventure on this one.

While I’m not going to into depth about how FlattenedNodesObserver works here, I am planning to write about at more length soon, so stay tuned.

The Short Game

As voted on by a plurality of people with opinions on such topics that are both forced to see my tweets in their Twitter feed and had a free minute this last week, a 9000+ word article is a no, no.

So, it is with the deepest reverence to you my dear reader that I’ve broken the ongoing conversations into a measly ten sections. Congratulations, you’re nearing the end of the fifth! Yes, that’s means you’re half way there. At this rate you might finish before your OKRs are due for the next quarter:

Special thanks to the team at Open Web Components for the great set of tools and recommendations that they’ve been putting together to support the ever growing community of engineers and companies bringing high quality web components into the industry. Visit them on GitHub and create and issue, submit a PR, or fork a repo to get in on the action!

--

--