datastar/
execute_script.rs

1//! [`ExecuteScript`] executes JavaScript in the browser.
2//!
3//! This is sugar for `PatchElements` specifically for executing scripts.
4
5use {
6    crate::{
7        DatastarEvent,
8        consts::{self, ElementPatchMode},
9    },
10    core::time::Duration,
11};
12
13/// [`ExecuteScript`] executes JavaScript in the browser
14#[derive(Debug, Clone, PartialEq, Eq, Hash)]
15pub struct ExecuteScript {
16    /// `id` can be used by the backend to replay events.
17    /// This is part of the SSE spec and is used to tell the browser how to handle the event.
18    /// For more details see <https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#id>
19    pub id: Option<String>,
20    /// `retry` is part of the SSE spec and is used to tell the browser how long to wait before reconnecting if the connection is lost.
21    /// For more details see <https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#retry>
22    pub retry: Duration,
23    /// `script` is a string that represents the JavaScript to be executed by the browser.
24    pub script: String,
25    /// Whether to remove the script after execution, if not provided the Datastar client side will default to `true`.
26    pub auto_remove: Option<bool>,
27    /// A list of attributes to add to the script element, if not provided the Datastar client side will default to `type="module"`.
28    /// Each item in the array ***must*** be properly formatted.
29    pub attributes: Vec<String>,
30}
31
32impl ExecuteScript {
33    /// Creates a new [`ExecuteScript`] event with the given script.
34    pub fn new(script: impl Into<String>) -> Self {
35        Self {
36            id: None,
37            retry: Duration::from_millis(consts::DEFAULT_SSE_RETRY_DURATION),
38            script: script.into(),
39            auto_remove: Default::default(),
40            attributes: Default::default(),
41        }
42    }
43
44    /// Sets the `id` of the [`ExecuteScript`] event.
45    pub fn id(mut self, id: impl Into<String>) -> Self {
46        self.id = Some(id.into());
47        self
48    }
49
50    /// Sets the `retry` of the [`ExecuteScript`] event.
51    pub fn retry(mut self, retry: Duration) -> Self {
52        self.retry = retry;
53        self
54    }
55
56    /// Sets the `script` of the [`ExecuteScript`] event.
57    pub fn auto_remove(mut self, auto_remove: bool) -> Self {
58        self.auto_remove = Some(auto_remove);
59        self
60    }
61
62    /// Sets the `attribute` of the [`ExecuteScript`] event.
63    pub fn attributes(mut self, attributes: impl IntoIterator<Item = impl Into<String>>) -> Self {
64        self.attributes = attributes.into_iter().map(Into::into).collect();
65        self
66    }
67
68    /// Converts this [`ExecuteScript`] into a [`DatastarEvent`].
69    #[inline]
70    pub fn into_datastar_event(mut self) -> DatastarEvent {
71        let id = self.id.take();
72        self.convert_to_datastar_event_inner(id)
73    }
74
75    /// Copy this [`ExecuteScript`] as a [`DatastarEvent`].
76    #[inline]
77    pub fn as_datastar_event(&self) -> DatastarEvent {
78        self.convert_to_datastar_event_inner(self.id.clone())
79    }
80
81    fn convert_to_datastar_event_inner(&self, id: Option<String>) -> DatastarEvent {
82        let mut data: Vec<String> = Vec::new();
83
84        data.push(format!("{} body", consts::SELECTOR_DATALINE_LITERAL));
85
86        data.push(format!(
87            "{} {}",
88            consts::MODE_DATALINE_LITERAL,
89            ElementPatchMode::Append.as_str(),
90        ));
91
92        let mut s = format!("{} <script", consts::ELEMENTS_DATALINE_LITERAL);
93
94        if self.auto_remove.unwrap_or(true) {
95            s.push_str(r##" data-effect="el.remove()""##);
96        }
97
98        for attribute in &self.attributes {
99            s.push(' ');
100            s.push_str(attribute.as_str());
101        }
102
103        let mut scripts_lines = self.script.lines();
104
105        s.push('>');
106        s.push_str(scripts_lines.next().unwrap_or_default());
107        data.push(s);
108
109        for line in scripts_lines {
110            data.push(format!("{} {}", consts::ELEMENTS_DATALINE_LITERAL, line));
111        }
112
113        data.last_mut().unwrap().push_str("</script>");
114
115        DatastarEvent {
116            event: consts::EventType::PatchElements,
117            id,
118            retry: self.retry,
119            data,
120        }
121    }
122}
123
124impl From<&ExecuteScript> for DatastarEvent {
125    #[inline]
126    fn from(val: &ExecuteScript) -> Self {
127        val.as_datastar_event()
128    }
129}
130
131impl From<ExecuteScript> for DatastarEvent {
132    #[inline]
133    fn from(val: ExecuteScript) -> Self {
134        val.into_datastar_event()
135    }
136}