Skip to content

Orders::request_url + msg_sender #522

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
- [BREAKING] Hidden and renamed module `effects` to `effect`.
- Added `App::update_with_option`.
- Added `Navigator` and `BeforeUnloadEvent` into Seed's `web_sys`.
- Fixed runtime exception when using binary data in WS on some browsers. (#470)
- Fixed runtime exception when using binary data in WS on some browsers (#470).
- Exported macro `with_dollar_sign!`.
- [deprecated] `RequestAnimationFrameTime` + `RequestAnimationFrameHandle` + `request_animation_frame` are deprecated.
- [deprecated] `set_interval` + `set_timeout` are deprecated.
Expand All @@ -22,11 +22,15 @@
- Exposed dependency `console_error_panic_hook`.
- Fixed double `UrlChanged` firing by removing `hashchange` listener.
- Added `Request::bytes`.
- Build Changes - Remove all workspace=false and instead defined default_to_workspace=false in the config
- Build Changes - Make all core cargo-make tasks private with default namespace and remove clear=true from all seed tasks
- Build Changes - Remove installation instructions and instead depend on core cargo-make installation tasks
- Build Changes - Remove all workspace=false and instead defined default_to_workspace=false in the config.
- Build Changes - Make all core cargo-make tasks private with default namespace and remove clear=true from all seed tasks.
- Build Changes - Remove installation instructions and instead depend on core cargo-make installation tasks.
- 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).
- Build Changes - Enforce minimal cargo-make version: 0.32.1
- Build Changes - Enforce minimal cargo-make version: 0.32.1.
- Added new `Orders` methods `request_url` (#518) and `msg_sender` (#502).
- [BREAKING] `Orders::msg_mapper` returns `Rc<..>` instead of `Box<..>`.
- Reexported `pub use wasm_bindgen_futures::{self, spawn_local};` in `lib.rs`.
- Updated example `websocket`.

## v0.7.0
- [BREAKING] Custom elements are now patched in-place (#364). Use `el_key` to force reinitialize an element.
Expand Down
2 changes: 1 addition & 1 deletion examples/url/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
*model = Model::new(url, orders.clone_base_path())
}
Msg::GoToUrl(url) => {
orders.notify(subs::UrlRequested::new(url));
orders.request_url(url);
}
}
}
Expand Down
78 changes: 46 additions & 32 deletions examples/websocket/src/client.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use seed::{prelude::*, *};
use std::rc::Rc;

mod shared;

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

