Skip to main content

cope_dom/
elements.rs

1use crate::sealed::Sealed;
2use cope::singleton::{react, Atom};
3use wasm_bindgen::{prelude::*, JsCast};
4use web_sys::{window, Element, Event, EventTarget, HtmlButtonElement};
5
6#[must_use]
7pub struct ElementBuilder<E> {
8    element: E,
9}
10
11impl<E> ElementBuilder<E> {
12    pub fn new(element: E) -> Self {
13        ElementBuilder { element }
14    }
15}
16
17impl<E> ElementBuilder<E> {
18    pub fn build(self) -> E {
19        self.element
20    }
21}
22
23impl<E> AsRef<E> for ElementBuilder<E> {
24    fn as_ref(&self) -> &E {
25        &self.element
26    }
27}
28
29impl<E: AsRef<Element>> ElementBuilder<E> {
30    pub fn child(self, value: impl ElementChild) -> Self {
31        value.append(&self);
32        self
33    }
34
35    pub fn class_name(self, value: &str) -> Self {
36        self.element.as_ref().set_class_name(value);
37        self
38    }
39
40    pub fn id(self, value: &str) -> Self {
41        self.element.as_ref().set_id(value);
42        self
43    }
44
45    pub fn on_click(self, f: impl FnMut() + 'static) -> Self {
46        self.element
47            .as_ref()
48            .add_event_listener_with_callback(
49                "click",
50                Closure::wrap(Box::new(f) as Box<dyn FnMut()>)
51                    .into_js_value()
52                    .unchecked_ref(),
53            )
54            .unwrap_throw();
55        self
56    }
57}
58
59pub trait ElementChild: Sealed {
60    fn append<P: AsRef<Element>>(self, parent: &ElementBuilder<P>);
61}
62
63impl<T: ElementChild> Sealed for T {}
64
65impl<E: AsRef<Element>> ElementChild for ElementBuilder<E> {
66    fn append<P: AsRef<Element>>(self, parent: &ElementBuilder<P>) {
67        let parent = parent.element.as_ref();
68        let node = self.build();
69        parent.append_with_node_1(node.as_ref()).unwrap_throw();
70    }
71}
72
73impl ElementChild for &str {
74    fn append<P: AsRef<Element>>(self, parent: &ElementBuilder<P>) {
75        parent
76            .element
77            .as_ref()
78            .append_with_str_1(self)
79            .unwrap_throw();
80    }
81}
82
83impl ElementChild for String {
84    fn append<P: AsRef<Element>>(self, parent: &ElementBuilder<P>) {
85        ElementChild::append(self.as_str(), parent);
86    }
87}
88
89impl ElementChild for &Atom<String> {
90    fn append<P: AsRef<Element>>(self, parent: &ElementBuilder<P>) {
91        let document = window().unwrap_throw().document().unwrap_throw();
92        let node = document.create_text_node("");
93
94        let parent = parent.as_ref().as_ref();
95        parent.append_with_node_1(&node).unwrap_throw();
96
97        let value = self.clone();
98        react(move || {
99            node.set_node_value(Some(&value.get()));
100        });
101    }
102}
103
104impl<E: AsRef<EventTarget>> ElementBuilder<E> {
105    pub fn add_event_listener_with_callback(
106        self,
107        type_: &str,
108        listener: impl Fn(Event) + 'static,
109    ) -> Self {
110        self.as_ref()
111            .as_ref()
112            .add_event_listener_with_callback(
113                type_,
114                Closure::wrap(Box::new(listener) as Box<dyn Fn(Event)>)
115                    .into_js_value()
116                    .unchecked_ref(),
117            )
118            .unwrap_throw();
119        self
120    }
121}
122
123impl<E: AsRef<HtmlButtonElement>> ElementBuilder<E> {
124    pub fn type_(self, value: &str) -> Self {
125        self.element.as_ref().set_type(value);
126        self
127    }
128}
129
130macro_rules! define_builder {
131    ($name:ident => $type:ident) => {
132        pub fn $name() -> ElementBuilder<::web_sys::$type> {
133            let document = window().unwrap_throw().document().unwrap_throw();
134            let element = document.create_element(stringify!($name)).unwrap_throw();
135            ElementBuilder::new(element.unchecked_into())
136        }
137    };
138}
139
140define_builder!(a => Element);
141define_builder!(button => HtmlButtonElement);
142define_builder!(div => Element);
143define_builder!(h1 => Element);
144define_builder!(span => Element);
145define_builder!(table => Element);
146define_builder!(tbody => Element);
147define_builder!(td => Element);
148define_builder!(tr => Element);
149
150#[cfg(test)]
151mod tests {
152    use crate::elements::{div, span};
153    use wasm_bindgen_test::wasm_bindgen_test;
154
155    #[wasm_bindgen_test]
156    fn build() {
157        let div = div().build();
158        assert_eq!(div.node_name(), "DIV");
159    }
160
161    #[wasm_bindgen_test]
162    fn child_element() {
163        let div = div().child(span()).build();
164        assert_eq!(div.outer_html(), "<div><span></span></div>");
165    }
166
167    #[wasm_bindgen_test]
168    fn child_str() {
169        let div = div().child("str").build();
170        assert_eq!(div.outer_html(), "<div>str</div>");
171    }
172
173    #[wasm_bindgen_test]
174    fn child_string() {
175        let div = div().child("string".to_string()).build();
176        assert_eq!(div.outer_html(), "<div>string</div>");
177    }
178}