Create an HTML table from nested maps (and vectors)

I am trying to create a table (work schedule) that I previously encoded using python, I think this will be a good introduction to the Clojure language for me.

I have very little experience with Clojure (or lisp for that matter), and I did my google rounds and a good bit of trial and error, but it seems to have coding.

Here is my sample data (in the future, it will come from the sqlite database):

(def smpl2 (ref {"Salaried" [{"John Doe" ["12:00-20:00" nil nil nil "11:00-19:00"]} {"Mary Jane" [nil "12:00-20:00" nil nil nil "11:00-19:00"]}] "Shift Manager" [{"Peter Simpson" ["12:00-20:00" nil nil nil "11:00-19:00"]} {"Joe Jones" [nil "12:00-20:00" nil nil nil "11:00-19:00"]}] "Other" [{"Super Man" ["07:00-16:00" "07:00-16:00" "07:00-16:00" "07:00-16:00" "07:00-16:00"]}]})) 

I tried to do this first with for , then moved to a dose and finally domap (which seems more successful) and dumping the contents into an html table (my source python program output this from sqlite database to Excel table using COM).

Here is my attempt (create-table fn):

 (defn html-doc [title & body] (html (doctype "xhtml/transitional") [:html [:head [:title title]] [:body body]])) (defn create-table [] [:h1 "Schedule"] [:hr] [:table (:style "border: 0; width: 90%") [:th "Name"][:th "Mon"][:th "Tue"][:th "Wed"] [:th "Thur"][:th "Fri"][:th "Sat"][:th "Sun"] [:tr (domap [ct @smpl2] [:tr [:td (key ct)] (domap [cl (val ct)] (domap [c cl] [:tr [:td (key c)]]))]) ]]) (defroutes tstr (GET "/" ((html-doc "Sample" create-table))) (ANY "*" 404)) 

This prints out a table with sections (salaried, manager, etc.) and section names, I just feel like I am abusing domap by nesting it too many times since I will probably have to add more domaps just to get the switch time in their respective columns, and the code gets a "dirty" feeling.

I apologize in advance if I do not include enough information, I usually do not ask for help on coding, this is also my first question :).

If you know any better approaches to this, or even tips or tricks that I should know as a newbie, they are certainly welcome.

Thanks.

+7
clojure compojure
source share
2 answers

There is no way to avoid any nested loop. But you don't need domap at all, Compojure is smart enough (sometimes) to extend seq for you. list and map and for . For example, Michał Marczyk will answer or:

 (defn map-tag [tag xs] (map (fn [x] [tag x]) xs)) (defn create-table [] (list [:h1 "Schedule"] [:hr] [:table {:style "border: 0; width: 90%"} [:tr (map-tag :th ["Name" "Mon" "Tue" "Wed" "Thu" "Fri" "Sat" "Sun"])] [:tr (for [[category people] smpl2] (list* [:tr [:td category]] (for [person people [name hours] person] [:tr [:td name] (map-tag :td hours)])))]])) 

The pattern "map over a seq and wrap all in the same tag" is common enough that I sometimes like the helper function.

Compojure extends one seq level for you. This way, you can add some things to the list so that the tags appear sequentially in your HTML output, which I did to display h1 and hr. In your code, you just throw h1 and hr.

But notice that it only extends one level. When you have a list of lists or a list of sections, the outer seq will expand, but the inner ones will not.

 user> (println (html (list [:div "foo"] (for [x [1 2 3]] [:div x])))) <div>foo</div>clojure.lang.LazySeq@ea73bbfd 

See how internal seq makes Compojure barf. compojure.html.gen/expand-seqs to find out how it works, or change / fix it if you want.

This can be a problem when you have nested for or for in list , since for returns lazy seq. But you just need to avoid using seq-in-a-seq. I am using list* above. The combination of list and html will work too. There are many other ways.

 user> (println (html (list* [:div "foo"] (for [x [1 2 3]] [:div x])))) <div>foo</div><div>1</div><div>2</div><div>3</div> user> (println (html (list [:div "foo"] (html (for [x [1 2 3]] [:div x]))))) <div>foo</div><div>1</div><div>2</div><div>3</div> 
+4
source share

I think you have some minor issues with your code. I tried to fix them in the code below. Having experienced this with Compojure 0.3.2, I dare say that it works. (Feel free to point out what needs improvement or doesn't seem to work for you, of course.)

 (use 'compojure) ; you'd use a ns form normally ;;; I'm not using a ref here; this doesn't change much, ;;; though with a ref / atom / whatever you'd have to take care ;;; to dereference it once per request so as to generate a consistent ;;; (though possibly outdated, of course) view of data; ;;; this doesn't come into play here anyway (def smpl2 {"Salaried" [{"John Doe" ["12:00-20:00" nil nil nil "11:00-19:00"]} {"Mary Jane" [nil "12:00-20:00" nil nil nil "11:00-19:00"]}] "Shift Manager" [{"Peter Simpson" ["12:00-20:00" nil nil nil "11:00-19:00"]} {"Joe Jones" [nil "12:00-20:00" nil nil nil "11:00-19:00"]}] "Other" [{"Super Man" ["07:00-16:00" "07:00-16:00" "07:00-16:00" "07:00-16:00" "07:00-16:00"]}]}) (defn html-doc [title & body] (html (doctype :xhtml-transitional) ; the idiomatic way to insert ; the xtml/transitional doctype [:html [:head [:title title]] [:body body]])) (defn create-table [] (html [:h1 "Schedule"] [:hr] [:table {:style "border: 0; width: 90%;"} [:tr [:th "Name"][:th "Mon"][:th "Tue"][:th "Wed"] [:th "Thur"][:th "Fri"][:th "Sat"][:th "Sun"]] (for [category smpl2] [:div [:tr [:td (key category)]] ; for returns just one thing per ; 'iteration', so I'm using a div ; to package two things together; ; it could be avoided, so tell me ; if it a problem (for [people (val category)] (for [person people] [:tr [:td (key person)] (for [hours (val person)] [:td hours])]))])])) (defn index-html [request] (html-doc "Sample" (create-table))) (defroutes test-routes (GET "/" index-html) (ANY "*" 404)) (defserver test-server {:port 8080} "/*" (servlet test-routes)) 
+2
source share

All Articles