Build a mobile template with Handlebars
Nathan Smith of Projekt202 explains how to use Handlebars.js to build lightweight HTML templates for JSON data
'Mobile': such an interesting and all-encompassing word. It seems these days, it is either the primary topic of conversation amongst web designers and developers, or is at the very least tangentially related. Just as the buzz words "HTML5" and "Ajax" have taken off, mobile usage has continued to gain traction. Perhaps unsurprisingly, the same approach taken to build the front-end of an Ajax driven site for a desktop browser can also be applied to embedded HTML5 apps that can run on mobile devices.
This is the topic of my talk at the Big Design Conference in Dallas, TX — Use Web Skills to Build Mobile Apps. Coincidentally (or conveniently, for me) it is also what I am going to cover in this article: How to use Handlebars.js to make a lightweight HTML5 "app" that can consume the Dribbble API. Sound good? Okay then, let's get to it.
The basics
Handlebars.js is a superset of Mustache, a logic-less template syntax that can be incorporated in a variety of programming languages. While the premise behind Mustache is admirable – the complete separation of business logic from presentation – often it can be quite limiting, requiring separate templates whether or not certain JSON properties are available to be rendered.
To me, Handlebars strikes the perfect balance of being lightweight, decipherable when interspersed with HTML, and providing just enough logic to adhere to the DRY (Don’t Repeat Yourself) principle.
As with Mustache, Handlebars uses "{{" and "}}" to encapsulate JSON properties. Turned sideways, these curly braces can look somewhat like a mustache (hence the wordplay on "handlebar mustache"). Going beyond Mustache, Handlebars contains the syntax necessary for basic conditionals, such as…
<p> {{#if something}} Do stuff with {{something}} {{else}} Sorry, we got nothing. {{/if}}</p>
From that code example, it is not difficult to determine what is happening. It is somewhat reminiscent of how ExpressionEngine syntax looks, or the templating approach taken in Django. Point being, it is considerably more human readable (and designer friendly) than doing old-school string concatenation in a JavaScript loop. Speaking of which, Handlebars also does looping, allowing reuse of a basic HTML snippet for a list of items.
{{#if items}} <ul> {{#each items}} <li> {{this.name}} </li> {{/each}} </ul>{{/if}}
Handlebars can also do multi-level JSON objects. Here's an example of a hypothetical restaurant menu. In the previous example, we had {{this.name}} but in the next example, we'll leave off the "this." prefix. It works just fine without, because Handlebars knows what {{name}} means, when contextually nested in {{#each}}.
Get top Black Friday deals sent straight to your inbox: Sign up now!
We curate the best offers on creative kit and give our expert recommendations to save you time this Black Friday. Upgrade your setup for less with Creative Bloq.
{{#with menu}} <dl> {{#each categories}} <dt> {{name}} </dt> <dd> {{#each subcategories}} <dt> {{name}} <dt> {{#each item}} <dd> {{name}} - {{price}} </dd> {{/each}} {{/each}} </dd> {{/each}} </dl>{{/with}}
Depending on the JSON data passed in, that would render something like this…
<dl> <dt> Beverages </dt> <dd> <dt> Wines by the Bottle </dt> <dd> Crystal White Wine - $20.95 </dd> <dd> Red, Red Wine - $25.00 </dd> <dt> Sodas </dt> <dd> Cola - $1.99 </dd> <dd> Seltzer - $1.99 </dd> </dd> <dt> Entres </dt> <dd> <dt> Seafood </dt> <dd> Etc. </dd> </dd></dl>
Demo app
Now that we have covered some of the introductory basics, let's delve into the demo. For the purposes of this discussion, I have focused solely on the front-end aspects, whereas for an actual site you would undoubtedly have some server-side components involved.
The reason I have gone this route is twofold. Firstly, brevity. Secondly, when building an HTML5 app to be embedded in a mobile device, one does not have the luxury of a server-side environment running on the phone. The way we get our data to the HTML view is to receive (and optionally, send) JSON from/to a remote API endpoint. In this case, Dribbble. Since this particular API is read-only, we will simply be fetching data and rendering it in our HTML view.
For lack of a better name, since we are utilizing Handlebars.js to consume the Dribbble API, I named this demo app "Handlebbbars." I am using Sass (via Compass) to write the stylesheets, which are then compiled to a single application.css file. In my opinion, that is the best CSS preprocssing workflow. It is beyond the scope of this article, but if you are curious, I wrote about it here…
That said, let's have a look at the demo.html and application.js files.
demo.html
The first noteworthy element in demo.html is the <select> drop-down menu, containing "Page 1" through "Page 25" (the Dribbble API offers up to 25 pages of 30 shots each). This serves two purposes:
1. It is a visual indicator of what page is being viewed.
2. It can be used to jump directly to any given page.
However, it is not the only method of navigation we are going to provide. Speaking of navigation, we also have an <aside> with instructions on how to paginate via either the J/K keys (if using a desktop browser), or swiping left/right on a touchscreen device.
Below that, we have the crux of the entire app, <ul id="list">. This is simply an empty container, into which we will pour our dynamically generated content, after JSON has been parsed and rendered into HTML by Handlebars. We also see <div id="loading"> and <div id="error">. The loading message is shown when Ajax (JSONP) requests are sent out to the Dribbble API, and hidden when data is returned. In the event that the Ajax requests takes too long (or fails completely), the error message is shown. This offers the user an opportunity to manually refresh the page.
Towards the middle of the page's code, the <script> tag containing our template bears a curious content type…
<script type="text/x-handlebars" id="_template-list-item"> {{#each shots}} <li> <p> <b class="big"> {{title}} </b> <img alt="{{title}}" class="frame" style="background-image:url({{image_url}})" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAEklEQVQIHWP8//8/AzJgJCgAAB+ICPuLaDnAAAAAAElFTkSuQmCC" /> </p> <table> <tr> <th> Designer: </th> <td> <b>{{player.name}}</b> </td> </tr> {{#if player.twitter_screen_name}} <tr> <th> Twitter: </th> <td> <a href="http://twitter.com/{{player.twitter_screen_name}}">@{{player.twitter_screen_name}}</a> </td> </tr> {{/if}} {{#if likes_count}} <tr> <th> Likes: </th> <td> {{likes_count}} <span class="mute">♥</span> </td> </tr> {{/if}} {{#if short_url}} <tr> <th> URL: </th> <td> <a href="{{short_url}}">{{short_url}}</a> </td> </tr> {{/if}} </table> </li> {{/each}}</script>
While most of that should be pretty straightforward, there are a few things I would like to call out specifically. The type attribute on the <script> tag contains a value that is nonsensical to all browsers: text/x-handlebars. Really, this could be anything — such as type="peanut-butter" — as long as it is not something like text/javascript, which would cause a browser to parse the content as JavaScript (which is not what we want). Instead, we want the <script> to just sit there and do nothing. We will retrieve the template later on, via the code in application.js.
It is worth noting the strange src="data:…" on the foreground image. I am encoding spacer.png (which can be found in the "/assets/images" folder), because this will potentially save HTTP requests, if this HTML ends up being served as a website. Alternatively, our code could embedded in a native app, in which case network latency would be a moot point, because spacer.png would be on the phone itself.
As an astute reader, you might be curious what spacer.png looks like. It is simply a 4x3 pixel transparent image. I did this, because the image canvas for Dribbble is a predictable 400x300 pixels. So, as the spacer.png stretches, this forces a consistent 4x3 aspect ratio to be maintained. I load each shot as a background image behind this transparent layer, forcing a best-fit via – background-size: auto 100% – just in case of shots that have a funky (not 4x3) aspect ratio. This has the added benefit of caching, as most browsers will not send an additional HTTP request if a background image already exists in cache.
At the very end of the page, we have the <script> tags that contain all the actual JavaScript. For IE9, I am serving jQuery and for other browsers I am serving Zepto.
<!--[if gt IE 9]><!--> <script src="./assets/js/zepto.js"></script><!--<![endif]--><!--[if lte IE 9]> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script><![endif]-->
You can think of Zepto as basically being a lighter weight version of jQuery – with all the browser support for legacy IE removed – that has more of a focus on mobile (swipe, longTap). I wanted to make sure to support IE9, but am not supporting IE8 or below, because none of the older IE versions have any notable presence on mobile devices. Instead, we are focusing on browsers engines that have significant market share: WebKit, Opera, and IE9. We are also implicitly supporting Firefox – despite it not having a lot of traction on mobile devices – simply because it is a decently capable modern browser.
application.js
There are a few comments at the top of this file, notably the one that begins /*global …*/. That is simply to tell the JSLint validator which variables we consider to be global, so that it will not treat them as variables we are attempting to access without having defined them. The actual code beings on line 8.
// Redefine: $, window, document, undefined.var APP = (function($, window, document, undefined) { // Note: All the app code here, abbreviated.// Parameters: Zepto/jQuery, window, document.})(typeof Zepto === 'function' ? Zepto : jQuery, this, this.document);
In case that looks a little confusing, let me explain. This is what is known as a closure, often referred to as a self-executing anonymous function. Though, if we were to really split hairs, it is technically an "immediately invoked function expression."
Basically, at the bottom of the closure, we pass in variables to the top. If it is available, I am aliasing the Zepto variable to $. If it is not present, then I know that I must have included jQuery instead (for IE9). I am passing this as a parameter – which (at the highest scope) refers to window. I am also passing in this.document. Lastly, I pass in undefined as the browser's concept of nothingness. I do this to protect against any shenanigans that might be present, just as a general best practice. Reason being, someone could do this…
var window = false;var document = 'pwnd';var undefined = true;
Why these aspects of JavaScript and the DOM are mutable in the first place, I will never understand. Point being, we have now reassured ourselves that crucial global variables are who they say they are. We have also provided a safe place for other variables we will define without our outermost function, to prevent stepping on anyone else's code. This all might seem like a bit of overkill, but trust me – these habits are considered best practices in JavaScript for good reason.
The first snippet of code inside our closure is…
// Fire things off, once the DOM is ready.$(document).ready(function() { APP.go();});
To anyone who has ever done the slightest bit of jQuery, this should look familiar. We are waiting until the page has loaded the HTML document skeleton (but not so long as to wait for all imagery to load), and then we immediately fire off the APP.go method, which will automatically call all the functions contained within the APP.init object.
There are also several variables set aside and declared as empty, which are defined later in the APP.init.assign_dom_vars function (called after the DOM is ready). These are accessible to all the functions inside of APP, but not beyond. That lets us have variables that act as "globals" for us, but nobody else.
You will notice that we are returning an object from within our closure…
// Expose contents of APP.return { // APP.go go: function() { // ... }, // APP.init init: { // ... }, // APP.util util: { // ... }};
This is what is commonly referred to as the Module Pattern. I will not go in-depth about how that works here, but if you are curious about this (and other JS design patterns), read more here.
Within the APP.init.assign_dom_vars function, there are several HTML related elements assigned to variables. There are two key lines that comprise pretty much all the JS you have to write in order to use Handlebars…
Note: We are not using the var keyword here, because we defined these empty variables further up (for a higher scope), and are simply assigning them real values now that the DOM is ready.
markup = $('#_template-list-item').html().replace(/\s\s+/g, '');template = Handlebars.compile(markup);
The markup variable is the result of grabbing the innerHTML of the <script> tag with id="_template-list-item", and removing all unnecessary whitespace. This results in a small footprint when each Dribbble shot's info is rendered into the template.
We then pass that HTML to Handlebars, which compiles it for speed-optimized reuse. You may be wondering why I prefixed that ID with an underscore. While not necessary, this ensures that I will not accidentally add any styling to it via CSS, and serves as a reminder that it is used only for retrieval via JS.
The rest of the functions in APP.init handle discreet bits of functionality, some of which are called by other functions within the app. By maintaining some level of granularity, we can trigger each more surgically. I will touch on each one briefly…
The APP.init.nav_shortcuts function adds event listeners for the J/K keys for paging left/right – which, admittedly I just chose because those are the keys used for pagination in Google Reader. Additionally, it assigns event listeners for swipeLeft and swipeRight. If the code is running on a mobile device, we also show the touch-related tip in at the top of the page. If not, we assume the user is browsing on a device that has a keyboard, and show the J/K keyboard navigation tip instead.
The APP.init.watch_hash_change function simply watches for changes to anything that comes after the "#" in the browser's address bar. When there is a change, it calls the APP.init.set_the_page function. This in turn grabs the window's hash (example: "#/page/25") and parses it out to a number (such as 25). It then sets the <select> drop-down to the appropriate page, and calls APP.util.change_hash – which checks if we have cached data and renders it – or fires off a request to the Dribbble API to get fresh data.
The API.init.refresh_links functions simply overrides the logo and error links, so that they call window.location.reload(false), which is the way to say "refresh the page" in JavaScript. The single false parameter tells the browser "You don't have to fetch from the server," allowing the browser to re-render anything it already has cached. This is just so that the user can manually force a refresh – especially helpful when embedding the code into PhoneGap, where commonly used the "Refresh" browser button is not present.
The APP.init.page_picker function handles interaction with the <select> drop-down. When the user chooses a page that is different than what is already selected, it fires off the API.util.change_hash function, causing the appropriate page to be loaded.
Lastly, the APP.init.external_links function just handles popping out non-local links in a new browser tab. Or, in the case of PhoneGap, passes the link to the user's browser of choice (example: Safari on iOS, or Opera on Android – if the user has set it to default). We do this, because PhoneGap treats links as internal to itself, allowing you to have several HTML pages within your embedded HTML5 app. That way, external links are treated just as every other web page would be – are bookmark-able, refresh-able, etc.
The APP.util object begins with a function we have already referenced several times, APP.util.change_hash. This does not live in APP.init, because by itself it does not really do anything, but is just a utility function that is called in several places by functions that are run once the DOM is ready.
Lastly, we have APP.util.load_from_api, which does exactly what it says – fetches data from the Dribbble API. Notice here how we are making use of our cache variable, which is a shortcut to window.localStorage for browsers that support it. After we make an Ajax call to the API, we cache the resulting compiled HTML and serve up any subsequent requests within an hour from our cached version, to avoid any unnecessary HTTP traffic.
The benefit is twofold: First, we play nice with Dribbble and do not constantly ping their API. Secondly, and more importantly, our app is more responsive because it renders content from under the "heat lamp" rather than starting it off cold, and having to fetch it from the Dribbble fridge (okay, terrible metaphor).
We are also making a few minor tweaks to the data that is returned from the Dribbble API. For instance, some users type in their Twitter name as "@nathansmith" whereas others just cut and paste the entire URL, like "http://twitter.com/#!/nathansmith" – Neither of which is what we actually want. Instead, we need to normalize anything returned in the player.twitter_screen_name to "nathansmith" because we will prefix our own "@" in the Handlebars template, to be rendered as "@nathansmith" in the UI. Without culling off unnecessary text, we might end up with "@@nathansmith" or worse yet "@http://twitter.com/#!/nathansmith".
Also worth noting: Since we are making a cross-domain Ajax call, we must use JSONP, which does not have the luxury of notifying us of an "error:" state. Instead, we are polling via a timer, assuming that if an API call takes longer than 10 seconds, something must have gone wrong. In which case, the error <div> appears. However, even after that error message is shown, if for some reason the Ajax request does arrive successfully, the error message will be hidden, and the content will be rendered. It is the best of both worlds, easy-peasy.
Conclusion
So there you have it, a JSON API driven demo app, complete with client-side caching, in less than 250 lines of code… spaced human-readably and generously commented. What is impressive here is not so much the code we wrote ourselves, but all the extra string concatenation we did not have to write, thanks to Handlebars.
If this has intrigued you, I would encourage you to also check out Ember.js, a full client-side MVC framework created by the guys who made Handlebars. Additionally, you might also want to take a look at Backbone.js, another popular client-side framework (made by the creator of CoffeeScript). While Backbone.js has its own templating approach (that builds upon Underscore.js), it also can be used in conjunction with Handlebars.
Thank you for reading 5 articles this month* Join now for unlimited access
Enjoy your first month for just £1 / $1 / €1
*Read 5 free articles per month without a subscription
Join now for unlimited access
Try first month for just £1 / $1 / €1
The Creative Bloq team is made up of a group of design fans, and has changed and evolved since Creative Bloq began back in 2012. The current website team consists of eight full-time members of staff: Editor Georgia Coggan, Deputy Editor Rosie Hilder, Ecommerce Editor Beren Neale, Senior News Editor Daniel Piper, Editor, Digital Art and 3D Ian Dean, Tech Reviews Editor Erlingur Einarsson and Ecommerce Writer Beth Nicholls and Staff Writer Natalie Fear, as well as a roster of freelancers from around the world. The 3D World and ImagineFX magazine teams also pitch in, ensuring that content from 3D World and ImagineFX is represented on Creative Bloq.