jQuery Mobile + CouchDB: Part 3 – Templates and Mustache.js
2010 December 28th by todd anderson
In the previous post, I covered displaying a document from a CouchDB database in the context of a jQuery Mobile page. A difference between local vs external jQuery Mobile pages was discussed, and it was concluded that external pages was beneficial for the client-side application. In finding this happy medium, I also talked about show functions in CouchDB and how they serve up documents upon request.
In this article, I am going to cover another aspect of delivering HTML content from CouchDB – templates. While doing so, I will modify the current document views to provide a cleaner solution (imo) for delivering and rendering document-based jQuery Mobile pages. Hopefully, this will set the basis for creating additional views for the application…
Templates
In the previous post, I covered using show functions to deliver HTML content as a jQuery Mobile page from a CouchDB instance. From the previous show function example for the Albums application, I basically constructed a string that would be interpreted as HTML mark-up using the values from the document. While this is a perfectly valid solution, its probably not the best if we want to reuse parts of that HTML or if the mark-up gets a little too unwieldy for string manipulation based on the request. To provide a little sanity as we start making more page documents for the application, we going to start using templates to stub out the mark-up of an HTML document and fill content dynamically from the show functions.
mustache.js
If you have been following along in the tutorials, you may remember that in the loader.js script there were a few JavaScript files being loaded that were not being referenced within the current application. One of those was mustache.js. I didn’t talk about it at the time and I said we could probably get rid of it for now, but might as well leave it in… hopefully you left it in. If not, you’re screwed. Kidding
I don’t want to go into a mustache explanation as there are already some great articles out there – especially this one from CouchOne – but I will say that mustache.js is a templating engine for rendering any type of content. For our purposes, we are going to use the _tohtml() function of Mustache to marry our dynamic album document values with an HTML template and deliver pages from our respective show functions from the CouchDB database.
Album Page Template
Our previous version of album.js from the /show directory of our Albums couchapp looked like the following:
/show/album.js
function(doc, req) {
var html = "<div data-role=\"page\" id=\"albumview\">" +
"<div data-role=\"header\" id=\"albumheader\">" +
"<h2 class=\"albumtitle\">" + doc.title + "<\/h2>" +
"<\/div>" +
"<div data-role=\"content\" id=\"albumcontent\">" +
"<h2 class=\"artist\">" + doc.artist + "<\/h2>" +
"<p class=\"title\">" + doc.title + "<\/p>" +
"<p class=\"description\">" + doc.description + "<\/p>" +
"<\/div>" +
"<div data-role=\"footer\" \/>" +
"<\/div>";
return html;
}
That served our purpose at the time, but such string manipulation for each page we are going to serve up makes my eyes bleed As such, we will use mustache.js to populate document values in an HTML template. First order of business: Remove that string construct from /shows/album.js and put it into a template, replacing the values with {{mustache}} directives.
Create a new directory called templates in your couchapp directory for the Albums application (for me that is at /Documents/workspace/custardbelly/couchdb/albums). Create a new HTML document in your favorite text editor and save the following as album.html in the /templates directory:
/templates/album.html
<div data-role="page" id="albumview" data-position="inline" data-back="true">
<div data-role="header" id="albumheader">
<h1 class="albumtitle">{{title}}</h1>
<a href="#home" data-icon="grid" class="ui-btn-right">Home</a>
</div>
<div data-role="content" id="albumcontent" data-identity="{{document}}">
<h2 class="artist">{{artist}}</h2>
<p class="title">{{title}}</p>
<p class="description">{{description}}</p>
</div>
<div data-role="footer" />
</div>
Essentially, we have just moved our mark-up from one document to another; the difference being that we are now employing {{mustache}}s as placeholders for dynamic content values. You may notice that the names used in the mustaches are just the property names of the document – similar to the previous albums.js example. In fact there are the property names of the object that will be passed to the Mustache engine to generate our page.
Some added jQuery Mobile element content has been added to the new album page document, as well. We added the data-position and data-back attributes to the page div in order to preserve the back button and add a Home page button to the header. This will allow us to always be able to get back to the index.html Home page from an album page.
<a href="#home" data-icon="grid" class="ui-btn-right">Home</a>
We will need to modify the page div in index.html to have an id value of “home”, but setting that hash as the href for the Home button in the header of our album page will, essentially, enable that link to direct the user back to the page content of the parenting document -index.html.
In album.html we have also declared a data-identity for the content div and provided a {{document}} value. This will be the _document.id from the album.js show function and we will later see how it will be used. Back to our album.js document…
Album Page Show Function
Open up the /show/album.js document in your favorite text editor and make replace the current content with the following:
/show/album.js
function(doc, req) {
var Mustache = require("vendor/couchapp/lib/mustache");
var stash = {
artist: doc.artist,
title : doc.title,
description: doc.description,
document: doc._id
};
return Mustache.to_html(this.templates.album, stash);
}
As mentioned previously, CouchApp includes the Mustache JavaScript file automatically when generating a new couchapp application. In this example we first assign a reference to the Mustache module through a require() call for the file in the vendor/couchapp/lib directory. A generic stash object is created to assign property values that are dynamically populated into the album.html template (previous snippet) using the Mustache:_tohtml method.
I am not entirely clear on how it is possible to reference files and directories with the this keyword in a show function, but i can only assume that couchapp has generated an object tree based on the file structure from which one can access values. If you do know, please leave a comment. In any event, the template file and the defining object are the first two arguments for Mustache:_tohtml. Upon request for an album document, this will return the album.html page dynamically populated with the values for the document related to the id in the request URL (eg. http://127.0.0.1:5984/albums/_design/albums/index.html#_show
/album/db04eb7e5c845ee0aa791ae1ed000fe8).
Hiccup
There may be a slight problem in this solution however…. Though CouchDB does request-cacheing based on header ETags on documents, jQuery Mobile also does cacheing of pages (or rather it accesses ‘pages’ already loaded into the DOM). So there may be a time that an album has been edited during the application session and jQuery Mobile serves up old information as it never went back out the make a GET request to CouchDB, utilizing this show function. We could make a request for the document each time the page is shown in the DOM using the jquery.couch library, but that may be unnecessary overhead; not to mention a waste of the templating engine.
Another solution, and one I think is viable and worth consideration, is to remove the page from the jquery Mobile page cache when this page is hidden in the view (when you navigate to another page). In order to do so, we will need to access the cached page in the DOM using JavaScript. Instead of adding this JavaScript directly in the HTML template file, we will use another templating feature available in Mustache – Partials.
Partials
Partials are, essentially, just a way to include common mark-up or code or what have you into the target template page. It’s important to remember that partials are rendered in first so any {{mustache}}s in the partial file will also be filled. For the purposes of our example application, we are just going to include mark-up to load a script. It may be a little overkill at the moment, but I wanted to show the use of partials as they can be very handy.
Open up your favorite text editor and save the following snippet as scripts.html in /templates/partials/album.
/templates/partials/album/scripts.html
<script src="../../script/album-page.js"></script>
[02-06-2010: Thank you to Steve and Stacy Braxton for leaving a comment noting that the filepath to scripts.html for the album page was incorrectly shown in italics. The correct filepath is now displayed. Thanks!]
Real boring stuff and not using the full breath of what Partials can do. Basically we are just loading in an associated JavaScript file with our page from the show function. Great. Now we have to create another file. I know, I know. It may appear convoluted, but in my head it is much cleaner and organized. The script we are about to create, could be included in this partial or in the template, but i prefer to work with another separate JavaScript file whose purpose I know based on its extension – its got script in it.
album-page.js
You may have looked at the path in the src attribute value from the Partial we just created and noticed that it is a relative path that goes out a couple directories. That is going back to the attachments_ directory to access the album-page.js file from the script folder. The attachments_ directory also houses the style folder in which we added the jQuery Mobile styles in the first post.
We’re going to create an add the album-page.js file to the _/attachments/script directory and it will provide the ability to access and clear the page from the page cache of jQuery Mobile so as to always serve up a the page from CouchDB (based on its own cacheing mechanism) using the show function. Open up your favorite text editor and save the following snippet as album-page.js in _/attachments/script:
_/attachments/script/album-page.js
var AlbumPageController = function() {
function handleView()
{
// Watch for bound hide of page to clear from cache.
var docId = $("#albumcontent").data("identity");
var albumPage = $(document.getElementById("_show/album/" + docId));
albumPage.bind( "pagehide", handlePageViewHide );
}
function handlePageViewHide()
{
var docId = $("#albumcontent").data("identity");
var albumPageCache = $(document.getElementById("_show/album/" + docId));
albumPageCache.unbind( "pagehide", handlePageViewHide );
albumPageCache.empty();
albumPageCache.remove();
}
return {
initialize : function() {
$("div[data-role='page']").live( "pageshow", function() {
$("div[data-role='page']").die( "pageshow" );
handleView();
});
}
};
}();
function handlePageViewReady()
{
AlbumPageController.initialize();
}
$().ready( handlePageViewReady );
In essence, we are just creating a view controller for our jQuery Mobile page served up from the show function. Once the page is recognized as part of the DOM, the controller is initialized and an event handler is assigned to the pageshow event for the current page – the album.html served up. We access the page in the DOM using the standard data-role attribute value for a jQuery Mobile page. I stumbled upon this solution from these two forum posts – http://forum.jquery.com/topic/force-page-update and http://forum.jquery.com/topic/binding-events-to-buttons-in-a-dialog – and seem to be the current agreed upon solution for accessing a loading jQuery Mobile page.
Now, it should be noted that setting that handler will actually set a handler on all divs with the data-role attributed as “page”. But since we are only concerned with the pageshow event, we know that the current target is the album page. Once that event is captured, we remove the handler and assign a pagehide event handler to clear the page from the jQuery Mobile (ie. DOM) cache and remove the elements from the DOM.
Since the album page is an external page (defined in the script in index.html when building the list items), its registered in the DOM and accessed using the _show path based on the document ID. So, when the page is fully loaded we access that page recognized in the DOM using the getElementById() method:
var docId = $("#albumcontent").data("identity");
var albumPage = $(document.getElementById("_show/album/" + docId));
Accessing the page as such, we can empty all its elements and remove it once the page has been fully hidden from the view in the page transition of jQuery Mobile:
var docId = $("#albumcontent").data("identity");
var albumPageCache = $(document.getElementById("_show/album/" + docId));
albumPageCache.unbind( "pagehide", handlePageViewHide );
albumPageCache.empty();
albumPageCache.remove();
That essentially will make a request each time to CouchDB for the album document page and not rely on the cacheing of pages within jQuery Mobile. This will also open up the ability to assign and manage handlers for other events, such a requesting pages for editing a document… but we’ll broach that subject in another post For now, let’s go back and update our album.js and album.html documents to include the partial.
[01-28-2010: Thank you to IR for leaving a comment alerting me to the fact that i totally missed out on updating the neccessary files after creating the partial]
Modifying album.js and album.html
Open up /shows/album.js in your favorite text editor and make the following modification to pass the partial directory in Mustache._tohtml():
/shows/album.js
function(doc, req) {
var Mustache = require("vendor/couchapp/lib/mustache");
var stash = {
artist: doc.artist,
title : doc.title,
description: doc.description,
document: doc._id
};
return Mustache.to_html(this.templates.album, stash, this.templates.partials.album);
}
That last argument in _tohtml() will allow you to stub out the documents included in the /album folder of the partials directory in the album.html template. We’ll need to update that document as well for our scripts partial to be included.
Open up /templates/album.html and make the following modification:
/templates/album.html
<div data-role="page" id="albumview" data-position="inline" data-back="true">
<div data-role="header" id="albumheader">
<h1 class="albumtitle">{{title}}</h1>
<a href="#home" data-icon="grid" class="ui-btn-right">Home</a>
</div>
<div data-role="content" id="albumcontent" data-identity="{{document}}">
<h2 class="artist">{{artist}}</h2>
<p class="title">{{title}}</p>
<p class="description">{{description}}</p>
</div>
<div data-role="footer" />
</div>
{{>scripts}}
A slight modification to the syntax on how you would include object property values, the partial is included by appending the name of the partial document to include with a greater than character. That will essentially write the content of the document inline in the template where it is declared.
Now we just need to make a small modification to the index.html file and then deploy our modified application to the CouchDB instance so we can view our wonderful changes (which won’t look like we changed a thing).
Modifying index.html
We added a Home button to the header bar of the album page so we could always navigate back to the Home page. Currently the album page is accessible from the album list on the Home page, but who knows, maybe that won’t always be the case in the future. In any event, we made the change and assigned the href value for the Home page button with the #home anchor. Currently that will go nowhere. We’ll set that as the div id on the main jQuery Mobile page in index.html.
Open up the _/attachments/index.html file in your favorite text editor and make the following modifications the #home page div:
_/attachments/index.html
<div id="home" data-role="page">
<div data-role="header"><h1>Albums</h1></div>
<div data-role="content">
<ul id="albums" data-role="listview" data-theme="c" data-dividertheme="b"></ul>
</div>
<div data-role="footer" class="ui-bar"><h4>a list of albums</h4></div>
</div>
[02-05-2010: Thank you to Otetz for leaving a comment about the missing call to refresh the list prior to shoe on the index.html file]
With index.html still open in your favorite text editor, add the following line to the handleDocumentReady() method in the inline JavaScript:
function handleDocumentReady()
{
$("#home").bind( "pagebeforeshow", refreshAlbums );
refreshAlbums();
Save that, and now we can add a Home button to any page and will always be directed back to that div with the list of albums from our CouchDB database. Cool. Time to deploy.
Deployment
We modified our show function to use templates and created a script to empty the page and its elements from the DOM upon hide within the jQuery Mobile context and are assured that each access of an album document will contain the latest changes. With these modifications saved, we can now push to the CouchDB database using the couchapp utility. Open a terminal and navigate to the directory where you create your CouchApp applications (for me that is /Documents/workspace/custardbelly/couchdb and in there i have a folder named albums which is the CouchApp application directory for these examples). Enter the following command to push the changes to the CouchDB instance:
couchapp push albums http://127.0.0.1:5984/albums
If all was successful and you now go to http://127.0.0.1:5984/albums/_design/albums/index.html, we’ll still have our old familiar list and still access each album by clicking on a list item. Basically it performs exactly as it had before, but we cleaned up a little on our end… a benefit to us as the developer and a benefit to the user though not visually noticeable… it still looks rather ugly
Conclusion
We delved a little deeper into how to serve up pages from CouchDB using templates and partials (thanks to Mustache!) and also touched on accessing pages from DOM cache in the context of jQuery Mobile framework. All nice stuff, but nothing really has changed in how the application worked for the end user. We ensured that _album page_s always had the correct and latest content, but we haven’t opened up the possibility to edit the document and commit changes. That’s to come Just wanted to lay the groundwork for easing into adding more pages to our application. Hopefully you found some of this useful.
[Note] This post was written against the following software versions:
CouchDB – 1.0.1
CouchApp – 0.7.2
jQuery – 1.4.4
jQuery Mobile – 1.0a2
If you have found this post and any piece has moved forward, hopefully the examples are still viable/useful. I will not be updating the examples in this post in parellel with updates to any of the previously mentioned software, unless explicitly noted.
Articles in this series:
- Getting Started
- Displaying a page detail of a single album.
- Templates and Mustache
- Displaying an editable page of an album.
- Creating and Adding an album document.
- Deleting an album document
- Authorization and Validation – Part 1
- Authorization and Validation – Part 2
Full source for albums couchapp here.
Posted in CouchDB, jquery, jquery-mobile.