yew_router_nested/agent/
mod.rs

1//! Routing agent.
2//!
3//! It wraps a route service and allows calls to be sent to it to update every subscriber,
4//! or just the element that made the request.
5use crate::service::RouteService;
6
7use yew_agent::{Agent, AgentLink, Context, HandlerId};
8
9use std::collections::HashSet;
10
11use serde::{Deserialize, Serialize};
12use std::fmt::{Debug, Error as FmtError, Formatter};
13
14use crate::route::{Route, RouteState};
15use log::trace;
16
17mod bridge;
18pub use bridge::RouteAgentBridge;
19
20mod dispatcher;
21pub use dispatcher::RouteAgentDispatcher;
22
23/// Internal Message used for the RouteAgent.
24#[derive(Debug)]
25pub enum Msg<STATE> {
26    /// Message for when the route is changed.
27    BrowserNavigationRouteChanged(Route<STATE>), // TODO make this a route?
28}
29
30/// Input message type for interacting with the `RouteAgent'.
31#[derive(Serialize, Deserialize, Debug)]
32pub enum RouteRequest<T = ()> {
33    /// Replaces the most recent Route with a new one and alerts connected components to the route
34    /// change.
35    ReplaceRoute(Route<T>),
36    /// Replaces the most recent Route with a new one, but does not alert connected components to
37    /// the route change.
38    ReplaceRouteNoBroadcast(Route<T>),
39    /// Changes the route using a Route struct and alerts connected components to the route change.
40    ChangeRoute(Route<T>),
41    /// Changes the route using a Route struct, but does not alert connected components to the
42    /// route change.
43    ChangeRouteNoBroadcast(Route<T>),
44    /// Gets the current route.
45    GetCurrentRoute,
46}
47
48/// The RouteAgent holds on to the RouteService singleton and mediates access to it.
49///
50/// It serves as a means to propagate messages to components interested in the state of the current
51/// route.
52///
53/// # Warning
54/// All routing-related components/agents/services should use the same type parameter across your application.
55///
56/// If you use multiple agents with different types, then the Agents won't be able to communicate to
57/// each other and associated components may not work as intended.
58pub struct RouteAgent<STATE = ()>
59where
60    STATE: RouteState,
61{
62    // In order to have the AgentLink<Self> below, apparently T must be constrained like this.
63    // Unfortunately, this means that everything related to an agent requires this constraint.
64    link: AgentLink<RouteAgent<STATE>>,
65    /// The service through which communication with the browser happens.
66    route_service: RouteService<STATE>,
67    /// A list of all entities connected to the router.
68    /// When a route changes, either initiated by the browser or by the app,
69    /// the route change will be broadcast to all listening entities.
70    subscribers: HashSet<HandlerId>,
71}
72
73impl<STATE: RouteState> Debug for RouteAgent<STATE> {
74    fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
75        f.debug_struct("RouteAgent")
76            .field("link", &"-")
77            .field("route_service", &self.route_service)
78            .field("subscribers", &self.subscribers.len())
79            .finish()
80    }
81}
82
83impl<STATE> Agent for RouteAgent<STATE>
84where
85    STATE: RouteState,
86{
87    type Input = RouteRequest<STATE>;
88    type Message = Msg<STATE>;
89    type Output = Route<STATE>;
90    type Reach = Context<Self>;
91
92    fn create(link: AgentLink<RouteAgent<STATE>>) -> Self {
93        let callback = link.callback(Msg::BrowserNavigationRouteChanged);
94        let mut route_service = RouteService::new();
95        route_service.register_callback(callback);
96
97        RouteAgent {
98            link,
99            route_service,
100            subscribers: HashSet::new(),
101        }
102    }
103
104    fn update(&mut self, msg: Self::Message) {
105        match msg {
106            Msg::BrowserNavigationRouteChanged(route) => {
107                trace!("Browser navigated");
108                for sub in &self.subscribers {
109                    self.link.respond(*sub, route.clone());
110                }
111            }
112        }
113    }
114
115    fn connected(&mut self, id: HandlerId) {
116        if id.is_respondable() {
117            self.subscribers.insert(id);
118        }
119    }
120
121    fn handle_input(&mut self, msg: Self::Input, who: HandlerId) {
122        match msg {
123            RouteRequest::ReplaceRoute(route) => {
124                let route_string: String = route.to_string();
125                self.route_service.replace_route(&route_string, route.state);
126                let route = self.route_service.get_route();
127                for sub in &self.subscribers {
128                    self.link.respond(*sub, route.clone());
129                }
130            }
131            RouteRequest::ReplaceRouteNoBroadcast(route) => {
132                let route_string: String = route.to_string();
133                self.route_service.replace_route(&route_string, route.state);
134            }
135            RouteRequest::ChangeRoute(route) => {
136                let route_string: String = route.to_string();
137                // set the route
138                self.route_service.set_route(&route_string, route.state);
139                // get the new route.
140                let route = self.route_service.get_route();
141                // broadcast it to all listening components
142                for sub in &self.subscribers {
143                    self.link.respond(*sub, route.clone());
144                }
145            }
146            RouteRequest::ChangeRouteNoBroadcast(route) => {
147                let route_string: String = route.to_string();
148                self.route_service.set_route(&route_string, route.state);
149            }
150            RouteRequest::GetCurrentRoute => {
151                let route = self.route_service.get_route();
152                self.link.respond(who, route);
153            }
154        }
155    }
156
157    fn disconnected(&mut self, id: HandlerId) {
158        if id.is_respondable() {
159            self.subscribers.remove(&id);
160        }
161    }
162}