Handling Events
Unlike other UI frameworks, Async UI does not let you set event handler callbacks.
Instead, we provide an async API.
Waiting for a user click is no different from waiting for the TimeoutFuture
we used in previous chapters.
#![allow(unused)] fn main() { use async_ui_web::event_traits::EmitElementEvent; // 👈 new import! async fn quick_example() { let button = Button::new(); join(( // render the button button.render("Click me!".render()), async { button.until_click().await; // 👈 wait for the button to be clicked // handle click }, )) .await; } }
The methods for waiting for events are all named until_*
.
They are provided in 3 different traits
-
EmitEvent is implemented on anything that is a JavaScript Event Target. It provides only one method:
#![allow(unused)] fn main() { fn until_event<E>(&self, name: Cow<'static, str>) -> EventFutureStream<E>; }
It listens to event of the given name.
We will discuss what
EventFutureStream
does shortly. -
EmitElementEvent is implemented for the Element JavaScript class. It provides methods like:
#![allow(unused)] fn main() { fn until_click(&self) -> EventFutureStream<MouseEvent>; fn until_focus(&self) -> EventFutureStream<UiEvent>; fn until_keydown(&self) -> EventFutureStream<KeyboardEvent>; // ... and more ... }
-
EmitHtmlElementEvent is implemented for the HTMLElement JavaScript class (note that this is not the same thing as the
Element
class). It provides methods like:#![allow(unused)] fn main() { fn until_input(&self) -> EventFutureStream<Event>; fn until_drag(&self) -> EventFutureStream<DragEvent>; // ... and more ... }
Usually, you would just type in the method you want to use, and rust-analyzer will figure out which trait to import for you.
EventFutureStram<E>
The return type of all those methods is EventFutureStream.
Use as Future
As you have already seen in the previous example,
you can await
an EventFutureStream. It is a Future.
When await
-ed, an EventFutureStream will return the JavaScript Event object that
it listens for (the object is translated to Rust via web-sys;
see for instance web_sys::MouseEvent).
You can, for example, call preventDefault
on the returned event object.
#![allow(unused)] fn main() { async fn return_type() { let link = Anchor::new(); link.set_href("https://example.com/"); join(( // render the link link.render("I'm a link!".render()), async { let ev = link.until_click().await; // 👈 wait for the button to be clicked ev.prevent_default(); // 👈 use the event object // we called preventDefault so example.com won't be opened }, )) .await; } }
In this example, notice that we only listen to click
event once.
The first time the user click the link, preventDefault
will be called and the
link won't be opened. The second time, however, the link will open normally.
If you want to handle the event every time it fires, you can put the code in a loop. Try it! now the link won't open however many times you click.
Use as Stream
EventFutureStream is not only a Future, it is also a Stream. It can be quite convenient to work with it through the Stream API instead of the Future API.
For example, let's use the Stream API from futures-lite.
The crate provides a for_each method for Streams. It is perfect for our use case.
#![allow(unused)] fn main() { use futures_lite::StreamExt; // 👈 new! async fn prevent_default_with_stream() { let link = Anchor::new(); link.set_href("https://example.com/"); join(( // render the link link.render("I'm a link!".render()), // for each click event, `preventDefault` it link.until_click().for_each(|ev| { ev.prevent_default(); }), )) .await; } }