Skip to main content
Dublin Library

The Publishing Project

Templating the web

 

There are times when writing the same thing over and over again gets to be really tedious. Think of populating large bulleted lists, select statements and other repetitive elements. We've been able to do this for a while using libraries... We'll look at it handlebars.js and then we'll look at using the `template` tag built into the HTML specification. We'll also look at why we might want to keep working with libraries to support older browsers and browsers with incomplete support. ## The current way: Template Libraries When I first looked at Handlebars it appeared to be too much work for the result I was looking for. As I started working with WordPress, particularly when they released their REST API, I realized that templating would be key to build custom interfaces. The most basic example shows how to use Handlebars to populate a template with information stored in the same script. The HTML contains both the placeholder element where we'll store the content and the script containing the actual template. {% raw %} ```html
``` {% endraw %} The script element captures the content of the template in the `source` variable and defines a shorthand for template compilation on `renderEntry`. We then define the content we want to insert. In this case, it's a variable holding an array of data we want to populate the content with. Finally, we use `renderEntry` to render the template, populated with the data, into HTML and insert it into our placeholder element. {% raw %} ```javascript let source = document.getElementById('entry-template').innerHTML; let renderEntry = Handlebars.compile(source); let blogEntry = { title: 'My New Post', body: 'This is my first post!' }; document.getElementById('entries').innerHTML = renderEntry(blogEntry); ``` {% endraw %} A next iteration could be used to pull from an array of values stored locally. For this, we use a helper method, `#each` to loop through the values on the array and provide the data to populate the template. The HTML is almost identical except that we wrap the content we'll iterate over with an #each helper (`{{#each cats}}` and `{{/each}}`. cats is a reference to the data we will pass on Javascript. {% raw %} ```html
``` {% endraw %} The Javascript is also similar to the prior example. The differences are: - The data we're passing is now an array - When we instantiate the template we wrap the data we pass in an array to make sure the helper works as intended {% raw %} ```javascript var myCats = [ { name: 'Fiona', age: 4 }, { name: 'Spot', age: 12 }, { name: 'Chestnut', age: 4 }, { name: 'Frisky', age: false }, { name: 'Biscuit', age: 4 } ]; var template = document.getElementById('cat-list-template').innerHTML; var renderCats = Handlebars.compile(template); document.getElementById('cat-list').innerHTML = renderCats({ cats: myCats }); ``` {% endraw %} The last bit of Handlebars magic we'll look at is how to use it to render templates with external data from a REST API; in this case Wordpress. The template is pretty similar to the cat example. The main difference is the use of nested values (`title.rendered` and `content.rendered`) and the use of the triple mustache around `content.rendered` to tell Handlebars that we don't want to escape HTML values for this variable. If you don't own the content then please **don't do this!** In this case, since it's my blog and I'm pretty sure I don't write malware (bad code, maybe but definitely not malware) I've accepted the risk. {% raw %} ```html
``` {% endraw %} The Javascript is different. I've chosen to use fetch and promises to make the code look nicer. We could go with async and await but that would limit the code to newer browsers (yes, I know they are evergreen but I also know of IT departments that block updates or choose to use LTS/ESR versions that lag behind in features), even more so than promises do. The code starts with a [fetch request](https://developers.google.com/web/fundamentals/primers/promises) to the [Wordpress REST API](https://developer.wordpress.org/rest-api/) requesting the 4 more recent posts on my blog. You can change the value by changing the value of the `per_page` parameter. This will generate a promise that **resolves** when the fetch request completes and the data download is finished and **rejects** otherwise. Once the promise resolves we move to the next step and convert it to JSON data using the [response](https://developer.mozilla.org/en-US/docs/Web/API/Response) object's [json](https://developer.mozilla.org/en-US/docs/Web/API/Body/json) method. Once the promise of `response.json()` fulfills we move to the next, and final, step. We compile the template and render it using the data we just fetched. These are the same commands that were at the bottom of the cat example. Since we are working with promises we must move them into the promise chain; otherwise, the fetch request will complete before we reach the part of the script where we compile and render the template. If any of the promises rejects the code will jump to the catch statement. In this case, we're only logging the error to console. we might also want to display something to the user to indicate the failure. No blank pages, please. {% raw %} ```javascript let myPosts = fetch( 'https://publishing-project.rivendellweb.net/wp-json/wp/v2/posts?per_page=4' ) .then(response => { return response.json(); }) .then(myJson => { let template = document.getElementById('post-list-template').innerHTML; let renderPosts = Handlebars.compile(template); document.getElementById('myContent').innerHTML = renderPosts({ posts: myJson }); }) .catch(err => { console.log("There's been an error getting the data", err); }); ``` {% endraw %} **This was meant as a proof of concept and is in no way, shape or form production code**. Some areas of further work: - Caching the fetch results to improve load times after the first visit - Pagination ## The template element Rather than use a library, wouldn't it be nice if we could use native HTML to create templates and then instantiate them with Javascript without having to add libraries and additional HTTP request. We can! HTML templates were first proposed as part of the web components family of specifications. They have since moved to the [HTML Specification](https://html.spec.whatwg.org/multipage/scripting.html#the-template-element) itself. The idea behind the template element is that it holds content on the page without the content being active... we can activate the template at any time using Javascript. Again it's worth repeating some of the characteristics of native HTML templates: 1. Its content is effectively inert until activated. Essentially, your markup is hidden DOM and does not render. This means that script won't run, images won't load, audio won't play until the template is used 2. Content is considered not to be in the document. Using document.getElementById() or querySelector() in the main page won't return child nodes of a template 3. Templates can be placed anywhere inside of ``, ``, or `` and can contain any type of content which is allowed in those elements - Note that "anywhere" means that `