Thursday, February 18, 2010

Textile and Partial Templates in CouchApp

‹prev | My Chain | next›

Yesterday, I was able to get nearly all of the necessary pieces of a couchapp show page working correctly. Today I will try to do it a little better. Specifically, I would like to figure out rendering Textile via Javascript and re-usable templates (e.g. headers and footers) in CouchDB / couchapp show templates.

I need to be able to display Textile because that is how I edit/store recipe summaries and instructions. On the actual web site, I am simply using Redcloth (by way of Sinatra) to convert for web display.

Happily, I do not have to do much work on Javascript Textile conversion—Jeff Minard and Stuart Langridge have a working demo for doing just this. To add this code to my couchapp, I create a new lib directory and add the "super textile" code to lib/super_textile.js:
/*
* Lifted from http://jrm.cc/extras/live-textile-preview.php
*
* - Jeff Minard (jeff aht creatimation daht net / http://www.jrm.cc/)
* - Stuart Langridge (http://www.kryogenix.org/)
*
*/
function superTextile(s) {
var r = s;
// quick tags first
var qtags = [['\\*', 'strong'],
['\\?\\?', 'cite'],
['\\+', 'ins'], //fixed
['~', 'sub'],
['\\^', 'sup'], // me
['@', 'code']];

// do all sorts of stuff to "r"...

return r;
}
(I make a small change to the code to insert double <br> tags after paragraphs)

To use that function, I pull it into my recipe.js show function with a couchapp !code directive and then, er, use it:
function(doc, req) {
// !json templates.recipe
// !code vendor/couchapp/template.js
// !code vendor/couchapp/path.js
// !code lib/super_textile.js

var image;
for (var prop in doc._attachments) {
image = prop;
}
return template(templates.recipe, {
title: doc.title,
docid: (doc && doc._id),
asset_path: assetPath(),
summary: superTextile(doc.summary),
instructions: superTextile(doc.instructions)
,
image: image
});
}
It is a simple matter to use those new template variables:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Recipe: <%= title %></title>
<link rel="stylesheet" href="<%= asset_path %>/style/main.css" type="text/css" />
</head>

<body>
<h1><%= title %></h1>

<%= summary %>

<%= instructions %>


<img src="../../../../<%= docid %>/<%= image %>" />


</body>
</html>
After uploading the couchapp to my DB, I see nicely formatted textile:


Easy enough.

To add a header and footer, I first create them in the templates directory. I will follow the Rails convention of prefixing partial templates with an underscore, so I create templates/_header.html and templates/_footer.html. To include these in the show function, I again use the couchapp !json directive. To render, I will send() chunks of HTML (first the header, the the main template) to the browser and finally return the footer to be sent last to the browser:
function(doc, req) {
// !json templates.recipe
// !json templates._header
// !json templates._footer

// !code vendor/couchapp/template.js
// !code vendor/couchapp/path.js
// !code lib/super_textile.js

var image;
for (var prop in doc._attachments) {
image = prop;
}

send(template(templates._header, {}));

send(template(templates.recipe, {
title: doc.title,
docid: (doc && doc._id),
asset_path: assetPath(),
summary: superTextile(doc.summary),
instructions: superTextile(doc.instructions),
image: image
}));

return template(templates._footer, {});
}
After pushing my couchapp to my recipes DB, I find this in the browser:


Ugh. Not quite what I was hoping for.

I believe that this is an indication that send() only works in list functions. It makes sense to send data in chunks in a list function—especially if there are many chunks. I expected it to work in the show functions as well. No matter, I can concatenate the template outputs together easily enough (flog scores be damned):
  return template(templates._header, {}) +

template(templates.recipe, {
title: doc.title,
docid: (doc && doc._id),
asset_path: assetPath(),
summary: superTextile(doc.summary),
instructions: superTextile(doc.instructions),
image: image
}) +

template(templates._footer, {});
Now when I load the page, I find:


Much better.

Tomorrow: updating documents with couchapp.

Day #18

2 comments:

  1. BTW, in the soon-to-be-released CouchDB 0.11 you'll be able to use the list-style API, such as send(), from your show functions.

    http://github.com/apache/couchdb/commit/15d10793a32a5fa57c80e9eab8803dc7d284ca6d

    ReplyDelete
  2. Good to know. I did eventually grok that send() is better suited to list functions. Even so, this newbie found it confusing. Hopefully that addition to 0.11 will help future newbies avoid similar confusion. Also, there may be some legit reasons for send() in show functions as well.

    Thanks!

    ReplyDelete