Lots of folks have been interested in ClojureScript lately, but have had a hard time figuring out what a CLJS app actually looks like. So today I recorded myself building an Overtone controller (that I use on an iPad) using noir, fetch, jayq, and crate. In the end, it looks like this:
Since I don't narrate in the video, I figured I'd give a breakdown of some of the main ideas below. If you want all the gory details though, you can watch the screencast or look at the code. Now to the fun part.
The first step is to generate a new noir project using lein-noir (if you're new to noir, check out noir's website)
Now to set up our project we just need to include our dependencies, which with the wonderful lein-cljsbuild means you do what you always do - add them to your project.clj. ClojureScript dependencies don't really work any differently than Clojure ones do:
Then just modify welcome.clj to get rid of the getting-started content, change /welcome to /, and add a div#piano so we have a container to put our buttons in.
I always end up creating a src/myapp/client/ directory where I keep my CLJS. So if you put the following in a main.cljs and fire up lein-cljsbuild, you'll see a nice little alert box:
Now we're off to the races.
Using crate and jayq
crate is a ClojureScript implementation of the HTML generation library Hiccup, which represents html as Clojure vectors and maps. We use a special macro called (defpartial ..) to create a function that will create dom objects for us.
One thing to note here is that there's a special directive for requiring macros in CLJS. Also, any namespace used by that macro must be required as well, or otherwise that code won't end up in the generated file. Now to do something with it, we'll use jayq.
jayq is a simple ClojureScript jQuery wrapper that I wrote, which makes it easy to do all your standard dom manipulations like you're used to.
It does, however, add some interesting bits. One of which, is that dom elements created with crate can be referenced by the function that was used to create them. This is actually immensely useful, because it basically gives you named controls for free. For example, we end up adding a click handler for all our buttons like so:
Time to make that handler a bit more interesting. We're here to make music afterall.
Interacting with the server - fetch
fetch is the next piece of the puzzle which helps us by removing the barrier between the server and the client. In this case, we're going to use remotes, which are functions defined on the server that are then called by the client. Normally, these would look something like this:
But since we want to call these dynamically based on whatever action our button is created with, we'll need to drop down one level and use (fetch.remotes/remote-callback remote-func params). To do this however, we also need to be able to get a reference to the dom element that was clicked. In jQuery, you usually use this, but in ClojureScript "this" is a symbol just like anything else. For us to get at the js "this", we'll simply use the macro (this-as some-symbol-meaning-this ... )
What that does is extract the action and param attributes from our button and then tells fetch to call the remote function whose name is the value of action with the params we give it. On the Noir side, you then just needs to do two things - add the wrap-remotes middleware in server.clj (make sure you restart the server!):
And define a remote in views/welcome.clj
If this is the first time your server has loaded overtone.live, it may take a few seconds for it to refresh as it has to startup supercollider and a few other things. Also, if this is your first time ever using the sampled piano, it has to download a pretty large set of samples (this can take an hour). Assuming you have both of those though, clicking the button will cause a tone to be played. In the video, this happens at 11:20.
Adding a bit more.
At this point the fundamentals of the app are there, the rest is just icing on the cake. I clean up the code so that it's easy to add a bunch of buttons to a container and create more piano keys for us to click:
Then I grab the code from the dubstep example in the Overtone repository and drop it in, create a couple more remote functions and we then have the ability to fully control our little dubstep machine. The final welcome.clj looks like this:
And here's the final main.cljs:
And there you have it - a complete overtone controller in about 20 minutes.