The following way to set a closure as a callback is slightly shorter than shown in Hossein Noroozpour's answer. It's adapted from this example:
let closure: Closure<dyn Fn()> = Closure::new(move || {
// Do something here
});
your_html_element.set_oninput(Some(closure.as_ref().unchecked_ref()));
closure.forget();
Here's a more complete example, showing how to set the callback on a file picker. The callback accesses a JavaScript worker:
let document = window().unwrap().document().unwrap();
let worker = Worker::new("./worker.js").unwrap();
let file_picker_elem_id = "file_picker";
match get_input_element(file_picker_elem_id, &document) {
Some(file_picker) => {
// We need "move" to access the worker inside the closure
let closure: Closure<dyn Fn()> = Closure::new(move || {
log_to_browser(format!(
"The file picker callback. Worker: {worker:?}"
));
});
file_picker.set_oninput(Some(closure.as_ref().unchecked_ref()));
closure.forget();
}
None => log_to_browser(format!(
"Couldn't get file picker. Used \"{file_picker_elem_id}\" as its ID"
)),
}
/// Gets an [[HtmlInputElement]] by its `elem_id`.
fn get_input_element(elem_id: &str, document: &Document) -> Option<HtmlInputElement> {
document
.get_element_by_id(elem_id)
.and_then(|elem| elem.dyn_ref::<HtmlInputElement>().map(ToOwned::to_owned))
}
fn log_to_browser(log_msg: String) {
console::log_1(&log_msg.into());
}
I tried simplifying the conversion of closures to js_sys::Function
s:
pub fn to_js_func<F>(closure: F) -> Option<&'static js_sys::Function>
where
F: IntoWasmClosure<dyn Fn()> + 'static,
{
let big_closure: Closure<dyn Fn()> = Closure::new(closure);
let func: &js_sys::Function = big_closure.as_ref().unchecked_ref();
big_closure.forget();
Some(func)
}
But this doesn't work since the function owns the created closure. Also, to use the type js_sys::Function
, I needed to add the js-sys
dependency to Cargo.toml. If anybody knows a way around this, please let us know.
A less universal helper function that works though:
fn set_on_input<F>(input_elem: &HtmlInputElement, closure: F)
where
F: IntoWasmClosure<dyn Fn()> + 'static,
{
let big_closure: Closure<dyn Fn()> = Closure::new(closure);
input_elem.set_oninput(Some(big_closure.as_ref().unchecked_ref()));
big_closure.forget();
}
This allows setting a Rust closure as the callback on the HTML element:
let closure = move || {
log_to_browser(format!(
"The file picker callback. Worker: {worker:?}"
));
};
set_on_input(&file_picker, closure);
Instead of a closure, you can also set a function as the callback:
set_on_input(&file_picker, test_callback);
Where test_callback
is just a regular Rust function:
fn test_callback() {
log_to_browser("test_callback ".into());
}