Routing
The router keeps the current route in a Computed, so the page can be rendered with render_value. The route type needs From<String> to parse the URL and Display to turn the route back into a URL.
use vertigo::{dom, router::Router}; #[derive(Clone, PartialEq)] enum Route { Main, Post(String), NotFound(String), } impl std::fmt::Display for Route { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Route::Main => write!(f, "/main"), Route::Post(slug) => write!(f, "/post/{slug}"), Route::NotFound(x) => write!(f, "{x}"), } } } // Allows to use Route directly in `dom!`, f. ex. as `href` impl vertigo::DomDisplay for Route {} impl From<String> for Route { fn from(value: String) -> Self { if value == "/main" { return Self::Main; } else if value.starts_with("/post/") { if let Some(slug) = value.split('/').nth(2) { return Self::Post(slug.to_string()); } } Self::NotFound(value) } } let routing = Router::new_history_router(); let page = routing.route.render_value(|route| match route { Route::Main => dom! { <main>"Main Page"</main> }, Route::Post(slug) => dom! { <main>"Post with slug " {slug}</main> }, Route::NotFound(path) => dom! { <main>"Page " {path} " not found"</main> }, }); dom! { <div> <nav> <a href={Route::Main}>"Main"</a> <a href={Route::Post("hello".to_string())}>"Hello post"</a> </nav> {page} </div> }
To use the route directly in dom!, like in href={Route::Main} above, the type has to opt in with impl vertigo::DomDisplay for Route {}. Since vertigo 0.13 a type isn't printed into the DOM just because it implements Display.
Clicks on links pointing to the same origin are handled by the app without reloading the page, so a plain <a href=...> is all you need. Links with rel="external", target or download attributes, and clicks with a modifier key (Ctrl/Cmd/Shift/Alt) are left to the browser.
Matching against current route
use vertigo::{dom, router::Router, Computed}; #[derive(Clone, PartialEq)] enum Route { Main, Post(String), NotFound(String), } impl Route { pub fn matches(self, current: &Computed<Self>) -> Computed<bool> { current.map(move |current| self == current) } } impl std::fmt::Display for Route { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Route::Main => write!(f, "/main"), Route::Post(slug) => write!(f, "/post/{slug}"), Route::NotFound(x) => write!(f, "{x}"), } } } impl From<String> for Route { fn from(value: String) -> Self { if value == "/main" { return Self::Main; } else if value.starts_with("/post/") { if let Some(slug) = value.split('/').nth(2) { return Self::Post(slug.to_string()); } } Self::NotFound(value) } } let routing = Router::new_history_router(); // Will generate Some(<p>) only if Main is the current route. let main_page_info = Route::Main .matches(&routing.route) .render_value_option(|is_active| { is_active.then(|| dom! { <p>"You're in the main menu!"</p> }) }); let page = routing.route.render_value(|route| match route { Route::Main => dom! { <main>"Main Page"</main> }, Route::Post(slug) => dom! { <main>"Post with slug " {slug}</main> }, Route::NotFound(path) => dom! { <main>"Page " {path} " not found"</main> }, }); dom! { <div> {main_page_info} {page} </div> }