An Architect's View

CFML, Clojure, Software Design, Frameworks and more...

An Architect's View

Getting Started with ClojureScript (and FW/1)

February 5, 2012 · 7 Comments

There's been quite a bit of buzz about ClojureScript since it's launch last July but it is a fast-moving target and a but daunting to get started with. Fortunately, there's a simple Leiningen plugin called lein-cljsbuild that can make it pretty simple to get up and running!

I'm going to assume you've already got Leiningen installed so let's get a small FW/1 application up and running and then add some ClojureScript to it.

First off, we'll install the Leiningen template for FW/1 (assumes you have lein-newnew installed):

lein plugin install fw1-template 0.0.6

Now let's create a skeleton FW/1 application:

lein new fw1 fw1cljs

If we drop into our new fw1cljs folder, we can fire up our new application:

cd fw1cljs
PORT=8888 lein run

Once you see Jetty started on port 8888, you can visit the application: http://localhost:8888

You should see a bare bones "Welcome to FW/1" page - success!

Now we're going to add ClojureScript to our project. Kill the Jetty process (press control-C in that terminal window to terminate lein run) then edit your project.clj file to add the following lines (between the :dependencies block and the :main):

        :cljsbuild {
          :source-path "src-cljs"
          :compiler {
            :output-to "src/fw1cljs/assets/js/main.js"
            :optimizations :whitespace
            :pretty-print true}}

This setup is fine for development. When you're ready for production, change the :optimizations to :advanced and :pretty-print to false. That will create a heavily optimized and minified JavaScript file.

Install the cljsbuild plugin:

lein plugin install lein-cljsbuild 0.0.11

That will download a lot of stuff including ClojureScript itself and the Google Closure "compiler". Now we can start the compiler-watcher (in the fw1cljs folder):

lein cljsbuild auto

Leave that running. In another terminal window, we need to create the src-cljs folder in the project and an example subfolder:

mkdir -p src-cljs/example

Now we'll create src-cljs/example/util.cljs containing this code:

(ns example.util)

(defn length [nodes] (. nodes -length))
(defn item [nodes n] (.item nodes n))
(defn as-seq [nodes]
  (for [i (range (length nodes))] (item nodes i)))
(defn by-id [id]
  (.getElementById js/document (name id)))
(defn by-tag [tag]
  (as-seq
    (.getElementsByTagName js/document (name tag))))
(defn html [dom] (. dom -innerHTML))
(defn set-html! [dom content]
  (set! (. dom -innerHTML) content))

When you save that file, you'll see the ClojureScript compiler spring into life and compile it to src/fw1cljs/assets/js/main.js. I'll walk thru this code in a minute but for now we'll create src-cljs/example/core.cljs containing this code:

(ns example.core
  (:use [example.util :only [by-id by-tag set-html!]]))

(defn ^:export play []
  (let [foo (by-id :foo)
        paras (by-tag :p)]
    (set-html! foo "Hello")
    (js/alert "ClojureScript says 'Boo!'")
    (doseq [p paras] (set-html! p "Gone!"))))

Again, the ClojureScript compiler will go to work and recreate main.js containing the compiled (to JavaScript) version of both .cljs files together.

Let's update the default view in our FW/1 application to call this JavaScript. Edit src/fw1cljs/views/main/default.html. We'll add these two lines to the bottom of the file:

<p><a href=""
  onclick="example.core.play(); return false;">
  Surprise me</a>!</p>
<p id="foo"></p>

We've added an empty paragraph tag with an ID of "foo" and we've added a clickable link that calls example.core.play(), the function we defined above. Now let's include the generated JavaScript file: edit src/fw1cljs/layouts/main/default.html and add the following line between the link and the end of the head section:

<script src="/assets/js/main.js"></script>

Let's start the FW/1 application again:

PORT=8888 lein run

Now you'll see the new text Surprise me! so go ahead and click it... You should see "Hello" appear below that paragraph and then a JavaScript alert containing "ClojureScript says 'Boo!'". Click OK and you should see all four paragraphs replaced with "Gone!". Success! [Setting the foo element to Hello doesn't seem to work on Safari for me but it works in Firefox and Chrome - go figure!]

If you edit views, layouts or controller code while Jetty is running, simply add ?reload=secret to the URL to force FW/1 to reload the changes. If you edit the ClojureScript files, the compiler will automatically regenerate the JavaScript so just reload the page in the browser to load the new source. Enjoy!

So what does all that ClojureScript code actually do?

(ns example.core
  (:use [example.util :only [by-id by-tag set-html!]]))

A namespace declaration that pulls in three specific functions from the utility namespace (the first .cljs file we created).

(defn ^:export play []

Define a function, play that takes no arguments. We declare it ^:export so that the Google Closure compiler won't munge the name in :optimizations :advanced mode (which minifies the JavaScript).

  (let [foo (by-id :foo)
        paras (by-tag :p)]

These give us handles on some DOM elements: foo refers to the element with the ID of "foo" (the paragraph we added) and paras refers to all the p tag elements.

    (set-html! foo "Hello")

This sets the inner HTML of the foo element.

    (js/alert "ClojureScript says 'Boo!'")

This runs the JavaScript function alert

    (doseq [p paras] (set-html! p "Gone!"))))

This loops over all the elements in paras and sets the inner HTML of each one.

Given that information, I'll leave comprehension of example/util.cljs as an exercise for the reader. If you've seen Java interop in Clojure, you'll recognize the notation for JavaScript interop in ClojureScript (forms that start with a dot).

Tags: clojure · fw1

7 responses so far ↓

  • 1 Base // Feb 6, 2012 at 10:14 AM

    Sean -

    Thanks for this. This is a fantastic skeleton to build upon.

    Most appreciated!

    -Base
  • 2 Base // Feb 6, 2012 at 5:32 PM

    Hi Sean -

    One thing that might be useful is to help understand how additional CLJS libraries fit into this equation, just as Domina or Apogee. I am not sure how to incorporate those into this framework.

    Thanks

    Base
  • 3 Sean Corfield // Feb 7, 2012 at 10:15 AM

    @Base, it's certainly not straightforward (yet) but things are improving. Last night I rebuilt my example with Chris Granger's jayq wrapper for jQuery (replacing my example.util namespace of DOM functions). I'll run thru reproducing it a few times - it was fussy to get working - and then I'll blog that too.
  • 4 Base // Feb 7, 2012 at 10:21 AM

    Thanks Sean. I saw your comment on the Clojure Google Group as well. I really appreciate your help on this. I have been so excited to give ClojureScript a whirl, but every time I try to get started I get confused by what (at last I thought) were pretty basic obstacles and got so frustrated that I gave up.

    Knowing that this is flummoxing people way above my pay-grade makes me feel a little better!
  • 5 Sean Corfield // Feb 11, 2012 at 11:57 AM

    I just added that new blog post about using jayq / jQuery with ClojureScript.
  • 6 David Golliath // Apr 16, 2012 at 6:59 PM

    I'm curious, where is the js namespace defined?
  • 7 Sean Corfield // Apr 17, 2012 at 11:42 PM

    @David, the js namespace is built into ClojureScript and is how you access global symbols in JavaScript from ClojureScript.

Leave a Comment

Leave this field empty