从嵌套地图(和矢量)创建 HTML 表格

Posted

技术标签:

【中文标题】从嵌套地图(和矢量)创建 HTML 表格【英文标题】:Create a HTML table from nested maps (and vectors) 【发布时间】:2011-01-27 12:01:15 【问题描述】:

我正在尝试创建一个我以前使用 python 编写过的表(工作时间表),我认为这对我来说是对 Clojure 语言的一个很好的介绍。

我在 Clojure(或 lisp 方面)方面的经验很少,而且我在 google 中进行了几轮测试,并进行了大量的试验和错误,但似乎无法理解这种编码风格。

这是我的示例数据(将来将来自 sqlite 数据库):

(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"]]))

我最初使用 for 尝试逐步完成,然后转到 doseq 最后是 domap(这似乎更成功)并转储内容到 html 表中(我最初的 python 程序使用 COM 将它从 sqlite 数据库输出到 excel 电子表格中)。

这是我的尝试(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))

输出带有部分(受薪、经理等)和部分名称的表格,我只是觉得我通过嵌套太多次来滥用 domap,因为我可能需要添加更多的 domap只是为了在适当的列中获得轮班时间,并且代码给人一种“肮脏”的感觉。

如果我没有提供足够的信息,我提前道歉,我通常不会寻求编码方面的帮助,这也是我的第一个 SO 问题 :)。

如果您知道任何更好的方法来做到这一点,甚至是我作为新手应该知道的提示或技巧,他们绝对是受欢迎的。

谢谢。

【问题讨论】:

对于未来,您不应该像这个社区维基一样标记可回答的问题。这破坏了我们的声誉收集游戏。 ;-) 对不起,我没有意识到它抛弃了代表系统。我只是认为这意味着我的问题是可编辑的(无论如何都不需要):(。但是无论如何都感谢您的回答,我从您的帖子中学到了很多东西。:) 感谢您的帮助。我已经让它正确显示数据,我只是还没有决定如何从 SQL 数据库(比如我的 python 版本)创建那个凌乱的哈希映射,或者使用另一种方法,比如 nosql(我没有经验)...我应该在这里问,还是开始另一个问题? 【参考方案1】:

我认为您的代码存在一些小问题。我已经尝试在下面的代码中更正它们。用 Compojure 0.3.2 进行测试,我敢说它有效。 (当然,请随意指出任何需要改进或似乎不适合您的地方。)

(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))

【讨论】:

hmm,实际上喜欢使用 div,这是包含嵌套循环的好方法(这是我从 for 移到第一个位置的主要原因,因为抱怨有太多参数)。谢谢 哎呀,Brian 的帖子让我意识到我未能将 :h1:hr:table 包装在 create-table 中的 html 表单中,所以我一直在扔它们离开太...将在几秒钟内修复。至于:div,我认为它也可以,实际上是为了最清晰的代码,尽管有一些concats / list*s 等,原则上你可以不用它。 我认为 HTML 标准不允许在 div 中包装表格行或单元格。不过我可能是错的。 显然这是真的——validator.w3.org 拒绝 <table><div><tr><td>asdf</td></tr></div></table> 并带有指向错误嵌套标签的错误消息。哦,好吧,更有理由采用您的清洁器 (list*) 方法。感谢您的提醒!【参考方案2】:

没有办法避免某种嵌套循环。但是您根本不需要domap,Compojure 足够聪明(有时)可以为您扩展序列。 listmapfor 就足够了。例如 Michał Marczyk 的回答,或者:

(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)])))]]))

“映射一个 seq 并将所有内容包装在同一个标​​签中”模式很常见,以至于我有时喜欢为其使用辅助函数。

Compojure 为您扩展了一层seq。因此,您可以将一些东西放入list 以使标签按顺序出现在您的 HTML 输出中,我这样做是为了让 h1 和 hr 显示出来。在您的代码中,您只是将 h1 和 hr 扔掉了。

但请注意,它只扩展了一层。当你有一个列表列表或序列列表时,外部序列会扩展,但内部序列不会。

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

看看内部 seq 如何让 Compojure 吐槽。查看 compojure.html.gen/expand-seqs 以了解其工作原理,或根据需要更改/修复它。

当您将forfor 嵌套在list 中时,这可能会出现问题,因为for 返回一个惰性序列。但是您只需要避免使用 seq-in-a-seq。我在上面使用list*listhtml 的组合也可以。还有很多其他方法。

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>

【讨论】:

哇,谢谢,我真的很喜欢 map-tag 辅助函数的想法,而且你给我做了研究列表*(我也不知道它存在)

以上是关于从嵌套地图(和矢量)创建 HTML 表格的主要内容,如果未能解决你的问题,请参考以下文章

html中的表单中怎么嵌套表格

求这套html嵌套表格代码

嵌套 HTML 表格的匹配表格高度

html5中怎样把表格和表单合并

用表格和表单制作如下网页。完整html代码

html中表格与表单的问题