windjammer_ui/
simple_vnode.rs1use std::cell::RefCell;
7use std::rc::Rc;
8
9#[cfg(target_arch = "wasm32")]
10use wasm_bindgen::prelude::*;
11#[cfg(target_arch = "wasm32")]
12use web_sys::{Document, Element, Event};
13
14#[derive(Clone)]
16pub enum VNode {
17 Element {
18 tag: String,
19 attrs: Vec<(String, VAttr)>,
20 children: Vec<VNode>,
21 },
22 Text(String),
23}
24
25#[derive(Clone)]
27pub enum VAttr {
28 Static(String),
29 Dynamic(String),
30 Event(Rc<RefCell<dyn FnMut()>>),
31}
32
33impl VNode {
34 pub fn element(tag: &str, attrs: Vec<(&str, VAttr)>, children: Vec<VNode>) -> Self {
36 VNode::Element {
37 tag: tag.to_string(),
38 attrs: attrs.into_iter().map(|(k, v)| (k.to_string(), v)).collect(),
39 children,
40 }
41 }
42
43 pub fn text(content: &str) -> Self {
45 VNode::Text(content.to_string())
46 }
47
48 #[cfg(target_arch = "wasm32")]
50 pub fn render(&self, document: &Document) -> Result<web_sys::Node, JsValue> {
51 match self {
52 VNode::Element {
53 tag,
54 attrs,
55 children,
56 } => {
57 let element = document.create_element(tag)?;
58
59 for (name, value) in attrs {
61 match value {
62 VAttr::Static(v) | VAttr::Dynamic(v) => {
63 if name == "class" {
64 element.set_attribute("class", v)?;
65 } else if name.starts_with("on_") {
66 } else {
68 element.set_attribute(name, v)?;
69 }
70 }
71 VAttr::Event(handler) => {
72 if let Some(event_name) = name.strip_prefix("on_") {
73 let handler_clone = handler.clone();
74 let closure = Closure::wrap(Box::new(move |_event: Event| {
75 handler_clone.borrow_mut()();
76 })
77 as Box<dyn FnMut(Event)>);
78
79 element.add_event_listener_with_callback(
80 event_name,
81 closure.as_ref().unchecked_ref(),
82 )?;
83 closure.forget(); }
85 }
86 }
87 }
88
89 for child in children {
91 let child_node = child.render(document)?;
92 element.append_child(&child_node)?;
93 }
94
95 Ok(element.into())
96 }
97 VNode::Text(content) => {
98 let text_node = document.create_text_node(content);
99 Ok(text_node.into())
100 }
101 }
102 }
103
104 #[cfg(target_arch = "wasm32")]
106 pub fn mount(&self, parent: &Element) -> Result<(), JsValue> {
107 let document = parent
108 .owner_document()
109 .ok_or_else(|| JsValue::from_str("No document"))?;
110 let node = self.render(&document)?;
111 parent.append_child(&node)?;
112 Ok(())
113 }
114}
115
116#[cfg(not(target_arch = "wasm32"))]
117impl VNode {
118 pub fn render(&self) -> Result<String, String> {
119 match self {
120 VNode::Element {
121 tag,
122 attrs: _,
123 children,
124 } => {
125 let mut html = format!("<{}>", tag);
126 for child in children {
127 html.push_str(&child.render()?);
128 }
129 html.push_str(&format!("</{}>", tag));
130 Ok(html)
131 }
132 VNode::Text(content) => Ok(content.clone()),
133 }
134 }
135}