Search code examples
clojureclojurescriptom

Using Om, Generate Nested divs From a Nested Map


Say I have the following map:

(def m {"a" {"d" {}
             "e" {}}
        "b" {"f" {}
             "g" {"h" {}
                  "i" {}}}
        "c" {}})

I need to render it like this:

(om.dom/div #js {} "a" 
  (om.dom/div #js {} "d")
  (om.dom/div #js {} "e"))

(om.dom/div #js {} "b" 
  (om.dom/div #js {} "f")
  (om.dom/div #js {} "g"
    (om.dom/div #js {} "h")
    (om.dom/div #js {} "i")))

(om.dom/div #js {} "c")

How would I go about doing this? I have messed around with clojure.walk, but couldn't get it to call om.dom/div on the leaves first, then the direct parents, etc.

I am thinking that the solution might involve mapping a recursive function over the vals of a given sub-map. It would break the map apart until it sees a leaf and then bubble the om.dom/div calls back up the map.

So far I have this function:

(defn build-tree [data]
  (apply dom/div #js {}
         (->> (clojure.walk/postwalk
                #(cond (map? %) (vec %)
                       (vector? %) %
                       (string? %) %) data)
              (clojure.walk/postwalk
                #(if (and (vector? %) (string? (first %)))
                   (apply dom/div #js {} %) %)))))

Which results in:

With this in the inspector:


Bonus points for generating nested dom/ul and dom/li elements..


Solution

  • When solving problems of this type, sometimes it's helpful to generate tree structure resembling the desired call tree at the REPL. Once the result looks all right, converting it to an actual call tree is generally straightforward.

    For example, here's a function to generate the om.dom/div call tree for your example; to use in ClojureScript, tweak as indicated in the comment:

    (defn div [m]
      (for [[k v] m]
        ;; replace with (apply om.dom/div #js {} k (div v)) in CLJS
        (list* 'om.dom/div {} k (div v))))
    

    Example call:

    (div {"a" {"d" {}
               "e" {}}
          "b" {"f" {}
               "g" {"h" {}
                    "i" {}}}
          "c" {}})
    

    Output from the above:

    ;; in CLJS, you'll want to use this as the ... in
    ;; (apply create-container-component initial-args ...)
    ((om.dom/div {} "a"
       (om.dom/div {} "d")
       (om.dom/div {} "e"))
     (om.dom/div {} "b"
       (om.dom/div {} "f")
       (om.dom/div {} "g"
         (om.dom/div {} "h")
         (om.dom/div {} "i")))
     (om.dom/div {} "c"))