Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Element.current_target() always points to the HtmlBodyElement #2572

Closed
mfreeborn opened this issue Apr 3, 2022 · 4 comments · Fixed by #2668
Closed

Element.current_target() always points to the HtmlBodyElement #2572

mfreeborn opened this issue Apr 3, 2022 · 4 comments · Fixed by #2668

Comments

@mfreeborn
Copy link

Environment:

  • Yew version: 0.19
  • Web-sys version 0.3.56
  • Rust version: 1.59.0 stable
  • Target: wasm32-unknown-unknown
  • Build tool: trunk
  • OS: Ubuntu 20.04
  • Chrome 100.0.4896.60 (Official Build) (64-bit)

I spent a long time going around in circles trying to get a handle for the html element I'm interacting with so that I can do some further processing. After a lot of print-debugging, I realised that whenever an event is received, current_target() appears to point to the HtmlBodyElement.

I would expect it to return a handle to the parent element from which the event was fired.

I reported it over at rustwasm #2849, but to save a click, the maintainer said:

Just stumbling over this issue, as a maintainer of yew. This is not a bug in wasm_bindgen, but a problem with the way event handling works in yew. For each event type, there is one event handler attached to the element yew is mounted on, not on the element itself. This causes current_target to point to the root element (the <body> in this case). wasm_bindgen is acting correctly, as far as I can see, I'd encourage you to open a bug at yewstack/yew to improve the documentation of this behaviour and to discuss work-arounds.

Here is a minimal yew app:

use yew::prelude::*;

#[function_component(App)]
pub fn app() -> Html {
    let oninput = {
        Callback::from(|e: InputEvent| {
            log::info!("{:?}", e.current_target()); // Some(EventTarget { obj: Object { obj: JsValue(HTMLBodyElement) } })
            log::info!("{:?}", e.target()); // Some(EventTarget { obj: Object { obj: JsValue(HTMLInputElement) } })
        })
    };

    html! {
        <input {oninput} />
    }
}

fn main() {
    wasm_logger::init(wasm_logger::Config::default());
    yew::start_app::<App>();
}

Here is the Cargo.toml:

[package]
name = "yew-testing"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
log = "0.4"
wasm-logger = "0.2"
web-sys = "0.3.56"
yew = "0.19"

In the app I'm developing, I noticed this behaviour on an <svg> element, to which I had added a click handler (i.e. a MouseEvent). The <svg> element has several <path> element children. Element.target() correctly gives me either the <svg> or the <path> elements, but I specifically always want the <svg> element, which is what I would expect current_target() to do.

@mfreeborn mfreeborn added the bug label Apr 3, 2022
@WorldSEnder
Copy link
Member

I'd suggest to use use_node_ref to attach a ref to the <svg> and get (or cast::<SvgElement>) the ref inside your handler. If you think we should improve documentation about event handling in general, leave the bug report open for that :)

@WorldSEnder
Copy link
Member

Just want to clarify that this is expected behaviour and there are no near-future plans to change this. We could introduce synthetic event types to limit the amount of implementation details we expose of the internal event handling, but that'd break more existing components than help, imo.

Actionable items: improve event handling documentation and explain what and what is expected to work "like normal". For example, I think it's also not mentioned, that all events are caught in the "capture" phase, currently, and the bubbling behaviour is purely virtual. This can be confusing if you're working with manually registered event handlers also. Apart from that, the event passed to handlers is if installed on the root element (either nearest portal, or the app root). In this sense, handlers are "grouped", the order of handlers is as if they are run during the bubbling phase, from bottom to top. Capture handlers are not support currently.

@mfreeborn
Copy link
Author

Thank you, that's got me another good step further.

To complete my full use case, I'm actually trying to reference elements which are generated programatically in a loop, something like:

html! {
    list_of_things.iter().map(|thing: &String| html! {
        <li ref={???)}>{thing}</li>
    }).collect::<Html>()
}

Does use_node_ref() support this use case?

@WorldSEnder
Copy link
Member

WorldSEnder commented Apr 3, 2022

It's less clear, but one approach could be to create a component for the inner things.

#[derive(PartialEq, Properties)]
struct ThingProps { thing: String }

#[function_component]
fn Thing(props: ThingProps) -> Html {
    let ref = use_node_ref();
    html! {
        <li ref={ref}>{props.thing}</li>
    }
}

...


    list_of_things.iter().map(|thing: &String| html! {
        <Thing {thing} />
    }).collect::<Html>()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants