Getting started with compojure

The goal of this tutorial is a simple tasklist written with clojure and compojure
I will be using Lein, so if you dont have that. Head over to: http://github.com/technomancy/leiningen and follow the instructions.
With that installed, lets start with:

lein new SimpleTasks

This will create a new project for us.
Lets begin by adding some dependencies that we will need. Open SimpleTasks/project.clj and add the following dependencies:

Save the file and invoke “lein deps”. This will download all the dependencies we need.
If you dont want to use lein, enclojure in netbeans for example works great. All you need to do then is to go to clojars.org and find the dependencies listed above and add them to your project.
The
:dev-dependencies [[swank-clojure “1.2.1”]]
is to be able to start a swank server and connect emacs to it. If you wont be using that, you can leave it out.

Now we can start coding!

Open up src/SimpleTasks/core.clj in your favorite editor. I use emacs with clojure mode and slime. But anything works.

you will see:

(ns SimpleTasks.core)

Now, lets start by getting a webpage running!

This is the basic setup of a simple compojure webbapp.
If you run this and point your browser to localhost:8080 you will see “Hello!”
If you dont know how to run it. the simplest way is to use

“lein repl” wait for the repl to load, and then:
clojure.core=> (require ’SimpleTasks.core)

Now what does al this code do?

(defonce server (run-jetty #’myroutes
{:join? false
:port 8080}))

Starts an instance of jetty at port 8080
join? false, makes the repl return instantly instead of blocking aslong as jetty is running.
#’myroutes tells compojure what function it should called when the server recieves a request.

(defroutes myroutes
(GET “/” [] (display)))

Here we define our routes, as you can see this is the method called by jetty.
It simply forwards any call to “/” ie “localhost:8080/” to the function display

(defn display []
(html [:h1 “hello!”]))

Finally we end up in display. Al it does is render html page with “hello!” in a <h1> tag

Now for a great thing about clojure and compojure.
edit the display funktion so it renders something else. ex:
(defn display []
(html [:h1 “hello world!”]))

To make compojure display this instead, al you need to do is to evalute that function, and reload your browser.

Ok, lets start with the tasklist. The first thing we want is a form to add tasks:

We start by adding another function to the routes:

And this is the render-form function:

Note: you also have to add: hiccup.form-helpers to the :use list in the ns declaration

Now, evalute the defroutes and the render-form functions and point your browser to http://localhost:8080/form
Hint: if you are using the repl you can use: (require ’SimpleTasks.core :reload) to evaluate it again.

your core.clj should now look like: http://gist.github.com/555496

Try to type something in the box and submit the form. You will get a “Not found” error in your browser.
This is because al our routes ar “GET” and the form use “POST

So we need to add another route:

(POST “/form” {params :params} (handle-form (params “task”)))

Note: if you want a route to take all forms of requests, you can use (ANY “/” (myfunction))
The {params :params} gives us a map of the parameters from the form. Since we only care about the “task” parameter. we pick that and send it to handle-form.

Lets check so the form works and just print the task:

(defn handle-form [task]
(html [:h2 task]))

Load http://localhost:8080/form and enter something and submit. you should see the task you just entered.

In a normal tasklist, you would probably store the tasks in a database or in textfiles. To make it simple, and to learn more of compojure/ring/hiccup. we will use the session.

We begin by taking care of the result from the post:

(defn handle-form [task]
{:body (str “stored” task)
:session {:tasks task}})

Instead of using the (html) function from earlier, we specify the body and session. After this, the task will be stored under the key :tasks in the users session.

To make ring/compojure understand and store data in the session, we also need:

(wrap! myroutes :session)

We also want a way to see what is in the session, this is pretty easy. we start by adding a new route:

(GET “/view” {session :session} (view session))

A simple GET route that in the same way we saw with the parameters from the form, take the session and forward it to a function.

The function that is called:

(defn view [session]
(html [:h1 (str "tasks: " (:tasks session))]))

My full core.clj at this point:
http://gist.github.com/557879

Just beeing able to store one task is not going to work very long. Time to expand our tasks and store it in a vector.
This means that we want to append more tasks to the session, not only replace the information for every task we store.
We will use a simple vector as our storage format. To be able to do this, we need not only the parameters from the form, but also the current session. We change our POST route:

(POST “/form” {session :session, params :params} (handle-form (params “task”) session)))

we now have the session and the task parameter from the form.
we change the form handler:

What this does, except for print the task from the form, and the tasks from the session. Is that it checks if the value of the session is a vector.
If it is. the task is added to the current vector of tasks. If it is not a vector, it is most likely empty, and we create a new vector with our task.

We dont need to change anything to our view route.
Try it out! We can now add multiple tasks to our simple tasklist!

Time to make that view look like a real tasklist.
We will do this the easy way, add hiccup.page-helpers to the ever groving list of namespaces in :use.
then simply wrap our task vector in (unordered-list)

We now have a list of tasks. This is how mine looks after adding three tasks:

Tasks

  • task1
  • task2
  • task3

This is enough for the first part.
We still have some work todo. We cant set tasks as completed or delete tasks. Later, we will also use jquery to make it smother too use.

update: A question came up on the clojure mailing list HERE about how to reload a namespace if you are not using emacs. Thanks to mark Rathwell who posted the answer:
(require ’[foo.something :as something] :reload)
(require ’[foo.something :as something] :reload-all)

blog comments powered by Disqus