Event Handling with Snabbdom

It's time to add some user interation capabilities to the framework!

A bit of refacto

Before starting handling events, I would like to split some separation concerns for the sake of clarity.

I would like to extract a function called createReducer to make the createElement function more readable.

It won't be that hard: we'll simply extract the Array.reduce transformation function somewhere else.

import { h } from "snabbdom/h";

// extract initial value with a template key, some other will appear next ;)
const initialState = {
  template: ""
};

// extract this outside the createElement function
const createReducer = args => (acc, currentString, index) => ({
  ...acc,
  template: acc.template + currentString + (args[index] || "")
});

const createElement = tagName => (strings, ...args) => {
  const { template } = strings.reduce(createReducer(args), initialState);

  return {
    type: "element",
    template: h(tagName, {}, template)
  };
};

export const div = createElement("div");
export const p = createElement("p");

Our first event handler

We're going to create our first event handler: onClick.

Open ./framework/event.js and add the following code:

export const onClick = f => ({
  type: "event",
  click: f
});

And import it in ./src/user.js:

import { div } from "../framework/element";
import { onClick } from "../framework/event";

export const User = ({ firstName, lastName }) =>
  div`${onClick(() => alert(firstName))} Hello ${firstName} ${lastName}`;

If everything is working well, you should now be able to see something weird on the browser like:

[Object object] Hello Marvin Frachet

Don't worry, it's normal 😄

The reason is that we only manage text nodes inside our template engine.

We need to modify its code to take care of the events:

import { h } from "snabbdom/h";

const initialState = {
  template: "",
  on: {} // This initial state property will help us manage event handlers in template literals
};

const createReducer = args => (acc, currentString, index) => {
  const currentArg = args[index];

  /**
   * Here, we define the behavior of an event node and this
   * is where the type is important :D
   */
  if (currentArg && currentArg.type === "event") {
    return { ...acc, on: { click: currentArg.click } };
  }

  return {
    ...acc,
    template: acc.template + currentString + (args[index] || "")
  };
};

const createElement = tagName => (strings, ...args) => {
  const { template, on } = strings.reduce(createReducer(args), initialState);

  return {
    type: "element",
    template: h(tagName, { on }, template) // the second argument concerns attributes, properties and events
  };
};

export const div = createElement("div");
export const p = createElement("p");

Snabbdom specific

By default Snabbdom doesn't know how to manage events. We need to tell him that we want to use its internal event handler. In fact, it's a good practice to split libraries into smaller modules to avoid huge size dependencies in our applications.

We have to modify our ./framework/index.js to look like:

import * as snabbdom from "snabbdom";
const patch = snabbdom.init([
  require("snabbdom/modules/eventlisteners").default
]);

export const init = (selector, component) => {
  const app = document.querySelector(selector);
  patch(app, component.template);
};

After the reload, if you click the text on the screen, you should see an alert on the screen 😃