How to generate Hiccup structures conditionally?

369 Views Asked by At

I have a bunch of maps that describe locations:

 (def pizzeria
  {
   :LocationId "pizzeria"
   :Desc       "Pizzeria where Timur works"
   :Address    "Vorgartenstraße 126, 1020 Wien"
   :Comment    ""
   })

(def dancing-school
  {
   :LocationId "dancing-school"
   :Desc       "Dancing school"
   :Comment    "4th district"
   })

Some of those maps have :Comments, others don't.

I want to create a Clojure function that, among other things, outputs the comment to HTML, if it is present.

I wrote this function:

(defn render-location-details
  [cur-location]
  (let [
        desc (get cur-location :Desc)
        address (get cur-location :Address)
        comment (get cur-location :Comment)
        address-title [:h4 "Address"]
        address-body [:p address]
        comment-hiccup [
                        (if (clojure.string/blank? comment)
                          nil
                          [:div
                           [:h4 "Comment"]
                           [:p comment]
                           ])
                        ]
        ]
    [:h3 desc
     address-title
     address-body
     comment-hiccup
     ]
    )
  )

If I run the code that uses this function, I get the error

Execution error (IllegalArgumentException) at
hiccup.compiler/normalize-element (compiler.clj:59).
 is not a valid element name.

If I change comment-hiccup to nil, the error disappears.

How can I output data to HTML conditionally using Hiccup?

Note: I am new to Clojure, so if my approach is completely wrong, please tell and show how to do it right.

2

There are 2 best solutions below

0
On BEST ANSWER

Your first attempt will create comment-hiccup like:

[nil]

where valid hiccup always starts with a keyword tag like:

[:h3 "hello"]
[:div nil]

so the error message basically says that nil is not a valid html tag. However, in clojure nil when converted to a string is of zero length, so the error message becomes is not a valid element name. instead of something like foo is not a valid element name.

However, Hiccup and many similar forms will accept nil instead of a valid Hiccup expression and just filter them out before rendering the remaining valid forms. This feature is present precisely to allow the use of inline if or when forms, which either produce valid hiccup or a nil.

Sample code:

(ns tst.demo.core
  (:use tupelo.core tupelo.test)
  (:require
    [hiccup.core :as h]))

(dotest
  (is= (h/html [:p "bar"]) "<p>bar</p>")
  (is= (h/html [:p nil]) "<p></p>")
  (is= (h/html nil) "")

  ; if uncommented, this creates an error during macroexpansion of `h/html`
  ; (println :err1 (h/html [nil]))  ; <=== line 12
  ;    `Syntax error macroexpanding h/html at (tst/demo/core.clj:12:18). 
  ;       java.lang.IllegalArgumentException:  is not a valid element name.`

  )

based on my favorite template project.

Side note: Cryptic error messages such as the above are a strong reason to avoid the unnecessary use of macros in your own code. The above error does not occur during code execution, but rather during code compilation. Thus, we cannot handle the error even if we wrap the offending line with a try/catch.


P.S.

Most clojurists would condense the above code into a more "inline" style like:

(defn render-location-details
  [cur-location]
  [:div
   [:h3 (:Desc cur-location)]
   [:h4 "Address"]
   [:p (:Address cur-location)]
   (when-not (clojure.string/blank? (:Comment cur-location))
     [:div
      [:h4 "Comment"]
      [:p (:Comment cur-location)]])])
1
On

The code below seems to work. Improvement proposals are still welcome.

(defn render-location-details
  [cur-location]
  (let [
        desc (get cur-location :Desc)
        address (get cur-location :Address)
        comment (get cur-location :Comment)
        address-title [:h4 "Address"]
        address-body [:p address]
        comment-hiccup (if (clojure.string/blank? comment)
                          nil
                          [:div
                           [:h4 "Comment"]
                           [:p comment]
                           ])

        ]
    [:div
      [:h3 desc]
      address-title
      address-body
      comment-hiccup
     ]
    )
  )