Skip to content

Commit 39718a4

Browse files
committed
Fixup gloo onion layers post based on Pauan's feedback
1 parent b64fa9b commit 39718a4

File tree

1 file changed

+72
-28
lines changed

1 file changed

+72
-28
lines changed

Diff for: _posts/2019-03-26-gloo-onion-layers.md

+72-28
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,13 @@ web_sys::window()
7474
.clear_timeout_with_handle(timeout_id);
7575
```
7676
77-
## The `callback` Layer
77+
## The `callbacks` Layer
7878
7979
When we look at the raw `web-sys` usage, there is a bit of type conversion
8080
noise, some unfortunate method names, and a handful of `unwrap`s for ignoring
8181
edge-case scenarios where we prefer to fail loudly rather than limp along. We
8282
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
8484
(which is also re-exported from the `gloo` umbrella crate as `gloo::timers`).
8585
8686
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
9090
essentially the least opinionated direct API translation to Rust.
9191
9292
```rust
93-
use gloo::timers::callback::Timeout;
93+
use gloo::timers::callbacks::Timeout;
9494
// 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;
9696
9797
// Already, much nicer!
9898
let timeout = Timeout::new(500, move || {
@@ -116,7 +116,7 @@ example, this means we implement a `Future` backed by `setTimeout`, and a
116116
117117
```rust
118118
use futures::prelude::*;
119-
use gloo::timers::future::TimeoutFuture;
119+
use gloo::timers::futures::TimeoutFuture;
120120
121121
// By using futures, we can use all the future combinator methods to build up a
122122
// description of some asynchronous task.
@@ -150,7 +150,7 @@ excited for `async`/`await` as well!
150150
That's all the layers we have for the `setTimeout` and `setInterval`
151151
APIs. Different Web APIs will have different sets of layers, and this is
152152
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
154154
actively identifying layers, making them public and reusable, and building
155155
higher-level layers on top of lower-level layers.
156156
@@ -166,8 +166,8 @@ correctness!
166166
Another future direction is adding more integration layers with more parts of
167167
the larger Rust crates ecosystem. For example, adding functional reactive
168168
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.
171171
172172
## Events
173173
@@ -181,27 +181,65 @@ On top of [`web_sys::Event`][web-sys-event] and
181181
[`web_sys::EventTarget::add_event_listener_with_callback`][web-sys-add-listener],
182182
we are building a layer for [adding and removing event
183183
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:
185188
186189
```rust
190+
use futures::sync::oneshot;
187191
use gloo::events::EventListener;
188192
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+
}
205243
```
206244
207245
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:
212250
```rust
213251
use gloo::events::{ClickEvent, on};
214252
215-
// Again, get an event target from somewhere.
253+
// Get an event target from somewhere.
216254
let target: web_sys::EventTarget = unimplemented!();
217255
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!
219258
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+
// ...
221265
});
222266
```
223267

0 commit comments

Comments
 (0)