HTML Templating: The functional Way
Tags: html, javascript, functional, templating
Published: 2021-02-17 15:52:50

---
title: HTML Templating: The functional Way
published: true
description: 
tags: html, javascript, functional, templating
//cover_image: https://direct_url_to_image.jpg
---

# Foreword

In this article I will reconstruct my development process for a tiny but powerful helper I've built myself to generate DOM nodes using Javascript. 😁

The reason why I built this in the first place is that I just don't like writing HTML, and most templating engines out there are essentially just glorified string interpolation. 😖

This also isn't really new code: for the most part, it's a rewrite of a [Lua library](https://github.com/darkwiiplayer/skooma) that implements the exact same mechanism. 😅

With that being said...

# Let's get started!

For a starting point, let's define a simple function that creates an HTML element:

```js
export const node = (name) => {
   const element = document.createElement(name)
   return element
}
```

Okay, simple enough. But that's little more than an alias to `document.createElement()`. We'll want to pass more arguments; the function should return the (mostly) finished DOM node.

## Setting up additional arguments

To allow nested structures, let's use a recursive function to handle the arguments. First add it to the `node` function:

```js
export const node = (name, args) => {
   const element = document.createElement(name)
   parseArgs(element, args)
   return element
}
```

> Note: some readers might already realize that we could collect many args into an array with `...args`; don't worry, I'll get to that in another step 😉

### Attributes

And now we implement it. Let's start with a simple case: an empty element with some attributes: `node("div", [{class:"box"}])` should return an element like `<div class="box">`.

```diff
+ const parseArgs = (element, args) => {
+    for (arg of args)
+       for (key in arg)
+          element.setAttribute(key, arg[key])
+ }
```

### Text Content

Good! That's progress! But what's the point of HTML elements if they can only be empty? When we call `node("p", ["Hello, World!"])`, it should return a `<p>` element with the text "Hello, World!". Let's add that logic as well:

```diff
  const parseArgs = (element, args) => {
     for (arg of args)
+       if (typeof(arg) == "string")
+          element.appendChild(document.createTextNode(arg))
+       else
            for (key in arg)
               element.setAttribute(key, arg[key])
  }

```

And with that, most of the hard work is done already. Structurally, we're done; all that's left is add more cases.

### Nested Arrays

First, let's do the recursion thing. It's useful if we don't have to flatten our input before passing it in, so let's add a recursive case for array arguments. Let's turn `node("p", ["foo", ["bar", "baz"]])` into `<p>foobarbaz</p>`:

```diff
  const parseArgs = (element, args) => {
     for (arg of args)
        if (typeof(arg) == "string")
           element.appendChild(document.createTextNode(arg))
+       else if ("length" in arg)
+          parseArgs(element, arg)
        else
           for (key in arg)
              element.setAttribute(key, arg[key])
  }

```

### Child Elements

And lastly, the nicest part: adding other HTML elements as children. This is very similar to the string case:

```diff
  const parseArgs = (element, args) => {
     for (arg of args)
        if (typeof(arg) == "string")
           element.appendChild(document.createTextNode(arg))
+       else if ("nodeName" in arg)
+          element.appendChild(arg)
        else if ("length" in arg)
           parseArgs(element, arg)
        else
           for (key in arg)
              element.setAttribute(key, arg[key])
  }

```

### Trying it out

And with that, our basic HTML rendering mechanism is done. We can now write code like:

```js
let navlink = (name) =>
   node("a", [{href: "/"+name}, name])

let menu = (items) => node("nav", [
   node("ul", [
      items
      .map(navlink)
      .map(link => node("li", [link])
   ])
])

body.appendChild(menu(["home", "about", "contact"]))
```

It works, and we can do a whole lot of code-reuse with that setup already. But it feels very cumbersome to use:

- Passing the node type as a string 💢
- Wrapping arguments in an array 😩
- Having to use a wrapper function with `map` 🤔

### Another layer of convenience

To remedy these, let's do some meta-programming and build ourselves a nice wrapper around this:

```js
export const html = new Proxy({}, {
   get: (_, prop) => (...args) => node(prop, args)
})
```

And that's it! When we index our new `html` Proxy object with a string, like `html.div`; it will automatically return a wrapper for the `node` function that adds the node type and collects all the arguments into an array.

This only leaves the problem with `map`: this array method passes the array item as the first argument to the callback function, but then also adds the index and the array as well. This means those values will get passed to our `node` function and cause problems.

To fix this, we can change the Proxy like this:

```js
export const html = new Proxy(Window, {
   get: (target, prop, receiver) => {
      if (prop.search(/^[A-Z]/)+1)
         return (arg) => node(prop, [arg])
      else
         return (...args) => node(prop, args)
   }
})
```

Now, when we index our `html` proxy with a string starting with uppercase, it will return a different closure that only passes its first argument.

### Trying out the changes

We can now rewrite the example above as:

```js
let navlink (name) => html.a({href: "/"+name}, name)

let menu = items => html.nav(
   html.ul(
      items
      .map(navlink)
      .map(html.Li)
   )
)

body.appendChild(menu(["home", "about", "contact"]))
```

-----

And that was it 😁

If you have any questions or feedback, please leave a comment. If there's anything in the code that needs clarifying,  tell me and I will extend the post or maybe write a new one 💖