Compojure/Tutorials and Tips

From Wikibooks, open books for an open world
Jump to: navigation, search

This is a collection of examples for using the compojure web framework.

Serving static files[edit]

Compojure does not serve static files by default, nor does it automatically deal out 404s when no route matches. Compojure doesn't make any assumptions about what you want to do; you have to be explicit.

In order to serve static files, you typically need to add in two handlers. One to serve files, and the other to raise a 404 error if the first handler fails:

(defroutes file-example 
  (GET "/*" 
    (or (serve-file (params :*)) :next)) 
  (ANY "*" 
    (page-not-found))) 

The "/*" pattern will match any string after the slash, including dots in the file name. This example uses two functions: serve-file and page-not-found. Here are their respective docstrings:

(page-not-found) 
(page-not-found filename) 
A shortcut to create a '404 Not Found' HTTP response. 
(serve-file path) 
(serve-file root path) 
Attempts to serve up a static file from a directory, which defaults to './public'. 
Nil is returned if the file does not exist. If the file is a directory, the function 
looks for a file in the directory called 'index.*'. 

So we try to serve a file, and if that comes up as nil, then we return the keyword :next. This tells Compojure to keep looking for a matching handler.

Remember to ensure that the (ANY "*" ...) route is always at the bottom. Handlers are matched in order, so you'll want your "catch-all" 404 handler to be matched last, when everything else fails.

Organizing your code around servlets[edit]

The only benefit two Compojure servlets offer over one is a clear division of functionality. If, for instance, your site has an administrator interface that is very different from the public site, you may wish to construct two servlets; one for public access, and one for the administrator control panel.

Another situation where you would want more than one servlet is if one of your servlets was constructed in Java. For example, Jetty includes a servlet for handling Cometd requests, ContinuationCometdServlet. In the application I'm building with Compojure, I bind this servlet to "/_cometd/*", and my main Compojure servlet to "/*".

But for normal use I'd be inclined to put everything in one servlet. If you take a look at frameworks like Ruby on Rails, all of the routing information is kept in one file. Compojure servlets serve a similar purpose, in that they act as a gateway to the Clojure functions that make up your site. The actual servlet itself should contain very little logic, and it should read almost like a high-level overview of your whole site.

Reading a date in a form[edit]

The following code defines the input-date-field function, which can be used just like any other input helper functions. The form will return the date in unixtime format. The parameters are: name of the form field, current value, and the start and end years for the year select.

 ;;; Date form field
 
 (def *date-class* (.getClass (new java.util.Date (long 0))))
 (def *date-format* (new java.text.SimpleDateFormat "yyyy-MM-dd"))
 
 (defn parseLong [s]
   (try (Long/parseLong (str s)) (catch java.lang.Exception e 0)))
 
 (defn get-date-from-anything [value]
   "Returns a Date object. Accepts a Date, number or String."
   (cond 
     (nil? value)
       (new java.util.Date (long 0))
     (instance? *date-class* value)
       value
     (number? value)
       (new java.util.Date (* 1000 (long value)))
     true
       (new java.util.Date (* 1000 (long (parseLong (str value)))))))
 
 (import '(java.util Calendar))
 (def monthNames ["January" "February" "March" "April" "May" "June" "July" "August" "September" "October" "November" "December"])
 
 (defn input-date-field 
   "Creates a form element to input a date"
   [name value fromYear toYear]
   (let [calendar (Calendar/getInstance)
         name (.replaceAll (str name) ":" "")]
     (.setTime calendar (get-date-from-anything value))
     (html
       (str "<script>
         function changed_date_" name "()
         {
           function $(id) { return document.getElementById(id); }
           var date = new Date(parseInt($('year_" name "').value), parseInt($('month_" name "').value), parseInt($('day_" name "').value));
           $('" name "').value = date.getTime() / 1000;
           //alert('c=:' + $('" name "').value);
         }
       </script>")
       [:input {:type 'hidden :name name :id name :value value}]
       [:input {:type 'text :id (str "day_" name) :value (.get calendar Calendar/DAY_OF_MONTH) :size 1 :onchange (str "changed_date_" name "();")}] " "
       [:select {:id (str "month_" name) :onchange (str "changed_date_" name "();")}
         (reduce #(str % (html [:option {:value %2 :selected (if (= %2 (.get calendar Calendar/MONTH)) "true" nil)} (nth monthNames %2)])) "" (range 12))] " "
       [:select {:id (str "year_" name) :onchange (str "changed_date_" name "();")}
         (reduce #(str % (html [:option {:value (+ fromYear %2)} (+ fromYear %2)])) "" (range (- (inc toYear) fromYear)))] " "
   )))