This is series of articles where I’m experimenting with Vanilla JS way of writing modern web applications. I’m trying to find a pattern, that would bring fast, fun and powerful development process without using libs.
- Part 1 was about creating old-fashion server-side apps with Node.js
- Part 2 was about finding a way to develop dynamic user interfaces
- Part 3 is going to extend Part 2, it's about client side JS too
What I came to so far:
- App consist of components
- Components are available on the client and on the server
- Components live in a global namespace (
window
/global
) - Each component is just a JS function which returns HTML string
- Events are mounted inline via attributes.
For example, Box
component:
function Box({ text }) {
return `
<div class="box" onClick="Box.onClick()">
${text}
</div>
`;
}
Box.onClick = () => {
alert('box was clicked');
};
Part 3
In previous article I ended with NameEditor
example:
Here is the code from part 2 (instanceIndex
is replaced with id
):
function App({ url }) {
return `
...
${NameEditor({ id: 0 })}
${NameEditor({ id: 1 })}
${NameEditor({ id: 2 })}
...
`
}
function NameEditor({ id }) {
return `
<div class="nameEditor">
<div>Enter you name:</div>
<input onKeyUp="NameEditor.onKeyUp(this, ${id})" />
<div>
Hello, <span id="nameEditor__name-${id}"></span>
</div>
</div>
`;
}
NameEditor.onKeyUp = (input, id) => {
document.getElementById(`nameEditor__name-${id}`).innerText = input.value;
};
Now I'm going to play with NameEditor
. Let's add this feature: editing name inside one NameEditor
would change the name of all other NameEditor
s of the same kind. Let me show what I mean:
Actually this is very easy to implement, I just need to use getElementsByClassName
instead of getElementById
:
function NameEditor({ id }) {
// Add color to distinguish name editors
const color = {
0: '#b6e1c1',
1: '#b7c4ed',
}[id];
return `
<div class="nameEditor" style="background: ${color}">
...
Hello, <span class="nameEditor__name-${id}"></span>
...
</div>
`;
}
NameEditor.onKeyUp = (input, id) => {
const elems = document.getElementsByClassName(`nameEditor__name-${id}`);
for (const elem of elems) {
elem.innerText = input.value;
}
};
It works. Nice. But whilst getElementsByClassName
is a very fast operation, it's not a good idea to call it every time I need to access elements.
I need some sort of cache system. The cache should keep references to elements, and it should be automatically updated whenever elements are added/removed.
Seems like it's not a trivial task. What a lame! I don't wanna write this cache system. How cool it would be if browsers had this caching mechanism out-of-the-box...
And you know what? Browsers do have it! Yes, all browsers starting from IE9 have this caching mechanism included. I was pretty shocked when I discovered it. It turns out, that getElementsByClassName
returns live HTMLCollection
. Here is what live means:
const Box = () => '<div class="box"></div>';
const boxes = document.getElementsByClassName('box');
document.body.innerHTML = `${Box()}${Box()}${Box()}`;
console.log(boxes.length); // 3
document.body.innerHTML = `${Box()}`;
console.log(boxes.length); // 1
boxes
variable is automatically updated when elements are added/removed. Isn't it crazy cool? I'm in a web development for a pretty long time, but I never heard of this feature.
What a relieve, now I don't have to worry about caching. And here is the same code, but using benefits of live HTMLCollection:
const nameElems = {};
function getNameElems(id) {
return nameElems[id] || (
nameElems[id] = document.getElementsByClassName(`nameEditor__name-${id}`)
);
}
NameEditor.onKeyUp = (input, id) => {
for (const elem of getNameElems(id)) {
elem.innerText = input.value;
}
};
This code can scare you, because it's very verbose. Let's imagine we have watchClass
function, which hides some verbosity:
const nameElems = watchClass(id => `nameEditor__name-${id}`);
NameEditor.onKeyUp = (input, id) => {
nameElems(id, elem => elem.innerText = input.value);
};
Or even imagine we have changeInnerText
which can shorten all the code into just one line:
NameEditor.onKeyUp = changeInnerText('nameEditor__name');
As you can see, verbosity is not a problem. Tiny helpers can hide all the verbosity.
I have not implemented something significant in this article, but I've found a great way to track elements. From now I will write all code using getElementsByClassName
and I will no longer use getElementById
.
In Part 4 (will be available soon) I'm going to use all the techniques I've discovered so far to write something more complex.