Skip to content

Commit

Permalink
make set_cancel_bubble work
Browse files Browse the repository at this point in the history
  • Loading branch information
voidpumpkin committed Nov 21, 2021
1 parent f549648 commit e0c2a55
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 10 deletions.
42 changes: 38 additions & 4 deletions packages/yew/src/virtual_dom/listeners.rs
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ impl Registry {

run_handler(&target);

if unsafe { BUBBLE_EVENTS } {
if unsafe { BUBBLE_EVENTS } && !event.cancel_bubble() {
let mut el = target;
loop {
el = match el.parent_element() {
Expand All @@ -523,7 +523,7 @@ mod tests {
use std::marker::PhantomData;

use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
use web_sys::{Event, EventInit};
use web_sys::{Event, EventInit, MouseEvent};
wasm_bindgen_test_configure!(run_in_browser);

use crate::{html, html::TargetCast, AppHandle, Component, Context, Html};
Expand Down Expand Up @@ -794,6 +794,40 @@ mod tests {
assert_count(&el, 4);
}

#[test]
fn cancel_bubbling() {
struct CencelBubbling;

impl Mixin for CencelBubbling {
fn view<C>(ctx: &Context<C>, state: &State) -> Html
where
C: Component<Message = Message>,
{
html! {
<div onclick={ctx.link().callback(|_| Message::Action)}>
<a onclick={ctx.link().callback(|mouse_event: MouseEvent| {
let event: Event = mouse_event.dyn_into().unwrap();
event.set_cancel_bubble(true);
Message::Action
})}>
{state.action}
</a>
</div>
}
}
}

let (_, el) = init::<CencelBubbling>("a");

assert_count(&el, 0);

el.click();
assert_count(&el, 1);

el.click();
assert_count(&el, 2);
}

fn test_input_listener<E>(make_event: impl Fn() -> E)
where
E: JsCast + std::fmt::Debug,
Expand Down Expand Up @@ -858,7 +892,7 @@ mod tests {
test_input_listener(|| {
web_sys::InputEvent::new_with_event_init_dict(
"input",
&web_sys::InputEventInit::new().bubbles(true),
web_sys::InputEventInit::new().bubbles(true),
)
.unwrap()
})
Expand All @@ -869,7 +903,7 @@ mod tests {
test_input_listener(|| {
web_sys::Event::new_with_event_init_dict(
"change",
&web_sys::EventInit::new().bubbles(true),
web_sys::EventInit::new().bubbles(true),
)
.unwrap()
})
Expand Down
4 changes: 2 additions & 2 deletions packages/yew/src/virtual_dom/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -721,7 +721,7 @@ mod benchmarks {
let static_ = Attributes::Static(&[]);
let dynamic = Attributes::Dynamic {
keys: &[],
values: vec![],
values: Box::new([]),
};
let map = Attributes::IndexMap(Default::default());

Expand Down Expand Up @@ -776,7 +776,7 @@ mod benchmarks {
fn make_dynamic(values: Vec<AttrValue>) -> Attributes {
Attributes::Dynamic {
keys: sample_keys(),
values: values.into_iter().map(|v| Some(v)).collect(),
values: values.into_iter().map(Some).collect(),
}
}

Expand Down
44 changes: 40 additions & 4 deletions website/docs/concepts/html/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,6 @@ listens for `click` events.
In this section **target ([Event.target](https://developer.mozilla.org/en-US/docs/Web/API/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.
:::

Expand Down Expand Up @@ -257,6 +256,7 @@ impl Component for Comp {
}
}
```

:::tip
Use `batch_callback` when you want to conditionally return a value from a `Callback`.
:::
Expand All @@ -274,7 +274,6 @@ At this point you might be thinking... when is the dangerous version ok to use?
is safe<sup>1</sup> as we've set the `Callback` on to an element with no children so the target can
only be that same element.


_<sup>1</sup> As safe as anything can be when JS land is involved._

### Using `TargetCast`
Expand Down Expand Up @@ -357,12 +356,12 @@ impl Component for Comp {
}
}
```

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`](../components/refs.md) can be used instead of querying the event given to a `Callback`.
Expand Down Expand Up @@ -421,6 +420,7 @@ impl Component for Comp {
}
}
```

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`.
Expand Down Expand Up @@ -491,6 +491,42 @@ impl Component for Comp {
Which approach you take depends on your component and your preferences, there is no _blessed_ way
per se.

## Event bubbling

:::caution

`event.stop_propagation()` will not work! Read further for solutions

:::

Yew is in early stages of optimizing and rewriting its events system.
Currently all events are managed by using 1 event listener on the top html element available to yew.
Then yew itself works through the event flow.
When you want to stop propagation there are the following two ways.

### set_cancel_bubble

To stop propagation, `set_cancel_bubble(true)` on `web_sys::Event` can be used.

```rust ,ignore
// In your event handler
let event: Event = mouse_event.dyn_into().unwrap();
event.set_cancel_bubble(true);
```

### set_event_bubbling

To improve yew performance it is recommended to use `set_event_bubbling` before the start of the app to disable bubbling all together, though there are known issues with some in-house yew components like `yew_router::Link`.

```rust ,ignore
use yew::prelude::*;

fn main() {
yew::set_event_bubbling(false);
yew::start_app::<app::App>();
}
```

## Manual event listener

You may want to listen to an event that is not supported by Yew's `html` macro, see the
Expand Down Expand Up @@ -693,4 +729,4 @@ component is about to be destroyed as the `EventListener` has a `drop` implement
which will remove the event listener from the element.

For more information on `EventListener`, see the
[gloo_events docs.rs](https://docs.rs/gloo-events/0.1.1/gloo_events/struct.EventListener.html).
[gloo_events docs.rs](https://docs.rs/gloo-events/0.1.1/gloo_events/struct.EventListener.html).

0 comments on commit e0c2a55

Please sign in to comment.