yew_router_nested/
router.rs

1//! Router Component.
2
3use crate::{
4    agent::{RouteAgentBridge, RouteRequest},
5    route::Route,
6    RouteState, Switch,
7};
8use std::{
9    fmt::{self, Debug, Error as FmtError, Formatter},
10    rc::Rc,
11};
12use yew::{html, virtual_dom::VNode, Component, Context, Html, Properties};
13
14/// Any state that can be managed by the `Router` must meet the criteria of this trait.
15pub trait RouterState: RouteState + PartialEq {}
16impl<STATE> RouterState for STATE where STATE: RouteState + PartialEq {}
17
18/// Rendering control flow component.
19///
20/// # Example
21/// ```
22/// use yew::{prelude::*, virtual_dom::VNode};
23/// use yew_router::{router::Router, Switch};
24///
25/// pub enum Msg {}
26///
27/// pub struct Model {}
28/// impl Component for Model {
29///     //...
30/// #   type Message = Msg;
31/// #   type Properties = ();
32/// #   fn create(_: &Context<Self>) -> Self {
33/// #       Model {}
34/// #   }
35///     fn view(&self, _: &Context<Self>) -> VNode {
36///         html! {
37///         <Router<S>
38///            render = Router::render(|switch: S| {
39///                match switch {
40///                    S::Variant => html!{"variant route was matched"},
41///                }
42///            })
43///         />
44///         }
45///     }
46/// }
47///
48/// #[derive(Switch, Clone)]
49/// enum S {
50///     #[to = "/v"]
51///     Variant,
52/// }
53/// ```
54// TODO, can M just be removed due to not having to explicitly deal with callbacks anymore? - Just get rid of M
55#[derive(Debug)]
56pub struct Router<SW: Switch + Clone + 'static, STATE: RouterState = ()> {
57    switch: Option<SW>,
58    router_agent: RouteAgentBridge<STATE>,
59}
60
61impl<SW, STATE> Router<SW, STATE>
62where
63    STATE: RouterState,
64    SW: Switch + PartialEq + Clone + 'static,
65{
66    /// Wrap a render closure so that it can be used by the Router.
67    /// # Example
68    /// ```
69    /// # use yew_router::Switch;
70    /// # use yew_router::router::{Router, Render};
71    /// # use yew::{html, Html};
72    /// # #[derive(Switch, Clone)]
73    /// # enum S {
74    /// #     #[to = "/route"]
75    /// #     Variant
76    /// # }
77    /// # pub enum Msg {}
78    ///
79    /// # fn dont_execute() {
80    /// let render: Render<S> = Router::render(|switch: S| -> Html {
81    ///     match switch {
82    ///         S::Variant => html! {"Variant"},
83    ///     }
84    /// });
85    /// # }
86    /// ```
87    pub fn render<F: RenderFn<Router<SW, STATE>, SW> + 'static>(f: F) -> Render<SW, STATE> {
88        Render::new(f)
89    }
90
91    /// Wrap a redirect function so that it can be used by the Router.
92    pub fn redirect<F: RedirectFn<SW, STATE> + 'static>(f: F) -> Option<Redirect<SW, STATE>> {
93        Some(Redirect::new(f))
94    }
95}
96
97/// Message for Router.
98#[derive(Debug, Clone)]
99pub enum Msg<STATE> {
100    /// Updates the route
101    UpdateRoute(Route<STATE>),
102}
103
104/// Render function that takes a switched route and converts it to HTML
105pub trait RenderFn<CTX: Component, SW>: Fn(SW) -> Html {}
106impl<T, CTX: Component, SW> RenderFn<CTX, SW> for T where T: Fn(SW) -> Html {}
107/// Owned Render function.
108#[derive(Clone)]
109pub struct Render<SW: Switch + Clone + 'static, STATE: RouterState = ()>(
110    pub(crate) Rc<dyn RenderFn<Router<SW, STATE>, SW>>,
111);
112
113impl<SW: Switch + Clone + 'static, STATE: RouterState> PartialEq for Render<SW, STATE> {
114    fn eq(&self, other: &Self) -> bool {
115        Rc::ptr_eq(&self.0, &other.0)
116    }
117}
118
119impl<STATE: RouterState, SW: Switch + PartialEq + Clone> Render<SW, STATE> {
120    /// New render function
121    fn new<F: RenderFn<Router<SW, STATE>, SW> + 'static>(f: F) -> Self {
122        Render(Rc::new(f))
123    }
124}
125impl<STATE: RouterState, SW: Switch + Clone> Debug for Render<SW, STATE> {
126    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
127        f.debug_struct("Render").finish()
128    }
129}
130
131/// Redirection function that takes a route that didn't match any of the Switch variants,
132/// and converts it to a switch variant.
133pub trait RedirectFn<SW, STATE>: Fn(Route<STATE>) -> SW {}
134impl<T, SW, STATE> RedirectFn<SW, STATE> for T where T: Fn(Route<STATE>) -> SW {}
135/// Clonable Redirect function
136#[derive(Clone)]
137pub struct Redirect<SW: Switch + 'static, STATE: RouterState>(
138    pub(crate) Rc<dyn RedirectFn<SW, STATE>>,
139);
140impl<SW: Switch + 'static, STATE: RouterState> PartialEq for Redirect<SW, STATE> {
141    fn eq(&self, other: &Self) -> bool {
142        Rc::ptr_eq(&self.0, &other.0)
143    }
144}
145impl<STATE: RouterState, SW: Switch + 'static> Redirect<SW, STATE> {
146    fn new<F: RedirectFn<SW, STATE> + 'static>(f: F) -> Self {
147        Redirect(Rc::new(f))
148    }
149}
150impl<STATE: RouterState, SW: Switch> Debug for Redirect<SW, STATE> {
151    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
152        f.debug_struct("Redirect").finish()
153    }
154}
155
156/// Properties for Router.
157#[derive(Properties, Clone, PartialEq)]
158pub struct Props<STATE: RouterState, SW: Switch + PartialEq + PartialEq + Clone + 'static> {
159    /// Render function that takes a Switch and produces Html
160    pub render: Render<SW, STATE>,
161    /// Optional redirect function that will convert the route to a known switch variant if explicit matching fails.
162    /// This should mostly be used to handle 404s and redirection.
163    /// It is not strictly necessary as your Switch is capable of handling unknown routes using `#[to="/{*:any}"]`.
164    #[prop_or_default]
165    pub redirect: Option<Redirect<SW, STATE>>,
166}
167
168impl<STATE: RouterState, SW: Switch + PartialEq + Clone> Debug for Props<STATE, SW> {
169    fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
170        f.debug_struct("Props").finish()
171    }
172}
173
174impl<STATE, SW> Component for Router<SW, STATE>
175where
176    STATE: RouterState,
177    SW: Switch + PartialEq + Clone + 'static,
178{
179    type Message = Msg<STATE>;
180    type Properties = Props<STATE, SW>;
181
182    fn create(ctx: &Context<Self>) -> Self {
183        let callback = ctx.link().callback(Msg::UpdateRoute);
184        let mut router_agent = RouteAgentBridge::new(callback);
185        router_agent.send(RouteRequest::GetCurrentRoute);
186
187        Router {
188            switch: Default::default(), /* This must be updated by immediately requesting a route
189                                         * update from the service bridge. */
190            router_agent,
191        }
192    }
193
194    fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
195        match msg {
196            Msg::UpdateRoute(route) => {
197                let mut switch = SW::switch(route.clone());
198
199                if switch.is_none() {
200                    if let Some(redirect) = &ctx.props().redirect {
201                        let redirected: SW = (&redirect.0)(route);
202
203                        log::trace!(
204                            "Route failed to match, but redirecting route to a known switch."
205                        );
206                        // Replace the route in the browser with the redirected.
207                        self.router_agent
208                            .send(RouteRequest::ReplaceRouteNoBroadcast(
209                                redirected.clone().into(),
210                            ));
211                        switch = Some(redirected)
212                    }
213                }
214
215                self.switch = switch;
216                true
217            }
218        }
219    }
220
221    fn view(&self, ctx: &Context<Self>) -> VNode {
222        match self.switch.clone() {
223            Some(switch) => (&ctx.props().render.0)(switch),
224            None => {
225                log::warn!("No route matched, provide a redirect prop to the router to handle cases where no route can be matched");
226                html! {"No route matched"}
227            }
228        }
229    }
230}