rust-dominator/src/routing.rs

146 lines
3.4 KiB
Rust
Raw Normal View History

use wasm_bindgen::{JsValue, UnwrapThrowExt};
use web_sys::{window, Url, EventTarget, HtmlElement};
2018-11-01 07:40:31 -04:00
use futures_signals::signal::{Mutable, Signal};
use gloo::events::EventListener;
use crate::dom::{Dom, DomBuilder};
use crate::events;
2018-11-01 07:40:31 -04:00
/*pub struct State<A> {
value: Mutable<Option<A>>,
callback: Value,
}
impl<A> State<A> {
pub fn new() -> Self {
// TODO replace with stdweb function
let value = Mutable::new(js!( return history.state; ).try_into().unwrap_throw());
2018-11-01 07:40:31 -04:00
let callback = |state: Option<A>| {
value.set(state);
};
Self {
value,
callback: js!(
var callback = @{callback};
addEventListener("popstate", function (e) {
callback(e.state);
}, true);
return callback;
),
}
}
pub fn set(&self, value: A) {
window().history().replace_state(value, "", None).unwrap_throw();
2018-11-01 07:40:31 -04:00
self.value.set(value);
}
}*/
2019-06-20 11:52:52 -04:00
fn current_url_string() -> Result<String, JsValue> {
Ok(window().unwrap_throw().location().href()?)
2018-11-01 07:40:31 -04:00
}
// TODO inline ?
2019-06-20 11:52:52 -04:00
fn change_url(mutable: &Mutable<Url>) -> Result<(), JsValue> {
2018-11-01 07:40:31 -04:00
let mut lock = mutable.lock_mut();
2019-06-20 11:52:52 -04:00
let new_url = current_url_string()?;
2018-11-01 07:40:31 -04:00
// TODO test that this doesn't notify if the URLs are the same
// TODO helper method for this
// TODO can this be made more efficient ?
if lock.href() != new_url {
2019-06-20 11:52:52 -04:00
*lock = Url::new(&new_url)?;
2018-11-01 07:40:31 -04:00
}
2019-06-20 11:52:52 -04:00
Ok(())
2018-11-01 07:40:31 -04:00
}
struct CurrentUrl {
value: Mutable<Url>,
_listener: EventListener,
2018-11-01 07:40:31 -04:00
}
impl CurrentUrl {
2019-06-20 11:52:52 -04:00
fn new() -> Result<Self, JsValue> {
// TODO can this be made more efficient ?
2019-06-20 11:52:52 -04:00
let value = Mutable::new(Url::new(&current_url_string()?)?);
2018-11-01 07:40:31 -04:00
2019-06-20 11:52:52 -04:00
Ok(Self {
_listener: EventListener::new(&window().unwrap_throw(), "popstate", {
let value = value.clone();
move |_| {
2019-06-20 11:52:52 -04:00
change_url(&value).unwrap_throw();
2018-11-01 07:40:31 -04:00
}
}),
value,
2019-06-20 11:52:52 -04:00
})
2018-11-01 07:40:31 -04:00
}
}
// TODO somehow share this safely between threads ?
thread_local! {
2019-06-20 11:52:52 -04:00
static URL: CurrentUrl = CurrentUrl::new().unwrap_throw();
2018-11-01 07:40:31 -04:00
}
#[inline]
pub fn current_url() -> Url {
URL.with(|url| url.value.get_cloned())
}
2018-11-01 07:40:31 -04:00
#[inline]
pub fn url() -> impl Signal<Item = Url> {
URL.with(|url| url.value.signal_cloned())
2018-11-01 07:40:31 -04:00
}
// TODO if URL hasn't been created yet, don't create it
#[inline]
pub fn go_to_url(new_url: &str) {
window()
.unwrap_throw()
.history()
.unwrap_throw()
2018-11-01 07:40:31 -04:00
// TODO is this the best state object to use ?
.push_state_with_url(&JsValue::NULL, "", Some(new_url))
.unwrap_throw();
2018-11-01 07:40:31 -04:00
URL.with(|url| {
2019-06-20 11:52:52 -04:00
change_url(&url.value).unwrap_throw();
});
2018-11-01 07:40:31 -04:00
}
// TODO somehow use &str rather than String, maybe Cow ?
2018-11-01 07:40:31 -04:00
#[inline]
pub fn on_click_go_to_url<A>(new_url: String) -> impl FnOnce(DomBuilder<A>) -> DomBuilder<A> where A: AsRef<EventTarget> {
2018-11-01 07:40:31 -04:00
#[inline]
move |dom| {
dom.event_preventable(move |e: events::Click| {
2018-11-01 07:40:31 -04:00
e.prevent_default();
go_to_url(&new_url);
})
}
}
// TODO better type than HtmlElement
2018-11-01 07:40:31 -04:00
#[inline]
pub fn link<F>(url: &str, f: F) -> Dom where F: FnOnce(DomBuilder<HtmlElement>) -> DomBuilder<HtmlElement> {
html!("a", {
.attribute("href", url)
// TODO somehow avoid this allocation
.apply(on_click_go_to_url(url.to_string()))
.apply(f)
})
}