yew_router/
scope_ext.rs

1use yew::context::ContextHandle;
2use yew::prelude::*;
3
4use crate::history::Location;
5use crate::navigator::Navigator;
6use crate::routable::Routable;
7use crate::router::{LocationContext, NavigatorContext};
8
9/// A [`ContextHandle`] for [`add_location_listener`](RouterScopeExt::add_location_listener).
10pub struct LocationHandle {
11    _inner: ContextHandle<LocationContext>,
12}
13
14/// A [`ContextHandle`] for [`add_navigator_listener`](RouterScopeExt::add_navigator_listener).
15pub struct NavigatorHandle {
16    _inner: ContextHandle<NavigatorContext>,
17}
18
19/// An extension to [`Scope`](yew::html::Scope) that provides location information and navigator
20/// access.
21///
22/// You can access them on `ctx.link()`
23///
24/// # Example
25///
26/// Below is an example of the implementation of the [`Link`](crate::components::Link) component.
27///
28/// ```
29/// # use std::marker::PhantomData;
30/// # use wasm_bindgen::UnwrapThrowExt;
31/// # use yew::prelude::*;
32/// # use yew_router::prelude::*;
33/// # use yew_router::components::LinkProps;
34/// #
35/// # pub struct Link<R: Routable + 'static> {
36/// #     _data: PhantomData<R>,
37/// # }
38/// #
39/// # pub enum Msg {
40/// #     OnClick,
41/// # }
42/// #
43/// impl<R: Routable + 'static> Component for Link<R> {
44///     type Message = Msg;
45///     type Properties = LinkProps<R>;
46///
47///     fn create(_ctx: &Context<Self>) -> Self {
48///         Self { _data: PhantomData }
49///     }
50///
51///     fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
52///         match msg {
53///             Msg::OnClick => {
54///                 ctx.link()
55///                     .navigator()
56///                     .expect_throw("failed to get navigator.")
57///                     .push(&ctx.props().to);
58///                 false
59///             }
60///         }
61///     }
62///
63///     fn view(&self, ctx: &Context<Self>) -> Html {
64///         html! {
65///             <a class={ctx.props().classes.clone()}
66///                 href={ctx.props().to.to_path()}
67///                 onclick={ctx.link().callback(|e: MouseEvent| {
68///                     e.prevent_default();
69///                     Msg::OnClick
70///                 })}
71///             >
72///                 { ctx.props().children.clone() }
73///             </a>
74///         }
75///     }
76/// }
77/// ```
78pub trait RouterScopeExt {
79    /// Returns current [`Navigator`].
80    fn navigator(&self) -> Option<Navigator>;
81
82    /// Returns current [`Location`].
83    fn location(&self) -> Option<Location>;
84
85    /// Returns current route.
86    fn route<R>(&self) -> Option<R>
87    where
88        R: Routable + 'static;
89
90    /// Adds a listener that gets notified when location changes.
91    ///
92    /// # Note
93    ///
94    /// [`LocationHandle`] works like a normal [`ContextHandle`] and it unregisters the callback
95    /// when the handle is dropped. You need to keep the handle for as long as you need the
96    /// callback.
97    fn add_location_listener(&self, cb: Callback<Location>) -> Option<LocationHandle>;
98
99    /// Adds a listener that gets notified when navigator changes.
100    ///
101    /// # Note
102    ///
103    /// [`NavigatorHandle`] works like a normal [`ContextHandle`] and it unregisters the callback
104    /// when the handle is dropped. You need to keep the handle for as long as you need the
105    /// callback.
106    fn add_navigator_listener(&self, cb: Callback<Navigator>) -> Option<NavigatorHandle>;
107}
108
109impl<COMP: Component> RouterScopeExt for yew::html::Scope<COMP> {
110    fn navigator(&self) -> Option<Navigator> {
111        self.context::<NavigatorContext>(Callback::from(|_| {}))
112            .map(|(m, _)| m.navigator())
113    }
114
115    fn location(&self) -> Option<Location> {
116        self.context::<LocationContext>(Callback::from(|_| {}))
117            .map(|(m, _)| m.location())
118    }
119
120    fn route<R>(&self) -> Option<R>
121    where
122        R: Routable + 'static,
123    {
124        let navigator = self.navigator()?;
125        let location = self.location()?;
126
127        let path = navigator.strip_basename(location.path().into());
128
129        R::recognize(&path)
130    }
131
132    fn add_location_listener(&self, cb: Callback<Location>) -> Option<LocationHandle> {
133        self.context::<LocationContext>(Callback::from(move |m: LocationContext| {
134            cb.emit(m.location())
135        }))
136        .map(|(_, m)| LocationHandle { _inner: m })
137    }
138
139    fn add_navigator_listener(&self, cb: Callback<Navigator>) -> Option<NavigatorHandle> {
140        self.context::<NavigatorContext>(Callback::from(move |m: NavigatorContext| {
141            cb.emit(m.navigator())
142        }))
143        .map(|(_, m)| NavigatorHandle { _inner: m })
144    }
145}