Events

Introduction

Yew integrates with the web-sys crate and uses the events from that crate. The table below lists all of the web-sys events that are accepted in the html! macro.

You can still add a Callback for an event that is not listed in the table below, see Manual event listener.

Event Types

Events - 图1提示

All the event types mentioned in the following table are re-exported under yew::events. Using the types from yew::events makes it easier to ensure version compatibility than if you were to manually include web-sys as a dependency in your crate because you won’t end up using a version which conflicts with the version that Yew specifies.

The event listener name is the expected name when adding an event Callback in the html macro:

  1. use yew::{html, Callback};
  2. html! {
  3. <button onclick={Callback::from(|_| ())}>
  4. // ^^^^^^^ event listener name
  5. { "Click me!" }
  6. </button>
  7. };

The event name is the listener without the “on” prefix, therefore, the onclick event listener listens for click events.

Event listener nameweb_sys Event Type
onabortEvent
onauxclickMouseEvent
onblurFocusEvent
oncancelEvent
oncanplayEvent
oncanplaythroughEvent
onchangeEvent
onclickMouseEvent
oncloseEvent
oncontextmenuMouseEvent
oncuechangeEvent
ondblclickMouseEvent
ondragDragEvent
ondragendDragEvent
ondragenterDragEvent
ondragexitDragEvent
ondragleaveDragEvent
ondragoverDragEvent
ondragstartDragEvent
ondropDragEvent
ondurationchangeEvent
onemptiedEvent
onendedEvent
onerrorEvent
onfocusFocusEvent
onfocusinFocusEvent
onfocusoutFocusEvent
onformdataEvent
oninputInputEvent
oninvalidEvent
onkeydownKeyboardEvent
onkeypressKeyboardEvent
onkeyupKeyboardEvent
onloadEvent
onloadeddataEvent
onloadedmetadataEvent
onloadstartProgressEvent
onmousedownMouseEvent
onmouseenterMouseEvent
onmouseleaveMouseEvent
onmousemoveMouseEvent
onmouseoutMouseEvent
onmouseoverMouseEvent
onmouseupMouseEvent
onpauseEvent
onplayEvent
onplayingEvent
onprogressProgressEvent
onratechangeEvent
onresetEvent
onresizeEvent
onscrollEvent
onsecuritypolicyviolationEvent
onseekedEvent
onseekingEvent
onselectEvent
onslotchangeEvent
onstalledEvent
onsubmitFocusEvent
onsuspendEvent
ontimeupdateEvent
ontoggleEvent
onvolumechangeEvent
onwaitingEvent
onwheelWheelEvent
oncopyEvent
oncutEvent
onpasteEvent
onanimationcancelAnimationEvent
onanimationendAnimationEvent
onanimationiterationAnimationEvent
onanimationstartAnimationEvent
ongotpointercapturePointerEvent
onloadendProgressEvent
onlostpointercapturePointerEvent
onpointercancelPointerEvent
onpointerdownPointerEvent
onpointerenterPointerEvent
onpointerleavePointerEvent
onpointerlockchangeEvent
onpointerlockerrorEvent
onpointermovePointerEvent
onpointeroutPointerEvent
onpointeroverPointerEvent
onpointerupPointerEvent
onselectionchangeEvent
onselectstartEvent
onshowEvent
ontouchcancelTouchEvent
ontouchendTouchEvent
ontouchmoveTouchEvent
ontouchstartTouchEvent
ontransitioncancelTransitionEvent
ontransitionendTransitionEvent
ontransitionrunTransitionEvent
ontransitionstartTransitionEvent

Typed event target

Events - 图2警告

In this section target (Event.target) is always referring to the element at which the event was dispatched from.

This will not always be the element at which the Callback is placed.

In event Callbacks you may want to get the target of that event. For example, the change event gives no information but is used to notify that something has changed.

In Yew getting the target element in the correct type can be done in a few ways and we will go through them here. Calling web_sys::Event::target on an event returns an optional web_sys::EventTarget type, which might not seem very useful when you want to know the value of your input element.

In all the approaches below we are going to tackle the same problem, so it’s clear where the approach differs opposed to the problem at hand.

The Problem:

We have an onchange Callback on my <input> element and each time it is invoked we want to send an update Msg to our component.

Our Msg enum looks like this:

  1. pub enum Msg {
  2. InputValue(String),
  3. }

Using JsCast

The wasm-bindgen crate has a useful trait; JsCast which allows us to hop and skip our way to the type we want, as long as it implements JsCast. We can do this cautiously, which involves some runtime checks and failure types like Option and Result, or we can do it dangerously.

