Basic concepts

Ganelon framework provides additional features on top of Ring/Compojure and can be used just as any other Ring library. What makes it different, are the provided JavaScript libraries, allowing server-side to fully control browser behaviour.

Basic concepts

The framework introduces three basic constructs, which allow the programmer to built dynamic web apps effectively, while still retaining the benefits of generating HTML server-side:

  • Widget is a function, returning part of the DOM tree, referenceable by an id attribute. Whole page can be built from standard content (e.g. menubar) and widgets, which hold the presentation logic.
  • Action is a standard Ring handler, which returns JSON operations. The action is a place to communicate with business services or persistence layers and can be be referenced from Widgets or even JavaScript code.
  • Operation is a JSON object, which is interpreted by browser-side JavaScript plugins. For example, an operation can modify widget's contents or display a modal window. The operation defines generic (or tailored to the particular case) communication protocol between Action and Widget.
    Most common operations, such as DOM manipulation, notifications, modals, etc. is already bundled with Ganelon.

An application built with these constructs is easy to understand and maintain due to a standarized approach, and contains as little boilerplate code as possible.

In addition to that, Ganelon provides:

  • ganelon.web.dyna-routes - helper utilities to manage page routes dynamically.
  • ganelon.web.middleware - helper middleware, such as CDN-ification of urls or access to remote IP when running behind a reverse proxy (e.g. lighttpd or nginx).
  • ganelon.util - general purpose utility helper functions.
  • ganelon.util.logging - general purpose logging functions on top of clojure.tools.logging

Using Ganelon

To apply Ganelon in your web app project, simply add the following leiningen dependency:

[ganelon "0.9.0"]

Client side JavaScript is provided through the following files (assuming that /public resources are published as /):

(hiccup/include-js "/ganelon/js/jquery-1.8.1.min.js") ;jQuery - required
(hiccup/include-js "/ganelon/js/bootstrap.js") ;Bootstrap - optional
(hiccup/include-js "/ganelon/js/ganelon.js") ;basic actions support
(hiccup/include-js "/ganelon/js/ext/ganelon.ops.bootstrap.js") ;additional Bootstrap related actions
(hiccup/include-js "/ganelon/js/ext/ganelon.ops.gritter.js") ;growl-style notifications through gritter.js
(hiccup/include-js "/ganelon/actions.js") ;dynamic actions interface

The Ganelon JavaScript files are available in public.ganelon.js package- in case you need to merge them with your other client-side modules.

The Bootstrap and gritter support is optional - you can easily overwrite them with your own plugins.

You might also want to include CSS file for Growl-style notifications or Bootstrap (2.3.0) - or merge them with your CSS files:

(hiccup/include-css "/ganelon/css/bootstrap.css") ;Bootstrap - optional
(hiccup/include-css "/ganelon/css/jquery.gritter.css") ;growl-style notifications - optional

The Ganelon CSS files are available in public.ganelon.css package- in case you need to merge them with your other client-side stylesheets.

To run Ganelon-powered application just use standard Ring mechanisms. For example, code used to run this entire site with ring.adapter.jetty is listed below:

(ns ganelon.test.demo
  (:require [ganelon.test.demo-pages]
            [ring.middleware.stacktrace]
            [ring.middleware.reload]
            [ring.adapter.jetty :as jetty]
            [ganelon.web.middleware :as middleware]
            [ganelon.web.app :as webapp]
            [noir.session :as sess]))

(defonce SERVER (atom nil))

(defn start-demo [port]
  (jetty/run-jetty
    (->
      (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 ["test/ganelon/test/pages"]}))
    {:port port :join? false}))

(let [mode :dev port (Integer. (get (System/getenv) "PORT" "8097"))]
  (swap! SERVER (fn [s] (when s (.stop s)) (start-demo port))))

Of course, it is also possible to use lein-war and put your whole web application in a neat .war file. Or use highly performant http-kit - anything that is compatible with Ring.

AJAX support

There is nothing easier in Ganelon framework than executing AJAX request and applying its results to the page contents.

The simple and easy to use mechanism translates JSON response into JavaScript calls. Ganelon provides out-of-the-box support for:

  • Almost all of the jQuery Manipulation methods.
  • Displaying/hiding Bootstrap's modal window.
  • Displaying Growl-style notification.
  • Manipulating web page url

The list above can be easily extended using only JavaScript.

Code below updates part of the page (widget) and displays Growl-style notifications:

(defn demo1-widget [msg]
  (widgets/with-div
    [:p "The message is: " [:b (hiccup.util/escape-html msg)]]
    [:ul [:li (widgets/action-link "demo-action"
      {:msg "test1"} {} "Set message to <b>test1</b>.")]
     [:li (widgets/action-link "demo-action"
       {:msg "test2"} {} "Set message to <b>test2</b>.")]]))

First, we have defined a widget rendering function. It is a function like any other, and we are just using ganelon.web.widgets/with-div macro to set widget id. Next, ganelon.web.widgets/widget-update-link generates ajax call link with msg parameter.

(actions/defjsonaction "demo-action" [msg widget-id]
  (actions/put-operation! (ui/notification "Success"
      (h/html "Message set to: " [:b (hiccup.util/escape-html msg)])))
  (actions/put-operation! (ui/notification "Another message"
      (h/html "Widget-id is: " [:b (hiccup.util/escape-html widget-id)])))
  (ui/fade widget-id (demo1-widget msg)))

The action handler returns two operations for client-side JavaScript to perform:

  • Display two notifications - ganelon.web.ui-operations/notification results are put into operation queue with ganelon.web.actions/put-operation!.
  • Replace with a fade effect div content with a new content of demo1-widget. The div id is provided from widget-id parameter, autogenerated by ganelon.web.widgets/with-div in a demo1-widget function in the step before.

Please note, that you can use Ganelon's AJAX action as any other Ring-compliant handlers. The defjsonaction macro is here merely for you convience!

Try it here:

The message is: demo1 widget

Defining pages

Ganelon is 100% Ring-compliant, so it is possible to define handler hierarchy as in any other Ring/Compojure app and still benefit from all of the features of the framework.
In addition to that, a ganelon.web.dyna-routes package is available.

This package provides a simple in-memory registry for handlers, and the simplest use is as follows:

(ganelon.web.dyna-routes/defpage "/routing/sample" []
            "Hello world!")

Try it (open a new window/tab)

It is also possible to use setroute! function and define a route as a direct Compojure (or other) route:

(ganelon.web.dyna-routes/setroute! :example2
            (compojure.core/GET "/routing/sample2" []
              "Hello world !!!"))

Run compojure sample (opens in a new browser tab/window)

More information...