Exploring Vanilla JS / part 2

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.

First part was about creating a simple basis for old-fashioned server-side applications in pure Node.js.

This part is about creating dynamic user interfaces.


Before continuing developing our application, let’s talk about client-side JavaScript, specially DOM-related stuff.

It has a rich history. First, we used plain JavaScript, it felt not very pleasant. Everybody created their own helper functions to fight verbosity of JS. Then jQuery popped up with its brilliant easy-to-use API. Then developers said, “imperative way does not feel right, let’s go declarative”. Handlebars, Knockout, Backbone, Ember, Angular, React and tons of other libraries and frameworks were created. All of them try to solve DOM manipulation problems. Nowadays developing often starts with choosing right lib, and this choice is not an easy one to make.

But what if we could reimagine plain JavaScript way, find a pattern, which would bring us fast, easy, and fun development process? I mean, what if Vanilla JS could (with no additional effort) let us do what all modern libraries do?

Honestly, I don’t think this is possible. But we are here for experiments, so let’s experimenting.


In the previous article, I wrote HTML, but I didn’t create any .html files. I wrote HTML inside JS functions and I called those functions components. I will keep doing things that way because I like the approach. But also I will create new components.js file, which will be available on the server and on the client:

vanilla/
  components.js <- all components will live here
  server.js
  styles.css

I will keep all the components in a global namespace (window /global). One guy told me, that this is a bad idea, but I don’t really like that guy, so I won’t follow his advice.

Also, I’ll add window.global = window; for the client in order to have the same name for global namespace on the server and on the client.

Cute. Here’s the code:

// server.js

const http = require('http');
const fs = require('fs');
require('./components');

const PORT = 4000;
const styles = fs.readFileSync('./styles.css');
const components = fs.readFileSync('./components.js');

http.createServer(server).listen(PORT);

function server(req, res) {
  const { url } = req;

  res.end(
    url === '/styles.css' ? styles :
    url === '/components.js' ? components :
    App({ url }) // App is in a global namespace (global.App)
  );
}
// components.js

function App({ url }) {
  return `
    <!doctype html>
    <html class="app">
      <head>
        <meta charset="utf-8" />
        <link rel="stylesheet" href="/styles.css" />
      </head>
      <body class="app__body">
        Nothing here yet, just a box: ${Box()}
        <div class="app__scripts">
          <script>window.global = window;</script>
          <script src="/components.js"></script>
        </div>
      </body>
    </html>
  `
}

function Box() {
  return `
    <div class="box"></div>
  `;
}

global.App = App;
global.Box = Box;

Let’s add some dynamics now. I’ll start with something very simple: user clicks on the box and it becomes larger, user clicks again and the box returns to its normal size:

title here

In terms of CSS it will be:

.box {
  width: 30px;
  height: 30px;
  background: #ccc;
}

.box_big {
  width: 100px;
  height: 100px;
}

In terms of JS I just need to toggle box_big class.

How to implement? I suppose, I need to add click listener to the box. I know two ways of doing this:

  1. elem.addEventListener('click', fn)
  2. <div onClick="fn()"></div>

The first one is more commonly used, it does not require your handler functions to live in a global namespace. But I prefer the second approach, because in such way you don’t have to worry about adding/removing listeners when elements are added/removed. This saves you from possible issues and allows you to write less code. By the way, we already keep elements in a global namespace, so… why not? And here is what I’ve got:

function Box() {
  return `
    <div class="box" onClick="Box.onClick(this)"></div>
  `;
}

Box.onClick = (box) => {
  box.classList.toggle('box_big');
};

I know, I know, this is kind of imperative style. But I’m just in a process of thinking and experimenting. Don’t judge me yet. This code does work, it’s simple and straightforward.

Let’s make something more complex now. Typical example: name editor. User enters his name and it is instantly shown below:

title here

Naive implementation:

function NameEditor() {
  return `
    <div class="nameEditor">
      <div>Enter you name:</div>
      <input onKeyUp="NameEditor.onKeyUp(this)" />
      <div>
        Hello, <span></span>
      </div>
    </div>
  `;
}

NameEditor.onKeyUp = (input) => {
  input.nextElementSibling.querySelector('span').innerText = input.value;
};

This code does work, but do you see what’s wrong with it? This is very imperative! JS needs to know about DOM structure. Every time I change HTML, I possibly need to update JS code too. It does not feel right at all.

I think I need to have direct reference to <span/> element. And what is the fastest way to get an element in JS? document.getElementById! So let’s add id attribute to our <span/> element:

<div>
  Hello, <span id="nameEditor__name"></span>
</div>

I would like to be able to have several NameEditor instances. It means that every instance should have a unique id for <span/>. The problem can be solved by passing instanceIndex as a prop to NameEditor:

function NameEditor({ instanceIndex }) {
  return `
    <div class="nameEditor">
      <div>Enter you name:</div>
      <input onKeyUp="NameEditor.onKeyUp(this, ${instanceIndex})" />
      <div>
        Hello, <span id="nameEditor__name-${instanceIndex}"></span>
      </div>
    </div>
  `;
}

NameEditor.onKeyUp = (input, instanceIndex) => {
  document.getElementById(`nameEditor__name-${instanceIndex}`).innerText = input.value;
};

And here is how NameEditor can be used:

function App({ url }) {
  return `
    <!doctype html>
    <html class="app">
      <head>
        ...
      </head>
      <body class="app__body">
        ${NameEditor({ instanceIndex: 0 })}
        ${NameEditor({ instanceIndex: 1 })}
        ${NameEditor({ instanceIndex: 2 })}

        <div class="app__scripts>
          ...
        </div>
      </body>
    </html>
  `
}

I still manually do DOM operations, but my JS doesn’t have to know about DOM structure.

So we have declarative templates and imperative code for updating user interface. Code for UI updates is very simple (which is nice) and is very-very fast because it is Vanilla JS. For now, I find this approach pretty fine. I don’t mind writing simple UI logic if it will give me benefits (speed, no need for users to load a lib, server side rendering out of the box).

The code is a little bit verbose and I have to manually pass instanceIndex to NameEditor which is not very convenient. But this is not a problem, I can write some wrapper functions which will hide all the verbosity and automatically pass auto incremented instanceIndex.


Well, I like the results I’ve got here. The interfaces I’ve implemented above were pretty simple and I need to test my approach on more complex and dynamic UIs. And this is what I’m going to do in the next article.

Write your comment…

This is interesting as well as exciting. Kudos for your effort man! Eagerly waiting for the next series. Thanks for this article!

This is the kind of article we need right now, where every other John Doe is creating his own module on top of another and it becomes one tangled web of mess. I prefer to code lean and I would love to use Vanilla JS for all tasks where I can use. But if the time is a bummer and need simple fix kind of stuff, well, we have modules. Let's hope there are some people out there who would prefer the same.

Thank you for kind response)

Reply to this…