An Architect's View

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

An Architect's View

Getting Started with ClojureScript and jQuery (and FW/1)

February 11, 2012 ·

About a week ago, I blogged about how to get started with ClojureScript and FW/1 and I showed some basic DOM manipulation functions that go down to the JavaScript metal. In any real world project, you're not going to want to do that - you'll want to leverage a battle-tested, industrial-strength library for that... something like jQuery for example. Using an external JavaScript library with ClojureScript isn't entirely straightforward, unfortunately, mostly due to how names are exported and how the Google Closure compiler munges them in its efforts to produce minified code.

Fortunately, Chris Granger has created a number of projects to make ClojureScript development easier and we're going to look at his jQuery wrapper, jayq, in this post.

As before, I'm going to assume you've already got Leiningen installed, along with the FW/1 template (which we installed in the previous blog post):

lein plugin install fw1-template 0.0.6

If you installed the lein-cljsbuild before, you'll want to upgrade it to the latest version (see the note at the end of this blog post for why!):

lein plugin uninstall lein-cljsbuild 0.0.11
lein plugin install lein-cljsbuild 0.0.12

We'll start by creating a skeleton FW/1 application and then add jQuery to it:

lein new fw1 fw1jq

Drop into the new fw1jq folder, and edit project.clj as before to add the ClojureScript compilation magic:

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

You also need to add a dependency for jayq:

    [jayq "0.1.0-SNAPSHOT"]

For production usage, see the important caveat below about changing :optimizations!

Create the src-cljs folder in the project and an example subfolder:

mkdir -p src-cljs/example

Now we can start up FW/1 in one window:

PORT=8888 lein run

and then start up the ClojureScript compiler in another:

lein cljsbuild auto

Now create src-cljs/example/core.cljs containing this code:

(ns example.core
  (:use [jayq.core :only [$ inner]]))

(defn ^:export play []
  (let [foo ($ :#foo)
        paras ($ :p)]
    (inner foo "Hello")
    (js/alert "ClojureScript says 'Boo!'")
    (doseq [p paras] (inner p "Gone!"))))

When you save it, the ClojureScript compiler will go to work and create main.js containing the compiled (to JavaScript) version. This is very similar to our previous example/core.cljs file but it now uses the jQuery wrapper and functions instead of my custom DOM functions. Note in particular that foo is bound to ($ :#foo) which is the jQuery selector for an ID of "foo" - #foo identifies an ID, .foo identifies a class and plain foo identifies a tag.

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

<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 jQuery and the generated JavaScript file: edit src/fw1jq/layouts/main/default.html and add the following lines between the link and the end of the head section:

<script
  src="http://code.jquery.com/jquery-1.7.1.min.js">
</script>
<script src="/assets/js/main.js"></script>

Visit the FW/1 app in your browser: http://localhost:8888 (or, if you need to reload it, use http://localhost:8888?reload=secret)

Click Surprise me and 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!]

As before, if you edit views, layouts or controller code while Jetty is running, simply click http://localhost:8888?reload=secret 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.

:optimizations - in the previous blog post, I noted that you could change this to :advanced for production usage (and change :pretty-print to false). When you're using external JavaScript libraries, there's an additional step! Because the Google Closure compiler munges names that are not specifically declared for exporting, we need to update our compiler options to include declarations for our library:

        :cljsbuild {
          :source-path "src-cljs"
          :compiler {
            :output-to "src/fw1jq/assets/js/main.js"
            :externs ["externs/jquery.js"]
            :optimizations :advanced
            :pretty-print false}}

The externs/jquery.js file is inside the jayq JAR file and is loaded from your class path automatically. After you've made these changes to your project.clj, quit the ClojureScript compiler process and run:

lein cljsbuild clean
lein cljsbuild auto

That will delete any generated files (including cached versions of the jQuery wrapper etc) and then restart the compiler. It'll take a bit longer to compile your ClojureScript now since it is running optimizations, and the resulting main.js file will be much harder to debug - but it will also be much, much smaller.

Note: it is important to upgrade the lein-cljsbuild plugin to 0.0.12 since that includes the necessary fixes for the externs file to be picked up from the class path - if you try to do this with the 0.0.11 build, you'll have problems (which can be solved by manually extracting the externs folder from the JAR file - but you're better off just upgrading to the new plugin version!)

Tags: clojure · fw1 · jquery

0 responses