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}