Skip to content

Commit 4387219

Browse files
committed
feat: Orders::msg_sender
1 parent b6f186a commit 4387219

File tree

6 files changed

+95
-40
lines changed

6 files changed

+95
-40
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
- Build Changes - Replace rust for_each implementation with duckscript which is much shorter, simpler and faster (in case you don't have cargo-script installed).
2929
- Build Changes - Enforce minimal cargo-make version: 0.32.1.
3030
- Added new `Orders` methods `request_url` (#518) and `msg_sender` (#502).
31+
- [BREAKING] `Orders::msg_mapper` returns `Rc<..>` instead of `Box<..>`.
32+
- Reexported `pub use wasm_bindgen_futures::{self, spawn_local};` in `lib.rs`.
33+
- Updated example `websocket`.
3134

3235
## v0.7.0
3336
- [BREAKING] Custom elements are now patched in-place (#364). Use `el_key` to force reinitialize an element.

examples/websocket/src/client.rs

Lines changed: 46 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use seed::{prelude::*, *};
2+
use std::rc::Rc;
23

34
mod shared;
45

@@ -8,7 +9,7 @@ const WS_URL: &str = "ws://127.0.0.1:9000/ws";
89
// Model
910
// ------ ------
1011

11-
struct Model {
12+
pub struct Model {
1213
sent_messages_count: usize,
1314
messages: Vec<String>,
1415
input_text: String,
@@ -32,24 +33,14 @@ fn init(_: Url, orders: &mut impl Orders<Msg>) -> Model {
3233
}
3334
}
3435

35-
fn create_websocket(orders: &impl Orders<Msg>) -> WebSocket {
36-
WebSocket::builder(WS_URL, orders)
37-
.on_open(|| Msg::WebSocketOpened)
38-
.on_message(Msg::MessageReceived)
39-
.on_close(Msg::WebSocketClosed)
40-
.on_error(|| Msg::WebSocketFailed)
41-
.build_and_open()
42-
.unwrap()
43-
}
44-
4536
// ------ ------
4637
// Update
4738
// ------ ------
4839

49-
enum Msg {
40+
pub enum Msg {
5041
WebSocketOpened,
51-
MessageReceived(WebSocketMessage),
52-
BytesReceived(Vec<u8>),
42+
TextMessageReceived(shared::ServerMessage),
43+
BinaryMessageReceived(shared::ServerMessage),
5344
CloseWebSocket,
5445
WebSocketClosed(CloseEvent),
5546
WebSocketFailed,
@@ -66,24 +57,13 @@ fn update(msg: Msg, mut model: &mut Model, orders: &mut impl Orders<Msg>) {
6657
model.web_socket_reconnector = None;
6758
log!("WebSocket connection is open now");
6859
}
69-
Msg::MessageReceived(message) => {
70-
log!("Client received a message");
71-
72-
if message.contains_text() {
73-
model
74-
.messages
75-
.push(message.json::<shared::ServerMessage>().unwrap().text);
76-
} else {
77-
orders.perform_cmd(async move {
78-
let bytes = message.bytes().await;
79-
bytes.map(Msg::BytesReceived).ok()
80-
});
81-
}
60+
Msg::TextMessageReceived(message) => {
61+
log!("Client received a text message");
62+
model.messages.push(message.text);
8263
}
83-
Msg::BytesReceived(bytes) => {
64+
Msg::BinaryMessageReceived(message) => {
8465
log!("Client received binary message");
85-
let msg: shared::ServerMessage = rmp_serde::from_slice(&bytes).unwrap();
86-
model.messages.push(msg.text);
66+
model.messages.push(message.text);
8767
}
8868
Msg::CloseWebSocket => {
8969
model.web_socket_reconnector = None;
@@ -139,6 +119,38 @@ fn update(msg: Msg, mut model: &mut Model, orders: &mut impl Orders<Msg>) {
139119
}
140120
}
141121

122+
fn create_websocket(orders: &impl Orders<Msg>) -> WebSocket {
123+
let msg_sender = orders.msg_sender();
124+
125+
WebSocket::builder(WS_URL, orders)
126+
.on_open(|| Msg::WebSocketOpened)
127+
.on_message(move |msg| decode_message(msg, msg_sender))
128+
.on_close(Msg::WebSocketClosed)
129+
.on_error(|| Msg::WebSocketFailed)
130+
.build_and_open()
131+
.unwrap()
132+
}
133+
134+
fn decode_message(message: WebSocketMessage, msg_sender: Rc<dyn Fn(Option<Msg>)>) {
135+
if message.contains_text() {
136+
let msg = message
137+
.json::<shared::ServerMessage>()
138+
.expect("Failed to decode WebSocket text message");
139+
140+
msg_sender(Some(Msg::TextMessageReceived(msg)));
141+
} else {
142+
spawn_local(async move {
143+
let bytes = message
144+
.bytes()
145+
.await
146+
.expect("WebsocketError on binary data");
147+
148+
let msg: shared::ServerMessage = rmp_serde::from_slice(&bytes).unwrap();
149+
msg_sender(Some(Msg::BinaryMessageReceived(msg)));
150+
});
151+
}
152+
}
153+
142154
// ------ ------
143155
// View
144156
// ------ ------
@@ -147,6 +159,7 @@ fn view(model: &Model) -> Vec<Node<Msg>> {
147159
vec![
148160
h1!["WebSocket example"],
149161
div![model.messages.iter().map(|message| p![message])],
162+
hr![],
150163
if model.web_socket.state() == web_socket::State::Open {
151164
div![
152165
div![
@@ -187,10 +200,11 @@ fn view(model: &Model) -> Vec<Node<Msg>> {
187200
"Send"
188201
],
189202
],
190-
div![button![
203+
hr![style! {St::Margin => px(20) + " " + &px(0)}],
204+
button![
191205
ev(Ev::Click, |_| Msg::CloseWebSocket),
192206
"Close websocket connection"
193-
],]
207+
],
194208
]
195209
} else {
196210
div![p![em!["Connecting or closed"]]]

src/app/orders.rs

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
use super::{App, CmdHandle, RenderInfo, StreamHandle, SubHandle, subs};
2-
use crate::virtual_dom::IntoNodes;
1+
use super::{subs, App, CmdHandle, RenderInfo, StreamHandle, SubHandle};
32
use crate::browser::Url;
3+
use crate::virtual_dom::IntoNodes;
44
use futures::stream::Stream;
55
use std::{any::Any, future::Future, rc::Rc};
66

@@ -127,13 +127,50 @@ pub trait Orders<Ms: 'static> {
127127

128128
/// Get the function that maps module's `Msg` to app's (root's) one.
129129
///
130+
/// _Note:_ You want to use `Orders::msg_sender` instead in most cases.
131+
///
130132
/// # Example
131133
///
132134
/// ```rust,no_run
133135
///let (app, msg_mapper) = (orders.clone_app(), orders.msg_mapper());
134136
///app.update(msg_mapper(Msg::AMessage));
135137
/// ```
136-
fn msg_mapper(&self) -> Box<dyn Fn(Ms) -> Self::AppMs>;
138+
fn msg_mapper(&self) -> Rc<dyn Fn(Ms) -> Self::AppMs>;
139+
140+
/// Get the function that invokes your `update` function.
141+
/// The most common use-case is passing the function into callbacks.
142+
///
143+
/// # Example
144+
///
145+
/// ```rust,no_run
146+
/// fn create_websocket(orders: &impl Orders<Msg>) -> WebSocket {
147+
/// let msg_sender = orders.msg_sender();
148+
///
149+
/// WebSocket::builder(WS_URL, orders)
150+
/// .on_message(move |msg| decode_message(msg, msg_sender))
151+
/// ...
152+
/// }
153+
///
154+
/// fn decode_message(message: WebSocketMessage, msg_sender: Rc<dyn Fn(Option<Msg>)>) {
155+
/// ...
156+
/// spawn_local(async move {
157+
/// let bytes = message
158+
/// .bytes()
159+
/// .await
160+
/// .expect("WebsocketError on binary data");
161+
///
162+
/// let msg: shared::ServerMessage = rmp_serde::from_slice(&bytes).unwrap();
163+
/// msg_sender(Some(Msg::BinaryMessageReceived(msg)));
164+
/// });
165+
/// ...
166+
/// }
167+
/// ```
168+
fn msg_sender(&self) -> Rc<dyn Fn(Option<Ms>)> {
169+
let (app, msg_mapper) = (self.clone_app(), self.msg_mapper());
170+
let msg_sender =
171+
move |msg: Option<Ms>| app.update_with_option(msg.map(|msg| msg_mapper(msg)));
172+
Rc::new(msg_sender)
173+
}
137174

138175
/// Register the callback that will be executed after the next render.
139176
///
@@ -277,7 +314,7 @@ pub trait Orders<Ms: 'static> {
277314
Rc::clone(&self.clone_app().cfg.base_path)
278315
}
279316

280-
/// Simulate `<a href="[url]">` element click.
317+
/// Simulate `<a href="[url]">` element click.
281318
///
282319
/// A thin wrapper for `orders.notify(subs::UrlRequested::new(url))`
283320
fn request_url(&mut self, url: Url) -> &mut Self {

src/app/orders/container.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ where
113113
self.app.clone()
114114
}
115115

116-
fn msg_mapper(&self) -> Box<dyn Fn(Ms) -> Self::AppMs> {
117-
Box::new(identity)
116+
fn msg_mapper(&self) -> Rc<dyn Fn(Ms) -> Self::AppMs> {
117+
Rc::new(identity)
118118
}
119119

120120
fn after_next_render<MsU: 'static>(

src/app/orders/proxy.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,9 @@ where
126126
}
127127

128128
#[allow(clippy::redundant_closure)]
129-
fn msg_mapper(&self) -> Box<dyn Fn(Ms) -> Self::AppMs> {
129+
fn msg_mapper(&self) -> Rc<dyn Fn(Ms) -> Self::AppMs> {
130130
let f = self.f.clone();
131-
Box::new(move |ms| f(ms))
131+
Rc::new(move |ms| f(ms))
132132
}
133133

134134
fn after_next_render<MsU: 'static>(

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ pub use crate::{
106106
pub use console_error_panic_hook;
107107
pub use futures::future::{FutureExt, TryFutureExt};
108108
use wasm_bindgen::{closure::Closure, JsCast};
109+
pub use wasm_bindgen_futures::{self, spawn_local};
109110

110111
#[macro_use]
111112
pub mod shortcuts;

0 commit comments

Comments
 (0)