From 1b977fdce91822418f97fb33d416c295ce77180f Mon Sep 17 00:00:00 2001 From: Stygmates Date: Sat, 2 Sep 2023 17:24:30 +0200 Subject: [PATCH] Add query segment example and documentation (#1417) * fix a few new clippy lints * Add query segment example * Add trait documentation * Change core package to root package The core package contains the virtual dom implementation ([here](https://github.com/DioxusLabs/dioxus/tree/master/packages/core)). Root package might be a more clear way to refer to the main directory --------- Co-authored-by: Evan Almloff --- Cargo.toml | 1 + examples/query_segments_demo/Cargo.toml | 12 ++++ examples/query_segments_demo/src/main.rs | 73 ++++++++++++++++++++++++ packages/router/src/contexts/router.rs | 34 ++++++----- packages/router/src/routable.rs | 6 +- packages/web/src/dom.rs | 1 + 6 files changed, 108 insertions(+), 19 deletions(-) create mode 100644 examples/query_segments_demo/Cargo.toml create mode 100644 examples/query_segments_demo/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 252e9c83e..54c54ebf1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ members = [ # Full project examples "examples/tailwind", "examples/PWA-example", + "examples/query_segments_demo", # Playwright tests "playwright-tests/liveview", "playwright-tests/web", diff --git a/examples/query_segments_demo/Cargo.toml b/examples/query_segments_demo/Cargo.toml new file mode 100644 index 000000000..4a7d784c7 --- /dev/null +++ b/examples/query_segments_demo/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "query_segments_demo" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +dioxus = { path = "../../packages/dioxus", version = "*" } +dioxus-router = { path = "../../packages/router", version = "*" } +dioxus-web = { path = "../../packages/web", version = "*" } +form_urlencoded = "1.2.0" diff --git a/examples/query_segments_demo/src/main.rs b/examples/query_segments_demo/src/main.rs new file mode 100644 index 000000000..2348f1fe5 --- /dev/null +++ b/examples/query_segments_demo/src/main.rs @@ -0,0 +1,73 @@ +#![allow(non_snake_case, unused)] +//! Example: Url query segments usage +//! ------------------------------------ +//! +//! This example shows how to access and use multiple query segments present in an url on the web. +//! +//! Run `dx serve` and navigate to `http://localhost:8080/blog?name=John&surname=Doe` +use std::fmt::Display; + +use dioxus::prelude::*; +use dioxus_router::prelude::*; + +// ANCHOR: route +#[derive(Routable, Clone)] +#[rustfmt::skip] +enum Route { + // segments that start with ?: are query segments + #[route("/blog?:query_params")] + BlogPost { + // You must include query segments in child variants + query_params: BlogQuerySegments, + }, +} + +#[derive(Debug, Clone, PartialEq)] +struct BlogQuerySegments { + name: String, + surname: String, +} + +/// The display impl needs to display the query in a way that can be parsed: +impl Display for BlogQuerySegments { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "name={}&surname={}", self.name, self.surname) + } +} + +/// The query segment is anything that implements https://docs.rs/dioxus-router/latest/dioxus_router/routable/trait.FromQuery.html. You can implement that trait for a struct if you want to parse multiple query parameters. +impl FromQuery for BlogQuerySegments { + fn from_query(query: &str) -> Self { + let mut name = None; + let mut surname = None; + let pairs = form_urlencoded::parse(query.as_bytes()); + pairs.for_each(|(key, value)| { + if key == "name" { + name = Some(value.clone().into()); + } + if key == "surname" { + surname = Some(value.clone().into()); + } + }); + Self { + name: name.unwrap(), + surname: surname.unwrap(), + } + } +} + +#[inline_props] +fn BlogPost(cx: Scope, query_params: BlogQuerySegments) -> Element { + render! { + div{"This is your blogpost with a query segment:"} + div{format!("{:?}", query_params)} + } +} + +fn App(cx: Scope) -> Element { + render! { Router::{} } +} + +fn main() { + dioxus_web::launch(App); +} diff --git a/packages/router/src/contexts/router.rs b/packages/router/src/contexts/router.rs index 5a9a3f0c4..b15956991 100644 --- a/packages/router/src/contexts/router.rs +++ b/packages/router/src/contexts/router.rs @@ -2,7 +2,7 @@ use std::{ any::Any, collections::HashSet, rc::Rc, - sync::{Arc, RwLock, RwLockWriteGuard}, + sync::{Arc, RwLock}, }; use dioxus::prelude::*; @@ -36,7 +36,7 @@ struct MutableRouterState { /// A collection of router data that manages all routing functionality. #[derive(Clone)] pub struct RouterContext { - state: Arc>, + state: Rc>, subscribers: Arc>>, subscriber_update: Arc, @@ -56,7 +56,7 @@ impl RouterContext { R: Clone, ::Err: std::fmt::Display, { - let state = Arc::new(RwLock::new(MutableRouterState { + let state = Rc::new(RefCell::new(MutableRouterState { prefix: Default::default(), history: cfg.take_history(), unresolved_error: None, @@ -105,7 +105,7 @@ impl RouterContext { // set the updater { - let mut state = myself.state.write().unwrap(); + let mut state = myself.state.borrow_mut(); state.history.updater(Arc::new(move || { for &id in subscribers.read().unwrap().iter() { (mark_dirty)(id); @@ -117,20 +117,20 @@ impl RouterContext { } pub(crate) fn route_from_str(&self, route: &str) -> Result, String> { - let state = self.state.read().unwrap(); + let state = self.state.borrow(); state.history.parse_route(route) } /// Check whether there is a previous page to navigate back to. #[must_use] pub fn can_go_back(&self) -> bool { - self.state.read().unwrap().history.can_go_back() + self.state.borrow().history.can_go_back() } /// Check whether there is a future page to navigate forward to. #[must_use] pub fn can_go_forward(&self) -> bool { - self.state.read().unwrap().history.can_go_forward() + self.state.borrow().history.can_go_forward() } /// Go back to the previous location. @@ -138,7 +138,7 @@ impl RouterContext { /// Will fail silently if there is no previous location to go to. pub fn go_back(&self) { { - self.state.write().unwrap().history.go_back(); + self.state.borrow_mut().history.go_back(); } self.change_route(); @@ -149,7 +149,7 @@ impl RouterContext { /// Will fail silently if there is no next location to go to. pub fn go_forward(&self) { { - self.state.write().unwrap().history.go_forward(); + self.state.borrow_mut().history.go_forward(); } self.change_route(); @@ -206,8 +206,7 @@ impl RouterContext { /// The route that is currently active. pub fn current(&self) -> R { self.state - .read() - .unwrap() + .borrow() .history .current_route() .downcast::() @@ -218,7 +217,7 @@ impl RouterContext { /// The route that is currently active. pub fn current_route_string(&self) -> String { - self.any_route_to_string(&*self.state.read().unwrap().history.current_route()) + self.any_route_to_string(&*self.state.borrow().history.current_route()) } pub(crate) fn any_route_to_string(&self, route: &dyn Any) -> String { @@ -243,7 +242,7 @@ impl RouterContext { /// The prefix that is currently active. pub fn prefix(&self) -> Option { - self.state.read().unwrap().prefix.clone() + self.state.borrow().prefix.clone() } fn external(&self, external: String) -> Option { @@ -261,8 +260,8 @@ impl RouterContext { } } - fn state_mut(&self) -> RwLockWriteGuard { - self.state.write().unwrap() + fn state_mut(&self) -> RefMut { + self.state.borrow_mut() } /// Manually subscribe to the current route @@ -283,15 +282,14 @@ impl RouterContext { /// Clear any unresolved errors pub fn clear_error(&self) { - self.state.write().unwrap().unresolved_error = None; + self.state.borrow_mut().unresolved_error = None; self.update_subscribers(); } pub(crate) fn render_error<'a>(&self, cx: Scope<'a>) -> Element<'a> { self.state - .read() - .unwrap() + .borrow() .unresolved_error .as_ref() .and_then(|_| (self.failure_external_navigation)(cx)) diff --git a/packages/router/src/routable.rs b/packages/router/src/routable.rs index cd3dd354d..d9f5b2346 100644 --- a/packages/router/src/routable.rs +++ b/packages/router/src/routable.rs @@ -22,7 +22,11 @@ impl std::fmt::Display for RouteParseError { } } -/// Something that can be created from a query string +/// Something that can be created from a query string. +/// +/// This trait needs to be implemented if you want to turn a query string into a struct. +/// +/// A working example can be found in the `examples` folder in the root package under `query_segments_demo` pub trait FromQuery { /// Create an instance of `Self` from a query string fn from_query(query: &str) -> Self; diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 9d0d837c5..52f2ab1a9 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -392,6 +392,7 @@ fn read_input_to_data(target: Element) -> Rc { .dyn_ref() .and_then(|input: &web_sys::HtmlInputElement| { input.files().and_then(|files| { + #[allow(clippy::arc_with_non_send_sync)] crate::file_engine::WebFileEngine::new(files) .map(|f| std::sync::Arc::new(f) as std::sync::Arc) })