In a recent project, I wanted a simple way to bind some JSON data to a DOM element without importing any libraries and I think I came up with a pretty neat solution (in my eyes) that fit all my needs for the project.
The solution encodes templating instructions inside DOM data attributes named
data-bind-*
, which are accessible on the DOM element in the dataset property
and it just so happens to camel-case the attributes automatically (i.e, to set
innerText
you would have an attribute data-bind_inner-text
- note the
hyphen).
Here is a sample template from the project:
<template id="itemTemplate">
<div class="item new" data-bind_id="guid" id="">
<h3><span data-bind_inner-text="title"></span></h3>
<p class="description" data-bind_inner-text="content:encoded|description"></p>
<div>
<a data-bind_href="link" data-bind_inner-text="pubDate" data-bind_title="title" href="" title=""></a>
<svg class="share" url="" title="" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="24" height="24">
<path fill="none" d="M0 0h24v24H0z"></path><path d="M18 16c-.8 0-1.4.4-2 .8l-7-4v-1.5l7-4c.5.4 1.2.7 2 .7 1.7 0 3-1.3 3-3s-1.3-3-3-3-3 1.3-3 3v.7l-7 4C7.5 9.4 6.8 9 6 9c-1.7 0-3 1.3-3 3s1.3 3 3 3c.8 0 1.5-.3 2-.8l7.2 4.2v.6c0 1.6 1.2 3 2.8 3 1.6 0 3-1.4 3-3s-1.4-3-3-3z"></path>
</svg>
</div>
</div>
</template>
As you can see, we use the <template>
element to ensure that we can keep our
HTML in the DOM and to keep it inert (this really improves the authoring
experience). Note, it doesn't have to be a template element, it can take anything that
is inside the DOM.
To map the above DOM into an actual element with all the live data applied to it, I use the following basic algorithm:
- Clone the element to bind data on to.
- Iterate across the elements and for each element:
- Check to see if it has an attribute of the form
data-bind_
- Get the keys to lookup on the
data
separated by a "|" - Map the first found key's value from the input
data
directly to the node's attribute defined bydata-bind_
- Check to see if it has an attribute of the form
- Return the new node.
The code for this is pretty simple, if a tad terse.
const applyTemplate = (templateElement, data) => {
const element = templateElement.content.cloneNode(true);
const treeWalker = document.createTreeWalker(element, NodeFilter.SHOW_ELEMENT, () => NodeFilter.FILTER_ACCEPT);
while(treeWalker.nextNode()) {
const node = treeWalker.currentNode;
for(let bindAttr in node.dataset) {
let isBindableAttr = (bindAttr.indexOf('bind_') == 0) ? true : false;
if(isBindableAttr) {
let dataKeyString = node.dataset[bindAttr];
let dataKeys = dataKeyString.split("|");
let bindKey = bindAttr.substr(5);
for(let dataKey of dataKeys) {
if(dataKey in data && data[dataKey] !== "") {
node[bindKey] = data[dataKey];
break;
}
}
}
}
}
return element;
}
I don't expect anyone to use this, but I wanted to show how you can build a data binding tool for simple tasks without having to resort to a full library or framework.
I lead the Chrome Developer Relations team at Google.
We want people to have the best experience possible on the web without having to install a native app or produce content in a walled garden.
Our team tries to make it easier for developers to build on the web by supporting every Chrome release, creating great content to support developers on web.dev, contributing to MDN, helping to improve browser compatibility, and some of the best developer tools like Lighthouse, Workbox, Squoosh to name just a few.
I love to learn about what you are building, and how I can help with Chrome or Web development in general, so if you want to chat with me directly, please feel free to book a consultation.
I'm trialing a newsletter, you can subscribe below (thank you!)