ClojureScript: JavaScript Interop


(this post was updated on 15th of March 2015)

As I mentioned before on this blog, I’m in the ongoing process of learning Clojure (and ClojureScript). To better understand the language, I’ve written small web application. For fun I decided that all my front end code will be written in ClojureScript. Because I needed to use external JavaScript API (Bing Maps AJAX Control) I wrote quite a bit of JavaScript interop code - the syntax wasn’t obvious for me and I couldn’t find a place that have all that info, so I wrote this post. Please be warn, this is quite a long post!

JavaScript example

To easier understand all examples lets define simple JavaScript code:

//global variable
globalName = "JavaScript Interop";
globalArray = globalArray = [1, 2, false, ["a", "b", "c"]];
globalObject = {
  a: 1,
  b: 2,
  c: [10, 11, 12],
  d: "some text"
};

//global function
window.hello = function() {
  alert("hello!");
}

//global function
window.helloAgain = function(name) {
  alert(name);
}

//a JS type
MyType = function() {
  this.name = "MyType";
}

MyComplexType = function(name) {
  this.name = name;
}

MyComplexType.prototype.hello = function() {
  alert(this.name);
}

MyComplexType.prototype.helloFrom = function(userName) {
  alert("Hello from " + userName);
}

Global scope

ClojureScript defines special js namespace to allow accessing JavaScript types/functions/methods/objects defined in global scope (i.e. window object for browser).

(def text js/globalName) ;; JS output: namespace.text = globalName;

Creating objects

We can create JavaScript objects from ClojureScript by adding . to the end of constructor function:

(def t1 (js/MyType.)) ;; JS output: namespace.t1 = new MyType;

(note: at first I thought that this generated JS code was wrong, because of the lack of parentheses, but as it clarifies it’s valid - if constructor function doesn’t have arguments, then parentheses can be skipped)

with arguments:

(def t2 (js/MyComplexType. "Bob")) ;; JS output: namespace.t2 = new MyComplexType("Bob");

There is also a different way of creating objects, by using the new function (the name of JS constructor function should be without period):

(def my-type (new js/MyComplexType "Bob")) ;; JS output: namespace.my_type = new MyComplexType("Bob");

Invoking methods

To invoke a JavaScript method we need to prefix the name of the method with the . (dot):

(.hello js/window) ;; JS output: window.hello();

which is a syntactic sugar of:

(. js/window (hello))

To pass arguments to the function we do:

(.helloAgain js/window "John") ;; JS output: window.helloAgain("John");

or

(. js/window (helloAgain "John"))

Same thing can be done with created object:

(def my-type (js/MyComplexType. "Bob")) ;; JS output: namespace.my_type = new MyComplexType("Bob");
(.hello my-type)                        ;; JS output: namespace.my_type.hello();

Accessing properties

ClojureScript provides a few ways of working with JavaScript properties. The simplest one is to use .- property access syntax:

(def my-type (js/MyType.))  ;; JS output: namespace.my_type = new MyType;
(def name (.-name my-type)) ;; JS output: namespace.name = namespace.my_type.name;

similar thing can be achieved by aget function, which takes object and the name of the property (as a string) as arguments:

(def name (aget my-type "name")) ;; JS output: namespace.name = namespace.my_type["name"];

The aget allows also accessing nested properties:

(aget js/object "prop1" "prop2" "prop3") ;; JS output: object["prop1"]["prop2"]["prop3"];

the same thing (generated code is different) can be done by using .. syntax:

(.. js/object -prop1 -prop2 -prop3) ;; JS output: object.prop1.prop2.prop3;

You can also set a value of a property from the ClojureScript, to do this you can use aset or set! functions:

The aset function takes name of the property as a string:

(def my-type (js/MyType.))
(aset my-type "name" "Bob") ;; JS output: namespace.my_type["name"] = "Bob";

and the set! takes a property access:

(set! (.-name my-type) "Andy") ;; JS output: namespace.my_type.name = "Andy";

Arrays

The aget function can be also used for accessing JavaScript array element:

(aget js/globalArray 1) ;; JS output: globalArray[1];

or if you want to get nested element you can use it in this way:

