2018-09-26 08:26:00 -07:00
|
|
|
use js_sys::{Array, Date};
|
2018-06-27 22:42:34 -07:00
|
|
|
use wasm_bindgen::prelude::*;
|
2018-09-26 08:26:00 -07:00
|
|
|
use wasm_bindgen::JsCast;
|
2018-09-20 16:20:42 -07:00
|
|
|
use web_sys::{Document, Element, HtmlElement, Window};
|
2018-04-06 08:49:21 -07:00
|
|
|
|
2018-11-28 09:25:51 -08:00
|
|
|
#[wasm_bindgen(start)]
|
2018-09-20 16:20:42 -07:00
|
|
|
pub fn run() -> Result<(), JsValue> {
|
|
|
|
let window = web_sys::window().expect("should have a window in this context");
|
|
|
|
let document = window.document().expect("window should have a document");
|
2018-04-06 08:49:21 -07:00
|
|
|
|
2018-09-20 16:20:42 -07:00
|
|
|
// One of the first interesting things we can do with closures is simply
|
|
|
|
// access stack data in Rust!
|
|
|
|
let array = Array::new();
|
|
|
|
array.push(&"Hello".into());
|
|
|
|
array.push(&1.into());
|
|
|
|
let mut first_item = None;
|
2018-09-26 08:26:00 -07:00
|
|
|
array.for_each(&mut |obj, idx, _arr| match idx {
|
|
|
|
0 => {
|
|
|
|
assert_eq!(obj, "Hello");
|
|
|
|
first_item = obj.as_string();
|
2018-09-20 16:20:42 -07:00
|
|
|
}
|
2018-09-26 08:26:00 -07:00
|
|
|
1 => assert_eq!(obj, 1),
|
|
|
|
_ => panic!("unknown index: {}", idx),
|
2018-09-20 16:20:42 -07:00
|
|
|
});
|
|
|
|
assert_eq!(first_item, Some("Hello".to_string()));
|
2018-04-06 08:49:21 -07:00
|
|
|
|
2018-09-20 16:20:42 -07:00
|
|
|
// Below are some more advanced usages of the `Closure` type for closures
|
|
|
|
// that need to live beyond our function call.
|
2018-04-06 08:49:21 -07:00
|
|
|
|
2018-09-20 16:20:42 -07:00
|
|
|
setup_clock(&window, &document)?;
|
|
|
|
setup_clicker(&document);
|
2018-04-06 08:49:21 -07:00
|
|
|
|
2018-09-20 16:20:42 -07:00
|
|
|
// And now that our demo is ready to go let's switch things up so
|
|
|
|
// everything is displayed and our loading prompt is hidden.
|
|
|
|
document
|
|
|
|
.get_element_by_id("loading")
|
|
|
|
.expect("should have #loading on the page")
|
|
|
|
.dyn_ref::<HtmlElement>()
|
|
|
|
.expect("#loading should be an `HtmlElement`")
|
|
|
|
.style()
|
|
|
|
.set_property("display", "none")?;
|
|
|
|
document
|
|
|
|
.get_element_by_id("script")
|
|
|
|
.expect("should have #script on the page")
|
|
|
|
.dyn_ref::<HtmlElement>()
|
|
|
|
.expect("#script should be an `HtmlElement`")
|
|
|
|
.style()
|
|
|
|
.set_property("display", "block")?;
|
|
|
|
|
|
|
|
Ok(())
|
2018-04-06 08:49:21 -07:00
|
|
|
}
|
|
|
|
|
2018-09-20 16:20:42 -07:00
|
|
|
// Set up a clock on our page and update it each second to ensure it's got
|
|
|
|
// an accurate date.
|
|
|
|
//
|
|
|
|
// Note the usage of `Closure` here because the closure is "long lived",
|
|
|
|
// basically meaning it has to persist beyond the call to this one function.
|
|
|
|
// Also of note here is the `.as_ref().unchecked_ref()` chain, which is who
|
|
|
|
// you can extract `&Function`, what `web-sys` expects, from a `Closure`
|
|
|
|
// which only hands you `&JsValue` via `AsRef`.
|
|
|
|
fn setup_clock(window: &Window, document: &Document) -> Result<(), JsValue> {
|
|
|
|
let current_time = document
|
|
|
|
.get_element_by_id("current-time")
|
|
|
|
.expect("should have #current-time on the page");
|
|
|
|
update_time(¤t_time);
|
2018-12-11 08:39:49 +01:00
|
|
|
let a = Closure::wrap(Box::new(move || update_time(¤t_time)) as Box<dyn Fn()>);
|
2018-09-26 08:26:00 -07:00
|
|
|
window
|
|
|
|
.set_interval_with_callback_and_timeout_and_arguments_0(a.as_ref().unchecked_ref(), 1000)?;
|
2018-09-20 16:20:42 -07:00
|
|
|
fn update_time(current_time: &Element) {
|
2018-09-26 08:26:00 -07:00
|
|
|
current_time.set_inner_html(&String::from(
|
|
|
|
Date::new_0().to_locale_string("en-GB", &JsValue::undefined()),
|
|
|
|
));
|
2018-04-06 08:49:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// The instances of `Closure` that we created will invalidate their
|
|
|
|
// corresponding JS callback whenever they're dropped, so if we were to
|
|
|
|
// normally return from `run` then both of our registered closures will
|
|
|
|
// raise exceptions when invoked.
|
|
|
|
//
|
|
|
|
// Normally we'd store these handles to later get dropped at an appropriate
|
|
|
|
// time but for now we want these to be global handlers so we use the
|
|
|
|
// `forget` method to drop them without invalidating the closure. Note that
|
|
|
|
// this is leaking memory in Rust, so this should be done judiciously!
|
|
|
|
a.forget();
|
|
|
|
|
2018-09-20 16:20:42 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
// We also want to count the number of times that our green square has been
|
|
|
|
// clicked. Our callback will update the `#num-clicks` div.
|
|
|
|
//
|
|
|
|
// This is pretty similar above, but showing how closures can also implement
|
|
|
|
// `FnMut()`.
|
|
|
|
fn setup_clicker(document: &Document) {
|
|
|
|
let num_clicks = document
|
|
|
|
.get_element_by_id("num-clicks")
|
|
|
|
.expect("should have #num-clicks on the page");
|
|
|
|
let mut clicks = 0;
|
|
|
|
let a = Closure::wrap(Box::new(move || {
|
|
|
|
clicks += 1;
|
|
|
|
num_clicks.set_inner_html(&clicks.to_string());
|
2018-12-11 08:39:49 +01:00
|
|
|
}) as Box<dyn FnMut()>);
|
2018-06-27 22:42:34 -07:00
|
|
|
document
|
2018-09-20 16:20:42 -07:00
|
|
|
.get_element_by_id("green-square")
|
|
|
|
.expect("should have #green-square on the page")
|
|
|
|
.dyn_ref::<HtmlElement>()
|
|
|
|
.expect("#green-square be an `HtmlElement`")
|
|
|
|
.set_onclick(Some(a.as_ref().unchecked_ref()));
|
|
|
|
a.forget();
|
2018-04-06 08:49:21 -07:00
|
|
|
}
|