1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
#[cfg(feature = "queue-render")]
use std::{cell::Cell, rc::Rc};
use wasm_bindgen::{JsCast, UnwrapThrowExt};
use super::{AChildNode, AttributeValueList, ElementTag, ElementType, Nodes};
#[derive(Debug)]
pub struct Element {
ws_element: WsElement,
attributes: AttributeValueList,
nodes: Nodes,
#[cfg(feature = "queue-render")]
unmounted: Rc<Cell<bool>>,
}
#[cfg(feature = "queue-render")]
impl Drop for Element {
fn drop(&mut self) {
self.unmounted.set(true);
}
}
impl Clone for Element {
fn clone(&self) -> Self {
let ws_element = self.ws_element.shadow_clone();
let nodes = self.nodes.clone();
nodes.append_to(ws_element.ws_node());
Self {
ws_element,
nodes,
attributes: self.attributes.clone(),
#[cfg(feature = "queue-render")]
unmounted: Rc::new(Cell::new(false)),
}
}
}
impl AChildNode for Element {
fn ws_node(&self) -> &web_sys::Node {
self.ws_element.ws_node()
}
}
impl Element {
pub fn new_ns<E: ElementTag>(tag: E) -> Self {
Self {
ws_element: WsElement::new(E::NAMESPACE, tag.tag_name()),
attributes: Default::default(),
nodes: Default::default(),
#[cfg(feature = "queue-render")]
unmounted: Rc::new(Cell::new(false)),
}
}
pub fn from_ws_element(ws_element: web_sys::Element) -> Self {
Self {
ws_element: WsElement {
element_type: ws_element.tag_name().to_ascii_lowercase().as_str().into(),
ws_element,
},
attributes: Default::default(),
nodes: Default::default(),
#[cfg(feature = "queue-render")]
unmounted: Rc::new(Cell::new(false)),
}
}
#[cfg(feature = "queue-render")]
pub fn unmounted(&self) -> Rc<Cell<bool>> {
self.unmounted.clone()
}
pub fn is_empty(&self) -> bool {
self.nodes.count() == 0
}
pub fn ws_element(&self) -> &WsElement {
&self.ws_element
}
pub fn ws_node_and_nodes_mut(&mut self) -> (&web_sys::Node, &mut Nodes) {
(self.ws_element.as_ref(), &mut self.nodes)
}
pub fn ws_html_element(&self) -> &web_sys::HtmlElement {
self.ws_element.ws_element.unchecked_ref()
}
pub fn element_type(&self) -> ElementType {
self.ws_element.element_type
}
pub fn attribute_list_mut(&mut self) -> &mut AttributeValueList {
&mut self.attributes
}
#[cfg(test)]
pub fn nodes(&self) -> &Nodes {
&self.nodes
}
pub fn nodes_mut(&mut self) -> &mut Nodes {
&mut self.nodes
}
}
#[derive(Debug, Clone)]
pub struct WsElement {
ws_element: web_sys::Element,
element_type: ElementType,
}
pub trait AttributeValueAsString {
fn to_string(self) -> String;
}
macro_rules! impl_string_attribute {
($($TypeName:ident)+) => {
$(
impl AttributeValueAsString for $TypeName {
fn to_string(self) -> String {
ToString::to_string(&self)
}
}
)+
};
}
impl_string_attribute! { i32 u32 f64 }
impl WsElement {
pub fn new(namespace: &str, tag: &str) -> Self {
Self {
ws_element: crate::utils::document()
.create_element_ns(Some(namespace), tag)
.expect_throw("dom::element::WsElement::new"),
element_type: tag.into(),
}
}
pub fn into_inner(self) -> web_sys::Element {
self.ws_element
}
pub fn as_ref(&self) -> &web_sys::Element {
&self.ws_element
}
pub fn ws_node(&self) -> &web_sys::Node {
self.ws_element.as_ref()
}
pub fn ws_event_target(&self) -> &web_sys::EventTarget {
self.ws_element.as_ref()
}
pub fn unchecked_ref<T: JsCast>(&self) -> &T {
self.ws_element.unchecked_ref::<T>()
}
pub fn html_element(&self) -> &web_sys::HtmlElement {
self.ws_element.unchecked_ref()
}
pub fn unchecked_into<T: JsCast>(&self) -> T {
self.ws_element.clone().unchecked_into::<T>()
}
fn shadow_clone(&self) -> Self {
Self {
ws_element: self
.ws_element
.clone_node_with_deep(false)
.expect_throw("render::element::WsElement::clone")
.unchecked_into(),
element_type: self.element_type,
}
}
pub fn set_id(&self, id: &str) {
self.ws_element.set_id(id);
}
pub fn set_text_content(&self, text: Option<&str>) {
self.ws_element.set_text_content(text);
}
pub fn set_str_attribute(&self, attribute_name: &str, attribute_value: &str) {
self.ws_element
.set_attribute(attribute_name, attribute_value)
.expect_throw("dom::element::WsElement::set_str_attribute");
}
pub fn remove_attribute(&self, attribute_name: &str) {
self.ws_element
.remove_attribute(attribute_name)
.expect_throw("dom::element::WsElement::remove_attribute");
}
pub fn set_attribute<T: AttributeValueAsString>(
&self,
attribute_name: &str,
attribute_value: T,
) {
self.set_str_attribute(attribute_name, &attribute_value.to_string());
}
pub fn set_bool_attribute(&self, name: &str, value: bool) {
if value {
self.set_str_attribute(name, "");
} else {
self.remove_attribute(name);
}
}
pub fn add_class(&self, class_name: &str) {
self.ws_element
.class_list()
.add_1(class_name)
.expect_throw("dom::element::WsElement::add_class");
}
pub fn remove_class(&self, class_name: &str) {
self.ws_element
.class_list()
.remove_1(class_name)
.expect_throw("dom::element::WsElement::remove_class");
}
pub fn add_class_optional(&self, class_name: Option<&str>) {
if let Some(class_name) = class_name {
self.add_class(class_name);
}
}
pub fn remove_class_optional(&self, class_name: Option<&str>) {
if let Some(class_name) = class_name {
self.remove_class(class_name);
}
}
#[must_use = "Make sure that the return value is handled if queue_render = false"]
pub fn set_value(&self, value: &str, queue_render: bool) -> bool {
match self.element_type {
ElementType::Input => {
let input = self.ws_element.unchecked_ref::<web_sys::HtmlInputElement>();
input.set_value(value);
}
ElementType::Select => {
if queue_render {
let select = self
.ws_element
.unchecked_ref::<web_sys::HtmlSelectElement>();
select.set_value(value);
}
return true;
}
ElementType::TextArea => {
let text_area = self
.ws_element
.unchecked_ref::<web_sys::HtmlTextAreaElement>();
text_area.set_value(value);
}
ElementType::Option => {
let option = self
.ws_element
.unchecked_ref::<web_sys::HtmlOptionElement>();
option.set_value(value);
}
ElementType::Other => {
log::warn!(
".value() is called on an element that is not <input>, <select>, <option>, <textarea>"
);
}
}
false
}
#[allow(clippy::ptr_arg)]
pub fn set_value_for_qr(&self, value: &String) {
let _user = self.set_value(value, true);
}
pub fn set_value_for_qr_optional(&self, value: &Option<String>) {
match value {
Some(value) => {
let _user = self.set_value(value, true);
}
None => self.set_selected_index(-1),
}
}
pub fn set_selected_index(&self, index: i32) {
match self.element_type {
ElementType::Select => {
let select = self
.ws_element
.unchecked_ref::<web_sys::HtmlSelectElement>();
select.set_selected_index(index);
}
_ => {
log::warn!(".set_selected_index() is called on an element that is not a <select>");
}
}
}
pub fn set_selected_index_ref(&self, index: &usize) {
self.set_selected_index(*index as i32);
}
pub fn set_selected_index_optional(&self, index: &Option<usize>) {
match index {
Some(index) => self.set_selected_index(*index as i32),
None => self.set_selected_index(-1),
}
}
pub fn checked_ref(&self, value: &bool) {
self.checked(*value);
}
pub fn checked(&self, value: bool) {
if self.element_type == ElementType::Input {
let input = self.ws_element.unchecked_ref::<web_sys::HtmlInputElement>();
input.set_checked(value);
} else {
log::warn!(".checked() is called on an element that is not an <input>");
}
}
pub fn enabled_ref(&self, value: &bool) {
self.enabled(*value);
}
pub fn enabled(&self, value: bool) {
self.set_bool_attribute("disabled", !value);
}
pub fn focus_ref(&self, value: &bool) {
self.focus(*value);
}
pub fn focus(&self, value: bool) {
if value {
self.html_element()
.focus()
.expect_throw("render::base::element::ElementUpdater::focus");
}
}
pub fn scroll_to_view_with_bool(&self, align_to_top: bool) {
self.ws_element.scroll_into_view_with_bool(align_to_top);
}
pub fn scroll_to_view_with_options(&self, options: &web_sys::ScrollIntoViewOptions) {
self.ws_element
.scroll_into_view_with_scroll_into_view_options(options);
}
}