Enough talk, more code:

Cargo.toml

  1. [dependencies]
  2. # need wasm-bindgen for JsCast
  3. wasm-bindgen = "0.2"
  1. use wasm_bindgen::JsCast;
  2. use web_sys::{EventTarget, HtmlInputElement};
  3. use yew::{
  4. events::Event,
  5. html,
  6. Component, Context, Html,
  7. };
  8. pub struct Comp;
  9. pub enum Msg {
  10. InputValue(String),
  11. }
  12. impl Component for Comp {
  13. type Message = Msg;
  14. type Properties = ();
  15. fn create(_: &Context<Self>) -> Self {
  16. Self
  17. }
  18. fn view(&self, ctx: &Context<Self>) -> Html {
  19. let link = ctx.link();
  20. // Use batch_callback so if something unexpected happens we can return
  21. // None and do nothing
  22. let on_cautious_change = link.batch_callback(|e: Event| {
  23. // When events are created the target is undefined, it's only
  24. // when dispatched does the target get added.
  25. let target: Option<EventTarget> = e.target();
  26. // Events can bubble so this listener might catch events from child
  27. // elements which are not of type HtmlInputElement
  28. let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
  29. input.map(|input| Msg::InputValue(input.value()))
  30. });
  31. let on_dangerous_change = link.callback(|e: Event| {
  32. let target: EventTarget = e
  33. .target()
  34. .expect("Event should have a target when dispatched");
  35. // You must KNOW target is a HtmlInputElement, otherwise
  36. // the call to value would be Undefined Behaviour (UB).
  37. Msg::InputValue(target.unchecked_into::<HtmlInputElement>().value())
  38. });
  39. html! {
  40. <>
  41. <label for="cautious-input">
  42. { "My cautious input:" }
  43. <input onchange={on_cautious_change}
  44. id="cautious-input"
  45. type="text"
  46. />
  47. </label>
  48. <label for="dangerous-input">
  49. { "My dangerous input:" }
  50. <input onchange={on_dangerous_change}
  51. id="dangerous-input"
  52. type="text"
  53. />
  54. </label>
  55. </>
  56. }
  57. }
  58. }

Events - 图3提示

Use batch_callback when you want to conditionally return a value from a Callback.

The methods from JsCast are dyn_into and unchecked_into and you can probably see, they allowed us to go from EventTarget to HtmlInputElement. The dyn_into method is cautious because at runtime it will check whether the type is actually a HtmlInputElement and if not return an Err(JsValue), the JsValue is a catch-all type and is essentially giving you back the object to try again.

At this point you might be thinking… when is the dangerous version ok to use? In the case above it is safe1 as we’ve set the Callback on to an element with no children so the target can only be that same element.

1 As safe as anything can be when JS land is involved.

Using TargetCast

It is highly recommended to read Using JsCast first!

Events - 图4备注

TargetCast was designed to feel very similar to JsCast - this is to allow new users to get a feel for the behaviour of JsCast but with the smaller scope of events and their targets.

TargetCast vs JsCast is purely preference, you will find that TargetCast implements something similar to what you would using JsCast.

The TargetCast trait is built on top of JsCast and is specialized towards getting typed event targets from events.

TargetCast comes with Yew so no need to add a dependency in order to use the trait methods on events but it works in a very similar way to JsCast.

  1. use web_sys::HtmlInputElement;
  2. use yew::{
  3. events::Event,
  4. html,
  5. // Need to import TargetCast
  6. Component, Context, Html, TargetCast,
  7. };
  8. pub struct Comp;
  9. pub enum Msg {
  10. InputValue(String),
  11. }
  12. impl Component for Comp {
  13. type Message = Msg;
  14. type Properties = ();
  15. fn create(_: &Context<Self>) -> Self {
  16. Self
  17. }
  18. fn view(&self, ctx: &Context<Self>) -> Html {
  19. let link = ctx.link();
  20. let on_cautious_change = link.batch_callback(|e: Event| {
  21. let input = e.target_dyn_into::<HtmlInputElement>();
  22. input.map(|input| Msg::InputValue(input.value()))
  23. });
  24. let on_dangerous_change = link.callback(|e: Event| {
  25. // You must KNOW target is a HtmlInputElement, otherwise
  26. // the call to value would be Undefined Behaviour (UB).
  27. Msg::InputValue(e.target_unchecked_into::<HtmlInputElement>().value())
  28. });
  29. html! {
  30. <>
  31. <label for="cautious-input">
  32. { "My cautious input:" }
  33. <input onchange={on_cautious_change}
  34. id="cautious-input"
  35. type="text"
  36. />
  37. </label>
  38. <label for="dangerous-input">
  39. { "My dangerous input:" }
  40. <input onchange={on_dangerous_change}
  41. id="dangerous-input"
  42. type="text"
  43. />
  44. </label>
  45. </>
  46. }
  47. }
  48. }

