DevOps Zone is brought to you in partnership with:

My name is Konrad Garus and I solve problems for a living. I am crazy about quality of code and life, zealous learner and believer in constant refinement and improvement. I fight stubborn ignorance and “good enough” with passion. Personally I also am a husband, father, passionate reader and music fan. Konrad is a DZone MVB and is not an employee of DZone and has posted 28 posts at DZone. You can read more from them at their website. View Full User Profile

ClojureScript Routing and Templating with Secretary and Enfocus

03.18.2014
| 3788 views |
  • submit to reddit

A good while ago I was looking for good ways to do client-side routing and templating in ClojureScript. I investigated using a bunch of JavaScript frameworks from ClojureScript, of which Angular probably gave the most promising results but still felt a bit dirty and heavy. I even implemented my own routing/templating mechanism based on Pedestal and goog.History, but something felt wrong still.

Things have changed and today there’s a lot buzz about React-based libraries like Reagent and Om. I suspect that React on the front with a bunch of “native” ClojureScript libraries may be a better way to go.

Before I get there though, I want to revisit routing and templating. Let’s see how we can marry together two nice libraries: Secretary for routing and Enfocus for templating.

Let’s say our app has two screens which fill the entire page. There are no various “fragments” to compose the page from yet. We want to see one page when we navigate to /#/add and another at /#/browse. The “browse” page will be a little bit more advanced and support path parameters. For example, for /#/browse/Stuff we want to parse the “Stuff” and display a header with this word.

The main HTML could look like:

<!DOCTYPE html>
<html>
<body>
<div class="container-fluid">
<div id="view">Loading...</div>
</div>
<script src="js/main.js"></script>
</body>
</html>

Then we have two templates.

add.html:

<h1>Add things</h1>
<form>
<!-- boring, omitted -->
</form>

browse.html:

<h1></h1>
<div>
<!-- boring, omitted -->
</div>

Now, all we want to do is to fill the #view element on the main page with one of the templates when location changes. The complete code for this is below.

(ns my.main
(:require [secretary.core :as secretary :include-macros true :refer [defroute]]
[goog.events :as events]
[enfocus.core :as ef])
(:require-macros [enfocus.macros :as em])
(:import goog.History
goog.History.EventType))
(em/deftemplate view-add "templates/add.html" [])
(em/deftemplate view-browse "templates/browse.html" [category]
["h1"] (ef/content category))
(defroute "/" []
(.setToken (History.) "/add"))
(defroute "/add" []
(em/wait-for-load
(ef/at
["#view"] (ef/content (view-add)))))
(defroute "/browse/:category" [category]
(em/wait-for-load
(ef/at
["#view"] (ef/content (view-browse category)))))
(doto (History.)
(goog.events/listen EventType/NAVIGATE #(secretary/dispatch! (.-token %)))
(.setEnabled true))

What’s going on?

  1. We define two Enfocus templates. view-add is trivial and simply returns the entire template. view-browse is a bit more interesting: Given category name, alter the template by replacing content of h1 tag with the category name.
  2. Then we define Secretary routes to actually use those templates. All they do now is replace content of the #view element with the template. In case of the “browse” route, it passes the category name parsed from path to the template.
  3. There is a default route that redirects from / to /add. It doesn’t lead to example.com/add, but only sets the fragment:example.com/#/add.
  4. Note that all templating is wrapped in em/wait-for-load. This bit seems to be necessary for Enfocus if you load templates with AJAX calls, and I haven’t found a way yet to do it once per app. It was the hardest part to get to work and I’m still confused about it.
  5. Finally, we plug in Secretary to goog.History. I’m not sure why it’s not in the box, but it’s straightforward enough.
Published at DZone with permission of Konrad Garus, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)