(aget js/globalArray 3 1) ;; JS output: globalArray[3][1];

Nested scopes

This subject was a bit confusing for me. In my project I wanted to translate such a code:

var map = new Microsoft.Maps.Map();

to ClojureScript. As you can see the Map function is in nested scope. The idiomatic way of accessing nested properties is to use .. or aget functions but this can’t be done for constructor function. In such case, we need to use the dot notation (even it’s not idiomatic for Clojure code):

(def m2 (js/Microsoft.Maps.Themes.BingTheme.))

or with the new function:

(def m1 (new js/Microsoft.Maps.Themes.BingTheme))

If we write this expression like this:

(def m3 (new (.. js/Microsoft -Maps -Themes -BingTheme)))

we will get an exception:

First arg to new must be a symbol at line
                core.clj:4403 clojure.core/ex-info
             analyzer.clj:268 cljs.analyzer/error
             analyzer.clj:265 cljs.analyzer/error
             analyzer.clj:908 cljs.analyzer/eval1316[fn]
             MultiFn.java:241 clojure.lang.MultiFn.invoke
            analyzer.clj:1444 cljs.analyzer/analyze-seq
            analyzer.clj:1532 cljs.analyzer/analyze[fn]
            analyzer.clj:1525 cljs.analyzer/analyze
             analyzer.clj:609 cljs.analyzer/eval1188[fn]
             analyzer.clj:608 cljs.analyzer/eval1188[fn]
             MultiFn.java:241 clojure.lang.MultiFn.invoke
            analyzer.clj:1444 cljs.analyzer/analyze-seq
            analyzer.clj:1532 cljs.analyzer/analyze[fn]
            analyzer.clj:1525 cljs.analyzer/analyze
            analyzer.clj:1520 cljs.analyzer/analyze
             compiler.clj:908 cljs.compiler/compile-file*
            compiler.clj:1022 cljs.compiler/compile-file

Creating JavaScript objects

There are many cases where we need to pass JavaScript object to a method from ClojureScript. In general ClojureScript works with its own data structures (immutable, persistent vector, map, set etc.) that can be converted to plain JS objects. There are several ways of doing it.

If we want to create a simple JavaScript object from the list of key value pairs we can use js-obj macro:

