vertigo/router.rs
1use crate::{computed::Value, get_driver, Computed, DomNode, EmbedDom, Reactive, ToComputed};
2
3/// Router based on path or hash part of current location.
4///
5/// Note: If you want your app to support dynamic mount point,
6/// you should use method [Driver::route_to_public](crate::Driver::route_to_public)
7/// which will always prefix your route with mount point.
8///
9/// ```rust
10/// use vertigo::{dom, DomNode, get_driver, router::Router};
11///
12/// #[derive(Clone, PartialEq, Debug)]
13/// pub enum Route {
14/// Page1,
15/// Page2,
16/// NotFound,
17/// }
18///
19/// impl Route {
20/// pub fn new(path: &str) -> Route {
21/// match path {
22/// "" | "/" | "/page1" => Self::Page1,
23/// "/page2" => Self::Page2,
24/// _ => Self::NotFound,
25/// }
26/// }
27/// }
28///
29/// impl std::fmt::Display for Route {
30/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31/// let str = match self {
32/// Self::Page1 => "/",
33/// Self::Page2 => "/page2",
34/// Self::NotFound => "/404",
35/// };
36/// f.write_str(&get_driver().route_to_public(str))
37/// }
38/// }
39///
40/// impl From<String> for Route {
41/// fn from(url: String) -> Self {
42/// Route::new(url.as_str())
43/// }
44/// }
45///
46/// #[derive(Clone)]
47/// pub struct State {
48/// route: Router<Route>,
49/// }
50///
51/// impl State {
52/// pub fn component() -> DomNode {
53/// let route = Router::new_history_router();
54///
55/// let state = State {
56/// route,
57/// };
58///
59/// render(state)
60/// }
61/// }
62///
63/// fn render(state: State) -> DomNode {
64/// dom! {
65/// <div>
66/// "..."
67/// </div>
68/// }
69/// }
70/// ```
71#[derive(Clone, PartialEq)]
72pub struct Router<T: Clone + ToString + From<String> + PartialEq + 'static> {
73 use_history_api: bool,
74 pub route: Computed<T>,
75}
76
77impl<T: Clone + ToString + From<String> + PartialEq + 'static> Router<T> {
78 /// Create new Router which sets route value upon hash change in browser bar.
79 /// If callback is provided then it is fired instead.
80 pub fn new_hash_router() -> Router<T> {
81 Self::new(false)
82 }
83
84 /// Create new Router which sets route value upon url change (works with browser history)
85 pub fn new_history_router() -> Router<T> {
86 Self::new(true)
87 }
88
89 fn new(use_history_api: bool) -> Self {
90 let driver = get_driver();
91
92 let init_value = match use_history_api {
93 false => T::from(driver.inner.api.get_hash_location()),
94 true => T::from(driver.inner.api.get_history_location()),
95 };
96
97 let route = Value::with_connect(init_value, move |value| {
98 let value = value.clone();
99 let callback = move |url: String| {
100 value.set(T::from(url));
101 };
102
103 match use_history_api {
104 false => driver.inner.api.on_hash_change(callback),
105 true => driver.inner.api.on_history_change(callback),
106 }
107 });
108
109 Self {
110 use_history_api,
111 route,
112 }
113 }
114
115 pub fn set(&self, route: T) {
116 let driver = get_driver();
117 match self.use_history_api {
118 false => driver.inner.api.push_hash_location(&route.to_string()),
119 true => driver.inner.api.push_history_location(&route.to_string()),
120 };
121 }
122
123 fn change(&self, change_fn: impl FnOnce(&mut T)) {
124 get_driver().inner.dependencies.transaction(|ctx| {
125 let mut value = self.get(ctx);
126 change_fn(&mut value);
127 self.set(value);
128 });
129 }
130}
131
132impl<T: Clone + PartialEq + ToString + From<String>> Reactive<T> for Router<T> {
133 fn set(&self, value: T) {
134 Router::set(self, value)
135 }
136
137 fn get(&self, context: &crate::Context) -> T {
138 self.route.get(context)
139 }
140
141 fn change(&self, change_fn: impl FnOnce(&mut T)) {
142 Router::change(self, change_fn)
143 }
144}
145
146impl<T: Clone + PartialEq + ToString + From<String>> ToComputed<T> for Router<T> {
147 fn to_computed(&self) -> Computed<T> {
148 self.route.to_computed()
149 }
150}
151
152impl<T: Clone + PartialEq + ToString + From<String>> EmbedDom for Router<T> {
153 fn embed(self) -> DomNode {
154 self.route.embed()
155 }
156}
157
158impl<T: Clone + PartialEq + ToString + From<String>> Default for Router<T> {
159 fn default() -> Self {
160 Router::new(true)
161 }
162}