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}