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 on create element function
71    /// gets called.
72    ///
73    /// If the keys are the same, the function does not get called.
74    ///
75    /// # Examples
76    ///
77    /// ```no_run
78    /// # use virtual_node::VirtualNodeWebSys;
79    /// use wasm_bindgen::JsValue;
80    ///
81    /// let mut node = VirtualNodeWebSys::new_element("div");
82    ///
83    /// // A key can be any `Into<Cow<'static, str>>`.
84    /// let key = "some-key";
85    ///
86    /// let on_create_elem = move |elem: web_sys::Element| {
87    ///     assert_eq!(elem.id(), "");
88    /// };
89    ///
90    /// let elem = node.as_elem_mut().unwrap();
91    /// elem.special_attributes.set_key(key);
92    /// elem.special_attributes.set_on_create_element(on_create_elem).unwrap();
93    /// ```
94    pub fn set_on_create_element<Func>(&mut self, func: Func) -> Result<(), KeyNotSetError>
95    where
96        Func: FnMut(Dom::Element) + 'static,
97    {
98        if self.key.is_none() {
99            return Err(KeyNotSetError);
100        }
101
102        self.on_create_element = Some(CreateOrRemoveElementFn {
103            func: RefCell::new(ElementFunc::OneArg(Box::new(func))),
104        });
105        Ok(())
106    }
107
108    // Used by the html-macro
109    #[doc(hidden)]
110    pub fn set_on_create_element_no_args<Func>(&mut self, func: Func) -> Result<(), KeyNotSetError>
111    where
112        Func: FnMut() + 'static,
113    {
114        if self.key.is_none() {
115            return Err(KeyNotSetError);
116        }
117
118        self.on_create_element = Some(CreateOrRemoveElementFn {
119            func: RefCell::new(ElementFunc::NoArgs(Box::new(func))),
120        });
121        Ok(())
122    }
123
124    /// If an `on_create_element` function was set, call it.
125    pub fn maybe_call_on_create_element(&self, element: &Dom::Element) {
126        if let Some(on_create_elem) = &self.on_create_element {
127            on_create_elem.call(element.clone());
128        }
129    }
130}
131
132impl<Dom: RealDom> SpecialAttributes<Dom> {
133    /// Returns the element's key if an `on_remove_element` function is set.
134    pub fn on_remove_element_key(&self) -> Option<&Cow<'static, str>> {
135        if self.on_remove_element.is_some() {
136            return self.key.as_ref();
137        }
138        None
139    }
140
141    /// Set the [`SpecialAttributes.on_remove_element`] function.
142    ///
143    /// # Key
144    ///
145    /// The key is used when one virtual-node is being patched over another.
146    ///
147    /// If the old node's key is different from the new node's key, the on remove element function
148    /// gets called for the old element.
149    ///
150    /// If the keys are the same, the function does not get called.
151    ///
152    /// # Examples
153    ///
154    /// ```no_run
155    /// # use virtual_node::VirtualNodeWebSys;
156    /// use wasm_bindgen::JsValue;
157    ///
158    /// let mut node = VirtualNodeWebSys::new_element("div");
159    ///
160    /// // A key can be any `Into<Cow<'static, str>>`.
161    /// let key = "some-key";
162    ///
163    /// let on_remove_elem = move |elem: web_sys::Element| {
164    ///     assert_eq!(elem.id(), "");
165    /// };
166    ///
167    /// let elem = node.as_elem_mut().unwrap();
168    /// elem.special_attributes.set_key(key);
169    /// elem.special_attributes.set_on_remove_element(on_remove_elem);
170    /// ```
171    pub fn set_on_remove_element<Func>(&mut self, func: Func) -> Result<(), KeyNotSetError>
172    where
173        Func: FnMut(Dom::Element) + 'static,
174    {
175        if self.key.is_none() {
176            return Err(KeyNotSetError);
177        }
178
179        self.on_remove_element = Some(CreateOrRemoveElementFn {
180            func: std::cell::RefCell::new(ElementFunc::OneArg(Box::new(func))),
181        });
182        Ok(())
183    }
184
185    // Used by the html-macro
186    #[doc(hidden)]
187    pub fn set_on_remove_element_no_args<Func>(&mut self, func: Func) -> Result<(), KeyNotSetError>
188    where
189        Func: FnMut() + 'static,
190    {
191        if self.key.is_none() {
192            return Err(KeyNotSetError);
193        }
194
195        self.on_remove_element = Some(CreateOrRemoveElementFn {
196            func: RefCell::new(ElementFunc::NoArgs(Box::new(func))),
197        });
198        Ok(())
199    }
200
201    /// If an `on_remove_element` function was set, call it.
202    pub fn maybe_call_on_remove_element(&self, element: &Dom::Element) {
203        if let Some(on_remove_elem) = &self.on_remove_element {
204            on_remove_elem.call(element.clone());
205        }
206
207        let _ = element;
208    }
209
210    pub(crate) fn map_dom<New: RealDom>(
211        self,
212        map: &dyn Fn(Box<dyn FnMut(Dom::Element)>) -> Box<dyn FnMut(New::Element)>,
213    ) -> SpecialAttributes<New> {
214        SpecialAttributes {
215            key: self.key,
216            on_create_element: self.on_create_element.map(|func| func.map_dom(map)),
217            on_remove_element: self.on_remove_element.map(|func| func.map_dom(map)),
218            dangerous_inner_html: self.dangerous_inner_html,
219        }
220    }
221}
222
223struct CreateOrRemoveElementFn<Dom: RealDom> {
224    func: RefCell<ElementFunc<Dom>>,
225}
226
227enum ElementFunc<Dom: RealDom> {
228    NoArgs(Box<dyn FnMut()>),
229    OneArg(Box<dyn FnMut(Dom::Element)>),
230}
231
232impl<Dom: RealDom> CreateOrRemoveElementFn<Dom> {
233    fn call(&self, element: Dom::Element) {
234        use std::ops::DerefMut;
235
236        match self.func.borrow_mut().deref_mut() {
237            ElementFunc::NoArgs(func) => func(),
238            ElementFunc::OneArg(func) => func(element),
239        };
240    }
241
242    fn map_dom<New: RealDom>(
243        self,
244        map: &dyn Fn(Box<dyn FnMut(Dom::Element)>) -> Box<dyn FnMut(New::Element)>,
245    ) -> CreateOrRemoveElementFn<New> {
246        let func = self.func.into_inner();
247        match func {
248            ElementFunc::NoArgs(func) => CreateOrRemoveElementFn {
249                func: RefCell::new(ElementFunc::NoArgs(func)),
250            },
251            ElementFunc::OneArg(func) => {
252                let func = map(func);
253                CreateOrRemoveElementFn {
254                    func: RefCell::new(ElementFunc::OneArg(func)),
255                }
256            }
257        }
258    }
259}
260
261impl<Dom: RealDom> PartialEq for CreateOrRemoveElementFn<Dom> {
262    fn eq(&self, rhs: &Self) -> bool {
263        let _ = rhs;
264        // TODO: Arbitrarily chosen
265        true
266    }
267}
268
269impl<Dom: RealDom> Default for SpecialAttributes<Dom> {
270    fn default() -> Self {
271        Self {
272            key: None,
273            on_create_element: None,
274            on_remove_element: None,
275            dangerous_inner_html: None,
276        }
277    }
278}
279
280impl<Dom: RealDom> PartialEq for SpecialAttributes<Dom> {
281    fn eq(&self, other: &Self) -> bool {
282        let SpecialAttributes {
283            key: key_lhs,
284            on_create_element: on_create_element_lhs,
285            on_remove_element: on_remove_element_lhs,
286            dangerous_inner_html: dangerous_inner_html_lhs,
287        } = self;
288        let SpecialAttributes {
289            key: key_rhs,
290            on_create_element: on_create_element_rhs,
291            on_remove_element: on_remove_element_rhs,
292            dangerous_inner_html: dangerous_inner_html_rhs,
293        } = other;
294
295        key_lhs == key_rhs
296            && on_create_element_lhs == on_create_element_rhs
297            && on_remove_element_lhs == on_remove_element_rhs
298            && dangerous_inner_html_lhs == dangerous_inner_html_rhs
299    }
300}
301
302#[cfg(test)]
303mod tests {
304    use super::*;
305    use crate::VirtualElement;
306
307    /// Verify that we cannot set `on_create_element` if no `key` has been set.
308    #[test]
309    fn error_setting_on_create_element_if_no_key() {
310        let mut elem = create_div();
311
312        let err1 = elem
313            .special_attributes
314            .set_on_create_element(|_| {})
315            .err()
316            .unwrap();
317        let err2 = elem
318            .special_attributes
319            .set_on_create_element_no_args(|| {})
320            .err()
321            .unwrap();
322
323        assert!(matches!(err1, KeyNotSetError));
324        assert!(matches!(err2, KeyNotSetError));
325    }
326
327    /// Verify that we cannot set `on_remove_element` if no `key` has been set.
328    #[test]
329    fn error_setting_on_remove_element_if_no_key() {
330        let mut elem = create_div();
331        let special = &mut elem.special_attributes;
332
333        let err1 = special.set_on_remove_element(|_| {}).err().unwrap();
334        let err2 = special.set_on_remove_element_no_args(|| {}).err().unwrap();
335
336        assert!(matches!(err1, KeyNotSetError));
337        assert!(matches!(err2, KeyNotSetError));
338    }
339
340    /// Verify that [`SpecialAttributes::on_create_element_key`] only returns the key if
341    /// `on_create_element` is set.
342    #[test]
343    fn on_create_element_key() {
344        let mut elem = create_div();
345        let special = &mut elem.special_attributes;
346
347        special.set_key("hello");
348        assert_eq!(special.on_create_element_key(), None);
349
350        special.set_on_create_element(|_| {}).unwrap();
351        assert_eq!(
352            special.on_create_element_key().map(|key| key.as_ref()),
353            Some("hello")
354        );
355    }
356
357    /// Verify that [`SpecialAttributes::maybe_call_on_remove_element`] only returns the key if
358    /// `on_remove_element` is set.
359    #[test]
360    fn on_remove_element_key() {
361        let mut elem = create_div();
362        let special = &mut elem.special_attributes;
363
364        special.set_key("hello");
365        assert_eq!(special.on_remove_element_key(), None);
366
367        special.set_on_remove_element(|_| {}).unwrap();
368        assert_eq!(
369            special.on_remove_element_key().map(|key| key.as_ref()),
370            Some("hello")
371        );
372    }
373
374    fn create_div() -> VirtualElement<()> {
375        VirtualElement::new("div")
376    }
377}