If you followed the advice above and read about JsCast, or know the trait, you can probably see that TargetCast::target_dyn_into feels similar to JsCast::dyn_into but specifically does the cast on the target of the event. TargetCast::target_unchecked_into is similar to JsCast::unchecked_into, and as such all the same warnings above JsCast apply to TargetCast.

Using NodeRef

NodeRef can be used instead of querying the event given to a Callback.

  1. use web_sys::HtmlInputElement;
  2. use yew::{html, Component, Context, Html, NodeRef};
  3. pub struct Comp {
  4. my_input: NodeRef,
  5. }
  6. pub enum Msg {
  7. InputValue(String),
  8. }
  9. impl Component for Comp {
  10. type Message = Msg;
  11. type Properties = ();
  12. fn create(_: &Context<Self>) -> Self {
  13. Self {
  14. my_input: NodeRef::default(),
  15. }
  16. }
  17. fn view(&self, ctx: &Context<Self>) -> Html {
  18. let link = ctx.link();
  19. let my_input_ref = self.my_input.clone();
  20. let onchange = link.batch_callback(move |_| {
  21. let input = my_input_ref.cast::<HtmlInputElement>();
  22. input.map(|input| Msg::InputValue(input.value()))
  23. });
  24. html! {
  25. <>
  26. <label for="my-input">
  27. { "My input:" }
  28. <input ref={self.my_input.clone()}
  29. {onchange}
  30. id="my-input"
  31. type="text"
  32. />
  33. </label>
  34. </>
  35. }
  36. }
  37. }

Using NodeRef, you can ignore the event and use the NodeRef::cast method to get an Option<HtmlInputElement> - this is optional as calling cast before the NodeRef has been set, or when the type doesn’t match will return None.

You might also see by using NodeRef we don’t have to send the String back in the Msg::InputValue as we always have my_input in the component state - so we could do the following:

  1. use web_sys::HtmlInputElement;
  2. use yew::{html, Component, Context, Html, NodeRef};
  3. pub struct Comp {
  4. my_input: NodeRef,
  5. }
  6. pub enum Msg {
  7. // Signal the input element has changed
  8. InputChanged,
  9. }
  10. impl Component for Comp {
  11. type Message = Msg;
  12. type Properties = ();
  13. fn create(_: &Context<Self>) -> Self {
  14. Self {
  15. my_input: NodeRef::default(),
  16. }
  17. }
  18. fn update(&mut self, _: &Context<Self>, msg: Self::Message) -> bool {
  19. match msg {
  20. Msg::InputChanged => {
  21. if let Some(input) = self.my_input.cast::<HtmlInputElement>() {
  22. let value = input.value();
  23. // do something with value
  24. true
  25. } else {
  26. false
  27. }
  28. }
  29. }
  30. }
  31. fn view(&self, ctx: &Context<Self>) -> Html {
  32. let link = ctx.link();
  33. let onchange = link.callback(|_| Msg::InputChanged);
  34. html! {
  35. <label for="my-input">
  36. { "My input:" }
  37. <input ref={self.my_input.clone()}
  38. {onchange}
  39. id="my-input"
  40. type="text"
  41. />
  42. </label>
  43. }
  44. }
  45. }

Which approach you take depends on your component and your preferences, there is no blessed way per se.

Manual event listener

You may want to listen to an event that is not supported by Yew’s html macro, see the supported events listed here.

In order to add an event listener to one of elements manually we need the help of NodeRef so that in the rendered method we can add a listener using the web-sys and wasm-bindgen API. We do this in rendered as this is the only time we can guarantee that the element exists in the browser, Yew needs some time to create them after view is called.

The examples below are going to show adding listeners for the made-up custard event. All events either unsupported by yew or custom can be represented as a web_sys::Event. If you need to access a specific method or field on a custom / unsupported event then you can use the methods of JsCast in order to convert to the type required.

Using Closure (verbose)

