yew icon indicating copy to clipboard operation
yew copied to clipboard

No way to listen to custom events exposed by custom elements

Open ranile opened this issue 5 years ago • 5 comments

Tracking issue for the point, "Custom events" described in https://github.com/yewstack/yew/issues/1666

Problem

Yew should allow users to listen to custom events as well and pass CustomEvent to the callback.

There should be a way to deal with names like the ones used here. It can be a function on NodeRef or special syntax in html!, not 100% sure on the API.

from #1666

Questionnaire

  • [ ] I'm interested in fixing this myself but don't know where to start
  • [ ] I would like to fix and I have a solution
  • [x] I don't have time to fix this right now, but maybe later

I apologize it took me so long to create this issue after being requested here (https://github.com/yewstack/yew/issues/1666#issuecomment-765852652)

ranile avatar Mar 02 '21 16:03 ranile

I created a discussion to discuss potential solutions: https://github.com/yewstack/yew/discussions/1779

lukechu10 avatar Mar 03 '21 04:03 lukechu10

For a NodeRef version I first thought of something like this:

    pub fn attach_event_listener<S, E>(
        &self,
        event: S,
        callback: Callback<E>,
    ) -> Option<gloo::events::EventListener>
    where
        S: Into<std::borrow::Cow<'static, str>>,
        E: AsRef<web_sys::Event> + JsCast + 'static,
    {
        use gloo::events::*;
        let event_target: EventTarget = self.get()?.into();
        match &callback {
            Callback::Callback { passive, .. } => {
                let passive = passive.unwrap_or_default();
                let listener = EventListener::new_with_options(
                    &event_target,
                    event,
                    EventListenerOptions {
                        phase: EventListenerPhase::Bubble,
                        passive,
                    },
                    move |e| callback.emit(e.clone().unchecked_into()),
                );
                Some(listener)
            }
            Callback::CallbackOnce(_) => {
                let listener = EventListener::once(&event_target, event, move |e| {
                    callback.emit(e.clone().unchecked_into())
                });
                Some(listener)
            }
        }
    }

We'd probably avoid leaking the gloo::events::EventListener type in the actual impl but just makes this example bit easier to read :)

Then in the rendered function of a component you might do something like this:

fn rendered(&mut self, ctx: &Context<Self>) {
    match (self.node_ref.get(), self.click_listener.as_ref()) {
        (None, Some(_)) => {
            // element has not been rendered so remove old listener
            self.click_listener.take();
        }
        (Some(_), None) => {
            // element has been rendered and no event listener has been attached
            // so add one this time.
            self.click_listener = self.node_ref.attach_event_listener(
                "click",
                ctx.link().callback(|_: yew::MouseEvent| Msg::Increment),
            );
        }
        // either the element is not rendered and there is no old listener OR
        // both the element and listener are still set - with both we do nothing :)
        _ => {}
    }
}

Also #1991 even though closed I have solved the hiccup and I'm currently keeping it up to date in my fork just in case there was any interest in that :)

mc1098 avatar Oct 04 '21 20:10 mc1098

Sorry for the accidental closing 😢

mc1098 avatar Oct 04 '21 20:10 mc1098

Is there no workaround for this issue :cry: ? newbie here

dfenerski avatar Dec 28 '22 22:12 dfenerski

@dfenerski Check the documentation here I think that should work. But I'm also new to Yew and would really like to see this solved in an easier way... @hamza1311 Has anything happened here after the discussion #1779 ?

Roba1993 avatar Jan 19 '23 14:01 Roba1993