Reputation: 53
I'm trying to create a table (a work schedule) I have coded previously using python, I think it would be a nice introduction to the Clojure language for me.
I have very little experience in Clojure (or lisp in that matter) and I've done my rounds in google and a good bit of trial and error but can't seem to get my head around this style of coding.
Here is my sample data (will be coming from an sqlite database in the future):
(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 was trying to step through this originally using for then moving onto doseq and finally domap (which seems more successful) and dumping the contents into a html table (my original python program outputed this from a sqlite database into an excel spreadsheet using COM).
Here is my attempt (the 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))
That outputs the table with the sections (salaried, manager, etc) and the names in the sections, I just feel like I'm abusing the domap by nesting it too many times as I'll probably need to add more domaps just to get the shift times in their proper columns and the code is getting a 'dirty' feel to it.
I apologize in advance if I'm not including enough information, I don't normally ask for help on coding, also this is my 1st SO question :).
If you know any better approaches to do this or even tips or tricks I should know as a newbie, they are definitely welcome.
Thanks.
Upvotes: 8
Views: 3665
Reputation: 84351
I think you've got a few minor problems with your code. I've made an attempt at correcting them in the code below. Testing this with Compojure 0.3.2, I dare say that it works. (Feel free to point out anything that requires improvement or seems not 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's 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))
Upvotes: 3
Reputation: 72946
There's no way to avoid some kind of nested loop. But you don't need domap
at all, Compojure is smart enough (sometimes) to expand a seq for you. list
and map
and for
are enough. For example Michał Marczyk's 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 "map over a seq and wrap everything in the same tag" pattern is common enough that I like to use a helper function for it sometimes.
Compojure expands one level of seq
for you. So you can throw some stuff into a list
to get tags to appear sequentially in your HTML output, which I did to get the h1 and hr to show up. In your code, you're just throwing the h1 and hr away.
But note that it only expands one level. When you have a list of lists, or a list of seqs, the outer seq will expand but the inner ones won't.
user> (println (html (list [:div "foo"] (for [x [1 2 3]] [:div x]))))
<div>foo</div>clojure.lang.LazySeq@ea73bbfd
See how the inner seq makes Compojure barf. Look into compojure.html.gen/expand-seqs
to see how this works, or change/fix it if you care to.
This can be an issue when you have nested for
or a for
in a list
, since for
returns a lazy seq. But you just have to avoid having a seq-in-a-seq. I use list*
above. A combination of list
and html
would work too. There are lots of 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>
Upvotes: 5