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;
}
}