2

How do I add an input event listener to an HtmlInputElement/HtmlTextAreaElement. I'm using web-sys and read this, but following that, all the elements I use inside the closure (in this case especially the input element) get moved into the closure and I can't attach the listener afterwards.

let closure = Closure::wrap(Box::new(|_: web_sys::InputEvent| {
    console_log!("{}", input.value());
}) as Box<dyn FnMut(_)>);

input.add_event_listener_with_callback("input", closure.as_ref().unchecked_ref())?;
// ^
// Doesn't work because the `input` variable was moved into the closure.

Concretely speaking I get:

borrow of moved value: `input`
Elias
  • 3,592
  • 2
  • 19
  • 42
  • Your code as is cannot be used to reproduce the issue, because we do not know what are those types, what receivers do their methods have, etc. So without any further information, all I can say is do not move the `input` variable into the closure. You can wrap it in a `Rc` or `Rc>` if need be, and move a clone of the `Rc` instead. But that might create a memory leak, because `input` will hold an `Rc` to itself. – Svetlin Zarev Aug 26 '21 at 19:04
  • @SvetlinZarev what "types" are you talking about? I mean all the types here are from `web-sys` (and `wasm-bindgen`). I will have to read up on the so called "Rc". – Elias Aug 26 '21 at 19:05
  • Well, how can anyone know they are from `web-sys` if there are no imports present and `web-sys` is never mentioned ? – Svetlin Zarev Aug 26 '21 at 19:07
  • @SvetlinZarev I assumed that was the standard. I'm sorry I'm new to all of this including rust. In theory the link leads to a wiki about `web-sys`, but you are right. I will add the imports. – Elias Aug 26 '21 at 19:08
  • @SvetlinZarev I added the imports and basically the rest of the code in case it helps :) – Elias Aug 26 '21 at 19:13
  • @SvetlinZarev so I went to sleep yesterday but I thought I saw an answer from you... now it seems to be gone... it didn't work? I'm kind of stuck on this right now :( – Elias Aug 27 '21 at 14:43
  • 1
    yeah I've deleted it because I'm not 100% sure. I've restored it, if it can be any help. In case the `Weak` pointer returns `None` you may try to use `Rc` in its place. – Svetlin Zarev Aug 27 '21 at 17:21

2 Answers2

6

First of all, I would like to thank Svetlin Zarev for taking the time to answer my question. I wouldn't have come to this without them. And please check out their answer.

For someone coming from javascript, all this stuff is quite a lot to take in and I wanted to present a "simple" answer.

To add an input (or really any kind) of event listener and capture the targets value, you may use the following code:

let cb = Closure::wrap(Box::new(|e: Event| {
    let input = e
        .current_target()
        .unwrap()
        .dyn_into::<web_sys::HtmlTextAreaElement>()
        .unwrap();

    console_log!("{:?}", input.value());
}) as Box<dyn FnMut(_)>);

input.add_event_listener_with_callback("input", &cb.as_ref().unchecked_ref())?;

cb.forget();

I would also recommend reading the following articles:

Elias
  • 3,592
  • 2
  • 19
  • 42
3

It compiles now, although I cannot test it if it actually works. What I've changed:

  1. Used Rc<RefCell<INPUT>> in order to avoid the error about the moved variable

  2. Used Rc::downgrade(&input) in order to provide a Weak inside the closure in order to avoid a cyclic reference, thus avoiding a memory leak. But I'm not sure how the whole thing works in a web env, so maybe a It should be Rc instead, because the Rc will be dropped at the end of the run() method.

You should check the relevant rustdoc for more detailed explanation on what those types are doing.

use wasm_bindgen::{prelude::*, JsCast};
use web_sys::HtmlElement;
use std::cell::RefCell;
use std::rc::Rc;

#[wasm_bindgen]
pub fn run(cont: &HtmlElement) -> Result<(), JsValue> {
    let window = web_sys::window().expect("could not get window handle");
    let document = window.document().expect("could not get document handle");
    let input = document
        .create_element("textarea")?
        .dyn_into::<web_sys::HtmlTextAreaElement>()?;

    let input = Rc::new(RefCell::new(input));
    let weak_input = Rc::downgrade(&input);

    let closure: Box<dyn FnMut(_)> = Box::new(move |_: web_sys::InputEvent| {
        let input = weak_input.upgrade().unwrap();
        let _a = &input.borrow().value();
    });

    let closure = Closure::wrap(closure);

    input
        .borrow_mut()
        .add_event_listener_with_callback("input", closure.as_ref().unchecked_ref())?;
    closure.forget();

    cont.append_child(&input.borrow())?;

    Ok(())
}

Svetlin Zarev
  • 14,713
  • 4
  • 53
  • 82
  • So I've read some of the links you've provided and it's quite a lot to take in for someone coming from JavaScript (I've not read all of them [yet] because I simply don't have the energy for that). I will try to make my code work with the details you have provided. What I don't understand, why such a simple use case causes such a headache. Wouldn't this be one of the first things someone tries to do? Why do you need 20 lines of rust code for 1 line of JavaScript? – Elias Aug 27 '21 at 18:07