yew_simple/
router.rs

1
2pub use url::Url;
3
4use yew::html::{Component, Env};
5use stdweb::Value;
6use stdweb::web::{self, IEventTarget};
7
8/// TODO:
9/// A handle which helps to cancel the router. Uses removeEventListener
10pub struct RouterTask<CTX: 'static, COMP: Component<CTX>> {
11    _handle1: web::EventListenerHandle,
12    handle2: Value,
13    history: web::History,
14    route_fn: &'static Fn(RouteInfo) -> COMP::Message,
15    window: web::Window,
16}
17
18/// State of the current route.
19#[derive(Debug, Clone)]
20pub struct RouteInfo {
21    /// Url
22    pub url: Url,
23    /// History state
24    pub state: Value,
25}
26
27impl RouteInfo {
28    /// Initialize the route state using the current window.
29    fn new(url: Url, state: Value) -> RouteInfo {
30        RouteInfo {
31            url: url,
32            state: state,
33        }
34    }
35}
36
37fn current_url(window: &web::Window) -> Url {
38    // TODO: better error messages around unwraps
39    let location = expect!(window.location(), "could not get location");
40    let href = expect!(location.href(), "could not get href");
41    expect!(Url::parse(&href), "location.href did not parse")
42}
43
44impl<'a, CTX: 'a, COMP: Component<CTX>> RouterTask<CTX, COMP> {
45    /// Start the Routing Task in the environment.
46    ///
47    /// Ownership of this Task should typically be put in the `Model`.
48    ///
49    /// Routing will stop if this Task is dropped.
50    pub fn new(
51        env: &mut Env<'a, CTX, COMP>,
52        route_fn: &'static Fn(RouteInfo) -> COMP::Message,
53    ) -> Self
54    {
55        let window = web::window();
56        let callback = env.send_back(route_fn);
57
58        let callback1 = callback.clone();
59        let callback2 = callback;
60
61        let cl_window = window.clone();
62        let handle1 = window
63            .add_event_listener(move |event: web::event::PopStateEvent| {
64                callback1.emit(RouteInfo::new(current_url(&cl_window), event.state()));
65            });
66
67        // TODO: koute/stdweb/issues/171
68        // self.handle2 = Some(self.window
69        //     .add_event_listener(move |_event: web::event::ResourceLoadEvent| {
70        //         callback2.emit(RouteInfo::new(Value::Null));
71        //     }));
72
73        let cl_window = window.clone();
74        let rs_handle = move || {
75            callback2.emit(RouteInfo::new(current_url(&cl_window), Value::Null));
76        };
77
78        let handle2 = js!{
79            var callback = @{rs_handle};
80            function listener() {
81                callback();
82            }
83            window.addEventListener("load", listener);
84            return {
85                callback: callback,
86                listener: listener
87            };
88        };
89
90        RouterTask {
91            _handle1: handle1,
92            handle2: handle2,
93            route_fn: route_fn,
94            history: window.history(),
95            window: window,
96        }
97    }
98
99    /// Retrieve the current url of the application.
100    pub fn current_url(&self) -> Url {
101        current_url(&self.window)
102    }
103
104    /// Set the state of the history, including the url.
105    ///
106    /// This will _not_ trigger the router to change. If a state change is required
107    /// it is the user's job to propogate the `Message`.
108    pub fn push_state(&self, state: Value, title: &str, url: Url) -> COMP::Message {
109        self.history.push_state(state.clone(), title, Some(url.as_str()));
110        let info = RouteInfo {
111            url: url,
112            state: state,
113        };
114        (*self.route_fn)(info)
115    }
116
117    /// Push a hash based on the current url.
118    pub fn push_hash(&self, hash: Option<&str>) -> COMP::Message {
119        let mut url = current_url(&self.window);
120        url.set_fragment(hash);
121        self.push_state(Value::Null, "", url)
122    }
123}
124
125impl<CTX, COMP: Component<CTX>> Drop for RouterTask<CTX, COMP> {
126    fn drop(&mut self) {
127        js! { @(no_return)
128            var handle = @{&self.handle2};
129            window.removeEventListener("load", handle.listener);
130            handle.callback.drop();
131        }
132    }
133}
134