If you want to see the final result of an entire tutorial, you can view the tutorial app running live at http://ganelon-tutorial.tomeklipski.com or you can check out/star/fork the source code at http://github.com/tlipski/ganelon-tutorial.
For more information regarding Ganelon, you can visit the project website: http://ganelon.tomeklipski.com.
Creating Ganelon project
lein new
to start a new project or set up the project.clj
file and src/
, script/
and resources/
directories manually.In either way, it is important that the project.clj file contains a dependency on ganelon 0.9.0 library. What is worth mentioning, is that ganelon comes with dependencies on lib-noir, Compojure & hiccup, so in simple cases, it is enough to just add this one dependency when it comes to Clojure web apps.
Ganelon 0.9.0 is deployed to clojars, so it will get fetched alongside other Clojure libraries.
Our project.clj
at this moment looks like this:
(defproject ganelon-tutorial "0.9-SNAPSHOT" :description "Ganelon tutorial" :url "http://ganelon.tomeklipski.com" :dependencies [[ganelon "0.9.0"] [org.clojure/clojure "1.5.1"]] :license {:name "Eclipse Public License - v 1.0" :url "http://www.eclipse.org/legal/epl-v10.html" :distribution :repo :comments "same as Clojure"} :plugins [[lein-ring "0.8.6"]] :ring {:handler ganelon.tutorial/handler :init ganelon.tutorial/initialize})
As we can see, there is only one dependency other than Clojure 1.5.1 - it is for ganelon 0.9.0. There is also a lein-ring configuration with a pretty standard settings - an initialize function, which at this moment does nothing but will establish a connection pool for MongoDB and a handler reference.
Handler definition
Our handler will be using Ganelon's simple mechanism for establishing routes - and we will mix app-handler
from Ganelon with standard Ring middleware.
You can add your own handlers and middleware here and it will just work. If you don't want to or cannot use Compojure routes, you can just skip this step altogether and use raw Ring handlers from ganelon.web.actions package.
(def handler (-> (ganelon.web.app/app-handler (ganelon.web.app/javascript-actions-route)) middleware/wrap-x-forwarded-for (ring.middleware.stacktrace/wrap-stacktrace) (ring.middleware.reload/wrap-reload {:dirs ["src/ganelon/tutorial/pages"]})))
View the entire ganelon/tutorial.clj file.
Please also note, that we are using a convienent ring.middleware.reload/wrap-reload
middleware, which will reload all the changes to Clojure files in specified directories - src/ganelon/tutorial/pages
in this case.
Defining the Hiccup templates
The simplest way (but sometimes not the most effective) to achieve common layout and HTML code for pages with Hiccup is to define them as functions, accepting page content as parameters:
(defn layout [& content] (hiccup/html5 [:head [:meta {:name "viewport" :content "width=device-width, initial-scale=1.0"}] [:title "Ganelon tutorial - Meetups"] ;real life site should use CDN/minify to serve static resources (hiccup/include-css "/ganelon/css/bootstrap.css") (hiccup/include-css "/ganelon/css/bootstrap-responsive.css") ] [:body.default-body [:div#navbar (navbar)] [:div.container {:style "padding-top: 70px"} content] [:footer {:style "opacity:0.9; text-align: center; padding: 30px 0; margin-top: 70px; border-top: 1px solid #E5E5E5; color: #f6f6f6; background-color: #161616;"} [:div.container [:p "The Ganelon framework has been designed, created and is maintained by " [:a {:href "http://twitter.com/tomeklipski"} "@tomeklipski"] "."] [:p "The code is available under " [:a {:href "http://opensource.org/licenses/eclipse-1.0.php"} "Eclipse Public License 1.0"] "."] [:p [:a {:href "http://github.com/tlipski/ganelon-tutorial"} "View the sources on GitHub."]] [:p "This interactive tutorial runs on " [:a {:href "http://cloudbees.com"} "CloudBees"] " and " [:a {:href "http://mongohq.com"} "MongoHQ"] "."] ]]]))
View the entire ganelon/tutorial/pages/common.clj file.
As you can see, we are using standard Bootstrap2 CSS/classes - nothing fancy.
We have also defined a helper layout with common UI components and put it routes.clj file:
(defn meetup-layout [& contents] (common/layout [:div.row-fluid [:div.span3 [:div {:style "border: 1px dashed #363636"} "TODO - new meetup widget here"] [:div {:style "border: 1px dashed #363636"} "TODO - meetup list widget here"]] [:div.span1 ] [:div.span8 [:div#contents contents]]]))
Defining routes
With templates already defined, we can set up our routes. They will contain placeholders for widgets, as these will be added in the next steps of the tutorial:
(dyna-routes/defpage "/" [] (meetup-layout [:div.hero-unit [:h1 "Welcome"] [:p "Welcome to the interactive tutorial for " [:a {:href "http://ganelon.tomeklipski.com"} "Ganelon micro-framework."]] [:p "This sample application used to manage meetups provides links to display source of every widget and action used. In addition to that, each widget has a dashed border to mark its boundary."]])) (dyna-routes/defpage "/meetup/edit/:id" [id] (meetup-layout [:div {:style "border: 1px dashed #363636"} "TODO - meetup details widget here"])) (dyna-routes/defpage "/i/:id" [id] (meetup-layout [:div {:style "border: 1px dashed #363636"} "TODO - invitation details widget here"]))
The ganelon.web.dyna-routes/defpage
macro simply creates compojure.core/ANY
route and adds it to a list maintained in the ganelon.web.dyna-routes
namespace. The routes can be grouped for easier management and middleware can be defined in a similar way. More information is available on the Routing page of Ganelon project website.
Running the project
After that, we have two general ways to run our project and there is nothing specific to Ganelon about them:
The simplest is to use lein-ring plugin and run lein ring server
:
$ lein ring server 2013-08-19 21:39:41.474:INFO:oejs.Server:jetty-7.6.1.v20120215 2013-08-19 21:39:41.502:INFO:oejs.AbstractConnector:Started SelectChannelConnector@0.0.0.0:3000 Started server on port 3000
We can also use lein ring uberjar
to obtain an executable jar file.
If we are using some IDE not compliant with Leiningen - for example IntelliJ IDEA, we can create a simple script to run our jetty-ring adapter:
(ns run (:require [ganelon.tutorial] [ring.adapter.jetty :as jetty])) (defonce SERVER (atom nil)) (defn start-demo [port] (jetty/run-jetty ganelon.tutorial/handler {:port port :join? false})) (ganelon.tutorial/initialize) (let [port (Integer. (get (System/getenv) "PORT" "3000"))] (swap! SERVER (fn [s] (when s (.stop s)) (start-demo port))))
This scripts just optionally stops and then starts jetty adapter for our handler - but can be loaded for example into IntelliJ IDEA's REPL.
After applying either method, we can just navigate to http://localhost:3000/ to see the running application.
What's next?
In the next part of the tutorial, I will show how to build widgets, actions and use them to propagate data back and forth between a web app and a MongoDB instance.