4. ClojureScript Interop

Freebie

Published 20 May 16

In this episode we’ll go over all the tools ClojureScript puts at your disposal to interoperate with JavaScript. You’ll learn how to call JavaScript libraries, how to work with JavaScript datatypes, how to convert data back and forth, and how to use “this”.

Klipse is a “fiddle” for ClojureScript, that shows you how your code compiles to JavaScript. If any of this interop stuff is bugging you, try it out there to see if it actually does what you think it does.

This ClojureScript Cheatsheet lists mosts of the functions in this episode. You can keep it handy as a reference.

Tagged literals

I mention in the episode that “Date” objects in ClojureScript are simply instance of JavaScript’s Date type, yet they look a bit funny in the output.

#inst "2016-05-20T15:58:57.135-00:00"

This representation has been standardized in EDN, which forms the foundation for Clojure’s parser. This means a date represented this way can be understood by Clojure, ClojureScript, or any system using EDN as an input format. In each case it will be converted to whatever “native” date type is available on the platform. In ClojureScript that’s a JavaScript Date, in Clojure a java.util.Date.

Performance

Be careful using js->clj on large data structures, such as a JSON response coming from an API. It does not perform well for such cases. If possible use an alternative format like Transit, or work with the JSON data directly to extract the bits you need.

aget / aset

The aget and aset functions are really intended for use with arrays, hence their name. They also exist in Clojure, where they will only work with arrays. The fact that you can also use them with JavaScript objects in ClojureScript is really an implementation detail, a result of the fact that JavaScript doesn’t do type checking, and that it uses the same syntax for array indices and object properties.

If you do need to access object properties dynamically, then all ClojureScript core provides are aget and aset, so what other option do we have? If you look a bit further you’ll find that ClojureScript comes bundled with the Google Closure Library, which is a bit like the missing “standard library” for JavaScript. The goog.object namespace has a few functions specifically for dealing with object properties.

There’s goog.object/get and goog.object/set, corresponding with aset and aget, but also goog.object/add, which is like a safer version of set, in that it will only set a property if it’s not present yet, or otherwise throw an exception.

(require '[goog.object :as gobj])

(def my-obj (js-obj))
(gobj/set my-obj "a-key" 42)
my-obj ;;=> #js {:a-key 42}
(gobj/get my-obj "a-key") ;;=> 42
(gobj/add my-obj "a-key" 43) ;;=> Error: The object already contains the key "a-key"

ClojureScript doesn’t try to hide the JavaScript underneath, instead it uses the host platform to its advantage. That means you still have JavaScript’s existing functionality, and its pre-existing libraries, at your fingertips.

If you’re already profecient in JavaScript, then you should learn early on how to map JavaScript code to the equivalent ClojureScript, so you can transfer your existing knowledge. That’s what we’ll do in this episode.

Essentials

Method calls

Let’s start simple with some strings. ClojureScript strings are just JavaScript strings. This means they have methods such as toUpperCase, and properties such as length. To call a method on an object, put a dot before the method name. In ClojureScript the function always comes first, and the same is true for methods.

(def s "The octopus concoted colorized octane.")

(type s)
;;=> #object[String "function String() {
;;       [native code]
;;   }"]

(.toUpperCase s)

Property access

To retreive a property, add a dash after the dot. Otherwise ClojureScript will assume the property is bound to a function, and try to call it.

(.-length s)

double-dot

To chain multiple method calls or property lookups, you can use the double-dot operator. It’s a bit like the thread-first macro, except that now you can omit the dot before each method or property name.

Suppose we have this line of JavaScript.

// js
s.charCodeAt(7).toString(16).toUpperCase()

Normally we would have to turn that inside out and write something like this.

(.toUpperCase (.toString (.charCodeAt s 7) 16))

But with this dot-dot form it’s much closer to the original. Notice that the dots before each method are gone now.

(.. s (charCodeAt 7) (toString 16) (toUpperCase))

In fact, since this last method doesn’t take any arguments, we can omit those parentheses as well.

(.. s (charCodeAt 7) (toString 16) toUpperCase)

The same works with property access. The dots are gone, the dashes remain. Notice that here I needed to access the global document variable. ClojureScript provides the special “js” namespace, which contains all JS globals.

;; document.body.lastChild.innerHTML.length

(.. js/document -body -lastChild -innerHTML -length)

Getting/Setting/Deleting properties

To do a JavaScript assignment, you use the set! function. We can use this to create a new global variable.

(set! js/newVar 3)
js/newVar ;;=> 3

More commonly you’ll use it to assign properties. For that take the expression that would look up the property you want to assign, and use that in set!.

(set! (.. js/document (getElementById "app") -innerHTML) "Hello, world!")
(set! (.-innerHTML (.getElementById js/document "app")) "Hello, world!!!")

This previous code will translate directly into a JavaScript assignment of the property using dot notation, but objects and arrays can also be accessed with square brackets.

// js
var arr = [1, 2, 3]
var obj = {x: 1, y: 2}

arr[1]
obj["z"] = 3

To achieve this we have two sibling functions, aget and aset. Here’s the equivalent ClojureScript. Two more functions enter the stage here, one create JavaScript array, the other one constructs objects. We’ll see more ways to create these JavaScript collections in a bit.

(def arr (array 1 2 3))
(def obj (js-obj "x" 1 "y" 2))

arr ;;=> #js [1 2 3]
obj ;;=> #js {:x 1, :y 2}

