1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
use std::borrow::Cow;

use web_sys::{EventTarget, HtmlElement};
use once_cell::sync::Lazy;
use futures_signals::signal::{Mutable, ReadOnlyMutable};

use crate::bindings;
use crate::bindings::WINDOW;
use crate::dom::{Dom, DomBuilder, EventOptions};
use crate::utils::EventListener;
use crate::events;


// TODO inline ?
fn change_url(mutable: &Mutable<String>) {
    let mut lock = mutable.lock_mut();

    let new_url = String::from(bindings::current_url());

    // TODO helper method for this
    // TODO can this be made more efficient ?
    if *lock != new_url {
        *lock = new_url;
    }
}


struct CurrentUrl {
    value: Mutable<String>,
}

impl CurrentUrl {
    fn new() -> Self {
        // TODO can this be made more efficient ?
        let value = Mutable::new(String::from(bindings::current_url()));

        // TODO clean this up somehow ?
        let _ = WINDOW.with(|window| {
            EventListener::new(window, "popstate", &EventOptions::default(), {
                let value = value.clone();
                move |_| {
                    change_url(&value);
                }
            })
        });

        Self {
            value,
        }
    }
}


static URL: Lazy<CurrentUrl> = Lazy::new(|| CurrentUrl::new());


#[inline]
pub fn url() -> ReadOnlyMutable<String> {
    URL.value.read_only()
}


// TODO if URL hasn't been created yet, don't create it
#[inline]
#[track_caller]
pub fn go_to_url(new_url: &str) {
    // TODO intern ?
    bindings::go_to_url(new_url);

    change_url(&URL.value);
}


#[deprecated(since = "0.5.1", note = "Use the on_click_go_to_url macro instead")]
#[inline]
pub fn on_click_go_to_url<A, B>(new_url: A) -> impl FnOnce(DomBuilder<B>) -> DomBuilder<B>
    where A: Into<Cow<'static, str>>,
          B: AsRef<EventTarget> {
    let new_url = new_url.into();

    #[inline]
    move |dom| {
        dom.event_with_options(&EventOptions::preventable(), move |e: events::Click| {
            e.prevent_default();
            go_to_url(&new_url);
        })
    }
}


// TODO better type than HtmlElement
// TODO maybe make this a macro ?
#[deprecated(since = "0.5.1", note = "Use the link macro instead")]
#[allow(deprecated)]
#[inline]
pub fn link<A, F>(url: A, f: F) -> Dom
    where A: Into<Cow<'static, str>>,
          F: FnOnce(DomBuilder<HtmlElement>) -> DomBuilder<HtmlElement> {
    let url = url.into();

    html!("a", {
        .attr("href", &url)
        .apply(on_click_go_to_url(url))
        .apply(f)
    })
}


// TODO test this
/// Changes an `<a>` element to work with routing.
///
/// Normally when the user clicks on an `<a>` element the browser will handle the URL
/// routing.
///
/// But if you are creating a [Single Page Application](https://developer.mozilla.org/en-US/docs/Glossary/SPA) (SPA)
/// then you want your app to always be in control of routing.
///
/// The `on_click_go_to_url!` macro disables the browser routing for the `<a>`, so your app remains in control of routing:
///
/// ```rust
/// html!("a", {
///     .on_click_go_to_url!("/my-url/foo")
/// })
/// ```
///
/// Also see the [`link!`](crate::link) macro.
#[macro_export]
macro_rules! on_click_go_to_url {
    ($this:ident, $url:expr) => {{
        let url = $url;

        $this.event_with_options(&$crate::EventOptions::preventable(), move |e: $crate::events::Click| {
            e.prevent_default();
            $crate::routing::go_to_url(&url);
        })
    }};
}


// TODO test this
/// Creates an `<a>` element which works with routing.
///
/// Normally when the user clicks on an `<a>` element the browser will handle the URL
/// routing.
///
/// But if you are creating a [Single Page Application](https://developer.mozilla.org/en-US/docs/Glossary/SPA) (SPA)
/// then you want your app to always be in control of routing.
///
/// The `link!` macro creates an `<a>` element which disables browser routing, so your app remains in control of routing:
///
/// ```rust
/// link!("/my-url/foo", {
///     .class(...)
///     .style(...)
/// })
/// ```
///
/// Also see the [`on_click_go_to_url!`] macro.
#[macro_export]
macro_rules! link {
    ($url:expr, { $($methods:tt)* }) => {{
        let url = $url;

        $crate::html!("a", {
            .attr("href", &url)
            .apply(move |dom| $crate::on_click_go_to_url!(dom, url))
            $($methods)*
        })
    }};
}