Sunday, 18 November 2007

Lisp web framework - Designer friendliness

As promised in an earlier post, I was going to take a look at another way to generate HTML for a web app written in Common Lisp. I decided that cl-who would be good for quick scripting but would be a major bottleneck when developing the front end. The main basis for this decision was that it required some knowledge of Lisp should things go horribly wrong and the person developing the HTML would not necessarily know Lisp. So I took a look at a templating library called html-template. Calling it html-template is a bit of a misnomer because the parser actually knows nothing about HTML. Thank *GOD* (get it?) This means that it is useful for generating more than just HTML. Not that we will make use of this capability just yet.

The way templating libraries work is that you write some text in a string or file with special characters that will be replaced with some values at runtime. Some templating libraries (including html-template) include control flow such as looping and if statements. A simple example follows:


# file: template.tmpl
# This template requires the variable: subject
Hello, <!-- tmpl_var subject -->!

The intended result here is that when a program or function is called with a given subject, it generates "Hello, Sohail!" for when the subject is Sohail. The variable is referenced with <!-- tmpl_var subject -->.

To generate the result, one usually gives the templating library the file or string containing the template (in this case, template.tmpl) and a data structure mapping names to values. So, in some hypothetical language called Lisp, with a hypothetical library called html-template, you might write:

CL-USER> (html-template:fill-and-print-template #p"template.tmpl"
(list :subject "Sohail"))
Hello, Sohail!

Quite simple really. Now imagine the content of the file containing some HTML directives around the variable reference. Presto! A web page is dynamically generated. Sweet.

Anyway, to determine whether this templating technique accomplishes my goal of being designer friendly, I donned the hat of a CSS/HTML artist. Pay no attention to the fact that I am actually quite clueless when it comes to CSS/HTML. I figured the technique would be golden if the designer could focus on the layout and aesthetic appeal of the site while I could focus on the backend. The CSS/HTML guru's name is Bopinder. Here is our conversation:

Sohail: Hey Bopinder, I've heard some great things about your web design skills. I'm writing a blog app and I want to make it look pretty.
Bopinder: Yes, my strengths lie in web page aesthetics. I hope there isn't much programming involved.
Sohail: No, there isn't! Or atleast I hope not. I am writing the backend in Lisp, which is quite possibly the second most obscure web programming language in existence. But I get stuff done quite fast and it is free! I figure we will use a templating library called html-template. If you have done templating before, it shouldn't be a problem.
Bopinder: Sounds good to me. In fact, I have done templating using something written in Java.
Sohail: Java sucks.
Bopinder: Yeah, I don't even program and I could tell you that.
Sohail: Looks like we'll get along just fine, you and I! So anyway, I figure we will start on a blog entry page. My current page looks like this:




Bopinder: Dear God.
Sohail: Hey, I'd like to see your Lisp code!
Bopinder: Good point.
Sohail: Ok so here is the file you need to edit: template.tmpl. I have set up a server for you here: http://omglol.com/bopinder. You can ftp the changes to that file here: ftp://omglol.com/bopnder/template.tmpl.
Bopinder: Ok... So is there some layout you like?
Sohail: Yeah, I'd like it to look classy. Like I'm environmentally friendly as well.
Bopinder: So lots of green.
Sohail: Sounds good to me!
Bopinder: I meant the colour.
Sohail: Oh. Yes, well that too. Why don't you just come back with something in 3 days and we can discuss where to go from here.
Bopinder: Sounds good.

So Bopinder went away, cursing my HTML skills under his breath and came back with something quite nice. It was CSS-based and there were no tables. More importantly, whenever he needed some more variables, he was able to put in stubs and sent me a list of the information he would have liked to put on the page. Even more importantly, he never touched Lisp. He only read the html-template documentation and asked me a few questions every now and then. That was awesome. Anyway, without further ado, the exact same page, after Bopinder got through with it:


Blog entry with some comments:



Adding comments:



So all in all, not bad for someone with multiple personalities. I later discovered that Bopinder ripped off the site design from Freelance Switch. Good thing I found out before I gave him the second half of the deposit! To anyone reading, do not hire Bopinder!

Here is the template (converted using Quick Escape). Something to notice is that Bopinder could reference variables within tag attributes without breaking a sweat. Of course this is possible because html-template knows nothing about HTML.

<!-- Hey emacs, this is a -*- html -*- file! -->
<!--
This template requires:
id: The blog entry id
title: The blog entry title
timestamp: A formatted representation of the timestamp
body: The blog entry itself
comments: A list of comments. The list of comments would contain author, timestamp and content variables
-->
<html>
<head>
<title>Testing</title>
<link rel="stylesheet" href="/static/style.css" type="text/css"/>
</head>
<body id="blog">
<div id="content">
<div id="post" class="post">
<a href="/entries?id=<!-- tmpl_var id -->">
<h2 id="title">
<!-- tmpl_var title -->
</h2>
</a>
<a href="">
<h3><!-- tmpl_var author --></h3>
</a>
<!-- tmpl_var timestamp -->
<p>
<div class="post-content">
<!-- tmpl_var body -->
</div>
</p>
<p>
<ol class="comments">
<!-- tmpl_loop comments -->
<li class="comment">
<div class="commentor">
<p class="comment-author">
<a href=""><!-- tmpl_var author --></a>
</p>
<p class="comment-metadata">
<!-- tmpl_var timestamp -->
</p>
</div>
<div class="the-comment">
<p>
<!-- tmpl_var content -->
</p>
</div>
</li>
<!-- /tmpl_loop -->
</ol>
</p>
</div>
<div style="clear: both;"/>
<br/>
<div>
<h2>Add Comment</h2>
</div>
<div id="formcontainer">
<form id="commentform" method="post" action="/add-comment">
<p>
<input type="text" value="" name="name"/>
<label for="author">
Name (required)
</label>
</p>
<p>
<textarea id="comment" name="comment"></textarea>
</p>
<p>
<input id="submit" class="button" type="submit" value="Submit Comment" name="submit"/>
<input type="hidden" name="id" value="<!-- tmpl_var id -->"/>
</p>
</form>
</div>
</div>
</body>
</html>

Just a reminder: Bopinder and I are the same person. Bopinder is to Sohail as Clark Kent is to Superman. The situation depicted above is purely fictional and I would wholly recommend Bopinder for your next project.

And for the extra curious, here is the Lisp code used to generate the above page (don't mind the weird parentheses placement, I'm trying something new):

(defun render-blog-post (post)
(let* ((comments
(loop for comment in (comments post)
collecting (list :timestamp (fmt-date (timestamp comment))
:author (author comment)
:content (content comment)))
)
(vars (list :id (id post)
:title (title post)
:body (body post)
:author (author post)
:timestamp (fmt-date (timestamp post))
:comments comments
))
(stream (make-string-output-stream))
)
(html-template:fill-and-print-template #p"render-blog-post.tmpl"
vars
:stream stream)
(get-output-stream-string stream)
))

Sweet.

No comments: