@@ -74,13 +74,13 @@ web_sys::window()
74
74
.clear_timeout_with_handle(timeout_id);
75
75
```
76
76
77
- ## The `callback ` Layer
77
+ ## The `callbacks ` Layer
78
78
79
79
When we look at the raw `web-sys` usage, there is a bit of type conversion
80
80
noise, some unfortunate method names, and a handful of `unwrap`s for ignoring
81
81
edge-case scenarios where we prefer to fail loudly rather than limp along. We
82
82
can clean all these things up with the first of our " mid - level " API layers,
83
- which in the case of timers is the `callback ` module in the `gloo_timers` crate
83
+ which in the case of timers is the `callbacks ` module in the `gloo_timers` crate
84
84
(which is also re-exported from the `gloo` umbrella crate as `gloo::timers`).
85
85
86
86
The first " mid - level " API built on top of the `-sys` bindings exposes all the
@@ -90,9 +90,9 @@ functions with `js_sys::Function`, we take any `F: FnOnce()`. This layer is
90
90
essentially the least opinionated direct API translation to Rust.
91
91
92
92
```rust
93
- use gloo::timers::callback ::Timeout;
93
+ use gloo::timers::callbacks ::Timeout;
94
94
// Alternatively, we could use the `gloo_timers` crate without the rest of Gloo:
95
- // use gloo_timers::callback ::Timeout;
95
+ // use gloo_timers::callbacks ::Timeout;
96
96
97
97
// Already, much nicer!
98
98
let timeout = Timeout::new(500, move || {
@@ -116,7 +116,7 @@ example, this means we implement a `Future` backed by `setTimeout`, and a
116
116
117
117
```rust
118
118
use futures::prelude::*;
119
- use gloo::timers::future ::TimeoutFuture;
119
+ use gloo::timers::futures ::TimeoutFuture;
120
120
121
121
// By using futures, we can use all the future combinator methods to build up a
122
122
// description of some asynchronous task.
@@ -150,7 +150,7 @@ excited for `async`/`await` as well!
150
150
That's all the layers we have for the `setTimeout` and `setInterval`
151
151
APIs. Different Web APIs will have different sets of layers, and this is
152
152
fine. Not every Web API uses callbacks, so it doesn't make sense to always have
153
- a `callback ` module in every Gloo crate. The important part is that we are
153
+ a `callbacks ` module in every Gloo crate. The important part is that we are
154
154
actively identifying layers, making them public and reusable, and building
155
155
higher-level layers on top of lower-level layers.
156
156
@@ -166,8 +166,8 @@ correctness!
166
166
Another future direction is adding more integration layers with more parts of
167
167
the larger Rust crates ecosystem. For example, adding functional reactive
168
168
programming-style layers via [the `futures-signals`
169
- crate][integrate-futures-signals] that the [`dominator`][dominator] framework is
170
- built upon .
169
+ crate][integrate-futures-signals] which is also used by the
170
+ [`dominator`][dominator] framework .
171
171
172
172
## Events
173
173
@@ -181,27 +181,65 @@ On top of [`web_sys::Event`][web-sys-event] and
181
181
[`web_sys::EventTarget::add_event_listener_with_callback`][web-sys-add-listener],
182
182
we are building a layer for [adding and removing event
183
183
listeners][raii-listeners] and managing their lifetimes from Rust via RAII-style
184
- automatic cleanup upon drop. This will enable usage like this:
184
+ automatic cleanup upon drop.
185
+
186
+ We can use this API to make idiomatic Rust types that attach event listeners
187
+ that automatically get removed from the DOM when the types is dropped:
185
188
186
189
```rust
190
+ use futures::sync::oneshot;
187
191
use gloo::events::EventListener;
188
192
189
- // Get an event target from somewhere. Maybe a DOM element or maybe the window
190
- // or document.
191
- let target: web_sys::EventTarget = unimplemented!();
192
-
193
- let listener = EventListener::new(&target, " click " , |event: web_sys::Event| {
194
- // Cast the `Event` into a `MouseEvent`
195
- let mouse_event = event.dyn_into::<web_sys::MouseEvent>().unwrap();
196
-
197
- // Do stuff on click...
198
- });
199
-
200
- // If we want to remove the listener, we just drop it:
201
- drop(listener);
202
-
203
- // Alternatively, if we want to listen forever, we can use `forget`:
204
- listener.forget();
193
+ // A prompt for the user.
194
+ pub struct Prompt {
195
+ receiver: oneshot::Receiver<String>,
196
+
197
+ // Automatically removed from the DOM on drop!
198
+ listener: EventListener,
199
+ }
200
+
201
+ impl Prompt {
202
+ pub fn new() -> Prompt {
203
+ // Create an `<input>` to prompt the user for something and attach it to the DOM.
204
+ let input: web_sys::HtmlInputElement = unimplemented!();
205
+
206
+ // Create a oneshot channel for sending/receiving the user's input.
207
+ let (sender, receiver) = oneshot::channel();
208
+
209
+ // Attach an event listener to the input element.
210
+ let listener = EventListener::new(&input, " input " , move |_event: &web_sys::Event| {
211
+ // Get the input element's value.
212
+ let value = input.value();
213
+
214
+ // Send the input value over the oneshot channel.
215
+ sender.send(value)
216
+ .expect_throw(
217
+ " receiver should not be dropped without first removing DOM listener "
218
+ );
219
+ });
220
+
221
+ Prompt {
222
+ receiver,
223
+ listener,
224
+ }
225
+ }
226
+ }
227
+
228
+ // A `Prompt` is also a future, that resolves after the user input!
229
+ impl Future for Prompt {
230
+ type Item = String;
231
+ type Error = ();
232
+
233
+ fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
234
+ self.receiver
235
+ .poll()
236
+ .map_err(|_| {
237
+ unreachable!(
238
+ " we don 't drop the sender without either sending a value or dropping the whole Prompt "
239
+ )
240
+ })
241
+ }
242
+ }
205
243
```
206
244
207
245
On top of that layer, we are using Rust's trait system to design [a
@@ -212,12 +250,18 @@ event types that you listen to:
212
250
```rust
213
251
use gloo::events::{ClickEvent, on};
214
252
215
- // Again, get an event target from somewhere.
253
+ // Get an event target from somewhere.
216
254
let target: web_sys::EventTarget = unimplemented!();
217
255
218
- // Listen to the " click " event, and get a nicer event type!
256
+ // Listen to the " click " event, know that you didn't misspell the event as
257
+ // " clik " , and also get a nicer event type!
219
258
let click_listener = on(&target, move |e: &ClickEvent| {
220
- // Do stuff on click...
259
+ // The `ClickEvent` type has nice getters for the `MouseEvent` that
260
+ // `" click " ` events are guaranteed to yield. No need to dynamically cast
261
+ // an `Event` to a `MouseEvent`.
262
+ let (x, y) = event.mouse_position();
263
+
264
+ // ...
221
265
});
222
266
```
223
267
0 commit comments