This post is part of the Web Application Development with Clojure tutorial. You might want to read the previous posts before this post for continuity’s sake.

Introduction

The story so far – In the last 3 parts, we completed setting up the project for a simple blog engine, loading the test data and finally displaying the blog posts on the “home” page. In this post first we’ll first add some nice CSS to make it look a bit better and finish the blog post detail page. Next we’ll add an authentication page for the blog.

Source code on github

The code for this series is now available on github and the source code is tagged with part names. If you want to checkout the code for a specific part of this tutorial you can do so using the following command:

git clone git://github.com/vijaykiran/clog.git cd clog git checkout part4 1 2 3 git clone git : / / github .com / vijaykiran / clog .git cd clog git checkout part4

Static Resources

To make our blog look a bit more nice, we need some CSS and styling. We’ll use twitter’s bootstrap CSS for styling. To serve the css files from the root path, we can use ring middleware file provides a function wrap-files which will serve the static resources. First create a folder called public in the resources folder. This will contain all the static stuff like images, CSS etc. Now download and copy the bootstrap folder to the newly created public folder. The folder structure in IntelliJ should look like the following:

Update the code in core.clj‘s routes and add the wrap-file as shown below:

(def routes (app (wrap-file "resources/public") [""] (delegate index))) 1 2 3 4 ( def routes ( app ( wrap-file "resources/public" ) [ "" ] ( delegate index ) ) )

Now update your HTML for the home page with the following code. The changes I made are adding the CSS declaration to the top, and restructuring HTML a bit.

Clog - The clojure Blog 1 Clog - The clojure Blog

1

Title of the Post Post Content



(use 'clog.templates :reload) 1 ( use ' clog . templates : reload )

in the REPL, that will reload the templates. Whenever you change the template you need to compile the template.clj, to see the changes. This is a limitation of Enlive – which I think is being addressed. But for now, the work around is usein the REPL, that will reload the templates.

Here’s how the home page looks now.



Hacking LESS (Optional)

If you are feeling adventurous, you can try editing the Bootstrap LESS and change the styling to your liking. I use less.js and the .less files directly, and experiment with changing the styles. Just download the latest less.js and keep it under bootstrap/js and replace the stylesheet link in the html’s head element to the following:

<script src="/bootstrap/js/less-1.2.1.min.js" type="text/javascript"></script> 1 <script src = "/bootstrap/js/less-1.2.1.min.js" type = "text/javascript" > </script>

Here’s how the blog looks like with Clojure Colors

Post Detail Page

Now we just have the home page, but it would be better if we show the single blog post page when someone clicks on the post title. So let us add that functionality. First we need a new route that will handle the post detail URLs. Whenever someone goes to http://host/{post_id} we’ll show the detail page of that post. So we repeat pretty much the same exercise we did for rendering the home page, i.e., creating a template (both html and a new deftemplate in templates.clj ), adding a controller function, and finally calling that function in updated routes.

Here’s the updated version of the routes declaration which adds a new handler to the route as shown below.

;; Routes definition (def routes (app (wrap-file "resources/public") [""] (delegate index) [id] (delegate post id))) 1 2 3 4 5 6 ;; Routes definition ( def routes ( app ( wrap-file "resources/public" ) [ "" ] ( delegate index ) [ id ] ( delegate post id ) ) )

For the HTML of the post detail page, we’ll just reuse the home page, copy the html from home.html to post.html. Update the templates.clj with the deftemplate macro, the code is also similar to the home-page template macro.

(deftemplate post-page "post.html" [post] [:title] (content (str "Clog - " (:title post))) [:span.title] (content (:title post)) [:div.content] (html-content (:content post))) 1 2 3 4 ( deftemplate post-page "post.html" [ post ] [ : title ] ( content ( str "Clog - " ( : title post ) ) ) [ : span . title ] ( content ( : title post ) ) [ : div . content ] ( html-content ( : content post ) ) )

And finally the controller method

(defn post "Post details page handler" [req id] (let [postId (Integer/parseInt id)] (->> (first (select posts (where {:id postId}))) post-page response))) 1 2 3 4 5 6 ( defn post "Post details page handler" [ req id ] ( let [ postId ( Integer / parseInt id ) ] ( - & gt ;> (first (select posts (where {:id postId}))) post-page response ) ) )

After these changes you should be able to see the post detail page by going to http://host:port/1 or http://host:port/2

Link to the Post pages

We’ll now modify the home page template and link the title of the post to the corresponding detail page. First modify the home.html and change the title span to an anchor. Here’s the relevant part of the modified html:

1

Now edit the templates.clj to add the href attribute to the post detail page. Since we need to add the href and modify the content of the a tag, we use the do-> function to add the transformations. Here’s the updated home-page macro in templates.clj:

