yew_nested_router/
lib.rs

1//! A router for Yew, supporting nesting.
2//!
3//! ## Usage
4//!
5//! The nested router makes use of Yew's `context` features. It injects a routing context, tied to
6//! a type implementing the [`target::Target`] trait. It then is possible to scope/translate between
7//! levels.
8//!
9//! ### Targets
10//!
11//! "Targets" are the route targets, things the page can point to. They must be an enum,
12//! implementing the [`target::Target`] trait. This can easily be done using the `Target` derive:
13//!
14//! ```
15//! # use yew_nested_router::prelude::*;
16//! #[derive(Clone, Debug, PartialEq, Eq, Target)]
17//! pub enum AppRoute {
18//!   #[target(index)]
19//!   Index,
20//!   Foo,
21//!   Bar,
22//! }
23//! ```
24//!
25//! This created a target enum with three paths (`/`, `/foo`, `/bar`).
26//!
27//! ### Main router
28//!
29//! Each application needs a main entry point for the router ([`Router`]). This simply injects the
30//! routing context, and provides the necessary information internally. All children of the
31//! component will simply be rendered.
32//!
33//! ```
34//! # use yew::prelude::*;
35//! # use yew_nested_router::prelude::*;
36//! # #[derive(Clone, Debug, PartialEq, Eq, Target)]
37//! # pub enum AppRoute { Index }
38//! # #[function_component(MyContent)] fn my_content() -> Html { html!() }
39//! #[function_component(MyApp)]
40//! pub fn my_app() -> Html {
41//!   html!(
42//!     <Router<AppRoute>>
43//!       <MyContent/>
44//!     </Router<AppRoute>>
45//!   )
46//! }
47//! ```
48//!
49//! ### Switching content
50//!
51//! Having the route context available, allows to switch based on its state. This is done using the
52//! [`Switch`] component, which searches (upwards) for a matching routing context (of the target
53//! type).
54//!
55//! ```
56//! # use yew::prelude::*;
57//! # use yew_nested_router::prelude::*;
58//! # #[derive(Clone, Debug, PartialEq, Eq, Target)]
59//! # pub enum AppRoute {
60//! #   #[target(index)]
61//! #   Index,
62//! #   Foo,
63//! #   Bar,
64//! # }
65//! # #[function_component(Index)] fn index() -> Html { html!() }
66//! # #[function_component(Foo)] fn foo() -> Html { html!() }
67//! # #[function_component(Bar)] fn bar() -> Html { html!() }
68//! #[function_component(MyContent)]
69//! pub fn my_content() -> Html {
70//!   html!(
71//!     <Switch<AppRoute> render={|target|match target {
72//!       AppRoute::Index => html!(<Index/>),
73//!       AppRoute::Foo => html!(<Foo/>),
74//!       AppRoute::Bar => html!(<Bar/>),
75//!     }}/>
76//!   )
77//! }
78//! ```
79//!
80//! The `Switch` component does not have any children, as its content is evaluated from the `render`
81//! callback.
82//!
83//! If no target matched, then none of the switches will match either. If is possible to define a
84//! default target on the router.
85//!
86//! ### Nesting
87//!
88//! When nesting, first the structure must be declared. Let's adapt the example from above:
89//!
90//! ```
91//! # use yew_nested_router_macros::Target;
92//! #[derive(Clone, Debug, PartialEq, Eq, Target)]
93//! pub enum AppRoute {
94//!   #[target(index)]
95//!   Index,
96//!   Foo(#[target(default)] Details),
97//!   Bar {
98//!     id: String,
99//!     #[target(nested, default)]
100//!     details: Details,
101//!   },
102//! }
103//!
104//! #[derive(Clone, Debug, PartialEq, Eq, Target)]
105//! pub enum Details {
106//!   Overview,
107//!   Code,
108//!   Metrics,
109//! }
110//!
111//! impl Default for Details {
112//!   fn default() -> Self {
113//!     Self::Overview
114//!   }
115//! }
116//! ```
117//!
118//! This changed the following:
119//!
120//! * It added a nested layer to `Foo`, which by default will use the `Details::Overview` target.
121//! * It added a nested layer to `Bar`
122//!   * Capturing a path variable into `id`
123//!   * Then using a nested layer to `Details`, again using the default target.
124//!
125//!  This will process the following routes
126//!
127//! | Path | Target |
128//! | ---- | ------ |
129//! | `/` | `AppRoute::Index` |
130//! | `/foo`, `/foo/overview` | `AppRoute::Foo({id}::Overview)` |
131//! | `/foo/code` | `AppRoute::Foo(Details::Code)` |
132//! | `/foo/metrics` | `AppRoute::Foo({id}::Metrics)` |
133//! | `/bar` | no match |
134//! | `/bar/{id}`, `/foo/{id}/overview` | `AppRoute::Bar {id: "id", details: Details::Overview}` |
135//! | `/foo/{id}/code` | `AppRoute::Bar {id: "id", details: Details::Code}` |
136//! | `/foo/{id}/metrics` | `AppRoute::Bar {id: "id", details: Details::Metrics}` |
137//!
138//! ### Scoping/Translating
139//!
140//! The main router will only insert an routing context for the `AppRoutes` context. Now we need to
141//! translate down the next level. This is done using the [`Scope`] component, which translates
142//! "from" -> "to", or `P`arent -> `C`hild.
143//!
144//! ```
145//! # use yew::prelude::*;
146//! # use yew_nested_router::prelude::*;
147//! # #[derive(Clone, Debug, PartialEq, Eq, Target)]
148//! # pub enum AppRoute {
149//! #   #[target(index)]
150//! #   Index,
151//! #   Foo(Details),
152//! #   Bar,
153//! # }
154//! # #[derive(Clone, Debug, PartialEq, Eq, Target)]
155//! # pub enum Details {
156//! #   Overview,
157//! #   Code,
158//! #   Metrics,
159//! # }
160//! #[function_component(Foo)]
161//! pub fn foo() -> Html {
162//!   html! (
163//!     <Scope<AppRoute, Details> mapper={AppRoute::mapper_foo}>
164//!       <Switch<Details> render={|target|html!(/* ... */)}/>
165//!     </Scope<AppRoute, Details>>
166//!   )
167//! }
168//! ```
169//!
170//! The `AppRoute::mapper_foo` function was automatically created by the `Target` derive. It
171//! translates upwards and downwards between the two levels.
172//!
173//! **NOTE:** Targets having additional information do not get a mapper automatically created, as
174//! that information might not be known on the lower levels.
175//! In these cases you will have to implement the mapper yourself.
176//! An example is provided for the target `Page::D` in the `examples` folder.
177//!
178//! For a more complete example on nesting, see the full example in the `examples` folder.
179//!
180//! ### Navigating
181//!
182//! There is an out-of-the-box component named [`components::Link`], which allows to navigate to a
183//! target. It is also possible to achieve the same, using the routing context, which can be
184//! acquired using [`prelude::use_router`].
185//!
186//! ```
187//! # use yew::prelude::*;
188//! # use yew_nested_router::prelude::*;
189//! # #[derive(Clone, Debug, PartialEq, Eq, Target)]
190//! # pub enum AppRoute {
191//! #   #[target(index)]
192//! #   Index,
193//! #   Foo(Details),
194//! #   Bar{id: String, #[target(nested)] details: Details},
195//! # }
196//! # #[derive(Clone, Debug, PartialEq, Eq, Target)]
197//! # pub enum Details {
198//! #   Overview,
199//! #   Code,
200//! #   Metrics,
201//! # }
202//! # #[function_component(Index)] fn index() -> Html { html!() }
203//! # #[function_component(Foo)] fn foo() -> Html { html!() }
204//! # #[function_component(Bar)] fn bar() -> Html { html!() }
205//! use yew_nested_router::components::Link;
206//!
207//! #[function_component(MyContent)]
208//! pub fn my_content() -> Html {
209//!   html!(
210//!     <>
211//!       <ul>
212//!         <li><Link<AppRoute> to={AppRoute::Index}>{"Index"}</Link<AppRoute>></li>
213//!         <li><Link<AppRoute> to={AppRoute::Foo(Details::Overview)}>{"Foo"}</Link<AppRoute>></li>
214//!         <li><Link<AppRoute> to={AppRoute::Bar{ id: "".into(), details: Details::Overview}}>{"Bar"}</Link<AppRoute>></li>
215//!       </ul>
216//!       <Switch<AppRoute> render={|target|match target {
217//!         AppRoute::Index => html!(<Index/>),
218//!         AppRoute::Foo(_) => html!(<Foo/>),
219//!         AppRoute::Bar{..} => html!(<Bar/>),
220//!       }}/>
221//!     </>
222//!   )
223//! }
224//! ```
225//!
226//! ## Interoperability
227//!
228//! This implementation makes use of the browser's history API. While it is possible to receive the current state
229//! from the History API, and trigger "back" operations, using [`web_sys::History::push_state`] directly will not
230//! trigger an event and thus no render a different page.
231//!
232//! As `gloo_history` creates its internal type and state system, it is not interoperable with this crate. It still is
233//! possible to use [`gloo_utils::history`] though, which is just a shortcut of getting [`web_sys::History`].
234//!
235//! ## More examples
236//!
237//! See the `examples` folder.
238
239pub mod components;
240pub mod target;
241
242mod base;
243mod history;
244mod router;
245mod scope;
246mod state;
247mod switch;
248
249pub use history::History;
250pub use router::Router;
251pub use scope::Scope;
252pub use switch::Switch;
253pub use yew_nested_router_macros::Target;
254
255/// Common includes.
256pub mod prelude {
257    pub use super::router::*;
258    pub use super::scope::*;
259    pub use super::state::*;
260    pub use super::switch::*;
261    pub use super::target::*;
262
263    pub use yew_nested_router_macros::Target;
264}