struct Model {
pub struct Model {
sent_messages_count: usize,
messages: Vec<String>,
input_text: String,
Expand All @@ -32,24 +33,14 @@ fn init(_: Url, orders: &mut impl Orders<Msg>) -> Model {
}
}

fn create_websocket(orders: &impl Orders<Msg>) -> WebSocket {
WebSocket::builder(WS_URL, orders)
.on_open(|| Msg::WebSocketOpened)
.on_message(Msg::MessageReceived)
.on_close(Msg::WebSocketClosed)
.on_error(|| Msg::WebSocketFailed)
.build_and_open()
.unwrap()
}

// ------ ------
// Update
// ------ ------

enum Msg {
pub enum Msg {
WebSocketOpened,
MessageReceived(WebSocketMessage),
BytesReceived(Vec<u8>),
TextMessageReceived(shared::ServerMessage),
BinaryMessageReceived(shared::ServerMessage),
CloseWebSocket,
WebSocketClosed(CloseEvent),
WebSocketFailed,
Expand All @@ -66,24 +57,13 @@ fn update(msg: Msg, mut model: &mut Model, orders: &mut impl Orders<Msg>) {
model.web_socket_reconnector = None;
log!("WebSocket connection is open now");
}
Msg::MessageReceived(message) => {
log!("Client received a message");

if message.contains_text() {
model
.messages
.push(message.json::<shared::ServerMessage>().unwrap().text);
} else {
orders.perform_cmd(async move {
let bytes = message.bytes().await;
bytes.map(Msg::BytesReceived).ok()
});
}
Msg::TextMessageReceived(message) => {
log!("Client received a text message");
model.messages.push(message.text);
}
Msg::BytesReceived(bytes) => {
Msg::BinaryMessageReceived(message) => {
log!("Client received binary message");
let msg: shared::ServerMessage = rmp_serde::from_slice(&bytes).unwrap();
model.messages.push(msg.text);
model.messages.push(message.text);
}
Msg::CloseWebSocket => {
model.web_socket_reconnector = None;
Expand Down Expand Up @@ -139,6 +119,38 @@ fn update(msg: Msg, mut model: &mut Model, orders: &mut impl Orders<Msg>) {
}
}

fn create_websocket(orders: &impl Orders<Msg>) -> WebSocket {
let msg_sender = orders.msg_sender();

WebSocket::builder(WS_URL, orders)
.on_open(|| Msg::WebSocketOpened)
.on_message(move |msg| decode_message(msg, msg_sender))
.on_close(Msg::WebSocketClosed)
.on_error(|| Msg::WebSocketFailed)
.build_and_open()
.unwrap()
}

fn decode_message(message: WebSocketMessage, msg_sender: Rc<dyn Fn(Option<Msg>)>) {
if message.contains_text() {
let msg = message
.json::<shared::ServerMessage>()
.expect("Failed to decode WebSocket text message");

msg_sender(Some(Msg::TextMessageReceived(msg)));
} else {
spawn_local(async move {
let bytes = message
.bytes()
.await
.expect("WebsocketError on binary data");

let msg: shared::ServerMessage = rmp_serde::from_slice(&bytes).unwrap();
msg_sender(Some(Msg::BinaryMessageReceived(msg)));
});
}
}

// ------ ------
// View
// ------ ------
Expand All @@ -147,6 +159,7 @@ fn view(model: &Model) -> Vec<Node<Msg>> {
vec![
h1!["WebSocket example"],
div![model.messages.iter().map(|message| p![message])],
hr![],
if model.web_socket.state() == web_socket::State::Open {
div![
div![
Expand Down Expand Up @@ -187,10 +200,11 @@ fn view(model: &Model) -> Vec<Node<Msg>> {
"Send"
],
],
div![button![
hr![style! {St::Margin => px(20) + " " + &px(0)}],
button![
ev(Ev::Click, |_| Msg::CloseWebSocket),
"Close websocket connection"
],]
],
]
} else {
div![p![em!["Connecting or closed"]]]
Expand Down
49 changes: 47 additions & 2 deletions src/app/orders.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::{App, CmdHandle, RenderInfo, StreamHandle, SubHandle};
use super::{subs, App, CmdHandle, RenderInfo, StreamHandle, SubHandle};
use crate::browser::Url;
use crate::virtual_dom::IntoNodes;
use futures::stream::Stream;
use std::{any::Any, future::Future, rc::Rc};
Expand Down Expand Up @@ -126,13 +127,50 @@ pub trait Orders<Ms: 'static> {

/// Get the function that maps module's `Msg` to app's (root's) one.
///
/// _Note:_ You want to use `Orders::msg_sender` instead in most cases.
///
/// # Example
///
/// ```rust,no_run
///let (app, msg_mapper) = (orders.clone_app(), orders.msg_mapper());
///app.update(msg_mapper(Msg::AMessage));
/// ```
fn msg_mapper(&self) -> Box<dyn Fn(Ms) -> Self::AppMs>;
fn msg_mapper(&self) -> Rc<dyn Fn(Ms) -> Self::AppMs>;

/// Get the function that invokes your `update` function.
/// The most common use-case is passing the function into callbacks.
///
/// # Example
///
/// ```rust,no_run
/// fn create_websocket(orders: &impl Orders<Msg>) -> WebSocket {
/// let msg_sender = orders.msg_sender();
///
/// WebSocket::builder(WS_URL, orders)
/// .on_message(move |msg| decode_message(msg, msg_sender))
/// ...
/// }
///
/// fn decode_message(message: WebSocketMessage, msg_sender: Rc<dyn Fn(Option<Msg>)>) {
/// ...
/// spawn_local(async move {
/// let bytes = message
/// .bytes()
/// .await
/// .expect("WebsocketError on binary data");
///
/// let msg: shared::ServerMessage = rmp_serde::from_slice(&bytes).unwrap();
/// msg_sender(Some(Msg::BinaryMessageReceived(msg)));
/// });
/// ...
/// }
/// ```
fn msg_sender(&self) -> Rc<dyn Fn(Option<Ms>)> {
let (app, msg_mapper) = (self.clone_app(), self.msg_mapper());
let msg_sender =
move |msg: Option<Ms>| app.update_with_option(msg.map(|msg| msg_mapper(msg)));
Rc::new(msg_sender)
}

/// Register the callback that will be executed after the next render.
///
Expand Down Expand Up @@ -275,4 +313,11 @@ pub trait Orders<Ms: 'static> {
fn clone_base_path(&self) -> Rc<Vec<String>> {
Rc::clone(&self.clone_app().cfg.base_path)
}

/// Simulate `<a href="[url]">` element click.
///
/// A thin wrapper for `orders.notify(subs::UrlRequested::new(url))`
fn request_url(&mut self, url: Url) -> &mut Self {
self.notify(subs::UrlRequested::new(url))
}
}
4 changes: 2 additions & 2 deletions src/app/orders/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ where
self.app.clone()
}

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

fn after_next_render<MsU: 'static>(
Expand Down
4 changes: 2 additions & 2 deletions src/app/orders/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,9 @@ where
}

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

fn after_next_render<MsU: 'static>(
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ pub use crate::{
pub use console_error_panic_hook;
pub use futures::future::{FutureExt, TryFutureExt};
use wasm_bindgen::{closure::Closure, JsCast};
pub use wasm_bindgen_futures::{self, spawn_local};

#[macro_use]
pub mod shortcuts;
Expand Down