AsciiMathML

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"
$(document).ready(
function() {
$("#kitty-img").click( function() {
alert("you're a kitty!"); })}) />

Not very pretty.

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

(html
[: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"
[js]
(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:

(html
[: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"
[js]
(html
[:script {:type "text/javascript"}
(scriptjure/js (clj js))]))

Now our example looks like

(html
[: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.

11 comments:

Alex Ott said...

It looks interesting. If you'll add clojure label to your Clojure-related posts, I can add your blog into Planet Clojure - your posts could be useful for many peoples

maacl said...

Very nice. This approach will remove a lot of repetition from my code.

Henrik Huttunen said...

One fun, not probably practical, option would be write Clojurescript, a Clojure interpreter in Javascript.

Similarly what I did for Factor: http://huttuh.blogspot.com/2010/04/factorscript-jquery-and-factor-proof-of.html

Allen Rohner said...

Alex: I've added a clojure label to a few posts. Thanks!

Kerry Todyruik said...

Great post. That's very helpful. This example shows composition of single functions. I've been getting stuck when I try to expand this example to multiple statements inside of the on-ready function, and multiple javascript functions inside the script tag, below the on-ready. Do you think you could show an example of this kind of more elaborate function composition using similar clojure techniques?

Sean Neilan said...

How is this any different from ClojureScript?
https://github.com/richhickey/clojure-contrib/tree/master/clojurescript

Allen Rohner said...

Sean, 1) Scriptjure works, clojurescript was a prototype that never got off the ground. 2) Clojurescript is an attempt to port all of Clojure to javascript. Scriptjure is a small DSL intended for writing javascript in clojure. Scriptjure is much simpler, and produces somewhat readable code.

Sean Neilan said...

Cool! I'm using it on this project at work at the moment and it's definitely working pretty well. I definitely need to add something to access an attribute like .id

I like it a lot better than coffeescript and it compiles to javascript anyway so nobody worries about it.

For example
Right now, to write something like $("#foo").id, I need to write (quote "$('#foo').id"). I can't write something like (get ($ "#foo") id) because it becomes
get($("#foo"),id);

Once I have time, I plan to write a program to input javascript or coffeescript into clojure. Maybe even make something to import/export python and import css/sass. That way web development can be in one language.

Sean Neilan said...

Sorry it took me so long to respond. I forgot to subscribe to the feed.

Allen Rohner said...

Sean, (.id ($ "#foo")), or (. ($ "#foo") id)

Sean Neilan said...

Actually, I was still unable to get that to work. I'm pretty sure anytime you access an attribute of something, it's automatically considered as a function.

I did write a macro to simplify some annoying ajax crap here.
You can use it like this:
(js (access "/url/$0/$1/$2/" param0 param1 param3))

Clojure & scriptjure kicks ass! I used to write ~10 lines of code for some of that ajax stuff and now I write one!