Wednesday, April 14, 2010

Writing JQuery code with Scriptjure

I'm the author of Scriptjure, a Clojure library / DSL for generating javascript from clojure code. I mainly developed it for embedding glue javascript in HTML pages. Let's take it out for a spin.

I'm not going to go over the basic rules. See the above link for more detail. Since 'everyone' uses jquery these days, let's make a simple example, setting up an on-click handler that pops up an alert box. Pretty much the simplest interesting thing you can do.

<img id="kitty-img" src="kitty.jpg">
<script type="text/javascript"
function() {
$("#kitty-img").click( function() {
alert("you're a kitty!"); })}) />

Not very pretty.

Let's naively translate that jquery code into compojure and scriptjure:

[:img#kitty-img { :src "kitty.jpg"}]
[:script {:type "text/javascript"}
(scriptjure/js (.ready ($ document)
(fn [] (.click ($ "#kitty-img")
(fn [e] (alert "you're a kitty"))))))])

Not much of an improvement in terms of length. Of course, we've gained a lot because now there's no string interpolation to generate javascript, but we can do better. Let's do some refactoring. We're going to define several clojure functions that output javascript.

(defn on-ready
"Runs a chunk of javascript when the document is ready"
(scriptjure/js* (.ready ($ document)
(fn [] (clj js)))))

(defn on-click
"installs js as an on-click handler"
[selector js]
(scriptjure/js* (.click ($ (clj selector))
(fn [e] (clj js)))))

Using our two shiny new functions, the example looks like:

[:img#kitty-img {:src="kitty.jpg"}]
[:script {:type "text/javascript"}
(scriptjure/js (on-ready (on-click (js*
(alert "you're a kitty!")))))])

Why did I use js*? Remember that (js) returns a string, a "compiled" chunk of javascript. Assume we didn't use js*: on-click would return a string. On-ready would treat the output of on-click as a javascript string rather than javascript code, and return

$(document).ready( function() { "$(\"kitty-img")..."}

Rather than

$(document).ready( function() { $("kitty-img")...}

Note the quotes.

So when writing clojure code that generates javascript, typically you'll want to use js*, and then make sure the final recipient calls (js) rather than (js*). But you know, remembering when to call (js) is annoying. Let's fix that.

(defn script
"wraps js code in an HTML script tag"
[:script {:type "text/javascript"}
(scriptjure/js (clj js))]))

Now our example looks like

[:img#kitty-img {:src "kitty.jpg"}]
(script (on-ready (on-click "#kitty-img"
(js* (alert "you're a kitty!"))))))

Much better! This technique makes generating javascript significantly less painful. You can the same approach to simplify e.g. Ajax updating pages. Maybe I'll get around to adding all of these little helper functions to scriptjure. If you find any bugs or have any suggestions for how to improve scriptjure, I'd love to hear them.