Hiccup Tips
Summary: Hiccup is a Clojure DSL for generating HTML. If you're using it, you might like these tips.
Hiccup is a Clojure Domain Specific Language (DSL) for programmatically generating HTML. It's one of the three tools I recommend in my Clojure Web stack. It's a thin layer over HTML, but oh, how I welcome that layer! The biggest win is that since it's an internal DSL, you can begin abstracting with functions in a way that you will never be able to, even in templating engines.
Hiccup is an example of a great Clojure DSL. It uses literal data structures pretty well, it's as or more readable than what it translates into, and, as a layer of indirection, solves a few sticky problems of HTML. If you don't know it, go check out the Clojure Cookbook recipe for Hiccup. Hiccup might take some getting used to, but once you do, you'll appreciate it.
This article will assume you are familiar with the syntax and want to up your game.
Cross-site Scripting Vulnerability
Ok, this one is a pretty bad problem. Hiccup generates Strings, just plain old Java strings. If you put a String in the body of a Hiccup tag, it will just concatenate it right in there, no questions asked. That's just asking to be exploited, because that String is going to be shipped off to the browser and rendered, scripts and all.
Most templating libraries will default to escaping any String you pass them. The non-default, usually obviously marked alternative is to pass in a String unescaped. Hiccup got this backwards and it just sucks. It means you have to do extra work to be secure, and if you forget just once, your site is vulnerable.
The fix: This is the work you have to do every time you're getting a String that could be from the "outside" (a form submission, API request, etc.). Normally, you'd do this:
[:div content-of-div]
That will work but it's unsafe. You should do this:
[:div (h content-of-div)]
That little h
(hiccup.core/h
to be exact) there means escape the
String. It sucks, but that's how you do it in Hiccup. One day I want to
write a secure fork of Hiccup.
Overloading vectors
One downside to Hiccup and any DSL that overloads the meaning of vectors is that vectors are no longer useful as sequences within Hiccup. They now mean "start a new HTML tag". It's not a huge deal, but I've spent a lot of time debugging my code, only to realize that I was using a vector to represent a sequence. I use literal vectors everywhere else (because they're convenient and readable), but in Hiccup land they're wrong.
The fix: You can't use a literal vector, but you can call list
to
create a list. Not as beautiful, but it is correct. Sometimes I will
call seq
on a return value to ensure that it's never a vector.
Multiple tags
I don't know why this still happens, but it's common, so I'll mention it. Sometimes I'll be looking at the HTML output in a browser and I just can't find an element. It's gone. Reload, still not there. When I look through the code, the hiccup to generate the tag is right there! Why won't it render?
Well, long story short, it's because in Clojure, only the last
value of a let
or fn
is returned. Doh! My missing element was
being rendered then discarded.
(defn list-with-header [header items]
[:h3 header] ;; this header is missing
[:ul
(for [i items]
[:li i])])
The fix: Wrap the two (or more) things in a list (not a vector!).
(defn list-with-header [header items]
(list ;; wrap in a list, not a vector
[:h3 header] ;; now it's there
[:ul
(for [i items]
[:li i])]))
Hiccup plays nice with nil
This one is just a little design touch with some perks, not a problem
that it's solving. In Hiccup, the empty list renders nothing. This is
extended to nil
as well. A common thing in HTML is that you want to
render a bunch of children in a parent tag, but you don't want the
parent tag if the list is empty.
Standard constructs will render the parent tag:
[:ul
(for [i items]
[:li i])]
When items
is empty, you still get <ul></ul>
. This is a problem with
lots of templating libraries.
The fix: The fix in Hiccup is due to it playing nice with nil
.
Wrap the parent in a when
:
(when (seq items)
[:ul
(for [i items]
[:li i])])
It's not beautiful, but then again, you could be using HTML with Moustache.
Use defn
for snippet abstraction
HTML has no way to abstract. The crap you see is the crap you get. Many templating libraries have some kind of snippet concept, often referring to different files. Well, because Hiccup is just code inside of Clojure, you've got a better designed way of making reusable pieces of Hiccup.
Let's say I'm repeating something a lot:
[:div
[:ul
(for [i list1]
[:li i])]
[:ul
(for [i list2]
[:li i])]
[:ul
(for [i list3]
[:li i])]]
That ul
is basically the same three times.
The fix: Standard functional abstraction. Pull out the repeated bit into a new, named function.
(defn make-list [items]
[:ul
(for [i items]
[:li i])])
Now you can call it from inside of Hiccup:
[:div
(make-list list1)
(make-list list2)
(make-list list3)]
Compiling your snippets
You may know this, but Hiccup is a compiling macro. That means it takes your literal vectors, maps, etc, and at compile time, generates code that will generate your HTML. All this means is that Hiccup is really fast, about as fast as concatenating strings can be.
But, because the Hiccup compiler doesn't do a full examination of your code, it can't compile everything. It inserts run time fallbacks for stuff it can't handle at compile time which will interpret it at run time. So, for instance, if you're calling a function that returns some Hiccup, it can't compile that automatically. It has to wait till the function returns to know what it is. That is, unless . . .
The fix: The way to get Hiccup to compile something is with the
hiccup.core/html
macro. That's the macro that does the compilation and it will do it
anywhere. So if you've got code like this:
(defn make-list [items]
[:ul
(for [i items]
[:li i])])
(defn my-three-lists []
[:div
(make-list list1)
(make-list list2)
(make-list list3)])
You should wrap the Hiccup form in its own compiler, like this:
(defn make-list [items]
(html
[:ul
(for [i items]
[:li i])]))
For this little example, it probably won't make a noticeable difference. But it can be significant for larger documents.
Just to note: the Hiccup compiler can understand if
and for
forms,
so there's no need to wrap them in the compiler. No harm, though, if
you do.
Autoterminating
Just a good thing to know about HTML.
Did you know that this is not legal HTML?
<script />
It's true. A script
tag can't be self-closing.
There's all sort of silly rules in HTML like this. And then there's XML mode versus HTML mode. We are lucky: Hiccup does all of this for you, so you don't have to wade through the HTML spec(s!) looking for why something won't render in IE7.
The fix: hiccup.core/html
takes a map of options as the first
argument (it's optional). If you pass in a :mode
option, it will set
the correct HTML mode. The various modes unfortunately are incompatible.
There are three modes, :xml
, :html
, and :xhtml
. The default is
:xhtml
.
Id/class DSL
Hiccup is a DSL. And it has its own sub DSL for HTML ids and classes. It's similar to CSS selectors.
Let's say you have a div
like this:
[:div
{:id "main-content"
:class "blue-background green-border"}
(h "Here's some content.")]
Well, Hiccup lets you make that shorter and easier to read.
The fix: Use the id/class DSL:
[:div#main-content.blue-background.green-border
(h "Here's some content")]
Here's how it works. Every element has an optional id and zero or more
classes. After the tag name (div
here), you can put the id after a
#
. Then list your classes starting with a .
each. Omit the #
if
there's no id. Ditto for the .
if there's no class. Oh, and the id
must come first. That will get converted to the id and class attributes
you want. Also, these will conflict with attributes in the map, so
choose one or the other, not both.
Generating Hiccup from HTML
Last thing, I promise!! Sometimes you have some HTML that someone gave you and you want to Hiccupify it so you can just stick it into your code. Manually doing that is kind of tedious. Luckily, there's a solution our there for you.
The fix: This page lists some options for outputting HTML from Hiccup. I have personally used Hiccup-bridge. It does a good job (and it even goes both ways). You call it like this:
lein hicv 2clj hello.html
That will output hicv/hello.clj
in hiccup format. Pretty rocking when
you need it.
Conclusion
Well, there you go. My Hiccup tips. Hiccup is pretty nice for templating. I recommend using it (or my future secure fork) in your web projects. If you'd like to learn more Hiccup and how to build web apps in Clojure, please check out Lispcast Web Development in Clojure. It's a video course teaching just that. All you need to know is Clojure and HTML.