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}