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 `&nbsp;` `&gt`
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("&gt;"),
164            '<' => Cow::from("&lt;"),
165            '\'' => Cow::from("&#39;"),
166            '"' => Cow::from("&quot;"),
167            _ => Cow::from(ch.to_string()),
168        })
169        .collect()
170}