Add examples/documentation for closures

This commit is contained in:
Alex Crichton
2018-04-06 08:49:21 -07:00
parent 176060cc8a
commit a3e5485b86
16 changed files with 493 additions and 68 deletions

View File

@@ -22,3 +22,5 @@ The examples here are:
operations in Rust
* `wasm-in-wasm` - how to interact with namespaced APIs like
`WebAssembly.Module` and shows off creation of a WebAssembly module from Rust
* `closures` - an example of how to invoke functions like `setInterval` or use
the `onclick` property in conjunction with closures.

4
examples/closures/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
package-lock.json
closures.js
closures_bg.js
closures_bg.wasm

View File

@@ -0,0 +1,10 @@
[package]
name = "closures"
version = "0.1.0"
authors = ["Alex Crichton <alex@alexcrichton.com>"]
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = { path = "../.." }

View File

@@ -0,0 +1,20 @@
# Closure examples
This directory is an example of using the `#[wasm_bindgen]` macro with closures
to interact with the DOM.
You can build the example with:
```
$ ./build.sh
```
(or running the commands on Windows manually)
and then opening up `index.html` in a web browser should show a hello message on
the web page generated by the wasm.
For more information about this example be sure to check out
[`hello_world`][hello] which also has more comments about caveats and such.
[hello]: https://github.com/alexcrichton/wasm-bindgen/tree/master/examples/hello_world

12
examples/closures/build.sh Executable file
View File

@@ -0,0 +1,12 @@
#!/bin/sh
# For more coments about what's going on here, see the `hello_world` example
set -ex
cargo +nightly build --target wasm32-unknown-unknown
cargo +nightly run --manifest-path ../../crates/cli/Cargo.toml \
--bin wasm-bindgen -- \
../../target/wasm32-unknown-unknown/debug/closures.wasm --out-dir .
#npm install
npm run serve

View File

@@ -0,0 +1,40 @@
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
<style>
#green-square {
background: green;
width: 200px;
height: 200px;
text-align: center;
line-height: 200px;
color: white;
}
#green-square span {
display: inline-block;
vertical-align: middle;
}
</style>
</head>
<body>
<div id='loading'>
Loading...
</div>
<div id='script' style='display:none'>
<p>
The current time is:
<span id='current-time'>...</span>
</p>
<div id='green-square'>
<span>Click me!</span>
</div>
<p>
You've clicked the green square
<span id='num-clicks'>0</span>
times
</p>
</div>
<script src='./index.js'></script>
</body>
</html>

View File

@@ -0,0 +1,4 @@
// For more comments about what's going on here, check out the `hello_world`
// example
const rust = import("./closures");
rust.then(m => m.run());

View File

@@ -0,0 +1,10 @@
{
"scripts": {
"serve": "webpack-dev-server"
},
"devDependencies": {
"webpack": "^4.0.1",
"webpack-cli": "^2.0.10",
"webpack-dev-server": "^3.1.0"
}
}

View File

@@ -0,0 +1,94 @@
#![feature(proc_macro, wasm_custom_section, wasm_import_module)]
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern {
// Binding for the `setInverval` method in JS. This function takes a "long
// lived" closure as the first argument so we use `Closure` instead of
// a bare `&Fn()` which only surives for that one stack frame.
//
// The second argument is then the interval and the return value is how we
// clear this interval. We're not going to clear our interval in this
// example though so the return value is ignored.
#[wasm_bindgen(js_name = setInterval)]
fn set_interval(cb: &Closure<FnMut()>, delay: u32) -> f64;
// Bindings for JS `Date` so we can update our local timer
type Date;
#[wasm_bindgen(constructor)]
fn new() -> Date;
#[wasm_bindgen(method, js_name = toLocaleString)]
fn to_locale_string(this: &Date) -> String;
// Bindings for `document` and various methods of updating HTML elements.
// Like with the `dom` example these'll ideally be upstream in a generated
// crate one day but for now we manually define them.
type HTMLDocument;
static document: HTMLDocument;
#[wasm_bindgen(method, js_name = getElementById)]
fn get_element_by_id(this: &HTMLDocument, id: &str) -> Element;
#[wasm_bindgen(method, js_name = getElementById)]
fn get_html_element_by_id(this: &HTMLDocument, id: &str) -> HTMLElement;
type Element;
#[wasm_bindgen(method, setter = innerHTML)]
fn set_inner_html(this: &Element, html: &str);
type HTMLElement;
#[wasm_bindgen(method, setter)]
fn set_onclick(this: &HTMLElement, cb: &Closure<FnMut()>);
#[wasm_bindgen(method, getter)]
fn style(this: &HTMLElement) -> CSS2Properties;
type CSS2Properties;
#[wasm_bindgen(method, setter)]
fn set_display(this: &CSS2Properties, display: &str);
}
#[wasm_bindgen]
pub fn run() {
// Set up a clock on our page and update it each second to ensure it's got
// an accurate date.
let a = Closure::new(update_time);
set_interval(&a, 1000);
update_time();
fn update_time() {
document.get_element_by_id("current-time")
.set_inner_html(&Date::new().to_locale_string());
}
// We also want to count the number of times that our green square has been
// clicked. Our callback will update the `#num-clicks` div
let square = document.get_html_element_by_id("green-square");
let mut clicks = 0;
let b = Closure::new(move || {
clicks += 1;
document.get_element_by_id("num-clicks")
.set_inner_html(&clicks.to_string());
});
square.set_onclick(&b);
// 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();
b.forget();
// And finally 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_html_element_by_id("loading")
.style()
.set_display("none");
document.get_html_element_by_id("script")
.style()
.set_display("block");
}

View File

@@ -0,0 +1,10 @@
const path = require('path');
module.exports = {
entry: "./index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "index.js",
},
mode: "development"
};