(deftemplate home-page "home.html" [posts] [:title] (content "Clog - the clojure blog engine!") [:div.post] (clone-for [post posts] [:a.title] (do-> (set-attr :href (str "/" (:id post))) (content (:title post))) [:div.content (html-content (:content post))])) 1 2 3 4 5 6 7 ( deftemplate home-page "home.html" [ posts ] [ : title ] ( content "Clog - the clojure blog engine!" ) [ : div . post ] ( clone-for [ post posts ] [ : a . title ] ( do - & gt ; ( set - attr : href ( str "/" ( : id post ) ) ) ( content ( : title post ) ) ) [ : div . content ( html-content ( : content post ) ) ] ) )

If everything went well, once you reload the templates namespace using (use ‘clog.templates :reload) you should see the home page with titles of the posts linking to the post detail pages.

Authentication

The next step is to create an administration area which is only accessible after login, and will be used to create the blog posts. First we need to create our login.html which will contain a form that will allow users to login. Notice that there’s an error div, which we will use to show the error message when the user enters invalid username and password. Take the following code and put it in a file login.html under resources

Clog - Login 1 Clog - Login

1

Username Password Login Cancel

Now let us add a macro to our templates.clj to render the login page. In this macro, we are checking if any message is passed to the template method, we’ll set the appropriate message in the error div. We are also using the set-attr and remove-attr functions from enlive.

(deftemplate login-page "login.html" [& msg] [:div#error] (if (nil? msg) (set-attr :style "display:none") (do-> (remove-attr :style) (content msg)))) 1 2 3 4 5 6 ( deftemplate login-page "login.html" [ & amp ; msg] [ : div # error ] ( if ( nil ? msg ) ( set - attr : style "display:none" ) ( do - & gt ; ( remove - attr : style ) ( content msg ) ) ) )

Now let us update the routes, so when someone goes to /login we’ll render the login page. We need to also add a POST handler that will process the login form’s post request. We’ll also use the ring’s params middleware function wrap-params that will wrap the request parameters and pass it on along with request. So update the clog.core namespace to include the ring.middleware.param:

(ns clog.core (:use ring.adapter.jetty ring.middleware.resource ring.middleware.reload ring.util.response ring.middleware.file ring.middleware.params net.cgrand.moustache clog.controller)) 1 2 3 4 5 6 7 8 9 ( ns clog . core ( : use ring . adapter . jetty ring . middleware . resource ring . middleware . reload ring . util . response ring . middleware . file ring . middleware . params net . cgrand . moustache clog . controller ) )

Now update the routes var with wrap-params and handler for the login page and login action as shown below:

(def routes (app (wrap-params) (wrap-file "resources/public") ["login"] (delegate login) [""] (delegate index) [id] (delegate post id))) 1 2 3 4 5 6 7 ( def routes ( app ( wrap-params ) ( wrap-file "resources/public" ) [ "login" ] ( delegate login ) [ "" ] ( delegate index ) [ id ] ( delegate post id ) ) )

The login route will handle both POST and GET, since we haven’t specified any request method. The function login is in the controller.clj which is shown below.

(defn login "Login Handler" [req] (let [params (:params req)] (if (empty? params) (response (login-page)) (if (= (get params "username") (get params "password")) (redirect "/admin") (response (login-page "Invalid username or password")))))) 1 2 3 4 5 6 7 8 9 ( defn login "Login Handler" [ req ] ( let [ params ( : params req ) ] ( if ( empty ? params ) ( response ( login-page ) ) ( if ( = ( get params "username" ) ( get params "password" ) ) ( redirect "/admin" ) ( response ( login-page "Invalid username or password" ) ) ) ) ) )

This is a bit “crude” way of handling login, but we are trying to keep this as simple as possible. What the above function is doing is checking if the params are empty, they’ll be empty when there’s a GET request on the /login URL, then just render the login-page with no parameters. When the params are not empty, check if username is equal to password, then redirect to the /admin url (which doesn’t exist yet, but we will add it soon). And finally, if the username isn’t equal to password, then render the same login-page but with a message “Invalid username or password”.

There’s bug here in the implementation that you can just click “Login” to get to the /admin, which is because empty strings are equal according to the logic. Fixing the bug is left as exercise to the reader!

Now that all the required pieces are in place, when you start the server (with port 8080), you should be able to see the login page at http://localhost:8080/login and when you enter two different strings for username and password, you should see an error message. You’ll get a redirect to /admin when you enter same string for username and password.

Conclusion

In this part we finished the “front-end” of the blog and started working on the admin area. In the Part 5of this series, we’ll wrap up the blog engine by completing the administration area that allows you to create the blog posts. In sixth and final part of this series we’ll see what deployment options are available and how to deploy the blog engine on Heroku.

Make sure you subscribe to the RSS feed or follow me on Twitter to get notified.