sauron_core/html.rs
1//! Provides functions and macros to build html elements
2use crate::vdom;
3use crate::vdom::Leaf;
4pub use crate::vdom::{element, element_ns};
5use crate::vdom::{Attribute, Node};
6use std::borrow::Cow;
7pub use tags::{commons::*, self_closing::*, *};
8
9#[macro_use]
10pub mod attributes;
11#[cfg(feature = "with-lookup")]
12pub mod lookup;
13pub mod tags;
14pub mod units;
15
16#[cfg(feature = "with-dom")]
17pub use crate::dom::events;
18
19/// A help function which render the view when the condition is met, otherwise
20/// just display a `span(vec![], vec![])`
21///
22/// # Examples
23/// ```rust
24/// use sauron::*;
25///
26/// let content = "hello world";
27/// let html: Node<()> = view_if(!content.is_empty(), p(vec![], vec![text(content)]));
28///
29/// assert_eq!(node!{<p>"hello world"</p>}, html);
30/// ```
31/// Note: that the node here is already evaluated therefore not suitable for building the nodes
32/// prior and will end up not being displayed.
33/// An alternative would be to just use if else statement like so:
34/// ```ignore
35/// if flag{
36/// expensive_code_to_build_the_view()
37/// }else{
38/// comment("not yet ready")
39/// }
40/// ```
41pub fn view_if<MSG>(flag: bool, node: Node<MSG>) -> Node<MSG> {
42 if flag {
43 node
44 } else {
45 comment("hidden")
46 }
47}
48
49/// evaluate the fn_node only if flag is true and return the evaluated Node
50pub fn lazy_view_if<F, MSG>(flag: bool, fn_node: F) -> Node<MSG>
51where
52 F: Fn() -> Node<MSG>,
53{
54 if flag {
55 fn_node()
56 } else {
57 comment("hidden")
58 }
59}
60
61/// Creates an html element with the element tag name and namespace
62/// This is specifically used for creating svg element where a namespace is needed, otherwise the
63/// browser will not render it correctly.
64/// # Examples
65/// ```rust
66/// use sauron::{*,html::html_element};
67///
68/// let html:Node<()> =
69/// html_element(Some("http://www.w3.org/2000/svg"),"svg", vec![width(200), height(200), xmlns("http://www.w3.org/2000/svg")], vec![], false);
70/// assert_eq!(node!{<svg width=200 height=200 xmlns="http://www.w3.org/2000/svg"></svg>}, html);
71/// ```
72pub fn html_element<MSG>(
73 namespace: Option<&'static str>,
74 tag: &'static str,
75 attrs: impl IntoIterator<Item = Attribute<MSG>>,
76 children: impl IntoIterator<Item = Node<MSG>>,
77 self_closing: bool,
78) -> Node<MSG> {
79 // we do a correction to children where text node siblings are next to each other by inserting
80 // a comment separator in between them, to prevent the browser from merging the 2 text node
81 // together
82 let mut corrected_children: Vec<Node<MSG>> = vec![];
83 for child in children {
84 if let Some(last) = corrected_children.last() {
85 //TODO: put this behind a flag: #auto-separator to automatically
86 //add separator between text nodes
87 if last.is_text() && child.is_text() {
88 corrected_children.push(comment("separator"));
89 }
90 }
91 corrected_children.push(child);
92 }
93 element_ns(namespace, tag, attrs, corrected_children, self_closing)
94}
95
96/// creates a text node using a formatter
97/// # Examples
98/// ```rust
99/// use sauron::*;
100///
101/// let number = 42;
102/// let title:Node<()> = h1(vec![], vec![text!("This is the content number: {}", number)]);
103///
104/// assert_eq!(node!{<h1>"This is the content number: 42"</h1>}, title);
105/// ```
106#[macro_export]
107macro_rules! text {
108 ( $($arg: tt)* ) => {
109 $crate::html::text(format!($($arg)*))
110 };
111}
112
113/// Create a text node element
114/// # Example
115/// ```rust
116/// use sauron::*;
117/// let node: Node<()> = text("hi");
118/// ```
119pub fn text<MSG>(s: impl ToString) -> Node<MSG> {
120 Node::Leaf(Leaf::Text(Cow::from(s.to_string())))
121}
122
123/// create a comment node
124/// # Example
125/// ```rust
126/// use sauron::*;
127/// let node: Node<()> = comment("This is a comment");
128/// ```
129pub fn comment<MSG>(s: impl Into<Cow<'static, str>>) -> Node<MSG> {
130 Node::Leaf(Leaf::Comment(s.into()))
131}
132
133/// fragment is a list of nodes
134/// # Example
135/// ```rust
136/// use sauron::{*, html::*};
137///
138/// let node: Node<()> = fragment([div([],[]), span([],[])]);
139/// ```
140pub fn fragment<MSG>(nodes: impl IntoIterator<Item = Node<MSG>>) -> Node<MSG> {
141 vdom::fragment(nodes)
142}
143
144/// create a doctype
145pub fn doctype<MSG>(s: impl Into<Cow<'static, str>>) -> Node<MSG> {
146 Node::Leaf(Leaf::DocType(s.into()))
147}
148
149/// create a node which contains a list of nodes
150pub fn node_list<MSG>(nodes: impl IntoIterator<Item = Node<MSG>>) -> Node<MSG> {
151 Node::Leaf(Leaf::NodeList(nodes.into_iter().collect()))
152}
153
154/// Create html entities such as ` ` `>`
155pub fn symbol<MSG>(s: &str) -> Node<MSG> {
156 let s = escape_html_text(s);
157 Node::Leaf(Leaf::Symbol(s.into()))
158}
159
160fn escape_html_text(s: &str) -> String {
161 s.chars()
162 .map(|ch| match ch {
163 '>' => Cow::from(">"),
164 '<' => Cow::from("<"),
165 '\'' => Cow::from("'"),
166 '"' => Cow::from("""),
167 _ => Cow::from(ch.to_string()),
168 })
169 .collect()
170}