(aget arr 1)     ;;=> 2
(aset obj "z" 3) ;;=> 3

obj ;;=> #js {:x 1, :y 2, :z 3}

The only thing we can’t do yet with object properties is delete them. Javascript has the delete keyword, ClojureScript comes with the js-delete function.

(def obj (js-obj "x" 1 "y" 2 "z" 3))

(js-delete obj "x") ;;=> true
obj ;;=> #js {:y 2, :z 3}

JS globals

When using this js namespace, you can chain property access directly onto it, the way you would do in JavaScript.

js/document.body.lastChild.innerHTML.length

If the resulting value is a function, we can call it just like a ClojureScript function, by wrapping it in parens.

(js/document.body.lastChild.innerHTML.charAt 7)

(js/encodeURI "this is a test")
;;=> "this%20is%20a%20test"

Constructor calls

You can also invoke a function as a constructor, in JavaScript you do that with the “new” keyword. The ClojureScript equivalent is putting a dot after the function name. We can demonstrate this with the built-in Date function. When called as a regular function, it returns a formatted string, when called as a constructor, it returns a Date object.

(js/Date "2016-05-19") ;;=> "Thu May 19 2016 21:18:44 GMT+0200 (CEST)"
(js/Date. "2016-05-19") ;;=> #inst "2016-05-19T00:00:00.000-00:00"
(js/Date.) ;;=> #inst "2016-05-19T19:18:56.430-00:00"

Working with Arrays and Objects

So you’re off to a good start. You know how to call JavaScript functions, you also know that ClojureScript functions are regular JavaScript functions, so you grab your favorite library, pass it an object with some configuration and a callback, and… nothing happens.

(js/$.ajax {:url "/"
            :success (fn [e] (js/console.log "--> " e))})

If you’ve been paying attention you might already realize what’s going on. We should have used js-obj, since a JavaScript object is what jQuery expects. There’s another gotcha here, when ClojureScript turns those keywords into strings, it keeps the initial colon, which isn’t quite what we wanted. When we use strings, then it works.

(js-obj :url "/"
        :success (fn [e] (js/console.log "--> " e)))
;;=> #js {::url "/", ::success #object[Function "function (e){
;;                             return console.log("--> ",e);
;;                           }"]}


(js/$.ajax (js-obj "url" "/"
                   "success" (fn [e] (js/console.log "--> " e))))


So how come this doesn’t work with a ClojureScript map?

The thing is that even though they look similar, they are actually very different things. ClojureScript piggiebacks on JavaScript and uses its strings, numbers, regular expressions, and functions as-is, but vectors and maps are not the same as, or compatible with, arrays and objects.

This is not an oversight. ClojureScript offers immutable, persistent data structures. These are state of the art functional data types, which are much more suitable for the the style of coding that is typical in ClojureScript.

So we could use the js-obj function, but there’s an even simpler solution. Put #js before the map, and ClojureScript reads it as a JavaScript object. It works with arrays as well.

(js/$.ajax #js {:url "/"
                 :success (fn [e] (js/console.log "--> " e))})

#js [1 2 3]
#js {:map {:ima "map"}
     :object #js {:ima "js-obj"}}

Note that #js only affects the outer level, so any nested maps or vectors will not be converted. To converted nested structures use clj->js. You can convert back as well with js->clj.

In JavaScript objects the keys are always strings, so when converting back they’ll still be strings, but js->clj will convert to keywords if you ask it to.

(def animals {:land ["foxes" "rabbits"]
              :sea ["turtles" "octopi"]})

(clj->js animals)
;;=> #js {:land #js ["foxes" "rabbits"], :sea #js ["turtles" "octopi"]}

(js->clj (clj->js animals))
;;=> {"land" ["foxes" "rabbits"], "sea" ["turtles" "octopi"]}

(js->clj (clj->js animals) :keywordize-keys true)
;;=> {:land ["foxes" "rabbits"], :sea ["turtles" "octopi"]}

Capturing this

When using callbacks or event handlers, you regularly need to reference the current object scope using this. This is not a regular variable, it’s a JavaScript keyword that ClojureScript has no use for. For the cases where you do need it there’s the this-as macro, which lets you temporarily bind it to a certain name.

In this example I’m using the DOM API to add a button to the page. Each time you click the button, the number on the button increases. While the handler executes, this is bound to the DOM node that represents the button, so we can check what number it has, and update it.

(defn click-handler []
  (this-as b
    (set! (.-innerHTML b) (inc (long (.-innerHTML b))))))

(let [button (.createElement js/document "button")]
  (set! (.-innerHTML button) "0")
  (set! (.-onclick button) click-handler)
  (.. js/document -body (appendChild button)))

Conclusion

This episode has been a bit encyclopedic. Thank you for sticking with me until the end. I prefer to show you how to actually build stuff, using close to real world examples, but I wanted to get this out of the way first, so when I use interop forms in the coming episodes it doesn’t come as a surprise.

I also find that sometimes people come from JavaScript, but only learn this interop stuff quite late, or only bit by bit, making them feel restricted. If this is you I hope this episode has been empowering.

There’s a handy tool for trying out ClojureScript in your browser called Klipse. The great thing about it is it also shows you how your code compiles to JavaScript. I recommend playing around with that, I’ll add a link in the show notes.

In the next ClojureScript episode I’ll be using this interop stuff to actually build something fun and interesting. In the meanwhile if you have any feedback on this episode please send it to arne@lambdaisland.com.