#
# You can define subclass and override methods head and body
#
+# ~~~nitish
# class MyPage
# super HTMLPage
# redef body do add("p").text("Hello World!")
# end
+# ~~~
#
# HTMLPage use fluent interface so you can chain calls as:
-# add("div").attr("id", "mydiv").text("My Div")
+#
+# ~~~nitish
+# add("div").attr("id", "mydiv").text("My Div")
+# ~~~
class HTMLPage
- super Streamable
+ super Writable
# Define head content
fun head do end
end
# Add a html tag to the current element
+ #
+ # ~~~nitish
# add("div").attr("id", "mydiv").text("My Div")
+ # ~~~
fun add(tag: String): HTMLTag do
var node = new HTMLTag(tag)
current.add(node)
end
# Add a raw html string
+ #
+ # ~~~nitish
# add_html("<a href='#top'>top</a>")
+ # ~~~
fun add_html(html: String) do current.add(new HTMLRaw("", html))
# Open a html tag
+ #
+ # ~~~nitish
# open("ul")
# add("li").text("item1")
# add("li").text("item2")
# close("ul")
+ # ~~~
fun open(tag: String): HTMLTag do
stack.push(current)
current = add(tag)
end
end
+# An HTML element.
class HTMLTag
- super Streamable
+ super Writable
- # HTML tagname: 'div' for <div></div>
+ # HTML element type.
+ #
+ # `"div"` for `<div></div>`.
var tag: String
init do
- self.is_void = (once ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"]).has(tag)
+ self.is_void = (once void_list).has(tag)
+ end
+
+ private fun void_list: Set[String]
+ do
+ return new HashSet[String].from(["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"])
end
# Is the HTML element a void element?
# assert (new HTMLTag("p")).is_void == false
var is_void: Bool is noinit
+ # Create a HTML elements with the specifed type and attributes.
init with_attrs(tag: String, attrs: Map[String, String]) do
- self.tag = tag
+ init(tag)
self.attrs = attrs
end
# Tag attributes map
- var attrs: Map[String, String] = new HashMap[String, String]
+ var attrs: Map[String, String] = new ArrayMap[String, String] is lazy
# Get the attributed value of 'prop' or null if 'prop' is undifened
+ #
# var img = new HTMLTag("img")
# img.attr("src", "./image.png").attr("alt", "image")
# assert img.get_attr("src") == "./image.png"
end
# Set a 'value' for 'key'
+ #
# var img = new HTMLTag("img")
# img.attr("src", "./image.png").attr("alt", "image")
# assert img.write_to_string == """<img src="./image.png" alt="image"/>"""
end
# Add a CSS class to the HTML tag
+ #
# var img = new HTMLTag("img")
# img.add_class("logo").add_class("fullpage")
# assert img.write_to_string == """<img class="logo fullpage"/>"""
end
# CSS classes
- var classes: Set[String] = new HashSet[String]
+ var classes: Set[String] = new ArraySet[String] is lazy
# Add multiple CSS classes
+ #
# var img = new HTMLTag("img")
# img.add_classes(["logo", "fullpage"])
# assert img.write_to_string == """<img class="logo fullpage"/>"""
end
# Set a CSS 'value' for 'prop'
+ #
# var img = new HTMLTag("img")
# img.css("border", "2px solid black").css("position", "absolute")
# assert img.write_to_string == """<img style="border: 2px solid black; position: absolute"/>"""
css_props[prop] = value
return self
end
- private var css_props: Map[String, String] = new HashMap[String, String]
+ private var css_props: Map[String, String] = new HashMap[String, String] is lazy
# Get CSS value for 'prop'
+ #
# var img = new HTMLTag("img")
# img.css("border", "2px solid black").css("position", "absolute")
# assert img.get_css("border") == "2px solid black"
end
# Add a HTML 'child' to self
+ #
# var ul = new HTMLTag("ul")
# ul.add(new HTMLTag("li"))
# assert ul.write_to_string == "<ul><li></li></ul>"
end
# List of children HTML elements
- var children: Set[HTMLTag] = new HashSet[HTMLTag]
+ var children: Array[HTMLTag] = new Array[HTMLTag] is lazy
# Clear all child and set the text of element
+ #
# var p = new HTMLTag("p")
# p.text("Hello World!")
# assert p.write_to_string == "<p>Hello World!</p>"
- # Text is escaped see: `standard::String::html_escape`
+ # Text is escaped see: `core::String::html_escape`
fun text(txt: String): HTMLTag do
- children.clear
+ if isset _children then children.clear
append(txt)
return self
end
# Append text to element
+ #
# var p = new HTMLTag("p")
# p.append("Hello")
# p.add(new HTMLTag("br"))
# p.append("World!")
# assert p.write_to_string == "<p>Hello<br/>World!</p>"
- # Text is escaped see: standard::String::html_escape
+ # Text is escaped see: core::String::html_escape
fun append(txt: String): HTMLTag do
add(new HTMLRaw("", txt.html_escape))
return self
end
redef fun write_to(stream) do
- var res = new Array[String]
- render_in(res)
- for r in res do
- stream.write(r)
- end
- end
-
- # In order to avoid recursive concatenation,
- # this function collects in `res` all the small fragments of `String`
- private fun render_in(res: Sequence[String])
- do
- res.add "<"
- res.add tag
- render_attrs_in(res)
- if is_void and children.is_empty then
- res.add "/>"
+ stream.write "<"
+ stream.write tag
+ render_attrs_in(stream)
+ if is_void and (not isset _children or children.is_empty) then
+ stream.write "/>"
else
- res.add ">"
- for child in children do child.render_in(res)
- res.add "</"
- res.add tag
- res.add ">"
+ stream.write ">"
+ if isset _children then for child in children do child.write_to(stream)
+ stream.write "</"
+ stream.write tag
+ stream.write ">"
end
end
- private fun render_attrs_in(res: Sequence[String]) do
+ private fun render_attrs_in(stream: Writer) do
+ if not isset _attrs and not isset _classes and not isset _css_props then return
if attrs.has_key("class") or not classes.is_empty then
- res.add " class=\""
+ stream.write " class=\""
+ var first = true
for cls in classes do
- res.add cls.html_escape
- res.add " "
+ if not first then stream.write " " else first = false
+ stream.write cls.html_escape
end
if attrs.has_key("class") then
- res.add attrs["class"].html_escape
- res.add " "
+ if not first then stream.write " " else first = false
+ stream.write attrs["class"].html_escape
end
- if res.last == " " then res.pop
- res.add "\""
+ stream.write "\""
end
if attrs.has_key("style") or not css_props.is_empty then
- res.add " style=\""
+ stream.write " style=\""
+ var first = true
for k, v in css_props do
- res.add k.html_escape
- res.add ": "
- res.add v.html_escape
- res.add "; "
+ if not first then stream.write "; " else first = false
+ stream.write k.html_escape
+ stream.write ": "
+ stream.write v.html_escape
end
if attrs.has_key("style") then
- res.add(attrs["style"].html_escape)
+ if not first then stream.write "; " else first = false
+ stream.write(attrs["style"].html_escape)
end
- if res.last == "; " then res.pop
- res.add "\""
+ stream.write "\""
end
if attrs.is_empty then return
for key, value in attrs do
if key == "class" or key == "style" then continue
- res.add " "
- res.add key.html_escape
- res.add "=\""
- res.add value.html_escape
- res.add "\""
+ stream.write " "
+ stream.write key.html_escape
+ stream.write "=\""
+ stream.write value.html_escape
+ stream.write "\""
end
end
end
super HTMLTag
var content: String
- redef fun render_in(res) do res.add content
+ redef fun write_to(stream) do stream.write content
end