Skip to main content

afia_component/dom/
element.rs

1//! Types related to the DOM elements.
2
3use std::slice;
4use std::sync::Arc;
5
6use crate::dom::node::AsDomNode;
7use crate::dom::node_list::NodeList;
8use crate::ComponentImports;
9
10mod child;
11
12/// A DOM element.
13#[derive(Clone)]
14pub struct DomElement {
15    inner: Arc<DomElementInner>,
16}
17struct DomElementInner {
18    element: i64,
19    component_imports: ComponentImports,
20}
21impl DomElement {
22    /// If the handle is `0`, returns `None`.
23    /// Otherwise, returns a `DomElement`.
24    pub fn maybe_new(handle: i64, component_imports: ComponentImports) -> Option<Self> {
25        if handle == 0 {
26            return None;
27        }
28
29        Some(Self {
30            inner: Arc::new(DomElementInner {
31                element: handle,
32                component_imports,
33            }),
34        })
35    }
36
37    /// Will be deleted once we stop using it.
38    pub fn temporary_way_to_get_i64(&self) -> i64 {
39        self.to_i64()
40    }
41
42    pub(crate) fn component_imports(&self) -> &ComponentImports {
43        &self.inner.component_imports
44    }
45
46    pub(crate) fn component_imports_ptr(&self) -> *const std::ffi::c_void {
47        self.inner.component_imports.component_imports_ptr
48    }
49
50    pub(crate) fn to_i64(&self) -> i64 {
51        self.inner.element
52    }
53
54    /// Adding this because some of our older components are using older patterns and this will help
55    ///  with migrating them towards using this `afia-component` crate.
56    ///  DO NOT use this. It was introduced in the middle of a huge PR to help avoid needing to
57    ///  refctor everything at once.
58    pub fn do_not_use_this_for_new_stuff_temporary_public_way_to_create_using_existing_i64_handle(
59        &self,
60        element_handle: i64,
61    ) -> Option<Self> {
62        Self::maybe_new(element_handle, self.inner.component_imports.clone())
63    }
64
65    /// Set the text content of this element.
66    pub fn set_text_content(&self, text: &str) {
67        unsafe {
68            afia_component_sys::dom_element_set_text_content(
69                self.inner.component_imports.pointer(),
70                self.inner.element,
71                text.as_ptr(),
72                text.len(),
73            )
74        }
75    }
76
77    /// Get this element's previous element sibling.
78    ///
79    /// ## MDN Documentation
80    /// https://developer.mozilla.org/en-US/docs/Web/API/Element/previousElementSibling
81    pub fn previous_element_sibling(&self) -> Option<DomElement> {
82        let sibling = unsafe {
83            afia_component_sys::dom_element_previous_element_sibling(
84                self.imports_ptr(),
85                self.handle(),
86            )
87        };
88
89        DomElement::maybe_new(sibling, self.inner.component_imports.clone())
90    }
91
92    /// Get this element's next element sibling.
93    ///
94    /// ## MDN Documentation
95    /// https://developer.mozilla.org/en-US/docs/Web/API/Element/nextElementSibling
96    pub fn next_element_sibling(&self) -> Option<DomElement> {
97        let sibling = unsafe {
98            afia_component_sys::dom_element_next_element_sibling(self.imports_ptr(), self.handle())
99        };
100
101        DomElement::maybe_new(sibling, self.inner.component_imports.clone())
102    }
103
104    /// Get the element's tag name, such as "DIV" or "SPAN".
105    ///
106    /// ## MDN Documentation
107    /// https://developer.mozilla.org/en-US/docs/Web/API/Element/tagName
108    pub fn tag_name(&self) -> String {
109        // TODO: `20` was chosen arbitrarily. This will fail if the tag is more than 20 chars.
110        let mut buf = [0; 20];
111
112        let tag_len = unsafe {
113            afia_component_sys::element_tag_name(
114                self.imports_ptr(),
115                self.handle(),
116                buf.as_mut_ptr(),
117                buf.len(),
118            )
119        };
120        // TODO: If the buffer is was small try again with a larger buffer.
121        assert!(buf.len() as i32 >= tag_len);
122
123        let tag_slice = unsafe { slice::from_raw_parts(buf.as_ptr(), tag_len as usize) };
124        let tag = str::from_utf8(tag_slice).unwrap();
125        tag.to_string()
126    }
127
128    /// Simulate a click on the element.
129    ///
130    /// ## MDN Documentation
131    /// https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click
132    pub fn click(&self) {
133        unsafe { afia_component_sys::element_click(self.imports_ptr(), self.handle()) }
134    }
135
136    /// Get the element's child nodes.
137    ///
138    /// ## MDN Documentation
139    /// https://developer.mozilla.org/en-US/docs/Web/API/Node/childNodes
140    pub fn child_nodes(&self) -> NodeList {
141        let child_nodes =
142            unsafe { afia_component_sys::element_child_nodes(self.imports_ptr(), self.handle()) };
143        NodeList::new(child_nodes, self.component_imports().clone())
144    }
145
146    /// Get one of the element's attributes.
147    ///
148    /// ## MDN Documentation
149    /// https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute
150    pub fn get_attribute(&self, key: &str) -> Option<String> {
151        let mut out = [0; 255];
152        let result = unsafe {
153            afia_component_sys::element_get_attribute(
154                self.imports_ptr(),
155                self.handle(),
156                key.as_ptr(),
157                key.len(),
158                out.as_mut_ptr(),
159                out.len(),
160            )
161        };
162        match result {
163            ..-1 => {
164                todo!("error handling")
165            }
166            -1 => None,
167            0 => None,
168            1.. => {
169                let result = result as usize;
170                if out.len() < result {
171                    todo!("handle scenario where buffer was smaller than the value")
172                };
173                let value = str::from_utf8(&out[0..result]).unwrap();
174                Some(value.to_string())
175            }
176        }
177    }
178
179    /// Get the DOM element's inner text.
180    ///
181    /// ## MDN Documentation
182    /// https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/innerText
183    pub fn inner_text(&self) -> String {
184        let mut out = [0u8; 1000];
185        let result = unsafe {
186            afia_component_sys::element_inner_text(
187                self.imports_ptr(),
188                self.handle(),
189                out.as_mut_ptr(),
190                out.len(),
191            )
192        };
193        match result {
194            ..=-1 => {
195                todo!("error handling")
196            }
197            0.. => {
198                let result = result as usize;
199                if out.len() < result {
200                    todo!("handle scenario where buffer was smaller than the value")
201                };
202                let value = str::from_utf8(&out[0..result]).unwrap();
203                value.to_string()
204            }
205        }
206    }
207}
208impl Drop for DomElementInner {
209    fn drop(&mut self) {
210        // TODO: Call a function to free the `self.element`
211    }
212}
213
214impl ComponentImports {
215    /// Create a new [`DomElement`].
216    pub fn create_element(&self, tag: &str) -> Option<DomElement> {
217        let element = unsafe {
218            afia_component_sys::create_element(self.component_imports_ptr, tag.as_ptr(), tag.len())
219        };
220
221        DomElement::maybe_new(element, self.clone())
222    }
223}