Skip to content

1. Introduction

Juha Lindstedt edited this page Feb 3, 2017 · 15 revisions

Background

Modern web development have gone crazy. To even get started with frontend, you'll need to learn dozens of libraries, bundlers, templating languages and so on. Instead of simplifying things we're actually made everything harder. Books like Eloquent JavaScript teach us how to write eloquent plain JavaScript, but the reality is different. Most of the JavaScript fatigue could be cured by just downshifting a bit.

What is RE:DOM?

RE:DOM is a tiny turboboosted JavaScript for creating user interfaces. The whole bundle is less than 2 KB after gzip. You can think of it like plain JavaScript but with some sugar on it. To learn RE:DOM it's good to know some basics of controlling the DOM. Let's start!

How to control the DOM with plain JavaScript

DOM is not that hard and especially not slow. Let's see how to do some basic stuff with it:

const hello = document.createElement('h1');

hello.textContent = 'Hello world!';

document.body.appendChild(hello);

Then let's build a login form:

const form = document.createElement('form');
const email = document.createElement('input');
const pass = document.createElement('input');
const submit = document.createElement('button');

email.type = 'email';
pass.type = 'password';
submit.type = 'submit';

submit.textContent = 'Sign in';

form.onsubmit = e => {
  e.preventDefault();

  console.log(email.value, pass.value);
};

form.appendChild(email);
form.appendChild(pass);
form.appendChild(submit);

document.body.appendChild(form);

How is RE:DOM different?

Let's see how we can simplify these with RE:DOM:

import { el, mount } from 'redom';

const hello = el('h1', 'Hello world!');

mount(document.body, hello);

Simple, right?

import { el, mount } from 'redom';

class Login {
  constructor () {
    this.el = el('form',
      this.email = el('input', { type: 'email' }),
      this.pass = el('input', { type: 'password' }),
      this.submit = el('button', { type: 'submit' }, 'Sign in')
    );

    this.el.onsubmit = e => {
      e.preventDefault();

      const { email, pass } = this;

      console.log(email.value, pass.value);
    };
  }
}

const login = new Login();

mount(document.body, login);

Here I'm saving references to DOM elements while I'm creating them. It's optional. You don't also have to use classes if you think they're evil (they're not, really).

What about lists?

Once again with plain JavaScript:

const ul = document.createElement('ul');

[1, 2, 3].forEach(item => {
  const li = document.createElement('li');

  li.textContent = item;

  ul.appendChild(li);
});

document.body.appendChild(ul);

I leave it to you figure out how to update it..

With RE:DOM:

import { list, mount } from 'redom';

class Li {
  constructor () {
    this.el = el('li');
  }

  update (item) {
    this.el.textContent = item;
  }
}

const ul = list('ul', Li);

ul.update([1, 2, 3]);

mount(document.body, ul);

setTimeout(() => {
  ul.update([2, 3, 4]);
}, 1000);

That example updates the DOM with minimum modifications. You can also reorder/reuse DOM elements by key:

import { list, mount } from 'redom';

class Li {
  constructor () {
    this.el = el('li');
  }

  update (item) {
    this.el.textContent = item.name;
  }
}

const ul = list('ul', Li, 'id');

ul.update([1, 2, 3].map(item => {
  return {
    id: item,
    name: 'Item ' + item
  }
});

mount(document.body, ul);

setTimeout(() => {
  ul.update([2, 3, 4].map(item => {
    return {
      id: item,
      name: 'Item ' + item
    }
  });
}, 1000);

How to structure a RE:DOM application?

It's recommended to create a base view and build the app hierarchially from that:

class App {
  constructor () {
    this.el = el('.app',
      this.menu = new Menu(),
      this.content = new Content()
    );

    this.el.addEventListener('section', e => {
      e.stopPropagation();

      this.data = {
        ...this.data,
        section: e.detail
      };

      this.update(this.data);
    });
  }

  update (data) {
    this.menu.update(data);
    this.content.update(data);

    this.data = data;
  }
}

class Menu {
  constructor () {
    this.el = el('.menu',
      this.sections = list('.sections', MenuSection, 'id')
    );
  }

  update (data) {
    const { sections } = data;

    this.sections.update(sections);
  }
}

class MenuSection {
  constructor () {
    this.el = el('.section');

    this.el.onclick = e => {
      const { id } = this.data;
      const event = new CustomEvent('section', { detail: id, bubbles: true });

      this.el.dispatchEvent(event);
    }
  }

  update (data) {
    const { id, name } = data;

    this.el.textContent = section;

    this.data = data;
  }
}

class Content {
  constructor () {
    this.el = el('.content',
      this.header = el('h1')
    );
  }

  update (data) {
    const { sections, section } = data; 

    const sectionLookup = sections.reduce((lookup, section) => {
      lookup[section.id] = section;
      return lookup;
    }, {});

    this.header.textContent = sectionLookup[section];
  }
}

const app = new App();

app.update({
  sections: [
    { id: 'home', name: 'Home' },
    { id: 'introduction', name: 'Introduction' }
  ],
  section: 'introduction'
});

mount(document.body, app);

Here's a further example how to build a web app with RE:DOM

Now that you know some basics, dive in to the API