Hi:) I have a question. How do I remove event listener?
When I tried to remove event listener I met some errors.
pub fn run() {
let window = web_sys::window().expect("no global `window` exists");
let document = window.document().expect("should have a document on window");
let body = document.body().expect("document should have a body");
let main = &document
.query_selector(&".main".to_owned())
.unwrap()
.unwrap();
let handler = Box::new(|| {
web_sys::console::log_1(&"click".into());
}) as Box<Fn()>;
let cb: Closure<Fn()> = Closure::wrap(handler);
(main.as_ref() as &web_sys::EventTarget)
.add_event_listener_with_callback(&"click".to_owned(), cb.as_ref().unchecked_ref())
.unwrap();
cb.forget();
// bellow code is meaningless sample code
(main.as_ref() as &web_sys::EventTarget)
.remove_event_listener_with_callback(&"click".to_owned(), cb.as_ref().unchecked_ref()) // ---> cb is already moved by forget.
.unwrap();
}
This is because cb's ownership is already moved forget.
What should I do to remove this event listener?
Then I did some investigation to solve it, i bellow code.
This code does not call forget, but it works fine.
Why does not this need forget ??? Why is not closure dropped???
https://github.com/utkarshkukreti/draco/blob/master/src/element.rs#L522-L531
Why does not this need forget ??? Why is not closure dropped???
Because of the last line of the code you linked to:
https://github.com/utkarshkukreti/draco/blob/master/src/element.rs#L531
Storing it in self.closure prevent it from dropping unless that self dropped!
A good question here! Could this perhaps be a case where the JsValue from cb.as_ref() is stored separately from the Closure<T> itself? That way you can pass it to removeEventListener later on and deouple it from the lifetime of the closure.
Although I may be missing some background as well? Or I may be missing some context as to why forget is being used and such.
@limira @alexcrichton Thanks for your quick response. I'll give it a try :)
Sorry, I was mistaken. forget is called here.
I am glad if an example of remove_event_listener is added. Close for now. thanks :)
@alexcrichton sorry.. I can not find better solution yet.I tried to store cb.as_ref() as &JsValue to use remove listener later.However it required static lifetime because it is pointer. For now, I forced lifetime with mem::transmute::<&JsValue, &'static JsValue>(cb.as_ref()); to static, because it is already leaked. It made the compilation successful, but it is so ugly..Do you have any idea to solve this.
I did some investigation. But It seems that nobody manage this properly..? 馃
https://github.com/rustwasm/wasm-bindgen/blob/master/examples/todomvc/src/view.rs#L120
I think If the Callback.forget() return ownership, it solve this issue. But was it impossible?
Thanks.
pub struct Listener {
cb: RefCell<Option<&'static JsValue>>,
}
pub fn run() {
let mut listner = Listenr {
cb: RefCell::new(None)
}
...
let handler = Box::new(|| {
web_sys::console::log_1(&"click".into());
}) as Box<Fn()>;
let cb: Closure<Fn()> = Closure::wrap(handler);
let mut cb_ref: &'static JsValue;
unsafe {
cb_ref = mem::transmute::<&JsValue, &'static JsValue>(cb.as_ref());
}
(main.as_ref() as &web_sys::EventTarget)
.add_event_listener_with_callback(&"click".to_owned(), cb_ref.unchecked_ref())
.unwrap();
listner.cb.replace(Some(cb_ref));
cb.forget();
...
}
@bokuweb Why are you using cb.forget()?
The purpose of cb.forget() is to keep the closure alive forever (so it will never be removed).
If you simply don't call cb.forget() then it should work:
pub fn run() {
let handler = Box::new(|| {
web_sys::console::log_1(&"click".into());
}) as Box<Fn()>;
let cb: Closure<Fn()> = Closure::wrap(handler);
(main.as_ref() as &web_sys::EventTarget)
.add_event_listener_with_callback(&"click".to_owned(), cb.as_ref().unchecked_ref())
.unwrap();
...
(main.as_ref() as &web_sys::EventTarget)
.remove_event_listener_with_callback(&"click".to_owned(), cb.as_ref().unchecked_ref())
.unwrap();
}
The above code registers the listener and then immediately removes it.
You probably don't want to remove it immediately, in that case you can return the Closure (and leave it up to the caller to remove it).
However, returning the Closure is pretty cumbersome, so instead let's create a Listener struct which makes things easy:
struct Listener {
element: web_sys::EventTarget,
name: &'static str,
cb: Closure<Fn()>,
}
impl Listener {
fn new<F>(element: web_sys::EventTarget, name: &'static str, cb: F) where F: Fn() + 'static {
let cb = Closure::wrap(Box::new(cb) as Box<Fn()>);
element.add_event_listener_with_callback(name, cb.as_ref().unchecked_ref()).unwrap();
Self { element, name, cb }
}
}
impl Drop for Listener {
fn drop(&mut self) {
self.element.remove_event_listener_with_callback(self.name, self.cb.as_ref().unchecked_ref()).unwrap();
}
}
Here I created a Listener struct. When you call Listener::new it will add the event listener, and when it is dropped it will automatically remove the event listener.
You can use it like this:
// When listener is dropped it will remove the listener
let listener = Listener::new(main, "click", || {
web_sys::console::log_1(&"click".into());
});
If you don't want the listener to be removed right away, you can return it from the function:
fn run() -> Listener {
Listener::new(main, "click", || {
web_sys::console::log_1(&"click".into());
})
}
This creates the Listener inside of the run() function, and then transfers ownership to whoever called the run() function:
// When the listener is dropped it will remove the listener
let listener = run();
You could also put the Listener into a struct (which moves ownership into the struct). This sort of "moving of ownership" is very common in Rust, so it's important to understand it.
It's also possible to move ownership into a closure! Here is an example which adds a listener and then 1000 milliseconds later removes it:
let listener = Listener::new(main, "click", || {
web_sys::console::log_1(&"click".into());
});
let callback = move || {
drop(listener);
};
let cb = Closure::wrap(Box::new(callback) as Box<Fn()>);
web_sys::window().unwrap().set_timeout_with_callback(cb.as_ref().unchecked_ref(), 1000);
cb.forget();
Notice the move keyword, that causes listener to be transferred into the callback closure. When the callback closure is called, it will then drop the listener (which then removes the event listener).
Right now wasm-bindgen has a lot of clunky syntax that makes everything more complicated than it should be. Here's the same example, but with stdweb:
let listener = main.add_event_listener(|_: ClickEvent| {
console!(log, "click");
});
set_timeout(move || {
listener.remove();
}, 1000);
There are some plans to make wasm-bindgen easier to use, but we're not there just yet.
@Pauan Cool 馃槃 Thanks for your quick and kind response 馃憤 I'll try it!! Thanks :)
Most helpful comment
@bokuweb Why are you using
cb.forget()?The purpose of
cb.forget()is to keep the closure alive forever (so it will never be removed).If you simply don't call
cb.forget()then it should work:The above code registers the listener and then immediately removes it.
You probably don't want to remove it immediately, in that case you can return the
Closure(and leave it up to the caller to remove it).However, returning the
Closureis pretty cumbersome, so instead let's create aListenerstruct which makes things easy:Here I created a
Listenerstruct. When you callListener::newit will add the event listener, and when it is dropped it will automatically remove the event listener.You can use it like this:
If you don't want the listener to be removed right away, you can return it from the function:
This creates the
Listenerinside of therun()function, and then transfers ownership to whoever called therun()function:You could also put the
Listenerinto a struct (which moves ownership into the struct). This sort of "moving of ownership" is very common in Rust, so it's important to understand it.It's also possible to move ownership into a closure! Here is an example which adds a listener and then 1000 milliseconds later removes it:
Notice the
movekeyword, that causeslistenerto be transferred into thecallbackclosure. When thecallbackclosure is called, it will then drop thelistener(which then removes the event listener).Right now wasm-bindgen has a lot of clunky syntax that makes everything more complicated than it should be. Here's the same example, but with stdweb:
There are some plans to make wasm-bindgen easier to use, but we're not there just yet.