sauron_core/dom/
dom_attr.rs1use crate::vdom::AttributeName;
2use crate::vdom::Namespace;
3use crate::vdom::Style;
4use crate::vdom::Value;
5#[cfg(feature = "ensure-attr-set")]
6use crate::vdom::{CHECKED, DISABLED, OPEN, VALUE};
7use wasm_bindgen::intern;
8#[cfg(feature = "ensure-attr-set")]
9use wasm_bindgen::JsCast;
10use wasm_bindgen::{closure::Closure, JsValue};
11use web_sys;
12use web_sys::Element;
13#[cfg(feature = "ensure-attr-set")]
14use web_sys::{
15 HtmlButtonElement, HtmlDataElement, HtmlDetailsElement, HtmlFieldSetElement, HtmlInputElement,
16 HtmlLiElement, HtmlLinkElement, HtmlMeterElement, HtmlOptGroupElement, HtmlOptionElement,
17 HtmlOutputElement, HtmlParamElement, HtmlProgressElement, HtmlSelectElement, HtmlStyleElement,
18 HtmlTextAreaElement,
19};
20
21#[derive(Debug)]
23pub struct DomAttr {
24 pub namespace: Option<&'static str>,
26 pub name: &'static str,
28 pub value: Vec<DomAttrValue>,
30}
31
32#[derive(Debug)]
34pub enum DomAttrValue {
35 Simple(Value),
37 Style(Vec<Style>),
39 EventListener(Closure<dyn FnMut(web_sys::Event)>),
41 Empty,
43}
44
45pub struct GroupedDomAttrValues {
48 pub listeners: Vec<Closure<dyn FnMut(web_sys::Event)>>,
50 pub plain_values: Vec<Value>,
52 pub styles: Vec<Style>,
54}
55
56impl DomAttr {
57 pub(crate) fn group_values(self) -> GroupedDomAttrValues {
59 let mut listeners = vec![];
60 let mut plain_values = vec![];
61 let mut styles = vec![];
62 for av in self.value {
63 match av {
64 DomAttrValue::Simple(v) => {
65 plain_values.push(v);
66 }
67 DomAttrValue::Style(s) => {
68 styles.extend(s);
69 }
70 DomAttrValue::EventListener(cb) => {
71 listeners.push(cb);
72 }
73 DomAttrValue::Empty => (),
74 }
75 }
76 GroupedDomAttrValues {
77 listeners,
78 plain_values,
79 styles,
80 }
81 }
82
83 pub(crate) fn set_element_style(
85 element: &Element,
86 attr_name: AttributeName,
87 styles: Vec<Style>,
88 ) {
89 if let Some(merged_styles) = Style::merge_to_string(&styles) {
90 element
92 .set_attribute(attr_name, &merged_styles)
93 .unwrap_or_else(|_| panic!("Error setting an attribute_ns for {element:?}"));
94 } else {
95 element
98 .remove_attribute(attr_name)
99 .expect("must remove attribute");
100 }
101 }
102
103 pub(crate) fn set_element_simple_values(
105 element: &Element,
106 attr_name: AttributeName,
107 attr_namespace: Option<Namespace>,
108 plain_values: Vec<Value>,
109 ) {
110 if let Some(merged_plain_values) = Value::merge_to_string(plain_values.iter()) {
111 if let Some(namespace) = attr_namespace {
112 element
117 .set_attribute_ns(Some(namespace), attr_name, &merged_plain_values)
118 .unwrap_or_else(|_| panic!("Error setting an attribute_ns for {element:?}"));
119 } else {
120 #[cfg(feature = "ensure-attr-set")]
121 if *VALUE == attr_name {
122 element
123 .set_attribute(attr_name, &merged_plain_values)
124 .unwrap_or_else(|_| panic!("Error setting an attribute for {element:?}"));
125 Self::set_value_str(element, &merged_plain_values);
126 Self::set_numeric_values(element, &plain_values);
127 } else if *OPEN == attr_name {
128 let is_open: bool = plain_values
129 .first()
130 .and_then(|v| v.as_bool())
131 .unwrap_or(false);
132
133 element
134 .set_attribute(attr_name, &is_open.to_string())
135 .unwrap_or_else(|_| panic!("Error setting an attribute for {element:?}"));
136 Self::set_open(element, is_open);
137 } else if *CHECKED == attr_name {
138 let is_checked: bool = plain_values
139 .first()
140 .and_then(|v| v.as_bool())
141 .unwrap_or(false);
142
143 element
144 .set_attribute(attr_name, &is_checked.to_string())
145 .unwrap_or_else(|_| panic!("Error setting an attribute for {element:?}"));
146 Self::set_checked(element, is_checked)
147 } else if *DISABLED == attr_name {
148 let is_disabled: bool = plain_values
149 .first()
150 .and_then(|v| v.as_bool())
151 .unwrap_or(false);
152
153 element
154 .set_attribute(attr_name, &is_disabled.to_string())
155 .unwrap_or_else(|_| panic!("Error setting an attribute for {element:?}"));
156 Self::set_disabled(element, is_disabled);
157 } else if "inner_html" == attr_name {
158 panic!("Setting inner_html is not allowed, as it breaks the tracking of the DomTree, use html-parse instead")
159 } else {
160 element
161 .set_attribute(attr_name, &merged_plain_values)
162 .unwrap_or_else(|_| panic!("Error setting an attribute for {element:?}"));
163 }
164 #[cfg(not(feature = "ensure-attr-set"))]
165 element
166 .set_attribute(attr_name, &merged_plain_values)
167 .unwrap_or_else(|_| panic!("Error setting an attribute for {element:?}"));
168 }
169 }
170 }
171
172 pub(crate) fn remove_element_dom_attr(
174 element: &Element,
175 attr: &DomAttr,
176 ) -> Result<(), JsValue> {
177 #[cfg(feature = "ensure-attr-set")]
178 if *VALUE == attr.name {
179 DomAttr::set_value_str(element, "");
180 } else if *OPEN == attr.name {
181 DomAttr::set_open(element, false);
182 } else if *CHECKED == attr.name {
183 DomAttr::set_checked(element, false);
184 } else if *DISABLED == attr.name {
185 DomAttr::set_disabled(element, false);
186 }
187 element.remove_attribute(intern(attr.name))?;
189
190 Ok(())
191 }
192
193 #[cfg(feature = "ensure-attr-set")]
200 pub(crate) fn set_checked(element: &Element, is_checked: bool) {
201 if let Some(input) = element.dyn_ref::<HtmlInputElement>() {
202 input.set_checked(is_checked);
203 }
204 }
205
206 #[cfg(feature = "ensure-attr-set")]
215 pub(crate) fn set_open(element: &Element, is_open: bool) {
216 if let Some(details) = element.dyn_ref::<HtmlDetailsElement>() {
217 details.set_open(is_open);
218 }
219 }
220
221 #[cfg(feature = "ensure-attr-set")]
238 pub(crate) fn set_disabled(element: &Element, is_disabled: bool) {
239 if let Some(elm) = element.dyn_ref::<HtmlInputElement>() {
240 elm.set_disabled(is_disabled);
241 } else if let Some(elm) = element.dyn_ref::<HtmlButtonElement>() {
242 elm.set_disabled(is_disabled);
243 } else if let Some(elm) = element.dyn_ref::<HtmlTextAreaElement>() {
244 elm.set_disabled(is_disabled);
245 } else if let Some(elm) = element.dyn_ref::<HtmlStyleElement>() {
246 elm.set_disabled(is_disabled);
247 } else if let Some(elm) = element.dyn_ref::<HtmlLinkElement>() {
248 elm.set_disabled(is_disabled);
249 } else if let Some(elm) = element.dyn_ref::<HtmlSelectElement>() {
250 elm.set_disabled(is_disabled);
251 } else if let Some(elm) = element.dyn_ref::<HtmlOptionElement>() {
252 elm.set_disabled(is_disabled);
253 } else if let Some(elm) = element.dyn_ref::<HtmlOptGroupElement>() {
254 elm.set_disabled(is_disabled);
255 } else if let Some(elm) = element.dyn_ref::<HtmlFieldSetElement>() {
256 elm.set_disabled(is_disabled);
257 }
258 }
259
260 #[cfg(feature = "ensure-attr-set")]
264 pub(crate) fn set_value_str(element: &Element, value: &str) {
265 if let Some(elm) = element.dyn_ref::<HtmlInputElement>() {
266 elm.set_value(value);
267 } else if let Some(elm) = element.dyn_ref::<HtmlTextAreaElement>() {
268 elm.set_value(value);
269 } else if let Some(elm) = element.dyn_ref::<HtmlSelectElement>() {
270 elm.set_value(value);
271 } else if let Some(elm) = element.dyn_ref::<HtmlOptionElement>() {
272 elm.set_value(value);
273 } else if let Some(elm) = element.dyn_ref::<HtmlButtonElement>() {
274 elm.set_value(value);
275 } else if let Some(elm) = element.dyn_ref::<HtmlDataElement>() {
276 elm.set_value(value);
277 } else if let Some(elm) = element.dyn_ref::<HtmlOutputElement>() {
278 elm.set_value(value);
279 } else if let Some(elm) = element.dyn_ref::<HtmlParamElement>() {
280 elm.set_value(value);
281 }
282 }
283
284 #[cfg(feature = "ensure-attr-set")]
286 pub(crate) fn set_value_i32(element: &Element, value: i32) {
287 if let Some(elm) = element.dyn_ref::<HtmlLiElement>() {
288 elm.set_value(value);
289 }
290 }
291
292 #[cfg(feature = "ensure-attr-set")]
294 pub(crate) fn set_value_f64(element: &Element, value: f64) {
295 if let Some(elm) = element.dyn_ref::<HtmlMeterElement>() {
296 elm.set_value(value);
297 } else if let Some(elm) = element.dyn_ref::<HtmlProgressElement>() {
298 elm.set_value(value);
299 }
300 }
301
302 #[cfg(feature = "ensure-attr-set")]
304 pub(crate) fn set_numeric_values(element: &Element, values: &[Value]) {
305 let value_i32 = values.first().and_then(|v| v.as_i32());
306
307 let value_f64 = values.first().and_then(|v| v.as_f64());
308
309 if let Some(value_i32) = value_i32 {
310 Self::set_value_i32(element, value_i32);
311 }
312 if let Some(value_f64) = value_f64 {
313 Self::set_value_f64(element, value_f64);
314 }
315 }
316}
317
318impl DomAttrValue {
319 pub fn as_simple(&self) -> Option<&Value> {
321 match self {
322 Self::Simple(v) => Some(v),
323 _ => None,
324 }
325 }
326
327 pub fn as_string(&self) -> Option<String> {
329 let simple = self.as_simple()?;
330 Some(simple.to_string())
331 }
332
333 pub fn as_i32(&self) -> Option<i32> {
335 let simple = self.as_simple()?;
336 simple.as_i32()
337 }
338
339 pub fn as_i64(&self) -> Option<i64> {
341 let simple = self.as_simple()?;
342 simple.as_i64()
343 }
344
345 pub fn as_f32(&self) -> Option<f32> {
347 let simple = self.as_simple()?;
348 simple.as_f32()
349 }
350
351 pub fn as_f64(&self) -> Option<f64> {
353 let simple = self.as_simple()?;
354 simple.as_f64()
355 }
356}