(def my-object (js-obj "a" 1 "b" true "c" nil)) ;; JS output: namespace.my_object_4 = (function (){var obj6284 = {"a":(1),"b":true,"c":null};return obj6284;

Note that js-obj forces you to use strings as keys and basic data literals (string, number, boolean) as values. The ClojureScript data structures won’t be changed, so this:

(def js-object (js-obj  :a 1 :b [1 2 3] :c #{"d" true :e nil}))

will create such JavaScript object:

{
  ":c" cljs.core.PersistentHashSet, 
  ":b" cljs.core.PersistentVector, 
  ":a" 1
}

as you can see there are used internal types such as:
cljs.core.PersistentHashSet
cljs.core.PersistentVector
and the ClojureScript keyword was changed to string prefixed with colon.

To solve this problem we can use clj->js function that:
“Recursively transforms ClojureScript values to JavaScript.
sets/vectors/lists become Arrays, Keywords and Symbol become Strings,
Maps become Objects.”

(def js-object (clj->js  :a 1 :b [1 2 3] :c #{"d" true :e nil}))

will produce such object:

{
  "a": 1,
  "b": [1, 2, 3],
  "c": [null, "d", "e", true]
}

There is also one more way of producing JavaScript objects - we can use #js reader literal:

(def js-object #js {:a 1 :b 2})

which generates code:

namespace.core.js_object = {"b": (2), "a": (1)};

When working with #js you need to be cautious, because this literal also won’t transform inner structures (it’s shallow):

(def js-object #js {:a 1 :b [1 2 3] :c {"d" true :e nil}})

will create such object:

{
  "c": cljs.core.PersistentArrayMap, 
  "b": cljs.core.PersistentVector, 
  "a": 1
}

to solve this you need to add #js before every ClojureScript structure:

(def js-object #js {:a 1 :b #js [1 2 3] :c #js ["d" true :e nil]})

JavaScript object:

{
  "c": {
    "e": null,
    "d": true
  },
  "b": [1, 2, 3 ],
  "a": 1
}

Using JavaScript objects

There are situations when we need to convert JavaScript object or array into ClojureScript data structure. We can do this by using js->clj function that:
"Recursively transforms JavaScript arrays into ClojureScript vectors, and JavaScript objects into ClojureScript maps. With
option ‘:keywordize-keys true’ will convert object fields from
strings to keywords.

(def my-array (js->clj (.-globalArray js/window)))
(def first-item (get my-array 0)) ;; 1

(def my-obj (js->clj (.-globalObject js/window)))
(def a (get my-obj "a")) ;; 1

as the function doc states we can use :keywordize-keys true to convert string keys of created map to keywords:

(def my-obj-2 (js->clj (.-globalObject js/window) :keywordize-keys true))
(def a-2 (:a my-obj-2)) ;; 1

Addition

If all other methods of working with JavaScript failed, there is a js* that takes a string as an argument and emits it as a JavaScript code:

(js* "alert('my special JS code')") ;; JS output: alert('my special JS code');

Exposing ClojureScript functions

It is worth noting that the exact form of JavaScript code generated from ClojureScript depends on compiler settings. Those settings can be defined in Leiningen project.clj file:

Part of project.clj file:

:cljsbuild {
    :builds [{:id "dev"
              :source-paths ["src"]
              :compiler {
                :main your-namespace.core
                :output-to "out/your-namespace.js"
                :output-dir "out"
                :optimizations :none
                :cache-analysis true
                :source-map true}}
             {:id "release"
              :source-paths ["src"]
              :compiler {
                :main blog-sc-testing.core
                :output-to "out-adv/your-namespace.min.js"
                :output-dir "out-adv"
                :optimizations :advanced
                :pretty-print false}}]}

As you can see above, there are two defined builds: dev and release. Please note the :optimizations parameter - for :advanced value the code will be truncated (not used code is removed) and renamed (shorter names are used).

For example, this ClojureScript code:

(defn add-numbers [a b]
  (+ a b))

will be compiled to such JavaScript code in :advanced mode:

function yg(a,b){return a+b}

The function name is completely “random”, so you can’t use it from JavaScript file. To be able to use defined in ClojureScript functions (with their original names) you should mark them with the :export metadata:

(defn ^:export add-numbers [a b]
  (+ a b))

The :export keyword tells compiler to export given function name to the outside world. (This is done by exportSymbol function from Google Closure Compiler - but I won’t go into the details). Then in your external JavaScript code you can invoke this function:

your_namespace.core.add_numbers(1,2);

Please, notice that all dashes were replaced by underscors.

Using external JavaScript libraries

The :advanced mode affects also invocation of the external libreries, because all functions/methods names are changed to minimal form. Lets take a ClojureScript code, that invokes PolarArea function from the Chart object:

(defn ^:export creat-chart []
  (let [ch (js/Chart.)]
    (. ch (PolarArea []))))

after compilation this code will look similar to this one:

function(){return(new Chart).Bc(zc)}

As you can see, the PolarArea method was changed to Bc name, which of course will cause runtime error. To prevent this, we need to tell compiler which names shouldn’t be changed. Those names should be defined in external JavaScript file (i.e. externs.js) and provided to the compiler. For our example the externs.js file should look like this one:

var Chart = {};
Chart.PolarArea = function() {};

The compiler should be informed about this file by :externs setting in project.clj file:

{:id "release"
              :source-paths ["src"]
              :compiler {
                         :main blog-sc-testing.core
                         :output-to "out-adv/your-namespace.min.js"
                         :output-dir "out-adv"
                         :optimizations :advanced
                         :externs ["externs.js"]
                         :pretty-print false}}

If we do all those things, created JavaScript code will contain correct invocation of PolarArea function:

function(){return(new Chart).PolarArea(Ec)}

To get more details about using external JavaScript libraries in ClojureScript I recommend you to read excellent blog post by Luke VanderHart about this.

As usual I’m appreciated for any comments.


You may also like

Lesser known Clojure: variants of threading macro

Template method pattern

Defining Node.js task for Heroku Scheduler