Introducing JTL: JSON Templating Language


What if you could convert json into html? This would allow you to use json as a templating language for html. Alternatively, you could map json data from an api into html markup. Now you can with JTL: JSON Templating Language.

This was a small pet project of mine; something that I thought might be useful down the road. I'm imaging using JTL as an alternative for html templating languages such as lit-html, handlebars, and the like. Instead of writing a component or controller that then gets rendered into html with a template, you simple maintain a json state representation of your component and render the json into html with JTL.

The easiest way to see JTL in action is by visiting the documentation site at jtl.alexlockhart.me. It can be installed with npm using the @megazear7/jtl package and the code is open source at github.com/megazear7/jtl.

A JTL example

Let's take an example use case to see how you might use JTL. We want to get a list of links from an API and allow the user to add a new link to the list. We won't consider saving the new links to the API, but it will be obvious enough how that could be done as well. To start with we need an HTML page with a custom web component that can serve as the starting point for our example.
<!DOCTYPE html>
<html>
<body>
  <jtl-example-api></jtl-example-api>
</body>
</html>

The web component

Now we need to implement the shell of this web component. We will add a render method which updates the html in the component. Inside of this render method we use JTL to create an empty <ul> inside of a <div>.
import { jtl } from '@megazear/jtl';

class ExampleApiComponent extends HTMLElement {
    constructor() {
        super();
        render();
    }

    render() {
        this.innerHTML = '';
        this.appendChild(jtl({
            name: "div",
            children: [
                {
                    name: "ul",
                }
            ]
        }).toHtmlElement());
    }
}

window.customElements.define('jtl-example-api', ExampleApiComponent);

Fetching data from the API

Next we want to pull in data from an API. To do this, we update the constructor to fetch data from a json endpoint and update the state of our component before calling the render method.
constructor() {
    super();
    fetch('/data.json')
        .then(res => res.json())
        .then(json => this.movies = json.movies)
        .then(() => this.render());
}

The render method

Now, we need to update our render method to use the state stored in the movies field. This is where most of the magic of JTL is happening. Notice that all we need to do is take the json from the API and map it into a format that JTL expects. We also add input fields for the title and link as well as a button for adding the new movie link. After rendering the component we then add event listeners for the add and remove buttons.
render() {
    this.innerHTML = '';
    this.appendChild(jtl({
        name: "div",
        children: [
            {
                name: "ul",
                children: this.movies.map(movie => ({
                    name: "li",
                    children: [
                        {
                            name: "a",
                            attrs: { href: movie.imdb },
                            content: movie.title
                        }, {
                            name: "span",
                            attrs: { class: "remove", "data-movie": movie.title },
                            content: " x"
                        }
                    ]
                }))
            }, {
                name: "input",
                attrs: { class: "add-title", placeholder: "Title" }
            }, {
                name: "input",
                attrs: { class: "add-imdb", placeholder: "IMDB" }
            }, {
                name: "button",
                attrs: { class: "add-btn" },
                content: "Add"
            }
        ]
    }).toHtmlElement());

    this.querySelector('.add-btn').addEventListener('click', () => this.addMovie());
    this.querySelectorAll('.remove')
        .forEach(btn => btn.addEventListener('click', (e) => this.removeMovie(e.target.dataset.movie)));
}

The add and remove methods

Our final step is to implement the add and remove events. To do this all we need to do is update our state object and then call the render method. This is an example of functional UI, since our UI is simply a function of our state. In this case the render method is the function that defines the UI in terms of the this.movies object.
class ExampleApiComponent extends HTMLElement {
    ...

    addMovie() {
        const title = this.querySelector('.add-title').value;
        const imdb = this.querySelector('.add-imdb').value;

        this.movies.push({ title, imdb });
        this.render();
    }

    removeMovie(movieTitle) {
        this.movies = this.movies.filter(movie => movie.title !== movieTitle);
        this.render();
    }
}

The final result

Below is a screen shot of the final result. You can see this example in action at jtl.alexlockhart.me/examples/api.html. If you want to make this same example a reality, you will need to create the HTML file and JS modules as shown above and then serve this along with the data.json file. You can see the full implementation on Github and an example of the data.json file at jtl.alexlockhart.me/data.json.









Conclusions

JTL provides a reliable way to turn data into json and fills the role that is traditionally served by html templating languages. It is essentially a different approach to html templating, focusing on the data format (json) instead of focusing on the display format (html). Now that you have seen an example use of JTL, it might be the time to go look at the implementation.

One issue I see with it is performance, since any update to the data requires rerendering the html. Currently you need to remove all the html from the containing element and rerender the whole dom tree. However, since we have the power of json at our fingertips, I am wondering if there would be some way to diff the json, and then only make the updates that are needed? A project for another time.

My goal for JTL was to do something that felt creative and fresh that might inspire a different way of thinking. What ideas does JTL spawn for you? Is it pointless? Does it give you any ideas on alternative approaches to UI? Let me know your thoughts in the comments.

Popular Posts