Skip to main content

virtual_node/velement/
special_attributes.rs

1use crate::event::RealDom;
2use std::borrow::Cow;
3use std::cell::RefCell;
4
5/// A specially supported attributes.
6pub struct SpecialAttributes<Dom: RealDom> {
7    key: Option<Cow<'static, str>>,
8    /// A function that gets called when the virtual node is first turned into a real node.
9    ///
10    /// See [`SpecialAttributes.set_on_create_element`] for more documentation.
11    on_create_element: Option<CreateOrRemoveElementFn<Dom>>,
12    /// A function that gets called when the virtual node is first turned into a real node.
13    ///
14    /// See [`SpecialAttributes.set_on_remove_element`] for more documentation.
15    on_remove_element: Option<CreateOrRemoveElementFn<Dom>>,
16    /// Allows setting the innerHTML of an element.
17    ///
18    /// # Danger
19    ///
20    /// Be sure to escape all untrusted input to avoid cross site scripting attacks.
21    pub dangerous_inner_html: Option<String>,
22}
23
24/// An error when attempting to perform an action that requires the [`SpecialAttributes::key`] to be
25/// set.
26#[derive(Debug)]
27pub struct KeyNotSetError;
28
29impl<Dom: RealDom> SpecialAttributes<Dom> {
30    /// Keys can distinguish two elements that have the same tag.
31    ///
32    /// For example, if one `div` has key `old-key`, and another `div` has key `new-key`, the two
33    /// elements are considered different.
34    pub fn key(&self) -> Option<&str> {
35        self.key.as_ref().map(|key| key.as_ref())
36    }
37
38    /// Set the element's `key`.
39    pub fn set_key<Key>(&mut self, key: Key)
40    where
41        Key: Into<Cow<'static, str>>,
42    {
43        self.key = Some(key.into());
44    }
45
46    /// Returns the element's key if an `on_create_element` function is set.
47    pub fn on_create_element_key(&self) -> Option<&Cow<'static, str>> {
48        if self.on_create_element.is_some() {
49            return self.key.as_ref();
50        }
51        None
52    }
53
54    /// Combines [`SpecialAttributes::set_key`] and [`SpecialAttributes::set_on_create_element`].
55    pub fn set_key_and_on_create_element<Key, Func>(&mut self, key: Key, func: Func)
56    where
57        Key: Into<Cow<'static, str>>,
58        Func: FnMut(Dom::Element) + 'static,
59    {
60        self.set_key(key);
61        self.set_on_create_element(func).unwrap()
62    }
63
64    /// Set the [`SpecialAttributes.on_create_element`] function.
65    ///
66    /// # Key
67    ///
68    /// The [`SpecialAttributes::key`] is used when one virtual-node is being patched over another.
69    ///
70    /// If the new node's key is different from the old node's key, the old element gets replaced,
71    /// so the on create element function gets called when the new element is created.
72    ///
73    /// If the keys are the same, the function only gets called if the old node did not have an on
74    /// create element function.
75    ///
76    /// # Examples
77    ///
78    /// ```no_run
79    /// # use virtual_node::VirtualNodeWebSys;
80    /// use wasm_bindgen::JsValue;
81    ///
82    /// let mut node = VirtualNodeWebSys::new_element("div");
83    ///
84    /// // A key can be any `Into<Cow<'static, str>>`.
85    /// let key = "some-key";
86    ///
87    /// let on_create_elem = move |elem: web_sys::Element| {
88    ///     assert_eq!(elem.id(), "");
89    /// };
90    ///
91    /// let elem = node.as_elem_mut().unwrap();
92    /// elem.special_attributes.set_key(key);
93    /// elem.special_attributes.set_on_create_element(on_create_elem).unwrap();
94    /// ```
95    pub fn set_on_create_element<Func>(&mut self, func: Func) -> Result<(), KeyNotSetError>
96    where
97        Func: FnMut(Dom::Element) + 'static,
98    {
99        if self.key.is_none() {
100            return Err(KeyNotSetError);
101        }
102
103        self.on_create_element = Some(CreateOrRemoveElementFn {
104            func: RefCell::new(ElementFunc::OneArg(Box::new(func))),
105        });
106        Ok(())
107    }
108
109    // Used by the html-macro
110    #[doc(hidden)]
111    pub fn set_on_create_element_no_args<Func>(&mut self, func: Func) -> Result<(), KeyNotSetError>
112    where
113        Func: FnMut() + 'static,
114    {
115        if self.key.is_none() {
116            return Err(KeyNotSetError);
117        }
118
119        self.on_create_element = Some(CreateOrRemoveElementFn {
120            func: RefCell::new(ElementFunc::NoArgs(Box::new(func))),
121        });
122        Ok(())
123    }
124
125    /// If an `on_create_element` function was set, call it.
126    pub fn maybe_call_on_create_element(&self, element: &Dom::Element) {
127        if let Some(on_create_elem) = &self.on_create_element {
128            on_create_elem.call(element.clone());
129        }
130    }
131}
132
133impl<Dom: RealDom> SpecialAttributes<Dom> {
134    /// Returns the element's key if an `on_remove_element` function is set.
135    pub fn on_remove_element_key(&self) -> Option<&Cow<'static, str>> {
136        if self.on_remove_element.is_some() {
137            return self.key.as_ref();
138        }
139        None
140    }
141
142    /// Set the [`SpecialAttributes.on_remove_element`] function.
143    ///
144    /// # Key
145    ///
146    /// The key is used when one virtual-node is being patched over another.
147    ///
148    /// If the old node's key is different from the new node's key, the old element gets replaced,
149    /// so the on remove element function gets called for the old element.
150    ///
151    /// If the keys are the same, the function only gets called if the new node does not have an on
152    /// remove element function.
153    ///
154    /// # Examples
155    ///
156    /// ```no_run
157    /// # use virtual_node::VirtualNodeWebSys;
158    /// use wasm_bindgen::JsValue;
159    ///
160    /// let mut node = VirtualNodeWebSys::new_element("div");
161    ///
162    /// // A key can be any `Into<Cow<'static, str>>`.
163    /// let key = "some-key";
164    ///
165    /// let on_remove_elem = move |elem: web_sys::Element| {
166    ///     assert_eq!(elem.id(), "");
167    /// };
168    ///
169    /// let elem = node.as_elem_mut().unwrap();
170    /// elem.special_attributes.set_key(key);
171    /// elem.special_attributes.set_on_remove_element(on_remove_elem);
172    /// ```
173    pub fn set_on_remove_element<Func>(&mut self, func: Func) -> Result<(), KeyNotSetError>
174    where
175        Func: FnMut(Dom::Element) + 'static,
176    {
177        if self.key.is_none() {
178            return Err(KeyNotSetError);
179        }
180
181        self.on_remove_element = Some(CreateOrRemoveElementFn {
182            func: std::cell::RefCell::new(ElementFunc::OneArg(Box::new(func))),
183        });
184        Ok(())
185    }
186
187    // Used by the html-macro
188    #[doc(hidden)]
189    pub fn set_on_remove_element_no_args<Func>(&mut self, func: Func) -> Result<(), KeyNotSetError>
190    where
191        Func: FnMut() + 'static,
192    {
193        if self.key.is_none() {
194            return Err(KeyNotSetError);
195        }
196
197        self.on_remove_element = Some(CreateOrRemoveElementFn {
198            func: RefCell::new(ElementFunc::NoArgs(Box::new(func))),
199        });
200        Ok(())
201    }
202
203    /// If an `on_remove_element` function was set, call it.
204    pub fn maybe_call_on_remove_element(&self, element: &Dom::Element) {
205        if let Some(on_remove_elem) = &self.on_remove_element {
206            on_remove_elem.call(element.clone());
207        }
208
209        let _ = element;
210    }
211
212    pub(crate) fn map_dom<New: RealDom>(
213        self,
214        map: &dyn Fn(Box<dyn FnMut(Dom::Element)>) -> Box<dyn FnMut(New::Element)>,
215    ) -> SpecialAttributes<New> {
216        SpecialAttributes {
217            key: self.key,
218            on_create_element: self.on_create_element.map(|func| func.map_dom(map)),
219            on_remove_element: self.on_remove_element.map(|func| func.map_dom(map)),
220            dangerous_inner_html: self.dangerous_inner_html,
221        }
222    }
223}
224
225struct CreateOrRemoveElementFn<Dom: RealDom> {
226    func: RefCell<ElementFunc<Dom>>,
227}
228
229enum ElementFunc<Dom: RealDom> {
230    NoArgs(Box<dyn FnMut()>),
231    OneArg(Box<dyn FnMut(Dom::Element)>),
232}
233
234impl<Dom: RealDom> CreateOrRemoveElementFn<Dom> {
235    fn call(&self, element: Dom::Element) {
236        use std::ops::DerefMut;
237
238        match self.func.borrow_mut().deref_mut() {
239            ElementFunc::NoArgs(func) => func(),
240            ElementFunc::OneArg(func) => func(element),
241        };
242    }
243
244    fn map_dom<New: RealDom>(
245        self,
246        map: &dyn Fn(Box<dyn FnMut(Dom::Element)>) -> Box<dyn FnMut(New::Element)>,
247    ) -> CreateOrRemoveElementFn<New> {
248        let func = self.func.into_inner();
249        match func {
250            ElementFunc::NoArgs(func) => CreateOrRemoveElementFn {
251                func: RefCell::new(ElementFunc::NoArgs(func)),
252            },
253            ElementFunc::OneArg(func) => {
254                let func = map(func);
255                CreateOrRemoveElementFn {
256                    func: RefCell::new(ElementFunc::OneArg(func)),
257                }
258            }
259        }
260    }
261}
262
263impl<Dom: RealDom> PartialEq for CreateOrRemoveElementFn<Dom> {
264    fn eq(&self, rhs: &Self) -> bool {
265        let _ = rhs;
266        // TODO: Arbitrarily chosen
267        true
268    }
269}
270
271impl<Dom: RealDom> Default for SpecialAttributes<Dom> {
272    fn default() -> Self {
273        Self {
274            key: None,
275            on_create_element: None,
276            on_remove_element: None,
277            dangerous_inner_html: None,
278        }
279    }
280}
281
282impl<Dom: RealDom> PartialEq for SpecialAttributes<Dom> {
283    fn eq(&self, other: &Self) -> bool {
284        let SpecialAttributes {
285            key: key_lhs,
286            on_create_element: on_create_element_lhs,
287            on_remove_element: on_remove_element_lhs,
288            dangerous_inner_html: dangerous_inner_html_lhs,
289        } = self;
290        let SpecialAttributes {
291            key: key_rhs,
292            on_create_element: on_create_element_rhs,
293            on_remove_element: on_remove_element_rhs,
294            dangerous_inner_html: dangerous_inner_html_rhs,
295        } = other;
296
297        key_lhs == key_rhs
298            && on_create_element_lhs == on_create_element_rhs
299            && on_remove_element_lhs == on_remove_element_rhs
300            && dangerous_inner_html_lhs == dangerous_inner_html_rhs
301    }
302}
303
304#[cfg(test)]
305mod tests {
306    use super::*;
307    use crate::VirtualElement;
308
309    /// Verify that we cannot set `on_create_element` if no `key` has been set.
310    #[test]
311    fn error_setting_on_create_element_if_no_key() {
312        let mut elem = create_div();
313
314        let err1 = elem
315            .special_attributes
316            .set_on_create_element(|_| {})
317            .err()
318            .unwrap();
319        let err2 = elem
320            .special_attributes
321            .set_on_create_element_no_args(|| {})
322            .err()
323            .unwrap();
324
325        assert!(matches!(err1, KeyNotSetError));
326        assert!(matches!(err2, KeyNotSetError));
327    }
328
329    /// Verify that we cannot set `on_remove_element` if no `key` has been set.
330    #[test]
331    fn error_setting_on_remove_element_if_no_key() {
332        let mut elem = create_div();
333        let special = &mut elem.special_attributes;
334
335        let err1 = special.set_on_remove_element(|_| {}).err().unwrap();
336        let err2 = special.set_on_remove_element_no_args(|| {}).err().unwrap();
337
338        assert!(matches!(err1, KeyNotSetError));
339        assert!(matches!(err2, KeyNotSetError));
340    }
341
342    /// Verify that [`SpecialAttributes::on_create_element_key`] only returns the key if
343    /// `on_create_element` is set.
344    #[test]
345    fn on_create_element_key() {
346        let mut elem = create_div();
347        let special = &mut elem.special_attributes;
348
349        special.set_key("hello");
350        assert_eq!(special.on_create_element_key(), None);
351
352        special.set_on_create_element(|_| {}).unwrap();
353        assert_eq!(
354            special.on_create_element_key().map(|key| key.as_ref()),
355            Some("hello")
356        );
357    }
358
359    /// Verify that [`SpecialAttributes::maybe_call_on_remove_element`] only returns the key if
360    /// `on_remove_element` is set.
361    #[test]
362    fn on_remove_element_key() {
363        let mut elem = create_div();
364        let special = &mut elem.special_attributes;
365
366        special.set_key("hello");
367        assert_eq!(special.on_remove_element_key(), None);
368
369        special.set_on_remove_element(|_| {}).unwrap();
370        assert_eq!(
371            special.on_remove_element_key().map(|key| key.as_ref()),
372            Some("hello")
373        );
374    }
375
376    fn create_div() -> VirtualElement<()> {
377        VirtualElement::new("div")
378    }
379}