https://github.com/stch-library/html.git
git clone 'https://github.com/stch-library/html.git'
(ql:quickload :stch-library.html)
DSL for HTML generation. Supports Clojure and ClojureScript.
Based on ideas and some code from Hoplon.
Add the following to your project dependencies:
[stch-library/html "0.1.2"]
http://stch-library.github.io/html
Note: This library uses stch.schema. Please refer to that project page for more information regarding type annotations and their meaning.
The following examples are all in Clojure.
(use 'stch.html)
(defhtml main [users]
(html5)
(html
(head
(title "My Page"))
(body
(section
(header
(h1 "Users"))
(ul :id "users"
(for [x users]
(li x))))
(footer "Copyright 2014"))))
(main ["Billy" "Bobby"])
; "<!DOCTYPE html><html><head><title>My Page</title></head><body><section><header><h1>Users</h1></header><ul id=\"users\"><li>Billy</li><li>Bobby</li></ul></section><footer>Copyright 2014</footer></body></html>"
(deffrag user [id first-name]
(div :id id first-name))
(user 1 "Billy")
; (#stch.html.Element{:tag "div", :attrs {:id 1}, :children ["Billy"]})
The first thing you'll notice is that we're calling fns instead of creating vectors of keywords. This has a number of advantages. First, the code itself is less cluttered and looks more like plain html. Second, we can compose html in really cool ways.
Child node appending:
(def users
(ul
(li "Billy")
(li "Bobby")))
(->html users)
; "<ul><li>Billy</li><li>Bobby</li></ul>"
Let's append an li element to our ul.
(-> (users (li "Joey"))
->html)
; "<ul><li>Billy</li><li>Bobby</li><li>Joey</li></ul>"
Attribute appending:
(def page-title
(h1 :class "big" "My Page Title"))
(->html page-title)
; "<h1 class=\"big\">My Page Title</h1>"
Let's add an id and one more class to our h1.
(->html (page-title :id "my-page" :class "blue"))
; "<h1 class=\"big blue\" id=\"my-page\">My Page Title</h1>"
This turns out to be really useful when you want to create fns that return generic html (think form elements like select) and add a specific class without having to pass a map of attributes up front.
Let's take a look at an example.
(use 'stch.html.form)
(def states [["CA" "California"]
["FL" "Florida"]])
(def sel
(->select "states" states))
(->html sel)
; "<select name=\"states\"><option value=\"CA\">California</option><option value=\"FL\">Florida</option></select>"
(->html (sel :class "us-states"))
; "<select name=\"states\" class=\"us-states\"><option value=\"CA\">California</option><option value=\"FL\">Florida</option></select>"
(-> (sel :class "us-states"
(option :value "NY" "New York"))
->html)
; "<select name=\"states\" class=\"us-states\"><option value=\"CA\">California</option><option value=\"FL\">Florida</option><option value=\"NY\">New York</option></select>"
No attributes, no children.
(div)
; #stch.html.Element{:tag "div", :attrs {}, :children []}
Keyword attributes.
(div :class "big" :id "my-div")
; #stch.html.Element{:tag "div", :attrs {:class "big", :id "my-div"}, :children []}
Map of attributes, where the keys are keywords and the values are any value that can be converted into a string.
(div {:class "big" :id "my-div"})
; #stch.html.Element{:tag "div", :attrs {:class "big", :id "my-div"}, :children []}
No attributes, with children.
(ul
(li)
(li))
; #stch.html.Element{:tag "ul", :attrs {}, :children [#stch.html.Element{:tag "li", :attrs {}, :children []} #stch.html.Element{:tag "li", :attrs {}, :children []}]}
Attributes and children.
(ul :class "user-list"
(li :class "user" "Billy")
(li :class "user" "Bobby"))
; #stch.html.Element{:tag "ul", :attrs {:class "user-list"}, :children [#stch.html.Element{:tag "li", :attrs {:class "user"}, :children ["Billy"]} #stch.html.Element{:tag "li", :attrs {:class "user"}, :children ["Bobby"]}]}
Element functions return an stch.html.Element record. It's best to keep your markup in this form as long as possible. When you're ready to convert to a string, use the →html fn.
(def user-list
(ul :class "user-list"
(li :class "user" "Billy")
(li :class "user" "Bobby")))
(->html user-list)
; "<ul class=\"user-list\"><li class=\"user\">Billy</li><li class=\"user\">Bobby</li></ul>"
The class attribute is special in that you can pass a String, Set, or Sequential type (vector, list, etc.) to it. Let's take a look at some examples.
(->html (div :class "big bold"))
; "<div class=\"big bold\"></div>"
(->html (div :class #{"big" "bold"}))
; "<div class=\"big bold\"></div>"
(->html (div :class ["big" "bold"]))
; "<div class=\"big bold\"></div>"
There are two macros that can be used to define functions that contains html elements.
(defhtml user [name age]
(div :class "user-name" name)
(div :class "user-age" age))
(user "Billy" 35)
; "<div class=\"user-name\">Billy</div><div class=\"user-age\">35</div>"
defhtml automatically wraps the fn body in a call to →html.
(deffrag user [name age]
(div :class "user-name" name)
(div :class "user-age" age))
(user "Billy" 35)
; (#stch.html.Element{:tag "div", :attrs {:class "user-name"}, :children ["Billy"]} #stch.html.Element{:tag "div", :attrs {:class "user-age"}, :children [35]})
deffrag, on the other hand, does not call →html. It does wrap the fn body in a list.
Text nodes are automatically escaped for security. If you need to render a string without having it escaped, wrap it in a call to raw.
; Escaped
(->html "<script></script>")
; "<script></script>"
; Unescaped
(->html (raw "<script></script>"))
; "<script></script>"
There are a few form helper functions.
(def states [["CA" "California"]
["FL" "Florida"]])
(->html (->options states))
; "<option value=\"CA\">California</option><option value=\"FL\">Florida</option>"
(->html (->select "states" states "CA"))
; "<select name=\"states\"><option selected=\"selected\" value=\"CA\">California</option><option value=\"FL\">Florida</option></select>"
(->html (->radio "states" states "CA"))
; "<input checked=\"checked\" type=\"radio\" name=\"states\" value=\"CA\" id=\"states-0\"><label class=\"form-radio-label\" for=\"states-0\">California</label><input type=\"radio\" name=\"states\" value=\"FL\" id=\"states-1\"><label class=\"form-radio-label\" for=\"states-1\">Florida</label>"
(->html (->checkbox "states" states "CA"))
; "<input checked=\"checked\" type=\"checkbox\" name=\"states\" value=\"CA\" id=\"states-0\"><label class=\"form-checkbox-label\" for=\"states-0\">California</label><input type=\"checkbox\" name=\"states\" value=\"FL\" id=\"states-1\"><label class=\"form-checkbox-label\" for=\"states-1\">Florida</label>"
Finally there are some fns for generating common doctypes.
(html5)
; #stch.html.Doctype{:declaration "html"}
(html4-strict)
; #stch.html.Doctype{:declaration "HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\"\n\"http://www.w3.org/TR/html4/strict.dtd\""}
(html4-trans)
; #stch.html.Doctype{:declaration "HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\n\"http://www.w3.org/TR/html4/loose.dtd\""}
Run “lein spec”