yew_stdweb/html/listener/
mod.rs

1#[macro_use]
2mod macros;
3
4use cfg_if::cfg_if;
5use cfg_match::cfg_match;
6
7cfg_if! {
8    if #[cfg(feature = "std_web")] {
9        mod listener_stdweb;
10
11        use stdweb::js;
12        use stdweb::unstable::{TryFrom, TryInto};
13        use stdweb::web::html_element::{InputElement, SelectElement, TextAreaElement};
14        use stdweb::web::{Element, EventListenerHandle, FileList, IElement, INode};
15        use stdweb::web::event::InputEvent;
16
17        pub use listener_stdweb::*;
18    } else if #[cfg(feature = "web_sys")] {
19        mod listener_web_sys;
20
21        use wasm_bindgen::JsCast;
22        use web_sys::{
23            Element, FileList, HtmlInputElement as InputElement, HtmlSelectElement as SelectElement,
24            HtmlTextAreaElement as TextAreaElement,
25            InputEvent
26        };
27
28        pub use listener_web_sys::*;
29    }
30}
31
32/// A type representing data from `oninput` event.
33#[derive(Debug)]
34pub struct InputData {
35    /// Inserted characters. Contains value from
36    /// [InputEvent](https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/data).
37    pub value: String,
38    /// The InputEvent received.
39    pub event: InputEvent,
40}
41
42// There is no '.../Web/API/ChangeEvent/data' (for onchange) similar to
43// https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/data (for oninput).
44// ChangeData actually contains the value of the InputElement/TextAreaElement
45// after `change` event occured or contains the SelectElement (see more at the
46// variant ChangeData::Select)
47
48/// A type representing change of value(s) of an element after committed by user
49/// ([onchange event](https://developer.mozilla.org/en-US/docs/Web/Events/change)).
50#[derive(Debug)]
51pub enum ChangeData {
52    /// Value of the element in cases of `<input>`, `<textarea>`
53    Value(String),
54    /// SelectElement in case of `<select>` element. You can use one of methods of SelectElement
55    /// to collect your required data such as: `value`, `selected_index`, `selected_indices` or
56    /// `selected_values`. You can also iterate throught `selected_options` yourself.
57    Select(SelectElement),
58    /// Files
59    Files(FileList),
60}
61
62fn oninput_handler(this: &Element, event: InputEvent) -> InputData {
63    // Normally only InputElement or TextAreaElement can have an oninput event listener. In
64    // practice though any element with `contenteditable=true` may generate such events,
65    // therefore here we fall back to just returning the text content of the node.
66    // See https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/input_event.
67    let (v1, v2) = cfg_match! {
68        feature = "std_web" => ({
69            (
70                this.clone()
71                    .try_into()
72                    .map(|input: InputElement| input.raw_value())
73                    .ok(),
74                this.clone()
75                    .try_into()
76                    .map(|input: TextAreaElement| input.value())
77                    .ok(),
78            )
79        }),
80        feature = "web_sys" => ({
81            (
82                this.dyn_ref().map(|input: &InputElement| input.value()),
83                this.dyn_ref().map(|input: &TextAreaElement| input.value()),
84            )
85        }),
86    };
87    let v3 = this.text_content();
88    let value = v1.or(v2).or(v3)
89        .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener");
90    InputData { value, event }
91}
92
93fn onchange_handler(this: &Element) -> ChangeData {
94    match this.node_name().as_ref() {
95        "INPUT" => {
96            let input = cfg_match! {
97                feature = "std_web" => InputElement::try_from(this.clone()).unwrap(),
98                feature = "web_sys" => this.dyn_ref::<InputElement>().unwrap(),
99            };
100            let is_file = input
101                .get_attribute("type")
102                .map(|value| value.eq_ignore_ascii_case("file"))
103                .unwrap_or(false);
104            if is_file {
105                let files: FileList = cfg_match! {
106                    feature = "std_web" => js!( return @{input}.files; ).try_into().unwrap(),
107                    feature = "web_sys" => input.files().unwrap(),
108                };
109                ChangeData::Files(files)
110            } else {
111                cfg_match! {
112                    feature = "std_web" => ChangeData::Value(input.raw_value()),
113                    feature = "web_sys" => ChangeData::Value(input.value()),
114                }
115            }
116        }
117        "TEXTAREA" => {
118            let tae = cfg_match! {
119                feature = "std_web" => TextAreaElement::try_from(this.clone()).unwrap(),
120                feature = "web_sys" => this.dyn_ref::<TextAreaElement>().unwrap(),
121            };
122            ChangeData::Value(tae.value())
123        }
124        "SELECT" => {
125            let se = cfg_match! {
126                feature = "std_web" => SelectElement::try_from(this.clone()).unwrap(),
127                feature = "web_sys" => this.dyn_ref::<SelectElement>().unwrap().clone(),
128            };
129            ChangeData::Select(se)
130        }
131        _ => {
132            panic!("only an InputElement, TextAreaElement or SelectElement can have an onchange event listener");
133        }
134    }
135}
136
137/// Handler to an event listener, only use is to cancel the event.
138#[cfg(feature = "std_web")]
139#[derive(Debug)]
140pub struct EventListener(Option<EventListenerHandle>);
141
142#[cfg(feature = "std_web")]
143impl Drop for EventListener {
144    fn drop(&mut self) {
145        if let Some(event) = self.0.take() {
146            event.remove()
147        }
148    }
149}