sycamore_web/
attributes.rs1use crate::*;
2
3pub trait AttributeValue: AttributeValueBoxed + 'static {
7 fn set_self(self, el: &mut HtmlNode, name: Cow<'static, str>);
8}
9
10pub type StringAttribute = MaybeDyn<Option<Cow<'static, str>>>;
12impl AttributeValue for StringAttribute {
13 fn set_self(self, el: &mut HtmlNode, name: Cow<'static, str>) {
14 el.set_attribute(name, self);
15 }
16}
17
18pub type BoolAttribute = MaybeDyn<bool>;
20impl AttributeValue for BoolAttribute {
21 fn set_self(self, el: &mut HtmlNode, name: Cow<'static, str>) {
22 el.set_bool_attribute(name, self);
23 }
24}
25
26impl AttributeValue for MaybeDyn<JsValue> {
27 fn set_self(self, el: &mut HtmlNode, name: Cow<'static, str>) {
28 el.set_property(name, self);
29 }
30}
31
32#[doc(hidden)]
34pub trait AttributeValueBoxed: 'static {
35 fn set_self_boxed(self: Box<Self>, el: &mut HtmlNode, name: Cow<'static, str>);
36}
37
38impl<T> AttributeValueBoxed for T
39where
40 T: AttributeValue,
41{
42 fn set_self_boxed(self: Box<Self>, el: &mut HtmlNode, name: Cow<'static, str>) {
43 self.set_self(el, name);
44 }
45}
46
47impl AttributeValue for Box<dyn AttributeValue> {
48 fn set_self(self, el: &mut HtmlNode, name: Cow<'static, str>) {
49 self.set_self_boxed(el, name);
50 }
51}
52
53pub trait SetAttribute {
55 fn set_attribute(&mut self, name: &'static str, value: impl AttributeValue);
56 fn set_event_handler(
57 &mut self,
58 name: &'static str,
59 value: impl FnMut(web_sys::Event) + 'static,
60 );
61}
62
63impl<T> SetAttribute for T
64where
65 T: AsHtmlNode,
66{
67 fn set_attribute(&mut self, name: &'static str, value: impl AttributeValue) {
68 value.set_self(self.as_html_node(), name.into());
69 }
70
71 fn set_event_handler(
72 &mut self,
73 name: &'static str,
74 value: impl FnMut(web_sys::Event) + 'static,
75 ) {
76 self.as_html_node().set_event_handler(name.into(), value);
77 }
78}
79
80#[derive(Default)]
82pub struct Attributes {
83 values: Vec<(Cow<'static, str>, Box<dyn AttributeValue>)>,
84 #[allow(clippy::type_complexity)]
85 event_handlers: Vec<(Cow<'static, str>, Box<dyn FnMut(web_sys::Event)>)>,
86}
87
88impl SetAttribute for Attributes {
89 fn set_attribute(&mut self, name: &'static str, value: impl AttributeValue) {
90 self.values.push((name.into(), Box::new(value)));
91 }
92
93 fn set_event_handler(
94 &mut self,
95 name: &'static str,
96 value: impl FnMut(web_sys::Event) + 'static,
97 ) {
98 self.event_handlers.push((name.into(), Box::new(value)));
99 }
100}
101
102impl Attributes {
103 pub fn new() -> Self {
105 Self::default()
106 }
107
108 pub fn apply_self(self, el: &mut HtmlNode) {
109 for (name, value) in self.values {
110 value.set_self(el, name);
111 }
112 for (name, handler) in self.event_handlers {
113 el.set_event_handler(name, handler);
114 }
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use expect_test::{expect, Expect};
121
122 use super::*;
123
124 fn check<T: Into<View>>(view: impl FnOnce() -> T, expect: Expect) {
125 let actual = render_to_string(move || view().into());
126 expect.assert_eq(&actual);
127 }
128
129 #[test]
130 fn attributes_apply_self() {
131 let mut attributes = Attributes::new();
132 attributes.set_attribute("class", StringAttribute::from("test-class"));
133 attributes.set_attribute("id", StringAttribute::from(move || "test-id"));
134
135 check(
136 move || crate::tags::div().spread(attributes),
137 expect![[r#"<div class="test-class" id="test-id" data-hk="0.0"></div>"#]],
138 );
139 }
140
141 #[test]
142 fn attributes_apply_self_macro() {
143 let mut attributes = Attributes::new();
144 attributes.set_attribute("class", StringAttribute::from("test-class"));
145 attributes.set_attribute("id", StringAttribute::from(move || "test-id"));
146
147 check(
148 move || view! { div(..attributes) },
149 expect![[r#"<div class="test-class" id="test-id" data-hk="0.0"></div>"#]],
150 );
151 }
152}