1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 # HTML output facilities
20 # You can define subclass and override methods head and body
25 # redef body do add("p").text("Hello World!")
29 # HTMLPage use fluent interface so you can chain calls as:
32 # add("div").attr("id", "mydiv").text("My Div")
42 private var root
= new HTMLTag("html")
43 private var current
: HTMLTag = root
44 private var stack
= new List[HTMLTag]
46 redef fun write_to
(stream
) do
54 stream
.write
"<!DOCTYPE html>"
58 # Add a html tag to the current element
61 # add("div").attr("id", "mydiv").text("My Div")
63 fun add
(tag
: String): HTMLTag do
64 var node
= new HTMLTag(tag
)
69 # Add a raw html string
72 # add_html("<a href='#top'>top</a>")
74 fun add_html
(html
: String) do current
.add
(new HTMLRaw("", html
))
80 # add("li").text("item1")
81 # add("li").text("item2")
84 fun open
(tag
: String): HTMLTag do
90 # Close previously opened tag
91 # Ensure: tag = previous.tag
92 fun close
(tag
: String) do
93 if not tag
== current
.tag
then
94 print
"Error: Trying to close '{tag}', last opened tag was '{current.tag}'."
107 # `"div"` for `<div></div>`.
110 self.is_void
= (once void_list
).has
(tag
)
113 private fun void_list
: Set[String]
115 return new HashSet[String].from
(["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"])
118 # Is the HTML element a void element?
120 # assert (new HTMLTag("img")).is_void == true
121 # assert (new HTMLTag("p")).is_void == false
122 var is_void
: Bool is noinit
124 # Create a HTML elements with the specifed type and attributes.
125 init with_attrs
(tag
: String, attrs
: Map[String, String]) do
131 var attrs
: Map[String, String] = new HashMap[String, String] is lazy
133 # Get the attributed value of 'prop' or null if 'prop' is undifened
135 # var img = new HTMLTag("img")
136 # img.attr("src", "./image.png").attr("alt", "image")
137 # assert img.get_attr("src") == "./image.png"
138 fun get_attr
(key
: String): nullable String do
139 if not attrs
.has_key
(key
) then return null
143 # Set a 'value' for 'key'
145 # var img = new HTMLTag("img")
146 # img.attr("src", "./image.png").attr("alt", "image")
147 # assert img.write_to_string == """<img src="./image.png" alt="image"/>"""
148 fun attr
(key
: String, value
: String): HTMLTag do
153 # Add a CSS class to the HTML tag
155 # var img = new HTMLTag("img")
156 # img.add_class("logo").add_class("fullpage")
157 # assert img.write_to_string == """<img class="logo fullpage"/>"""
158 fun add_class
(klass
: String): HTMLTag do
164 var classes
: Set[String] = new HashSet[String] is lazy
166 # Add multiple CSS classes
168 # var img = new HTMLTag("img")
169 # img.add_classes(["logo", "fullpage"])
170 # assert img.write_to_string == """<img class="logo fullpage"/>"""
171 fun add_classes
(classes
: Collection[String]): HTMLTag do
172 self.classes
.add_all
(classes
)
176 # Set a CSS 'value' for 'prop'
178 # var img = new HTMLTag("img")
179 # img.css("border", "2px solid black").css("position", "absolute")
180 # assert img.write_to_string == """<img style="border: 2px solid black; position: absolute"/>"""
181 fun css
(prop
: String, value
: String): HTMLTag do
182 css_props
[prop
] = value
185 private var css_props
: Map[String, String] = new HashMap[String, String] is lazy
187 # Get CSS value for 'prop'
189 # var img = new HTMLTag("img")
190 # img.css("border", "2px solid black").css("position", "absolute")
191 # assert img.get_css("border") == "2px solid black"
192 # assert img.get_css("color") == null
193 fun get_css
(prop
: String): nullable String do
194 if not css_props
.has_key
(prop
) then return null
195 return css_props
[prop
]
198 # Replace `self` by `parent`.
200 # var elem = new HTMLTag("li")
201 # elem.add_outer(new HTMLTag("ul"))
202 # assert elem.write_to_string == "<ul><li></li></ul>"
203 fun add_outer
(parent
: HTMLTag) do
204 # copy self in new object
205 var child
= new HTMLTag(self.tag
)
206 child
.attrs
= self.attrs
207 child
.classes
= self.classes
208 child
.css_props
= self.css_props
209 child
.children
= self.children
210 # add copy in parent children elements
211 parent
.children
.add
(child
)
212 # replace self by parent
213 self.tag
= parent
.tag
214 self.attrs
= parent
.attrs
215 self.classes
= parent
.classes
216 self.css_props
= parent
.css_props
217 self.is_void
= parent
.is_void
218 self.children
= parent
.children
221 # Add a HTML 'child' to self
223 # var ul = new HTMLTag("ul")
224 # ul.add(new HTMLTag("li"))
225 # assert ul.write_to_string == "<ul><li></li></ul>"
226 # returns `self` for fluent programming
227 fun add
(child
: HTMLTag): HTMLTag
233 # Create a new HTMLTag child and return it
235 # var ul = new HTMLTag("ul")
236 # ul.open("li").append("1").append("2")
237 # ul.open("li").append("3").append("4")
238 # assert ul.write_to_string == "<ul><li>12</li><li>34</li></ul>"
239 fun open
(tag
: String): HTMLTag
241 var res
= new HTMLTag(tag
)
246 # List of children HTML elements
247 var children
: Set[HTMLTag] = new HashSet[HTMLTag] is lazy
249 # Clear all child and set the text of element
251 # var p = new HTMLTag("p")
252 # p.text("Hello World!")
253 # assert p.write_to_string == "<p>Hello World!</p>"
254 # Text is escaped see: `core::String::html_escape`
255 fun text
(txt
: String): HTMLTag do
257 if isset _children
then children
.clear
262 # Append text to element
264 # var p = new HTMLTag("p")
266 # p.add(new HTMLTag("br"))
268 # assert p.write_to_string == "<p>Hello<br/>World!</p>"
269 # Text is escaped see: core::String::html_escape
270 fun append
(txt
: String): HTMLTag do
271 add
(new HTMLRaw("", txt
.html_escape
))
275 # Append raw HTML to element
277 # var p = new HTMLTag("p")
279 # p.add_raw_html("<bla/>foo")
280 # assert p.write_to_string == "<p>Hello<bla/>foo</p>"
282 # Note: the HTML in insered as it, no verification is done.
283 fun add_raw_html
(txt
: String): HTMLTag do
284 add
(new HTMLRaw("", txt
))
288 redef fun write_to
(stream
) do
289 var res
= new Array[String]
296 # In order to avoid recursive concatenation,
297 # this function collects in `res` all the small fragments of `String`
298 private fun render_in
(res
: Sequence[String])
303 if is_void
and (not isset _children
or children
.is_empty
) then
307 if isset _children
then for child
in children
do child
.render_in
(res
)
314 private fun render_attrs_in
(res
: Sequence[String]) do
315 if not isset _attrs
and not isset _classes
and not isset _css_props
then return
316 if attrs
.has_key
("class") or not classes
.is_empty
then
318 for cls in classes do
319 res.add cls.html_escape
322 if attrs.has_key("class") then
323 res.add attrs["class"].html_escape
326 if res.last == " " then res.pop
330 if attrs
.has_key
("style") or not css_props
.is_empty
then
332 for k, v in css_props do
333 res.add k.html_escape
335 res.add v.html_escape
338 if attrs.has_key("style
") then
339 res.add(attrs["style
"].html_escape)
341 if res.last == "; " then res.pop
345 if attrs
.is_empty
then return
347 for key
, value
in attrs
do
348 if key
== "class" or key
== "style" then continue
350 res
.add key
.html_escape
352 res.add value.html_escape
358 private class HTMLRaw
362 redef fun render_in
(res
) do res
.add content