Skip to content

Commit

Permalink
feat: add back a global instance to listen to events
Browse files Browse the repository at this point in the history
  • Loading branch information
ctron committed Dec 19, 2023
1 parent c9f353b commit d73b1da
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 9 deletions.
68 changes: 68 additions & 0 deletions src/history.rs
@@ -0,0 +1,68 @@
use std::cell::RefCell;
use std::rc::{Rc, Weak};
use wasm_bindgen::JsValue;

thread_local! {
static INSTANCE: RefCell<InnerHistory> = RefCell::new(InnerHistory {
listeners: vec![],
});
}

/// Handle to a history listener.
///
/// Disposes the listener when dropped.
pub struct HistoryListener(Rc<CallbackFn>);

pub struct History;

impl History {
/// Subscribe to events of the browser history.
///
/// This will receive events when popping items from the stack, as well as changes triggered by calling
/// [`History::push_state`].
#[must_use = "The listener will only be active for as long as the returned instance exists."]
pub fn listener<F: Fn() + 'static>(f: F) -> HistoryListener {
INSTANCE.with(|instance| instance.borrow_mut().listener(f))
}

/// Push a new state to the history stack.
///
/// This will send out events and update the browser's history. Ultimately calling
/// [`web_sys::History::push_state_with_url`].
pub fn push_state(state: JsValue, url: &str) -> Result<(), JsValue> {
INSTANCE.with(|instance| instance.borrow_mut().push_state(state, url))
}
}

type CallbackFn = dyn Fn() + 'static;

struct InnerHistory {
listeners: Vec<Weak<CallbackFn>>,
}

impl InnerHistory {
fn push_state(&mut self, state: JsValue, url: &str) -> Result<(), JsValue> {
let result = gloo_utils::history().push_state_with_url(&state, "", Some(url));
self.notify();
result
}

fn listener<F: Fn() + 'static>(&mut self, f: F) -> HistoryListener {
let callback = Rc::new(f) as Rc<CallbackFn>;
self.listeners.push(Rc::downgrade(&callback));
HistoryListener(callback)
}

fn notify(&mut self) {
let mut new = vec![];

for listener in &mut self.listeners {
if let Some(cb) = listener.upgrade() {
(*cb)();
new.push(listener.clone());
}
}

self.listeners = new;
}
}
10 changes: 10 additions & 0 deletions src/lib.rs
Expand Up @@ -223,6 +223,15 @@
//! }
//! ```
//!
//! ## Interoperability
//!
//! This implementation makes use of the browser's history API. While it is possible to receive the current state
//! from the History API, and trigger "back" operations, using [`web_sys::History::push_state`] directly will not
//! trigger an event and thus no render a different page.
//!
//! As `gloo_history` creates its internal type and state system, it is not interoperable with this crate. It still is
//! possible to use [`gloo_utils::history`] though, which is just a shortcut of getting [`web_sys::History`].
//!
//! ## More examples
//!
//! See the `examples` folder.
Expand All @@ -231,6 +240,7 @@ pub mod components;
pub mod target;

mod base;
mod history;
mod router;
mod scope;
mod state;
Expand Down
14 changes: 5 additions & 9 deletions src/router.rs
@@ -1,8 +1,8 @@
use crate::base;
use crate::history::{History, HistoryListener};
use crate::scope::{NavigationTarget, ScopeContext};
use crate::state::State;
use crate::target::Target;
use gloo_events::EventListener;
use gloo_utils::window;
use std::borrow::Cow;
use std::fmt::Debug;
Expand Down Expand Up @@ -159,7 +159,7 @@ pub enum Msg<T: Target> {

/// Top-level router component.
pub struct Router<T: Target> {
_listener: EventListener,
_listener: HistoryListener,
target: Option<T>,

scope: Rc<ScopeContext<T>>,
Expand Down Expand Up @@ -189,8 +189,8 @@ where
let target = Self::parse_location(&base, window().location())
.or_else(|| ctx.props().default.clone());

let listener = EventListener::new(&window(), "popstate", move |_| {
cb.emit(gloo_utils::window().location());
let listener = History::listener(move || {
cb.emit(window().location());
});

let (scope, router) = Self::build_context(base.clone(), &target, ctx);
Expand All @@ -205,8 +205,6 @@ where
}

fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
// log::debug!("update: {msg:?}");

match msg {
Msg::RouteChanged => {
let target = Self::parse_location(&self.base, window().location())
Expand All @@ -219,9 +217,7 @@ where
}
Msg::ChangeTarget(target) => {
let route = Self::render_target(&self.base, &target.target);
// log::debug!("Push URL: {route}");
// log::debug!("Push State: {:?}", target.state);
let _ = gloo_utils::history().push_state_with_url(&target.state, "", Some(&route));
let _ = History::push_state(target.state, &route);
ctx.link().send_message(Msg::RouteChanged)
}
}
Expand Down

0 comments on commit d73b1da

Please sign in to comment.