Using the web-sys and wasm-bindgen API’s directly for this can be a bit painful.. so brace yourself (there is a more concise way thanks to gloo).

  1. use wasm_bindgen::{prelude::Closure, JsCast};
  2. use web_sys::HtmlElement;
  3. use yew::{
  4. events::Event,
  5. html,
  6. Component, Context, Html, NodeRef,
  7. };
  8. pub struct Comp {
  9. my_div: NodeRef,
  10. custard_listener: Option<Closure<dyn Fn(Event)>>,
  11. }
  12. pub enum Msg {
  13. Custard,
  14. }
  15. impl Component for Comp {
  16. type Message = Msg;
  17. type Properties = ();
  18. fn create(_: &Context<Self>) -> Self {
  19. Self {
  20. my_div: NodeRef::default(),
  21. custard_listener: None,
  22. }
  23. }
  24. fn update(&mut self, _: &Context<Self>, msg: Self::Message) -> bool {
  25. match msg {
  26. Msg::Custard => {
  27. // do something about custard..
  28. true
  29. }
  30. }
  31. }
  32. fn view(&self, _: &Context<Self>) -> Html {
  33. html! {
  34. <div ref={self.my_div.clone()} id="my-div"></div>
  35. }
  36. }
  37. fn rendered(&mut self, ctx: &Context<Self>, first_render: bool) {
  38. if !first_render {
  39. return;
  40. }
  41. if let Some(element) = self.my_div.cast::<HtmlElement>() {
  42. // Create your Callback as you normally would
  43. let oncustard = ctx.link().callback(|_: Event| Msg::Custard);
  44. // Create a Closure from a Box<dyn Fn> - this has to be 'static
  45. let listener =
  46. Closure::<dyn Fn(Event)>::wrap(
  47. Box::new(move |e: Event| oncustard.emit(e))
  48. );
  49. element
  50. .add_event_listener_with_callback(
  51. "custard",
  52. listener.as_ref().unchecked_ref()
  53. )
  54. .unwrap();
  55. // Need to save listener in the component otherwise when the
  56. // event is fired it will try and call the listener that no longer
  57. // exists in memory!
  58. self.custard_listener = Some(listener);
  59. }
  60. }
  61. fn destroy(&mut self, _: &Context<Self>) {
  62. // All done with the component but need to remove
  63. // the event listener before the custard_listener memory
  64. // goes out of scope.
  65. if let (Some(element), Some(listener)) = (
  66. self.my_div.cast::<HtmlElement>(),
  67. self.custard_listener.take(),
  68. ) {
  69. element
  70. .remove_event_listener_with_callback(
  71. "custard",
  72. listener.as_ref().unchecked_ref()
  73. )
  74. .unwrap();
  75. }
  76. }
  77. }

For more information on Closures, see The wasm-bindgen Guide.

Using gloo (concise)

The easier way is with gloo, more specifically gloo_events which is an abstraction for web-sys, wasm-bindgen.

gloo_events has the EventListener type which can be used to create and store the event listener.

Cargo.toml

  1. [dependencies]
  2. gloo-events = "0.1"
  1. use web_sys::HtmlElement;
  2. use yew::{
  3. events::Event,
  4. html,
  5. Component, Context, Html, NodeRef,
  6. };
  7. use gloo_events::EventListener;
  8. pub struct Comp {
  9. my_div: NodeRef,
  10. custard_listener: Option<EventListener>,
  11. }
  12. pub enum Msg {
  13. Custard,
  14. }
  15. impl Component for Comp {
  16. type Message = Msg;
  17. type Properties = ();
  18. fn create(_: &Context<Self>) -> Self {
  19. Self {
  20. my_div: NodeRef::default(),
  21. custard_listener: None,
  22. }
  23. }
  24. fn update(&mut self, _: &Context<Self>, msg: Self::Message) -> bool {
  25. match msg {
  26. Msg::Custard => {
  27. // do something about custard..
  28. true
  29. }
  30. }
  31. }
  32. fn view(&self, _: &Context<Self>) -> Html {
  33. html! {
  34. <div ref={self.my_div.clone()} id="my-div"></div>
  35. }
  36. }
  37. fn rendered(&mut self, ctx: &Context<Self>, first_render: bool) {
  38. if !first_render {
  39. return;
  40. }
  41. if let Some(element) = self.my_div.cast::<HtmlElement>() {
  42. // Create your Callback as you normally would
  43. let oncustard = ctx.link().callback(|_: Event| Msg::Custard);
  44. let listener = EventListener::new(
  45. &element,
  46. "custard",
  47. move |e| oncustard.emit(e.clone())
  48. );
  49. self.custard_listener = Some(listener);
  50. }
  51. }
  52. }

Notice that when using an EventListener you don’t need to do anything when the component is about to be destroyed as the EventListener has a drop implementation which will remove the event listener from the element.

For more information on EventListener, see the gloo_events docs.rs.