yew/html/listener/mod.rs
1#[macro_use]
2mod events;
3
4pub use events::*;
5use wasm_bindgen::JsCast;
6use web_sys::{Event, EventTarget};
7
8use crate::Callback;
9
10/// Cast [Event] `e` into it's target `T`.
11///
12/// This function mainly exists to provide type inference in the [impl_action] macro to the compiler
13/// and avoid some verbosity by not having to type the signature over and over in closure
14/// definitions.
15#[inline]
16pub(crate) fn cast_event<T>(e: Event) -> T
17where
18 T: JsCast,
19{
20 e.unchecked_into()
21}
22
23/// A trait to obtain a generic event target.
24///
25/// The methods in this trait are convenient helpers that use the [`JsCast`] trait internally
26/// to do the conversion.
27pub trait TargetCast
28where
29 Self: AsRef<Event>,
30{
31 /// Performs a dynamic cast (checked at runtime) of this events target into the type `T`.
32 ///
33 /// This method can return [`None`] for two reasons:
34 /// - The event's target was [`None`]
35 /// - The event's target type did not match `T`
36 ///
37 /// # Example
38 ///
39 /// ```
40 /// use web_sys::HtmlTextAreaElement;
41 /// use yew::prelude::*;
42 /// # enum Msg {
43 /// # Value(String),
44 /// # }
45 /// # struct Comp;
46 /// # impl Component for Comp {
47 /// # type Message = Msg;
48 /// # type Properties = ();
49 /// # fn create(ctx: &Context<Self>) -> Self {
50 /// # Self
51 /// # }
52 ///
53 /// fn view(&self, ctx: &Context<Self>) -> Html {
54 /// html! {
55 /// <div
56 /// onchange={ctx.link().batch_callback(|e: Event| {
57 /// if let Some(input) = e.target_dyn_into::<HtmlTextAreaElement>() {
58 /// Some(Msg::Value(input.value()))
59 /// } else {
60 /// None
61 /// }
62 /// })}
63 /// >
64 /// <textarea />
65 /// <input type="text" />
66 /// </div>
67 /// }
68 /// }
69 /// # }
70 /// ```
71 /// _Note: if you can apply the [`Callback`] directly onto an element which doesn't have a child
72 /// consider using [`TargetCast::target_unchecked_into<T>`]_
73 #[inline]
74 fn target_dyn_into<T>(&self) -> Option<T>
75 where
76 T: AsRef<EventTarget> + JsCast,
77 {
78 self.as_ref()
79 .target()
80 .and_then(|target| target.dyn_into().ok())
81 }
82
83 #[inline]
84 /// Performs a zero-cost unchecked cast of this events target into the type `T`.
85 ///
86 /// This method **does not check whether the event target is an instance of `T`**. If used
87 /// incorrectly then this method may cause runtime exceptions in both Rust and JS, this should
88 /// be used with caution.
89 ///
90 /// A common safe usage of this method is within a [`Callback`] that is applied directly to an
91 /// element that has no children, thus `T` will be the type of the element the [`Callback`] is
92 /// applied to.
93 ///
94 /// # Example
95 ///
96 /// ```
97 /// use web_sys::HtmlInputElement;
98 /// use yew::prelude::*;
99 /// # enum Msg {
100 /// # Value(String),
101 /// # }
102 /// # struct Comp;
103 /// # impl Component for Comp {
104 /// # type Message = Msg;
105 /// # type Properties = ();
106 /// # fn create(ctx: &Context<Self>) -> Self {
107 /// # Self
108 /// # }
109 ///
110 /// fn view(&self, ctx: &Context<Self>) -> Html {
111 /// html! {
112 /// <input type="text"
113 /// onchange={ctx.link().callback(|e: Event| {
114 /// // Safe to use as callback is on an `input` element so this event can
115 /// // only come from this input!
116 /// let input: HtmlInputElement = e.target_unchecked_into();
117 /// Msg::Value(input.value())
118 /// })}
119 /// />
120 /// }
121 /// }
122 /// # }
123 /// ```
124 fn target_unchecked_into<T>(&self) -> T
125 where
126 T: AsRef<EventTarget> + JsCast,
127 {
128 self.as_ref().target().unwrap().unchecked_into()
129 }
130}
131
132impl<E: AsRef<Event>> TargetCast for E {}
133
134/// A trait similar to `Into<T>` which allows conversion of a value into a [`Callback`].
135/// This is used for event listeners.
136pub trait IntoEventCallback<EVENT> {
137 /// Convert `self` to `Option<Callback<EVENT>>`
138 fn into_event_callback(self) -> Option<Callback<EVENT>>;
139}
140
141impl<EVENT> IntoEventCallback<EVENT> for Callback<EVENT> {
142 fn into_event_callback(self) -> Option<Callback<EVENT>> {
143 Some(self)
144 }
145}
146
147impl<EVENT> IntoEventCallback<EVENT> for &Callback<EVENT> {
148 fn into_event_callback(self) -> Option<Callback<EVENT>> {
149 Some(self.clone())
150 }
151}
152
153impl<EVENT> IntoEventCallback<EVENT> for Option<Callback<EVENT>> {
154 fn into_event_callback(self) -> Option<Callback<EVENT>> {
155 self
156 }
157}
158
159impl<T, EVENT> IntoEventCallback<EVENT> for T
160where
161 T: Fn(EVENT) + 'static,
162{
163 fn into_event_callback(self) -> Option<Callback<EVENT>> {
164 Some(Callback::from(self))
165 }
166}
167
168impl<T, EVENT> IntoEventCallback<EVENT> for Option<T>
169where
170 T: Fn(EVENT) + 'static,
171{
172 fn into_event_callback(self) -> Option<Callback<EVENT>> {
173 Some(Callback::from(self?))
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 #[test]
182 fn supported_into_event_callback_types() {
183 let f = |_: usize| ();
184 let cb = Callback::from(f);
185
186 // Callbacks
187 let _: Option<Callback<usize>> = cb.clone().into_event_callback();
188 let _: Option<Callback<usize>> = (&cb).into_event_callback();
189 let _: Option<Callback<usize>> = Some(cb).into_event_callback();
190
191 // Fns
192 let _: Option<Callback<usize>> = f.into_event_callback();
193 let _: Option<Callback<usize>> = Some(f).into_event_callback();
194 }
195}