boss-level.com
14Aug/113

Clojurescript AJAX, or is that AJACS ?

Introduction

This short post shows a minimal example of performing asynchronous GETs from Clojurescript, and how to pass pure Clojure data structures over the wire. No XML, no JSON, just Clojure. So its AJAX, with Clojurescript rather than XML; can we call this AJACS :) As usual the code is all to be had on GitHub.

Structure

For illustration the app performs multiplication. The structure is a Clojurescript front end, with two text inputs for numbers and a shiny green button (stolen shamelessly from Rich's demo) that sends those two numbers asyncronously to a Ring server which multiplies them, conj's the answer into the request map, and returns it for display.

Client Side

The client is contained in ./www/multiplier.html and ./www/src/multiplier.cljs. The same instructions for compling up the Clojurescript apply as for the last post.


The HTML file is pretty dull, not much to note, except that the forms are all done in CSS style, as stolen from Rich (gracias !).


The Clojurescript is more interesting.


Starting at the bottom the main function wires up the Closure event system to the shiny green button.

;; Main entry function
(defn ^:export main []
  ;; Wire up the button
  (ev/listen
   (dom/getElement "multiply-button")
   "click"
   multiply-request)) 

Its passed the callback multiply-request. That in turn jiggles the DOM to get the values of the two boxes as Numbers. It then places them into the request map.

(defn multiply-request
  "Send a request for multiplication to the server"
  [e]
  (let [x1 (extract-from-dom "multiplier")
        x2 (extract-from-dom "multiplicand")
        request {"x1" x1 "x2" x2}]
    (xhr/send (multiply-api request) multiply-callback)))

The core of the function is the call to (xhr/send ...). This is the main Google Closure asynchronous call. You pass it a URI and a callback, and it GETs the URI and calls the callback with the response. Notice that when we create the URI we simple pass the request map through pr-str, which is the Clojure printer, and pop it on the end of the URL.

defn- form-uri
  "Note nasty side effect code in here."
  [form]
  (let [uri (goog.Uri. "http://localhost:9090")
         qr (. uri (getQueryData))]
    (.add qr "form" (pr-str form))
    (.setPath uri "multiply")))

(defn- multiply-api [form]
  (. (form-uri form) (toString)))

That's the kicker here. I didn't have to turn it into any other intermediate format such as XML or json. The callback is really just the inverse of this process

(defn- extract-response [message]
  (reader/read-string
   (. message/target (getResponseText))))

The inner function gets the string out of the response and passes it the the ClojureScript reader which turns it into a Clojure object. In this case a map.

Server Side

The server is a stripped down Ring server. There's intentionally not much to say here, beyond pointing out the roundtripping code

 (GET "/multiply" {params :params}
       (pr-str (multiply (read-string (:form params)))))

Conclusion

This is pretty cute. The only problem I have is that encoding keywords raw into the URI query is not working on account of the colons. Hence I had to settle for using strings rather than keywords in the request map. I'm hoping somebody will let me know how to fix that. Chas Emerick has pointed out in the comments (before my host lost them) that I should really use a POST rather than a GET here, and I'll look at that in the future.

Comments (3) Trackbacks (0)
  1. How about server side security? How would you clean up the incoming string to avoid injections using #=( ?


Leave a comment

Trackbacks are disabled.