Compojure/Tutorials and Tips

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

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

Serving static files[edit | edit source]

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:

  (compojure.route/files "public")
  (compojure.route/not-found "Not found.")

as per documentation at https://weavejester.github.io/compojure/compojure.route.html

Using a namespace and :require map, such as

  (ns coolproject.handler
    (:require [compojure.core :refer :all]
              [compojure.route :as route]))
  

then your invocation of compojure.route can be simplifed:

  (route/files "public")

which will automatically look in your src/resources/public directory for static files (.css, images, ...)

Organizing your code around servlets[edit | edit source]

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 | edit source]

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)))] " "
   )))