yew_interop/
script.rs

1use js_sys::JsString;
2use std::borrow::Cow;
3use std::rc::Rc;
4use wasm_bindgen::JsCast;
5use wasm_bindgen_futures::JsFuture;
6use web_sys::{Request, RequestInit, RequestMode, Response};
7
8use yew::{create_portal, function_component, html, Properties, Reducible};
9
10#[doc(hidden)]
11pub use wasm_bindgen_futures;
12
13#[doc(hidden)]
14pub async fn fetch_script(url: Cow<'static, str>) -> String {
15    let mut opts = RequestInit::new();
16    opts.method("GET");
17    opts.mode(RequestMode::Cors);
18
19    let request = Request::new_with_str_and_init(&url, &opts).unwrap();
20
21    let window = web_sys::window().unwrap();
22    let resp_value = JsFuture::from(window.fetch_with_request(&request))
23        .await
24        .unwrap();
25
26    // `resp_value` is a `Response` object.
27    assert!(resp_value.is_instance_of::<Response>());
28    let resp: Response = resp_value.dyn_into().unwrap();
29
30    let text: JsString = JsFuture::from(resp.text().unwrap())
31        .await
32        .unwrap()
33        .unchecked_into();
34    text.into()
35}
36
37#[doc(hidden)]
38pub enum ScriptLoader {
39    NotRequested,
40    Started,
41    Completed(Script),
42}
43
44impl Default for ScriptLoader {
45    fn default() -> Self {
46        Self::NotRequested
47    }
48}
49
50#[doc(hidden)]
51pub enum ScriptLoaderAction {
52    Start,
53    Finish(Rc<String>),
54}
55
56impl Reducible for ScriptLoader {
57    type Action = ScriptLoaderAction;
58
59    fn reduce(self: Rc<Self>, action: Self::Action) -> Rc<Self> {
60        match action {
61            ScriptLoaderAction::Start => match *self {
62                ScriptLoader::NotRequested => Rc::new(Self::Started),
63                ScriptLoader::Started => unreachable!(
64                    "script already started to load, can't receive another start request"
65                ),
66                ScriptLoader::Completed(_) => unreachable!(
67                    "script load already completed, can't receive another start request"
68                ),
69            },
70            ScriptLoaderAction::Finish(content) => match *self {
71                ScriptLoader::NotRequested => {
72                    unreachable!("script finished load, request should have started")
73                }
74                ScriptLoader::Started => Rc::new(Self::Completed(Script { content })),
75                ScriptLoader::Completed(_) => {
76                    unreachable!("script completed load, can't receive another complete request")
77                }
78            },
79        }
80    }
81}
82
83/// A JavaScript cached globally
84#[derive(Clone, PartialEq)]
85pub struct Script {
86    content: Rc<String>,
87}
88
89#[cfg_attr(documenting, doc(cfg(feature = "script")))]
90#[derive(Properties, PartialEq)]
91pub struct ScriptEffectProps {
92    pub script: Script,
93}
94
95#[cfg_attr(documenting, doc(cfg(feature = "script")))]
96/// This component runs javascript __on render__.
97/// Note this is different from the [`yew::use_effect`] hook, which runs __after rendering__.
98#[function_component(ScriptEffect)]
99pub fn script_effect(props: &ScriptEffectProps) -> Html {
100    create_portal(
101        html! {
102            <script type="text/javascript">
103                {props.script.content.clone()}
104            </script>
105        },
106        web_sys::window()
107            .unwrap()
108            .document()
109            .unwrap()
110            .head()
111            .unwrap()
112            .into(),
113    )
114}