ss_web_utils/
dom.rs

1use std::rc::Rc;
2use serde::{self, Serialize, Deserialize, de::DeserializeOwned};
3use wasm_bindgen::JsValue;
4
5
6///////////////////////////////////////////////////////////////////////////////
7// INTERNAL UTILS
8///////////////////////////////////////////////////////////////////////////////
9
10pub mod event {
11    use super::*;
12    
13    pub fn get_oninput_value(event: &JsValue) -> String {
14        let event: web_sys::Event = From::from(event.clone());
15        let target: web_sys::EventTarget = event
16            .target()
17            .expect("target failed");
18        let target: JsValue = From::from(target);
19        let target: web_sys::HtmlInputElement = From::from(target);
20        let value = target.value();
21        value
22    }
23    pub fn prevent_default(event: &JsValue) {
24        let event: web_sys::Event = From::from(event.clone());
25        event.prevent_default();
26    }
27}
28
29pub mod tag {
30    pub fn is_svg(tag: &str) -> bool {
31        match tag.to_lowercase().as_str() {
32            "animate" => true,
33            "animatemotion" => true,
34            "animatetransform" => true,
35            "circle" => true,
36            "clippath" => true,
37            "defs" => true,
38            "desc" => true,
39            "discard" => true,
40            "ellipse" => true,
41            "feblend" => true,
42            "fecolormatrix" => true,
43            "fecomponenttransfer" => true,
44            "fecomposite" => true,
45            "feconvolvematrix" => true,
46            "fediffuselighting" => true,
47            "fedisplacementmap" => true,
48            "fedistantlight" => true,
49            "fedropshadow" => true,
50            "feflood" => true,
51            "fefunca" => true,
52            "fefuncb" => true,
53            "fefuncg" => true,
54            "fefuncr" => true,
55            "fegaussianblur" => true,
56            "feimage" => true,
57            "femerge" => true,
58            "femergenode" => true,
59            "femorphology" => true,
60            "feoffset" => true,
61            "fepointlight" => true,
62            "fespecularlighting" => true,
63            "fespotlight" => true,
64            "fetile" => true,
65            "feturbulence" => true,
66            "filter" => true,
67            "foreignobject" => true,
68            "g" => true,
69            "line" => true,
70            "lineargradient" => true,
71            "marker" => true,
72            "mask" => true,
73            "metadata" => true,
74            "mpath" => true,
75            "path" => true,
76            "pattern" => true,
77            "polygon" => true,
78            "polyline" => true,
79            "radialgradient" => true,
80            "rect" => true,
81            "set" => true,
82            "stop" => true,
83            "svg" => true,
84            "switch" => true,
85            "symbol" => true,
86            "text" => true,
87            "textpath" => true,
88            "title" => true,
89            "tspan" => true,
90            "unknown" => true,
91            "use" => true,
92            "view" => true,
93            _ => false,
94        }
95    }
96}
97
98pub mod core {
99    pub fn get_window() -> web_sys::Window {
100        let window: web_sys::Window = web_sys::window()
101            .expect("window not available");
102        window
103    }
104    pub fn get_document() -> web_sys::Document {
105        let window: web_sys::Window = web_sys::window()
106            .expect("window not available");
107        let document = window
108            .document()
109            .expect("document not available");
110        document
111    }
112    pub fn get_body_as_node() -> web_sys::Node {
113        let window: web_sys::Window = web_sys::window()
114            .expect("window not available");
115        let document = window
116            .document()
117            .expect("document not available");
118        let body: web_sys::Node = std::convert::From::from(
119            document.body().expect("document.body not available")
120        );
121        body
122    }
123    pub fn get_body() -> web_sys::Element {
124        let window: web_sys::Window = web_sys::window()
125            .expect("window not available");
126        let document = window
127            .document()
128            .expect("document not available");
129        let body: web_sys::Element = std::convert::From::from(
130            document.body().expect("document.body not available")
131        );
132        body
133    }
134    pub fn new_element(tag: &str) -> web_sys::Element {
135        let document = get_document();
136        let result = document.create_element(tag)
137            .expect("failed to create element");
138        result
139    }
140    pub fn new_svg_element(tag: &str) -> web_sys::Element {
141        let document = get_document();
142        let result = document.create_element_ns(
143                Some("http://www.w3.org/2000/svg"),
144                tag,
145            )
146            .expect("failed to create element");
147        result
148    }
149    pub fn new_text(value: &str) -> web_sys::Text {
150        let document = get_document();
151        let result = document.create_text_node(value);
152        result
153    }
154}
155
156///////////////////////////////////////////////////////////////////////////////
157// GENERIC EVENTS
158///////////////////////////////////////////////////////////////////////////////
159
160pub trait Callback {
161    fn as_js_function(&self) -> &js_sys::Function;
162}
163
164
165///////////////////////////////////////////////////////////////////////////////
166// GENERIC DOM TREE API
167///////////////////////////////////////////////////////////////////////////////
168
169pub trait DomRef {
170    fn dom_ref(&self) -> &JsValue;
171    fn dom_ref_as_node(&self) -> &web_sys::Node;
172
173    fn add_event_listener(&self, event_name: &str, cb: &Callback) {
174        self.dom_ref_as_node().add_event_listener_with_callback(event_name, cb.as_js_function())
175            .expect("addEventListener failed");
176    }
177    fn remove_event_listener(&self, event_name: &str, cb: &Callback) {
178        self.dom_ref_as_node().remove_event_listener_with_callback(event_name, cb.as_js_function())
179            .expect("removeEventListener failed");
180    }
181    fn append_child(&self, child: &DomRef) {
182        self.dom_ref_as_node()
183            .append_child(&child.dom_ref_as_node())
184            .expect("appendChild failed");
185    }
186    fn remove_child(&self, child: &DomRef) {
187        self.dom_ref_as_node()
188            .remove_child(&child.dom_ref_as_node())
189            .expect("removeChild failed");
190    }
191    fn try_remove_child(&self, child: &DomRef) -> Result<(), JsValue> {
192        match self.dom_ref_as_node().remove_child(&child.dom_ref_as_node()) {
193            Err(x) => Err(x),
194            Ok(_) => Ok(())
195        }
196    }
197    fn replace_child(&self, new_child: &DomRef, old_child: &DomRef) {
198        self.dom_ref_as_node()
199            .replace_child(&new_child.dom_ref_as_node(), &old_child.dom_ref_as_node())
200            .expect("replacedNode failed");
201    }
202}
203
204pub trait DomNode: DomRef {
205    fn dom_ref_as_element(&self) -> &web_sys::Element;
206    
207    fn set_attribute(&self, key: &str, value: &str) {
208        self.dom_ref_as_element().set_attribute(key, value)
209            .expect("setAttribute failed");
210    }
211    fn remove_attribute(&self, key: &str) {
212        self.dom_ref_as_element().remove_attribute(key)
213            .expect("removeAttribute failed");
214    }
215}
216
217
218///////////////////////////////////////////////////////////////////////////////
219// WINDOW
220///////////////////////////////////////////////////////////////////////////////
221
222
223thread_local! {
224    pub static GLOBAL_WINDOW: Window = {
225        let window = Window {
226            instance: core::get_window(),
227            local_storage: Storage::new(),
228            document: Document::new(),
229            location: Location::new(),
230            history: History::new(),
231        };
232        window
233    };
234}
235
236
237pub fn window() -> Window {
238    let win = GLOBAL_WINDOW.with(|win| win.clone());
239    win
240}
241
242
243#[derive(Clone, Debug)]
244pub struct Window {
245    pub instance: web_sys::Window,
246    pub local_storage: Storage,
247    pub document: Document,
248    pub location: Location,
249    pub history: History,
250}
251
252impl Window {
253    pub fn device_pixel_ratio(&self) -> f64 {
254        self.instance.device_pixel_ratio()
255    }
256    pub fn request_animation_frame(&self, cb: &Callback) {
257        self.instance.request_animation_frame(cb.as_js_function());
258    }
259    pub fn set_timeout(&self, cb: &Callback, timeout: i32) {
260        self.instance.set_timeout_with_callback_and_timeout_and_arguments_0(
261            cb.as_js_function(),
262            timeout
263        );
264    }
265}
266
267
268///////////////////////////////////////////////////////////////////////////////
269// LOCATION
270///////////////////////////////////////////////////////////////////////////////
271#[derive(Clone, Debug)]
272pub struct Location {
273    pub instance: web_sys::Location
274}
275
276impl Location {
277    pub fn new() -> Self {
278        Location {
279            instance: core::get_window()
280                .location()
281        }
282    }
283    pub fn pathname(&self) -> String {
284        self.instance
285            .pathname()
286            .expect("pathname failed")
287    }
288}
289
290
291
292///////////////////////////////////////////////////////////////////////////////
293// HISTORY
294///////////////////////////////////////////////////////////////////////////////
295
296#[derive(Clone, Debug)]
297pub struct History {
298    pub instance: web_sys::History
299}
300
301impl History {
302    pub fn new() -> Self {
303        History {
304            instance: core::get_window()
305                .history()
306                .expect("window.history getter failed"),
307        }
308    }
309    pub fn push_state(&self, url_path: &str) {
310        self.instance.push_state_with_url(
311            &JsValue::null(),
312            "",
313            Some(url_path)
314        )
315        .expect("pushState failed");
316    }
317}
318
319
320///////////////////////////////////////////////////////////////////////////////
321// STORAGE
322///////////////////////////////////////////////////////////////////////////////
323
324#[derive(Clone, Debug)]
325pub struct Storage {
326    pub instance: web_sys::Storage
327}
328
329impl Storage {
330    pub fn new() -> Self {
331        let instance = core::get_window()
332            .local_storage()
333            .expect("localStorage failed")
334            .expect("localStorage missing");
335        Storage {
336            instance: instance,
337        }
338    }
339    pub fn get<Value>(&self, key: &str) -> Option<Value>
340    where
341        Value: DeserializeOwned
342    {
343        let value = self.instance
344            .get_item(key)
345            .expect("getItem method failed");
346        match value {
347            None => None,
348            Some(value) => match serde_json::from_str(value.clone().as_str()) {
349                Err(msg) => None,
350                Ok(value) => Some(value)
351            }
352        }
353    }
354    pub fn set<Value: Serialize>(&self, key: &str, value: &Value) {
355        match serde_json::to_string(value) {
356            Err(msg) => (),
357            Ok(value) => self.instance
358                .set_item(key, value.as_str())
359                .expect("setItem method failed")
360        }
361    }
362    pub fn remove(&self, key: &str) {
363        self.instance
364            .remove_item(key)
365            .expect("removeItem method failed")
366    }
367}
368
369///////////////////////////////////////////////////////////////////////////////
370// DOCUMENT
371///////////////////////////////////////////////////////////////////////////////
372
373#[derive(Clone, Debug)]
374pub struct Document {
375    pub body: Body
376}
377
378impl Document {
379    pub fn new() -> Self {
380        Document {
381            body: Body::new()
382        }
383    }
384    pub fn create_element(&self, tag: &str) -> Tag {
385        let element = {
386            if tag::is_svg(tag) {
387                core::new_svg_element(tag)
388            } else {
389                core::new_element(tag)
390            }
391        };
392        let dom_ref: JsValue = From::from(element.clone());
393        Tag {
394            tag: String::from(tag),
395            dom_ref_as_node: From::from(dom_ref.clone()),
396            dom_ref: dom_ref,
397            dom_ref_as_element: element,
398        }
399    }
400    pub fn create_text_node(&self, initial_value: &str) -> Text {
401        let dom_ref_as_text: web_sys::Text = core::new_text(initial_value);
402        let dom_ref: JsValue = From::from(dom_ref_as_text.clone());
403        let dom_ref_as_node: web_sys::Node = From::from(dom_ref.clone());
404        Text {
405            dom_ref_as_text: dom_ref_as_text,
406            dom_ref: dom_ref,
407            dom_ref_as_node: dom_ref_as_node,
408        }
409    }
410}
411
412///////////////////////////////////////////////////////////////////////////////
413// HEAD
414///////////////////////////////////////////////////////////////////////////////
415
416#[derive(Clone, Debug)]
417pub struct Head {
418
419}
420
421
422
423///////////////////////////////////////////////////////////////////////////////
424// BODY
425///////////////////////////////////////////////////////////////////////////////
426
427#[derive(Clone, Debug)]
428pub struct Body {
429    pub i_dom_ref: JsValue,
430    pub i_dom_ref_as_node: web_sys::Node,
431    pub i_dom_ref_as_element: web_sys::Element,
432}
433
434impl Body {
435    pub fn new() -> Self {
436        let i_dom_ref_as_element = core::get_body();
437        let i_dom_ref: JsValue = From::from(i_dom_ref_as_element.clone());
438        let i_dom_ref_as_node: web_sys::Node = From::from(i_dom_ref.clone());
439        Body {i_dom_ref, i_dom_ref_as_element, i_dom_ref_as_node}
440    }
441}
442
443impl DomRef for Body {
444    fn dom_ref_as_node(&self) -> &web_sys::Node {
445        &self.i_dom_ref_as_node
446    }
447    fn dom_ref(&self) -> &JsValue {
448        &self.i_dom_ref
449    }
450}
451
452impl DomNode for Body {
453    fn dom_ref_as_element(&self) -> &web_sys::Element {
454        &self.i_dom_ref_as_element
455    }
456}
457
458
459
460///////////////////////////////////////////////////////////////////////////////
461// TAG
462///////////////////////////////////////////////////////////////////////////////
463
464#[derive(Clone, Debug)]
465/// Generic HTML tag
466pub struct Tag {
467    pub tag: String,
468    pub dom_ref: JsValue,
469    pub dom_ref_as_node: web_sys::Node,
470    pub dom_ref_as_element: web_sys::Element,
471}
472
473impl Tag {
474    pub fn new(tag: &str) -> Self {
475        let element = {
476            if tag::is_svg(tag) {
477                core::new_svg_element(tag)
478            } else {
479                core::new_element(tag)
480            }
481        };
482        let dom_ref: JsValue = From::from(element.clone());
483        Tag {
484            tag: String::from(tag),
485            dom_ref_as_node: From::from(dom_ref.clone()),
486            dom_ref: dom_ref,
487            dom_ref_as_element: element,
488        }
489    }
490}
491
492impl DomRef for Tag {
493    fn dom_ref_as_node(&self) -> &web_sys::Node {
494        &self.dom_ref_as_node
495    }
496    fn dom_ref(&self) -> &JsValue {
497        &self.dom_ref
498    }
499}
500
501impl DomNode for Tag {
502    fn dom_ref_as_element(&self) -> &web_sys::Element {
503        &self.dom_ref_as_element
504    }
505}
506
507
508
509///////////////////////////////////////////////////////////////////////////////
510// TEXT
511///////////////////////////////////////////////////////////////////////////////
512
513#[derive(Clone, Debug)]
514pub struct Text {
515    pub dom_ref: JsValue,
516    pub dom_ref_as_text: web_sys::Text,
517    pub dom_ref_as_node: web_sys::Node,
518}
519
520impl Text {
521    pub fn new(initial_value: &str) -> Self {
522        let dom_ref_as_text: web_sys::Text = core::new_text(initial_value);
523        let dom_ref: JsValue = From::from(dom_ref_as_text.clone());
524        let dom_ref_as_node: web_sys::Node = From::from(dom_ref.clone());
525        Text {
526            dom_ref_as_text: dom_ref_as_text,
527            dom_ref: dom_ref,
528            dom_ref_as_node: dom_ref_as_node,
529        }
530    }
531    pub fn set_text_content(&self, value: &str) {
532        self.dom_ref_as_node.set_text_content(Some(value));
533    }
534}
535
536impl DomRef for Text {
537    fn dom_ref_as_node(&self) -> &web_sys::Node {
538        &self.dom_ref_as_node
539    }
540    fn dom_ref(&self) -> &JsValue {
541        &self.dom_ref
542    }
543}
544