4

I'm brand new to clojure and the web development stack. I'm trying to use enlive to set values in an HTML template:

(en/deftemplate project-main-page
  (en/xml-resource "project-main.html")
  [id]
  [:#project-name] (en/content (str "Name: " ((get-project id) :name)))
  [:#project-desc] (en/content (str "Desc: " ((get-project id) :desc))))

This works fine to set my two HTML elements, but it involves a repeated call to my function get-project. At the moment this just reads from a local map, but eventually it will involve some external storage access, so I'd prefer to just perform it once in this function.

I was thinking of using let:

(en/deftemplate project-main-page
  (en/xml-resource "project-main.html")
  [id]
  (let [project (get-project id)]
    [:#project-name] (en/content (str "Name: " (project :name)))
    [:#project-desc] (en/content (str "Desc: " (project :desc)))))

But this only affects the description element and ignores the name forms.

What is the best way to bind a local var within deftemplate?

DCWoods
  • 123
  • 4

2 Answers2

2

If I have understood what you are trying to achieve; you could also try using the transformation macro provided by enlive.

(defn main-page [{:keys [name desc] :as project}]
 (en/transformation
   [:#project-name] (en/content (str "Name: " name)
   [:#project-desc] (en/content (str "Desc: " desc))))


(en/deftemplate project-main-page
  (en/xml-resource "project-main.html")
  [id]
   (main-page (get-project id)))

The code is untested, but I hope it conveys a different way to do what you need

Sid Kurias
  • 172
  • 2
  • 7
1

Enlive's deftemplate macro expects a series of tag/content pairs after the args vector (the args vector is [id] in your example). You can't just stick a let in there because the macro isn't expecting a let form, so when it does its splicing everything gets messed up and results in the behavior you described above.

One way you could fix this would be to write your own deftemplate macro that allows binding definitions using the identifiers in the args vector. Example:

(alt/deftemplate project-main-page
  (en/xml-resource "project-main.html")
  [id]
  [project (get-project id)]
  [:#project-name] (en/content (str "Name: " (project :name)))
  [:#project-desc] (en/content (str "Desc: " (project :desc))))

The deftemplate macro is a simple wrapper around template, which uses snippet* and this is probably where you'd need to insert your changes:

(defmacro snippet* [nodes args & forms]
  `(let [nodes# (map annotate ~nodes)]
     (fn ~args
       ; You could add let bindings here since args are in scope
       (doall (flatmap (transformation ~@forms) nodes#)))))

The other option—which might be simpler since you don't have to muck around in the library code—would be to add a level of indirection to your get-project function to cache results. You might try the core.cache library.

DaoWen
  • 32,589
  • 6
  • 74
  • 101
  • I have updated (albeit untested) versions of the `deftemplate` and `snippet*` macros if you need them, but I wouldn't want to rob you of the chance to make the changes yourself and learn a bit more about macros! – DaoWen Aug 13 '12 at 04:58
  • Thanks. I'm still really just starting to learn and haven't got my head around macro syntax yet. I'll have a go and see what I can come up with. – DCWoods Aug 13 '12 at 08:03
  • Macros are kind of crazy at first, but once you get your head around the syntax-quote they're not so bad. Just don't forget to come back and share what you come up with! :D – DaoWen Aug 13 '12 at 08:16