My FeedDiscussionsHeadless CMS
New
Sign in
Log inSign up
Learn more about Hashnode Headless CMSHashnode Headless CMS
Collaborate seamlessly with Hashnode Headless CMS for Enterprise.
Upgrade ✨Learn more
Exploring Vanilla JS / part 3

Exploring Vanilla JS / part 3

K. Zemtsovsky's photo
K. Zemtsovsky
·Dec 5, 2016

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:

NameEditor

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 NameEditors of the same kind. Let me show what I mean:

NameEditorMulti

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.