One of the things I’ve been struggling with on the HTML/JS app I’ve been working on is how to get binding-like functionality. It seems like there are a few ways to do it, but the ones I looked at didn’t seem to fit with the general pattern of JavaScript and I wanted something a bit more elegant. Luckily, I just didn’t know what to look for, and once I came across templates, I realized I had found exactly what I wanted. Templates in JavaScript act like ItemRenderers but in my example I was able to use them to create a page, and then dynamically change the data on that page almost like I would if I were binding an object to a form in Flex.
And that’s almost exactly what templating lets you do. Here’s my example. I’m creating a list of beers, and when I click on one of those beers, I want to load a bunch of information about that beer in a form so I can make changes to it and save those changes to my database. With Flex Mobile I was able to do that pretty robustly because I can pass data between views very easily and bind elements in the second view to my data. With jQuery mobile, there’s nothing quite that fancy, so I had to rely on templates.
It turns out there are a ton of JavaScript templating libraries out there. Underscore.js includes a templating feature and looks interesting but I tried Mustache.js off the bat and found it pretty simple to use.
I started off with the template itself. In my case, it was a basic form that I called beer_detail.mustache
<form action="#" method="post">
<div id="beername" data-role="fieldcontain">
<span id="beerlabel">{{ beername }}</span>
</div>
<div id="brewername">{{ brewername }}</div>
<div id="brewerlocation">{{ brewerlocation }}</div>
<div id="beerstyle">{{ beerstyle }}</div>
<hr>
<div id="quantity" data-role="fieldcontain">
<label for="quantityslider">Quantity: {{quantity}} </label>
<input type="range" name="quantityslider" id="quantityslider" value="{{ quantity }}" min="0" max="25" />
</div>
<div id="purchasedate" data-role="fieldcontain">
<label for="purchasedatefield">Purchase Date:</label>
<input type="date" name="purchasedatefield" id="purchasedatefield" value="{{ purchasedate }}" />
</div>
<div id="price" data-role="fieldcontain"><input type="text" value="{{ price }}"></div>
<div id="cellardate" data-role="fieldcontain">
<input type="date" name="cellarfield" id="cellardatefield" value="{{ cellardate }}" />
</div>
<div id="cellartemp">Cellar Temperature: {{ cellarTemperature }}</div>
<div id="brewdate" data-role="fieldcontain">
<input type="date" name="brewdatefield" id="brewdatefield" value="{{ brewdate }}" />
</div>
<a href="#pintley">This beer on Pintely</a>
<button type="submit" data-theme="a">Submit</button>
</form>The double brackets {{ }} are all of the variable names that the template looks for so those will be replaced with actual data when the template gets rendered. I liked the similarity to Flex’s binding syntax.
One of the things you can do in jQuery Mobile is pass url variables, so for the first part of my application, I use a list of the beer names with a link to a page named beer_details.html that includes an id parameter that I use to look up the beer. In this example I’m using a Lawnchair database called “beers” and just calling the all() method to get all of the beers in the database. Then I iterate through those and build the list.
beers.all(function(arrBeers){
for(var i = 0; i<arrBeers.length;i++)
{
console.log(arrBeers.length);
var listdiv = document.createElement('li');
listdiv.setAttribute('id','listdiv');
listdiv.innerHTML = '<a href="beer_details.html?id='+arrBeers[i].key+'">'+arrBeers[i].value.beername+'</a>';
$('#beer_list').append(listdiv);
}
$('#beer_list').listview("refresh");
});The beer_details.html page itself just contains some skeleton code for jQuery mobile. The code will inject the template into the div id="content" tag. This way, all of the jQuery functionality stays with the application so it can use the back button, has the header/footer, etc and plays the transitions back and forth. So it’s pretty bare bones:
<div data-role="page" id="beerdetail" style="type-inline">
<div data-role="header">
<h1>Your Beers</h1>
</div>
<div id="content" data-role="content" align="center">
</div>
<div data-role="footer" data-id="foo1" data-position="fixed">
<div data-role="navbar">
<ul>
<li><a href="beers.html">Beers</a></li>
<li><a href="styles.html">Styles</a></li>
<li><a href="scan.html">Scan Barcode</a></li>
<li><a href="locations.html">Locations</a></li>
</ul>
</div>
</div>
</div>So now that the structure is set up, we can use Mustache to populate our template with data and inject the resulting HTML into the DOM. The first step is to set up the data object. In this code I parse the URL to figure out the ID of the beer, then use Lawnchair to retrieve that data. I’m listening for the pagebeforeshow method, but I’m not sure if that’s the best one. But it allowed me to get the data and change the DOM before the page showed, which is important for being able to render the template.
$('#beerdetail').live('pagebeforeshow',function(e) { var beerID; beerData = {}; var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&'); for(var i = 0; i < hashes.length; i++) { hash = hashes[i].split('='); if(hash[0] == "id") { beerID = hash[1]; } } });
Once I have the beerID, I can use LawnChair to grab the information. Then I can use that object and pass it to Mustache to build the template. The Mustache library uses a .to_html() method that takes a reference to the template and the object that holds the data, in this case beerData. In the example below, there’s a bit of chaining going on. First I have to get the data, then when that is returned, it calls a function that will go out and load my .mustache template using Ajax, and finally, when that page is retrieved, I call Mustache.to_html() to get the HTML and then inject that HTML into the content so it will render correctly. This code goes inside the pagebeforeshow event handler.
beers.get(beerID,function(obj){ beerData = obj.value; $.get('templates/beer_detail.mustache', function(data){ template = data; var renderedhtml = Mustache.to_html( template,beerData); $("#content").html(renderedhtml); }); });
And voila! Now when we click on a beer from the list, the data gets inserted into the template and when we move back and forth the data will update as we click on new beers.
This is much, much easier than trying to go through the DOM and insert/modify the form variables and also lets us potentially use different templates depending on the beer I choose. So far I’ve found templates to be helpful for getting around concepts like binding in HTML/JS.
I’ve gone ahead and put this project up on GitHub. It’s kind of a disaster at this point, since it’s basically my learning project, but you can see how this was done.
TweetRelated posts: