use super::super::{
util::{self, ClosureNew},
Url,
};
use crate::app::{subs, Notification};
use std::rc::Rc;
use wasm_bindgen::{closure::Closure, JsCast, JsValue};
pub fn push_route<U: Into<Url>>(url: U) -> Url {
let url = url.into();
let data =
JsValue::from_str(&serde_json::to_string(&url).expect("Problem serializing route data"));
util::history()
.push_state_with_url(&data, "", Some(&url.to_string()))
.expect("Problem pushing state");
url
}
pub fn setup_popstate_listener<Ms>(
update: impl Fn(Ms) + 'static,
updated_listener: impl Fn(Closure<dyn FnMut(web_sys::Event)>) + 'static,
notify: impl Fn(Notification) + 'static,
routes: Option<fn(Url) -> Option<Ms>>,
base_path: Rc<Vec<String>>,
) where
Ms: 'static,
{
let closure = Closure::new(move |ev: web_sys::Event| {
let ev = ev
.dyn_ref::<web_sys::PopStateEvent>()
.expect("Problem casting as Popstate event");
let url = match ev.state().as_string() {
Some(state_str) => {
serde_json::from_str(&state_str).expect("Problem deserializing popstate state")
}
None => Url::current(),
};
notify(Notification::new(subs::UrlChanged(
url.clone().skip_base_path(&base_path),
)));
if let Some(routes) = routes {
if let Some(routing_msg) = routes(url) {
update(routing_msg);
}
}
});
(util::window().as_ref() as &web_sys::EventTarget)
.add_event_listener_with_callback("popstate", closure.as_ref().unchecked_ref())
.expect("Problem adding popstate listener");
updated_listener(closure);
}
pub fn setup_hashchange_listener<Ms>(
update: impl Fn(Ms) + 'static,
updated_listener: impl Fn(Closure<dyn FnMut(web_sys::Event)>) + 'static,
notify: impl Fn(Notification) + 'static,
routes: Option<fn(Url) -> Option<Ms>>,
base_path: Rc<Vec<String>>,
) where
Ms: 'static,
{
let closure = Closure::new(move |ev: web_sys::Event| {
let ev = ev
.dyn_ref::<web_sys::HashChangeEvent>()
.expect("Problem casting as hashchange event");
let url: Url = ev
.new_url()
.parse()
.expect("cast hashchange event url to `Url`");
notify(Notification::new(subs::UrlChanged(
url.clone().skip_base_path(&base_path),
)));
if let Some(routes) = routes {
if let Some(routing_msg) = routes(url) {
update(routing_msg);
}
}
});
(util::window().as_ref() as &web_sys::EventTarget)
.add_event_listener_with_callback("hashchange", closure.as_ref().unchecked_ref())
.expect("Problem adding hashchange listener");
updated_listener(closure);
}
#[allow(clippy::needless_pass_by_value)]
pub(crate) fn url_request_handler(
sub_data: subs::UrlRequested,
base_path: Rc<Vec<String>>,
notify: impl Fn(Notification) + 'static,
) {
let subs::UrlRequested(url, request) = sub_data;
match request.status() {
subs::url_requested::UrlRequestStatus::Unhandled => {
push_route(url.clone());
if let Some(event) = request.event.borrow_mut().take() {
event.prevent_default(); }
notify(Notification::new(subs::UrlChanged(
url.skip_base_path(&base_path),
)));
}
subs::url_requested::UrlRequestStatus::Handled(prevent_default) => {
if prevent_default {
if let Some(event) = request.event.borrow_mut().take() {
event.prevent_default(); }
}
}
}
}
#[allow(clippy::option_map_unit_fn)]
pub fn setup_link_listener<Ms>(
update: impl Fn(Ms) + 'static,
notify: impl Fn(Notification) + 'static,
routes: Option<fn(Url) -> Option<Ms>>,
) where
Ms: 'static,
{
let closure = Closure::new(move |event: web_sys::Event| {
event.target()
.and_then(|et| et.dyn_into::<web_sys::Element>().ok())
.and_then(|el| el.closest("[href]").ok())
.flatten()
.and_then(|href_el| match href_el.tag_name().to_lowercase().as_str() {
"base" | "link" | "use" => None,
_ => Some(href_el)
})
.and_then(|href_el| href_el.get_attribute("href"))
.and_then(|href| {
if href.is_empty() || href.starts_with('/') {
Some(href)
} else {
None
}
})
.map(|href| {
if href.is_empty() {
event.prevent_default(); } else {
let url: Url = href.parse().expect("cast link href to `Url`");
notify(Notification::new(subs::UrlRequested(
url.clone(),
subs::url_requested::UrlRequest::new(
subs::url_requested::UrlRequestStatus::default(),
Some(event.clone()),
),
)));
if let Some(routes) = routes {
if let Some(redirect_msg) = routes(url.clone()) {
push_route(url);
event.prevent_default(); update(redirect_msg);
}
}
}
});
});
(util::document().as_ref() as &web_sys::EventTarget)
.add_event_listener_with_callback("click", closure.as_ref().unchecked_ref())
.expect("Problem setting up link interceptor");
closure.forget(); }