Tuesday, August 20, 2013

Ganelon tutorial, part 1: basic setup, routes & templates

This part of Ganelon tutorial shows how to start Ganelon-enabled project with leiningen, configure basic pages with Hiccup templates and run the jetty-ring adapter. You can see the complete application for this step in a GitHub repository: https://github.com/tlipski/ganelon-tutorial/tree/PART1_ROUTES.

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

Ganelon is just a standard Leiningen/Maven/Gradle/etc. dependency, so you can just use 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.

0 comments :

Post a Comment