yew_stdweb/html/component/
scope.rs

1//! Component scope module
2
3use super::{
4    lifecycle::{
5        ComponentLifecycleEvent, ComponentRunnable, ComponentState, CreateEvent, UpdateEvent,
6    },
7    Component,
8};
9use crate::callback::Callback;
10use crate::html::NodeRef;
11use crate::scheduler::{scheduler, Shared};
12use crate::utils::document;
13use crate::virtual_dom::{insert_node, VNode};
14use cfg_if::cfg_if;
15use std::any::{Any, TypeId};
16use std::cell::{Ref, RefCell};
17use std::fmt;
18use std::ops::Deref;
19use std::rc::Rc;
20cfg_if! {
21    if #[cfg(feature = "std_web")] {
22        use stdweb::web::{Element, Node};
23    } else if #[cfg(feature = "web_sys")] {
24        use web_sys::{Element, Node};
25    }
26}
27
28/// Untyped scope used for accessing parent scope
29#[derive(Debug, Clone)]
30pub struct AnyScope {
31    pub(crate) type_id: TypeId,
32    pub(crate) parent: Option<Rc<AnyScope>>,
33    pub(crate) state: Rc<dyn Any>,
34}
35
36impl<COMP: Component> From<Scope<COMP>> for AnyScope {
37    fn from(scope: Scope<COMP>) -> Self {
38        AnyScope {
39            type_id: TypeId::of::<COMP>(),
40            parent: scope.parent,
41            state: Rc::new(scope.state),
42        }
43    }
44}
45
46impl AnyScope {
47    /// Returns the parent scope
48    pub fn get_parent(&self) -> Option<&AnyScope> {
49        self.parent.as_deref()
50    }
51
52    /// Returns the type of the linked component
53    pub fn get_type_id(&self) -> &TypeId {
54        &self.type_id
55    }
56
57    /// Attempts to downcast into a typed scope
58    pub fn downcast<COMP: Component>(self) -> Scope<COMP> {
59        Scope {
60            parent: self.parent,
61            state: self
62                .state
63                .downcast_ref::<Shared<Option<ComponentState<COMP>>>>()
64                .expect("unexpected component type")
65                .clone(),
66        }
67    }
68}
69
70pub(crate) trait Scoped {
71    fn to_any(&self) -> AnyScope;
72    fn root_vnode(&self) -> Option<Ref<'_, VNode>>;
73    fn destroy(&mut self);
74}
75
76impl<COMP: Component> Scoped for Scope<COMP> {
77    fn to_any(&self) -> AnyScope {
78        self.clone().into()
79    }
80
81    fn root_vnode(&self) -> Option<Ref<'_, VNode>> {
82        let state_ref = self.state.borrow();
83
84        // check that component hasn't been destroyed
85        state_ref.as_ref()?;
86
87        Some(Ref::map(state_ref, |state_ref| {
88            &state_ref.as_ref().unwrap().root_node
89        }))
90    }
91
92    /// Process an event to destroy a component
93    fn destroy(&mut self) {
94        self.process(ComponentLifecycleEvent::Destroy);
95    }
96}
97
98/// A context which allows sending messages to a component.
99pub struct Scope<COMP: Component> {
100    parent: Option<Rc<AnyScope>>,
101    state: Shared<Option<ComponentState<COMP>>>,
102}
103
104impl<COMP: Component> fmt::Debug for Scope<COMP> {
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        f.write_str("Scope<_>")
107    }
108}
109
110impl<COMP: Component> Clone for Scope<COMP> {
111    fn clone(&self) -> Self {
112        Scope {
113            parent: self.parent.clone(),
114            state: self.state.clone(),
115        }
116    }
117}
118
119impl<COMP: Component> Scope<COMP> {
120    /// Returns the parent scope
121    pub fn get_parent(&self) -> Option<&AnyScope> {
122        self.parent.as_deref()
123    }
124
125    /// Returns the linked component if available
126    pub fn get_component(&self) -> Option<impl Deref<Target = COMP> + '_> {
127        self.state.try_borrow().ok().and_then(|state_ref| {
128            state_ref.as_ref()?;
129            Some(Ref::map(state_ref, |state| {
130                state.as_ref().unwrap().component.as_ref()
131            }))
132        })
133    }
134
135    pub(crate) fn new(parent: Option<AnyScope>) -> Self {
136        let parent = parent.map(Rc::new);
137        let state = Rc::new(RefCell::new(None));
138        Scope { parent, state }
139    }
140
141    /// Mounts a component with `props` to the specified `element` in the DOM.
142    pub(crate) fn mount_in_place(
143        self,
144        parent: Element,
145        next_sibling: NodeRef,
146        node_ref: NodeRef,
147        props: COMP::Properties,
148    ) -> Scope<COMP> {
149        let placeholder = {
150            let placeholder: Node = document().create_text_node("").into();
151            insert_node(&placeholder, &parent, next_sibling.get());
152            node_ref.set(Some(placeholder.clone()));
153            VNode::VRef(placeholder)
154        };
155
156        self.schedule(UpdateEvent::First.into());
157        self.process(ComponentLifecycleEvent::Create(CreateEvent {
158            parent,
159            next_sibling,
160            placeholder,
161            node_ref,
162            props,
163            scope: self.clone(),
164        }));
165
166        self
167    }
168
169    pub(crate) fn reuse(&self, props: COMP::Properties, node_ref: NodeRef, next_sibling: NodeRef) {
170        self.process(UpdateEvent::Properties(props, node_ref, next_sibling).into());
171    }
172
173    pub(crate) fn process(&self, event: ComponentLifecycleEvent<COMP>) {
174        let scheduler = scheduler();
175        scheduler.component.push(
176            event.as_runnable_type(),
177            Box::new(ComponentRunnable {
178                state: self.state.clone(),
179                event,
180            }),
181        );
182        scheduler.start();
183    }
184
185    fn schedule(&self, event: ComponentLifecycleEvent<COMP>) {
186        let scheduler = &scheduler().component;
187        scheduler.push(
188            event.as_runnable_type(),
189            Box::new(ComponentRunnable {
190                state: self.state.clone(),
191                event,
192            }),
193        );
194    }
195
196    /// Send a message to the component.
197    ///
198    /// Please be aware that currently this method synchronously
199    /// schedules a call to the [Component](Component) interface.
200    pub fn send_message<T>(&self, msg: T)
201    where
202        T: Into<COMP::Message>,
203    {
204        self.process(UpdateEvent::Message(msg.into()).into());
205    }
206
207    /// Send a batch of messages to the component.
208    ///
209    /// This is useful for reducing re-renders of the components
210    /// because the messages are handled together and the view
211    /// function is called only once if needed.
212    ///
213    /// Please be aware that currently this method synchronously
214    /// schedules calls to the [Component](Component) interface.
215    pub fn send_message_batch(&self, messages: Vec<COMP::Message>) {
216        // There is no reason to schedule empty batches.
217        // This check is especially handy for the batch_callback method.
218        if messages.is_empty() {
219            return;
220        }
221
222        self.process(UpdateEvent::MessageBatch(messages).into());
223    }
224
225    /// Creates a `Callback` which will send a message to the linked
226    /// component's update method when invoked.
227    ///
228    /// Please be aware that currently the result of this callback
229    /// synchronously schedules a call to the [Component](Component)
230    /// interface.
231    pub fn callback<F, IN, M>(&self, function: F) -> Callback<IN>
232    where
233        M: Into<COMP::Message>,
234        F: Fn(IN) -> M + 'static,
235    {
236        let scope = self.clone();
237        let closure = move |input| {
238            let output = function(input);
239            scope.send_message(output);
240        };
241        closure.into()
242    }
243
244    /// Creates a `Callback` from an `FnOnce` which will send a message
245    /// to the linked component's update method when invoked.
246    ///
247    /// Please be aware that currently the result of this callback
248    /// will synchronously schedule calls to the
249    /// [Component](Component) interface.
250    pub fn callback_once<F, IN, M>(&self, function: F) -> Callback<IN>
251    where
252        M: Into<COMP::Message>,
253        F: FnOnce(IN) -> M + 'static,
254    {
255        let scope = self.clone();
256        let closure = move |input| {
257            let output = function(input);
258            scope.send_message(output);
259        };
260        Callback::once(closure)
261    }
262
263    /// Creates a `Callback` which will send a batch of messages back
264    /// to the linked component's update method when invoked.
265    ///
266    /// The callback function's return type is generic to allow for dealing with both
267    /// `Option` and `Vec` nicely. `Option` can be used when dealing with a callback that
268    /// might not need to send an update.
269    ///
270    /// ```ignore
271    /// link.batch_callback(|_| vec![Msg::A, Msg::B]);
272    /// link.batch_callback(|_| Some(Msg::A));
273    /// ```
274    ///
275    /// Please be aware that currently the results of these callbacks
276    /// will synchronously schedule calls to the
277    /// [Component](Component) interface.
278    pub fn batch_callback<F, IN, OUT>(&self, function: F) -> Callback<IN>
279    where
280        F: Fn(IN) -> OUT + 'static,
281        OUT: SendAsMessage<COMP>,
282    {
283        let scope = self.clone();
284        let closure = move |input| {
285            let messages = function(input);
286            messages.send(&scope);
287        };
288        closure.into()
289    }
290
291    /// Creates a `Callback` from an `FnOnce` which will send a batch of messages back
292    /// to the linked component's update method when invoked.
293    ///
294    /// The callback function's return type is generic to allow for dealing with both
295    /// `Option` and `Vec` nicely. `Option` can be used when dealing with a callback that
296    /// might not need to send an update.
297    ///
298    /// ```ignore
299    /// link.batch_callback_once(|_| vec![Msg::A, Msg::B]);
300    /// link.batch_callback_once(|_| Some(Msg::A));
301    /// ```
302    ///
303    /// Please be aware that currently the results of these callbacks
304    /// will synchronously schedule calls to the
305    /// [Component](Component) interface.
306    pub fn batch_callback_once<F, IN, OUT>(&self, function: F) -> Callback<IN>
307    where
308        F: FnOnce(IN) -> OUT + 'static,
309        OUT: SendAsMessage<COMP>,
310    {
311        let scope = self.clone();
312        let closure = move |input| {
313            let messages = function(input);
314            messages.send(&scope);
315        };
316        Callback::once(closure)
317    }
318}
319
320/// Defines a message type that can be sent to a component.
321/// Used for the return value of closure given to [Scope::batch_callback](struct.Scope.html#method.batch_callback).
322pub trait SendAsMessage<COMP: Component> {
323    /// Sends the message to the given component's scope.
324    /// See [Scope::batch_callback](struct.Scope.html#method.batch_callback).
325    fn send(self, scope: &Scope<COMP>);
326}
327
328impl<COMP> SendAsMessage<COMP> for Option<COMP::Message>
329where
330    COMP: Component,
331{
332    fn send(self, scope: &Scope<COMP>) {
333        if let Some(msg) = self {
334            scope.send_message(msg);
335        }
336    }
337}
338
339impl<COMP> SendAsMessage<COMP> for Vec<COMP::Message>
340where
341    COMP: Component,
342{
343    fn send(self, scope: &Scope<COMP>) {
344        scope.send_message_batch(self);
345    }
346}