Marrying ClojureScript and JS Frameworks – Knockout Edition

A while ago I began to play with ClojureScript and tried to get it to work with popular frameworks. I played with a few of them, most recently with Knockout.js. This post sums up those efforts and my not-so-optimistic view on ClojureScript.

  • I tried “bare” jQuery. It was pretty smooth.
  • I tried Backbone.js. I got it to work on a simple example, though one reader on Twitter rightfully commented that ClojureScript was hideous. Yes, that Backbone example is hideous. Later on I tried to do something less trivial. Eventually I fled in horror, thanks to impedance mismatch between heavily OO Backbone and non-OO ClojureScript sauced with my ignorance in CLJS (and Backbone).
  • I also gave Angular.js a shot. It started really smooth, because Angular proudly states that it doesn’t rely on object-oriented programming so much. It was great. Right to the moment when I started arguing with the compiler renaming my variables, soon followed by discovery that Angular and Closure are no go.

So, the time has come to another experiment – this time Knockout.js. I followed the official tutorial and here is what I eventually came up with.

The Page

The complete page in Hiccup looks like this. Nothing particularly exciting here.

(defn render-body []
  (hp/html5
     [:head]
     [:body
 
      [:p "First name: " [:strong {:data-bind "text: firstName"} "todo"]]
      [:p "Last name: " [:strong {:data-bind "text: lastName"} "todo"]]
      [:p "Full name: " [:strong {:data-bind "text: fullName"} "todo"]]
      
      [:p "First name: " [:input {:data-bind "value: firstName"}]]
      [:p "Last name: " [:input {:data-bind "value: lastName"}]]
      
      [:button {:data-bind "click: capitalizeLastName"} "Go caps"]
   
      (hp/include-js 
        "//ajax.aspnetcdn.com/ajax/knockout/knockout-2.1.0.js"
        "js/cljs.js")
      (hp/include-css "css/todo.css")
      ]))

ClojureScript

The most interesting part is the ClojureScript code. Here’s one way to do it:

(ns hello-clojurescript)

(defn app-view-model []
  (this-as this
           (set! (.-firstName this) (.observable js/ko "Bert"))
           (set! (.-lastName this) (.observable js/ko "Bertington"))
           (set! 
             (.-fullName this)
             (.computed js/ko 
               (fn []
                 (str (.firstName this) " " (.lastName this))) this))
           (set!
             (.-capitalizeLastName this) 
             (fn []
               (.lastName this (-> this .lastName .toUpperCase)))))
  nil
  )

(.applyBindings js/ko (app-view-model.))

Yes, I do need to explicitly return “nil” there. Otherwise it returns this.fullName = ..., and that breaks KO.

It works, but it’s hard to defend it in comparison to the JS equivalent:

function AppViewModel() {
    this.firstName = ko.observable("Bert");
    this.lastName = ko.observable("Bertington");
    this.fullName = ko.computed(function() {
        return this.firstName() + " " + this.lastName();    
    }, this);
    this.capitalizeLastName = function() {
        var currentVal = this.lastName();     
        this.lastName(currentVal.toUpperCase());
    };
    
}

ko.applyBindings(new AppViewModel());

Complete code can be found at my GitHub repository.

Better Way – Macro

This code can be made a lot better with a custom macro, as the one presented at StackOverflow:

(defvar name_model
    first_name (observable "My")
    last_name (observable "Name")
    name (computed (fn [] (str (. this first_name) " " (. this last_name)))))

(. js/ko (applyBindings name_model));

Now, that would be something!

… except for that defining macros in ClojureScript is harder than in plain Clojure, to the point that I haven’t gotten it to work yet.

Conclusions on ClojureScript

I spent quite a few hours poking at ClojureScript, and I have mixed feelings.

  1. It’s pretty hard to get ClojureScript to work with existing JS frameworks, mostly because using objects in CLJS requires so much ceremony.
  2. Contrary to plain Clojure, “fun” and “productive” aren’t the words that come to mind when I think of my adventures in CLJS. “Frustrating” and “intimidating” are much more appropriate. I’m constantly arguing with the compiler and trying to beat it to do the right thing, not having fun solving problems.
  3. Some stuff can be covered with macros, but all in all it feels very… rigid and constraining. I feel like every once in a while I’m bound to hit another rough corner, spend too much time on it, write another macro, and so on. All that only to bridge the gaps and make ClojureScript look more like… JavaScript. In fact, that feels like writing my own layer of macros to compile JS-like-DSL to ClojureScript.
  4. Perhaps there is a better way and I’m just doing something wrong. Maybe you’re not supposed to use those frameworks at all, but roll your own or use those few written in ClojureScript?
  5. Perhaps all of this makes little sense on such a small scale, and you need something really big to appreciate ClojureScript. You just need to invest many hours in passing the learning curve and macroing your way out. Maybe then it becomes more productive, modular and whatnot. I don’t know, in fact I have too little experience in JavaScript itself to answer such questions. I’m not very optimistic about it, though.

One thought on “Marrying ClojureScript and JS Frameworks – Knockout Edition

  1. Probably most AngularJS users are using Closure compiler with AngularJS (it came out of Google after all). You just need to use the array syntax for dependency injection: [‘$http’, function($http){…}]

Leave a Reply

Your email address will not be published. Required fields are marked *

Spam protection by WP Captcha-Free