1use super::{
2 ElementTarget, InputTarget, JsValue, Listener, ListenerTarget, Scope, strings, text_value,
3};
4use crate::{Effect, effect};
5use std::{cell::RefCell, rc::Rc};
6use web_sys::{Event, Text};
7
8impl Scope {
9 pub(super) fn bind(
10 &mut self,
11 update: impl FnMut() -> Result<(), JsValue> + 'static,
12 ) -> Result<(), JsValue> {
13 self.bind_internal(update, false)
14 }
15
16 #[doc(hidden)]
19 pub fn bind_dom(
20 &mut self,
21 update: impl FnMut() -> Result<(), JsValue> + 'static,
22 ) -> Result<(), JsValue> {
23 self.bind_internal(update, self.hydrating)
24 }
25
26 fn bind_internal(
27 &mut self,
28 mut update: impl FnMut() -> Result<(), JsValue> + 'static,
29 deferred: bool,
30 ) -> Result<(), JsValue> {
31 let first_error = Rc::new(RefCell::new(None));
32 let captured = first_error.clone();
33 let mut first = true;
34 let owner = self.owner();
35 let callback = move || {
36 if owner.is_disposed() {
37 return;
38 }
39 if let Err(error) = update() {
40 if first {
41 *captured.borrow_mut() = Some(error);
42 } else {
43 web_sys::console::error_1(&error);
44 }
45 }
46 first = false;
47 };
48 let errors = first_error.clone();
49 let binding = self.install_effect(deferred, callback, move || {
50 errors.take().map_or(Ok(()), Err)
51 })?;
52 if let Some(error) = first_error.take() {
53 return Err(error);
54 }
55 self.effects.push(binding);
56 Ok(())
57 }
58
59 pub(super) fn bind_dom_infallible(
63 &mut self,
64 mut update: impl FnMut() + 'static,
65 ) -> Result<(), JsValue> {
66 let owner = self.owner();
67 let callback = move || {
68 if !owner.is_disposed() {
69 update();
70 }
71 };
72 let binding = self.install_effect(self.hydrating, callback, || Ok(()))?;
73 self.effects.push(binding);
74 Ok(())
75 }
76
77 fn install_effect(
80 &mut self,
81 deferred: bool,
82 callback: impl FnMut() + 'static,
83 initialized: impl FnOnce() -> Result<(), JsValue> + 'static,
84 ) -> Result<Effect, JsValue> {
85 if !deferred {
86 return Ok(effect(callback));
87 }
88 let binding = crate::reactive::prepared_effect(callback);
89 let initialize = binding.initializer();
90 self.before_commit(move || {
91 initialize();
92 initialized()
93 })?;
94 Ok(binding)
95 }
96
97 pub fn text<T: ToString>(
99 &mut self,
100 target: impl ElementTarget,
101 read: impl Fn() -> T + 'static,
102 ) -> Result<(), JsValue> {
103 let node = target.resolve(self)?;
104 self.bind_dom(move || {
105 let value = read().to_string();
106 if node.text_content().as_deref() != Some(value.as_str()) {
107 node.set_text_content(Some(&value));
108 }
109 Ok(())
110 })
111 }
112
113 pub fn text_node<T: ToString>(
115 &mut self,
116 text: &Text,
117 read: impl Fn() -> T + 'static,
118 ) -> Result<(), JsValue> {
119 self.text_node_string(text, move || read().to_string())
120 }
121
122 #[doc(hidden)]
123 pub fn text_node_string(
124 &mut self,
125 text: &Text,
126 read: impl Fn() -> String + 'static,
127 ) -> Result<(), JsValue> {
128 let text = text.clone();
129 self.bind_dom_infallible(move || strings::set_text_if_changed(&text, &read()))
130 }
131
132 #[doc(hidden)]
133 pub fn text_node_value(
134 &mut self,
135 text: &Text,
136 read: impl Fn() -> text_value::Output + 'static,
137 ) -> Result<(), JsValue> {
138 let text = text.clone();
139 self.bind_dom_infallible(move || match read() {
140 text_value::Output::String(value) => strings::set_text_if_changed(&text, &value),
141 text_value::Output::Integer(value) => {
142 strings::set_integer_text_if_changed(&text, value)
143 }
144 })
145 }
146
147 pub fn attr(
149 &mut self,
150 target: impl ElementTarget,
151 name: &str,
152 read: impl Fn() -> Option<String> + 'static,
153 ) -> Result<(), JsValue> {
154 let node = target.resolve(self)?;
155 let name = name.to_owned();
156 self.bind_dom(move || match read() {
157 Some(value) => node.set_attribute(&name, &value),
158 None => node.remove_attribute(&name),
159 })
160 }
161
162 #[doc(hidden)]
165 pub fn attr_value(
166 &mut self,
167 target: impl ElementTarget,
168 name: &str,
169 read: impl Fn() -> text_value::Output + 'static,
170 ) -> Result<(), JsValue> {
171 let node = target.resolve(self)?;
172 let name = name.to_owned();
173 self.bind_dom(move || match read() {
174 text_value::Output::String(value) => node.set_attribute(&name, &value),
175 text_value::Output::Integer(value) => {
176 strings::set_integer_attribute(&node, &name, value)
177 }
178 })
179 }
180
181 pub fn class(
182 &mut self,
183 target: impl ElementTarget,
184 name: &str,
185 read: impl Fn() -> bool + 'static,
186 ) -> Result<(), JsValue> {
187 let node = target.resolve(self)?;
188 let name = name.to_owned();
189 self.bind_dom(move || {
190 node.class_list()
191 .toggle_with_force(&name, read())
192 .map(|_| ())
193 })
194 }
195
196 pub fn on(
198 &mut self,
199 target: impl ElementTarget,
200 event: &str,
201 handler: impl FnMut(Event) + 'static,
202 ) -> Result<(), JsValue> {
203 let target = ListenerTarget::Node(target.resolve(self)?.into());
204 self.listen(target, event, handler)
205 }
206
207 pub(super) fn on_bundle(
209 &mut self,
210 nodes: &Rc<JsValue>,
211 index: u32,
212 event: &str,
213 handler: impl FnMut(Event) + 'static,
214 ) -> Result<(), JsValue> {
215 self.listen(
216 ListenerTarget::Bundle(Rc::clone(nodes), index),
217 event,
218 handler,
219 )
220 }
221
222 fn listen(
223 &mut self,
224 target: ListenerTarget,
225 event: &str,
226 handler: impl FnMut(Event) + 'static,
227 ) -> Result<(), JsValue> {
228 let owner = self.owner();
229 let listener = Listener::batched(target, event, move || owner.is_active(), handler)?;
230 self.listeners.push(listener);
231 Ok(())
232 }
233
234 pub fn checked(
235 &mut self,
236 target: impl InputTarget,
237 read: impl Fn() -> bool + 'static,
238 ) -> Result<(), JsValue> {
239 let input = target.resolve_input(self)?;
240 self.bind_dom(move || {
241 input.set_checked(read());
242 Ok(())
243 })
244 }
245
246 pub fn value(
248 &mut self,
249 target: impl InputTarget,
250 read: impl Fn() -> String + 'static,
251 ) -> Result<(), JsValue> {
252 let input = target.resolve_input(self)?;
253 self.bind_dom(move || {
254 let value = read();
255 if input.value() != value {
256 input.set_value(&value);
257 }
258 Ok(())
259 })
260 }
261}