This is unreleased documentation for Yew Next version.
For up-to-date documentation, see the latest version on docs.rs.

yew/html/component/
scope.rs

1//! Component scope module
2
3use std::any::{Any, TypeId};
4use std::future::Future;
5use std::marker::PhantomData;
6use std::ops::Deref;
7use std::rc::Rc;
8use std::{fmt, iter};
9
10use futures::{Stream, StreamExt};
11
12#[cfg(any(feature = "csr", feature = "ssr"))]
13use super::lifecycle::ComponentState;
14use super::BaseComponent;
15use crate::callback::Callback;
16use crate::context::{ContextHandle, ContextProvider};
17use crate::platform::spawn_local;
18#[cfg(any(feature = "csr", feature = "ssr"))]
19use crate::scheduler::Shared;
20
21/// Untyped scope used for accessing parent scope
22#[derive(Clone)]
23pub struct AnyScope {
24    type_id: TypeId,
25    parent: Option<Rc<AnyScope>>,
26    typed_scope: Rc<dyn Any>,
27}
28
29impl fmt::Debug for AnyScope {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        f.write_str("AnyScope<_>")
32    }
33}
34
35impl<COMP: BaseComponent> From<Scope<COMP>> for AnyScope {
36    fn from(scope: Scope<COMP>) -> Self {
37        AnyScope {
38            type_id: TypeId::of::<COMP>(),
39            parent: scope.parent.clone(),
40            typed_scope: Rc::new(scope),
41        }
42    }
43}
44
45impl AnyScope {
46    /// Returns the parent scope
47    pub fn get_parent(&self) -> Option<&AnyScope> {
48        self.parent.as_deref()
49    }
50
51    /// Returns the type of the linked component
52    pub fn get_type_id(&self) -> &TypeId {
53        &self.type_id
54    }
55
56    /// Attempts to downcast into a typed scope
57    ///
58    /// # Panics
59    ///
60    /// If the self value can't be cast into the target type.
61    pub fn downcast<COMP: BaseComponent>(&self) -> Scope<COMP> {
62        self.try_downcast::<COMP>().unwrap()
63    }
64
65    /// Attempts to downcast into a typed scope
66    ///
67    /// Returns [`None`] if the self value can't be cast into the target type.
68    pub fn try_downcast<COMP: BaseComponent>(&self) -> Option<Scope<COMP>> {
69        self.typed_scope.downcast_ref::<Scope<COMP>>().cloned()
70    }
71
72    /// Attempts to find a parent scope of a certain type
73    ///
74    /// Returns [`None`] if no parent scope with the specified type was found.
75    pub fn find_parent_scope<COMP: BaseComponent>(&self) -> Option<Scope<COMP>> {
76        iter::successors(Some(self), |scope| scope.get_parent())
77            .find_map(AnyScope::try_downcast::<COMP>)
78    }
79
80    /// Accesses a value provided by a parent `ContextProvider` component of the
81    /// same type.
82    pub fn context<T: Clone + PartialEq + 'static>(
83        &self,
84        callback: Callback<T>,
85    ) -> Option<(T, ContextHandle<T>)> {
86        let scope = self.find_parent_scope::<ContextProvider<T>>()?;
87        let scope_clone = scope.clone();
88        let component = scope.get_component()?;
89        Some(component.subscribe_consumer(callback, scope_clone))
90    }
91}
92
93/// A context which allows sending messages to a component.
94pub struct Scope<COMP: BaseComponent> {
95    _marker: PhantomData<COMP>,
96    parent: Option<Rc<AnyScope>>,
97
98    #[cfg(any(feature = "csr", feature = "ssr"))]
99    pub(crate) pending_messages: MsgQueue<COMP::Message>,
100
101    #[cfg(any(feature = "csr", feature = "ssr"))]
102    pub(crate) state: Shared<Option<ComponentState>>,
103
104    pub(crate) id: usize,
105}
106
107impl<COMP: BaseComponent> fmt::Debug for Scope<COMP> {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        f.write_str("Scope<_>")
110    }
111}
112
113impl<COMP: BaseComponent> Clone for Scope<COMP> {
114    fn clone(&self) -> Self {
115        Scope {
116            _marker: PhantomData,
117
118            #[cfg(any(feature = "csr", feature = "ssr"))]
119            pending_messages: self.pending_messages.clone(),
120            parent: self.parent.clone(),
121
122            #[cfg(any(feature = "csr", feature = "ssr"))]
123            state: self.state.clone(),
124
125            id: self.id,
126        }
127    }
128}
129
130impl<COMP: BaseComponent> Scope<COMP> {
131    /// Returns the parent scope
132    pub fn get_parent(&self) -> Option<&AnyScope> {
133        self.parent.as_deref()
134    }
135
136    /// Creates a `Callback` which will send a message to the linked
137    /// component's update method when invoked.
138    ///
139    /// If your callback function returns a [Future],
140    /// use [`callback_future`](Scope::callback_future) instead.
141    pub fn callback<F, IN, M>(&self, function: F) -> Callback<IN>
142    where
143        M: Into<COMP::Message>,
144        F: Fn(IN) -> M + 'static,
145    {
146        let scope = self.clone();
147        let closure = move |input| {
148            let output = function(input);
149            scope.send_message(output);
150        };
151        Callback::from(closure)
152    }
153
154    /// Creates a `Callback` which will send a batch of messages back
155    /// to the linked component's update method when invoked.
156    ///
157    /// The callback function's return type is generic to allow for dealing with both
158    /// `Option` and `Vec` nicely. `Option` can be used when dealing with a callback that
159    /// might not need to send an update.
160    ///
161    /// ```ignore
162    /// link.batch_callback(|_| vec![Msg::A, Msg::B]);
163    /// link.batch_callback(|_| Some(Msg::A));
164    /// ```
165    pub fn batch_callback<F, IN, OUT>(&self, function: F) -> Callback<IN>
166    where
167        F: Fn(IN) -> OUT + 'static,
168        OUT: SendAsMessage<COMP>,
169    {
170        let scope = self.clone();
171        let closure = move |input| {
172            let messages = function(input);
173            messages.send(&scope);
174        };
175        closure.into()
176    }
177
178    /// Accesses a value provided by a parent `ContextProvider` component of the
179    /// same type.
180    pub fn context<T: Clone + PartialEq + 'static>(
181        &self,
182        callback: Callback<T>,
183    ) -> Option<(T, ContextHandle<T>)> {
184        AnyScope::from(self.clone()).context(callback)
185    }
186
187    /// This method asynchronously awaits a [Future] that returns a message and sends it
188    /// to the linked component.
189    ///
190    /// # Panics
191    /// If the future panics, then the promise will not resolve, and will leak.
192    pub fn send_future<Fut, Msg>(&self, future: Fut)
193    where
194        Msg: Into<COMP::Message>,
195        Fut: Future<Output = Msg> + 'static,
196    {
197        let link = self.clone();
198        spawn_local(async move {
199            let message: COMP::Message = future.await.into();
200            link.send_message(message);
201        });
202    }
203
204    /// This method creates a [`Callback`] which, when emitted, asynchronously awaits the
205    /// message returned from the passed function before sending it to the linked component.
206    ///
207    /// # Panics
208    /// If the future panics, then the promise will not resolve, and will leak.
209    pub fn callback_future<F, Fut, IN, Msg>(&self, function: F) -> Callback<IN>
210    where
211        Msg: Into<COMP::Message>,
212        Fut: Future<Output = Msg> + 'static,
213        F: Fn(IN) -> Fut + 'static,
214    {
215        let link = self.clone();
216
217        let closure = move |input: IN| {
218            link.send_future(function(input));
219        };
220
221        closure.into()
222    }
223
224    /// Asynchronously send a batch of messages to a component. This asynchronously awaits the
225    /// passed [Future], before sending the message batch to the linked component.
226    ///
227    /// # Panics
228    /// If the future panics, then the promise will not resolve, and will leak.
229    pub fn send_future_batch<Fut>(&self, future: Fut)
230    where
231        Fut: Future + 'static,
232        Fut::Output: SendAsMessage<COMP>,
233    {
234        let link = self.clone();
235        let js_future = async move {
236            future.await.send(&link);
237        };
238        spawn_local(js_future);
239    }
240
241    /// This method asynchronously awaits a [`Stream`] that returns a series of messages and sends
242    /// them to the linked component.
243    ///
244    /// # Panics
245    /// If the stream panics, then the promise will not resolve, and will leak.
246    ///
247    /// # Note
248    ///
249    /// This method will not notify the component when the stream has been fully exhausted. If
250    /// you want this feature, you can add an EOF message variant for your component and use
251    /// [`StreamExt::chain`] and [`stream::once`](futures::stream::once) to chain an EOF message to
252    /// the original stream. If your stream is produced by another crate, you can use
253    /// [`StreamExt::map`] to transform the stream's item type to the component message type.
254    pub fn send_stream<S, M>(&self, stream: S)
255    where
256        M: Into<COMP::Message>,
257        S: Stream<Item = M> + 'static,
258    {
259        let link = self.clone();
260        let js_future = async move {
261            futures::pin_mut!(stream);
262            while let Some(msg) = stream.next().await {
263                let message: COMP::Message = msg.into();
264                link.send_message(message);
265            }
266        };
267        spawn_local(js_future);
268    }
269
270    /// Returns the linked component if available
271    pub fn get_component(&self) -> Option<impl Deref<Target = COMP> + '_> {
272        self.arch_get_component()
273    }
274
275    /// Send a message to the component.
276    pub fn send_message<T>(&self, msg: T)
277    where
278        T: Into<COMP::Message>,
279    {
280        self.arch_send_message(msg)
281    }
282
283    /// Send a batch of messages to the component.
284    ///
285    /// This is slightly more efficient than calling [`send_message`](Self::send_message)
286    /// in a loop.
287    pub fn send_message_batch(&self, messages: Vec<COMP::Message>) {
288        self.arch_send_message_batch(messages)
289    }
290}
291
292#[cfg(feature = "ssr")]
293mod feat_ssr {
294    use std::fmt::Write;
295
296    use super::*;
297    use crate::feat_ssr::VTagKind;
298    use crate::html::component::lifecycle::{
299        ComponentRenderState, CreateRunner, DestroyRunner, RenderRunner,
300    };
301    use crate::platform::fmt::BufWriter;
302    use crate::platform::pinned::oneshot;
303    use crate::scheduler;
304    use crate::virtual_dom::Collectable;
305
306    impl<COMP: BaseComponent> Scope<COMP> {
307        pub(crate) async fn render_into_stream(
308            &self,
309            w: &mut BufWriter,
310            props: Rc<COMP::Properties>,
311            hydratable: bool,
312            parent_vtag_kind: VTagKind,
313        ) {
314            // Rust's Future implementation is stack-allocated and incurs zero runtime-cost.
315            //
316            // If the content of this channel is ready before it is awaited, it is
317            // similar to taking the value from a mutex lock.
318            let (tx, rx) = oneshot::channel();
319            let state = ComponentRenderState::Ssr { sender: Some(tx) };
320
321            scheduler::push_component_create(
322                self.id,
323                Box::new(CreateRunner {
324                    initial_render_state: state,
325                    props,
326                    scope: self.clone(),
327                    #[cfg(feature = "hydration")]
328                    prepared_state: None,
329                }),
330                Box::new(RenderRunner {
331                    state: self.state.clone(),
332                }),
333            );
334            scheduler::start();
335
336            let collectable = Collectable::for_component::<COMP>();
337
338            if hydratable {
339                collectable.write_open_tag(w);
340            }
341
342            let html = rx.await.unwrap();
343
344            let self_any_scope = AnyScope::from(self.clone());
345            html.render_into_stream(w, &self_any_scope, hydratable, parent_vtag_kind)
346                .await;
347
348            if let Some(prepared_state) = self.get_component().unwrap().prepare_state() {
349                let _ = w.write_str(r#"<script type="application/x-yew-comp-state">"#);
350                let _ = w.write_str(&prepared_state);
351                let _ = w.write_str(r#"</script>"#);
352            }
353
354            if hydratable {
355                collectable.write_close_tag(w);
356            }
357
358            scheduler::push_component_destroy(Box::new(DestroyRunner {
359                state: self.state.clone(),
360                parent_to_detach: false,
361            }));
362            scheduler::start();
363        }
364    }
365}
366
367#[cfg(not(any(feature = "ssr", feature = "csr")))]
368mod feat_no_csr_ssr {
369    use super::*;
370
371    // Skeleton code to provide public methods when no renderer are enabled.
372    impl<COMP: BaseComponent> Scope<COMP> {
373        pub(super) fn arch_get_component(&self) -> Option<impl Deref<Target = COMP> + '_> {
374            Option::<&COMP>::None
375        }
376
377        pub(super) fn arch_send_message<T>(&self, _msg: T)
378        where
379            T: Into<COMP::Message>,
380        {
381        }
382
383        pub(super) fn arch_send_message_batch(&self, _messages: Vec<COMP::Message>) {}
384    }
385}
386
387#[cfg(any(feature = "ssr", feature = "csr"))]
388mod feat_csr_ssr {
389    use std::cell::{Ref, RefCell};
390    use std::sync::atomic::{AtomicUsize, Ordering};
391
392    use super::*;
393    use crate::html::component::lifecycle::UpdateRunner;
394    use crate::scheduler::{self, Shared};
395
396    #[derive(Debug)]
397    pub(crate) struct MsgQueue<Msg>(Shared<Vec<Msg>>);
398
399    impl<Msg> MsgQueue<Msg> {
400        pub fn new() -> Self {
401            MsgQueue(Rc::default())
402        }
403
404        pub fn push(&self, msg: Msg) -> usize {
405            let mut inner = self.0.borrow_mut();
406            inner.push(msg);
407
408            inner.len()
409        }
410
411        pub fn append(&self, other: &mut Vec<Msg>) -> usize {
412            let mut inner = self.0.borrow_mut();
413            inner.append(other);
414
415            inner.len()
416        }
417
418        pub fn drain(&self) -> Vec<Msg> {
419            let mut other_queue = Vec::new();
420            let mut inner = self.0.borrow_mut();
421
422            std::mem::swap(&mut *inner, &mut other_queue);
423
424            other_queue
425        }
426    }
427
428    impl<Msg> Clone for MsgQueue<Msg> {
429        fn clone(&self) -> Self {
430            MsgQueue(self.0.clone())
431        }
432    }
433
434    static COMP_ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
435
436    impl<COMP: BaseComponent> Scope<COMP> {
437        /// Crate a scope with an optional parent scope
438        pub(crate) fn new(parent: Option<AnyScope>) -> Self {
439            let parent = parent.map(Rc::new);
440
441            let state = Rc::new(RefCell::new(None));
442
443            let pending_messages = MsgQueue::new();
444
445            Scope {
446                _marker: PhantomData,
447
448                pending_messages,
449
450                state,
451                parent,
452
453                id: COMP_ID_COUNTER.fetch_add(1, Ordering::SeqCst),
454            }
455        }
456
457        #[inline]
458        pub(super) fn arch_get_component(&self) -> Option<impl Deref<Target = COMP> + '_> {
459            self.state.try_borrow().ok().and_then(|state_ref| {
460                Ref::filter_map(state_ref, |state| {
461                    state.as_ref().and_then(|m| m.downcast_comp_ref::<COMP>())
462                })
463                .ok()
464            })
465        }
466
467        #[inline]
468        fn schedule_update(&self) {
469            scheduler::push_component_update(Box::new(UpdateRunner {
470                state: self.state.clone(),
471            }));
472            // Not guaranteed to already have the scheduler started
473            scheduler::start();
474        }
475
476        #[inline]
477        pub(super) fn arch_send_message<T>(&self, msg: T)
478        where
479            T: Into<COMP::Message>,
480        {
481            // We are the first message in queue, so we queue the update.
482            if self.pending_messages.push(msg.into()) == 1 {
483                self.schedule_update();
484            }
485        }
486
487        #[inline]
488        pub(super) fn arch_send_message_batch(&self, mut messages: Vec<COMP::Message>) {
489            let msg_len = messages.len();
490
491            // The queue was empty, so we queue the update
492            if self.pending_messages.append(&mut messages) == msg_len {
493                self.schedule_update();
494            }
495        }
496    }
497}
498
499#[cfg(any(feature = "ssr", feature = "csr"))]
500pub(crate) use feat_csr_ssr::*;
501
502#[cfg(feature = "csr")]
503mod feat_csr {
504    use std::cell::Ref;
505
506    use web_sys::Element;
507
508    use super::*;
509    use crate::dom_bundle::{BSubtree, Bundle, DomSlot, DynamicDomSlot};
510    use crate::html::component::lifecycle::{
511        ComponentRenderState, CreateRunner, DestroyRunner, PropsUpdateRunner, RenderRunner,
512    };
513    use crate::scheduler;
514
515    impl AnyScope {
516        #[cfg(any(test, feature = "test"))]
517        pub(crate) fn test() -> Self {
518            Self {
519                type_id: TypeId::of::<()>(),
520                parent: None,
521                typed_scope: Rc::new(()),
522            }
523        }
524    }
525
526    fn schedule_props_update(
527        state: Shared<Option<ComponentState>>,
528        props: Rc<dyn Any>,
529        next_sibling_slot: DomSlot,
530    ) {
531        scheduler::push_component_props_update(Box::new(PropsUpdateRunner {
532            state,
533            next_sibling_slot: Some(next_sibling_slot),
534            props: Some(props),
535        }));
536        // Not guaranteed to already have the scheduler started
537        scheduler::start();
538    }
539
540    impl<COMP> Scope<COMP>
541    where
542        COMP: BaseComponent,
543    {
544        /// Mounts a component with `props` to the specified `element` in the DOM.
545        pub(crate) fn mount_in_place(
546            &self,
547            root: BSubtree,
548            parent: Element,
549            slot: DomSlot,
550            internal_ref: DynamicDomSlot,
551            props: Rc<COMP::Properties>,
552        ) {
553            let bundle = Bundle::new();
554            let sibling_slot = DynamicDomSlot::new(slot);
555            internal_ref.reassign(sibling_slot.to_position());
556
557            let state = ComponentRenderState::Render {
558                bundle,
559                root,
560                own_slot: internal_ref,
561                parent,
562                sibling_slot,
563            };
564
565            scheduler::push_component_create(
566                self.id,
567                Box::new(CreateRunner {
568                    initial_render_state: state,
569                    props,
570                    scope: self.clone(),
571                    #[cfg(feature = "hydration")]
572                    prepared_state: None,
573                }),
574                Box::new(RenderRunner {
575                    state: self.state.clone(),
576                }),
577            );
578            // Not guaranteed to already have the scheduler started
579            scheduler::start();
580        }
581
582        pub(crate) fn reuse(&self, props: Rc<COMP::Properties>, slot: DomSlot) {
583            schedule_props_update(self.state.clone(), props, slot)
584        }
585    }
586
587    pub(crate) trait Scoped {
588        fn to_any(&self) -> AnyScope;
589        /// Get the render state if it hasn't already been destroyed
590        fn render_state(&self) -> Option<Ref<'_, ComponentRenderState>>;
591        /// Shift the node associated with this scope to a new place
592        fn shift_node(&self, parent: Element, slot: DomSlot);
593        /// Process an event to destroy a component
594        fn destroy(self, parent_to_detach: bool);
595        fn destroy_boxed(self: Box<Self>, parent_to_detach: bool);
596    }
597
598    impl<COMP: BaseComponent> Scoped for Scope<COMP> {
599        fn to_any(&self) -> AnyScope {
600            self.clone().into()
601        }
602
603        fn render_state(&self) -> Option<Ref<'_, ComponentRenderState>> {
604            let state_ref = self.state.borrow();
605
606            // check that component hasn't been destroyed
607            state_ref.as_ref()?;
608
609            Some(Ref::map(state_ref, |state_ref| {
610                &state_ref.as_ref().unwrap().render_state
611            }))
612        }
613
614        /// Process an event to destroy a component
615        fn destroy(self, parent_to_detach: bool) {
616            scheduler::push_component_destroy(Box::new(DestroyRunner {
617                state: self.state,
618                parent_to_detach,
619            }));
620            // Not guaranteed to already have the scheduler started
621            scheduler::start();
622        }
623
624        fn destroy_boxed(self: Box<Self>, parent_to_detach: bool) {
625            self.destroy(parent_to_detach)
626        }
627
628        fn shift_node(&self, parent: Element, slot: DomSlot) {
629            let mut state_ref = self.state.borrow_mut();
630            if let Some(render_state) = state_ref.as_mut() {
631                render_state.render_state.shift(parent, slot)
632            }
633        }
634    }
635}
636#[cfg(feature = "csr")]
637pub(crate) use feat_csr::*;
638
639#[cfg(feature = "hydration")]
640mod feat_hydration {
641    use wasm_bindgen::JsCast;
642    use web_sys::{Element, HtmlScriptElement};
643
644    use super::*;
645    use crate::dom_bundle::{BSubtree, DynamicDomSlot, Fragment};
646    use crate::html::component::lifecycle::{ComponentRenderState, CreateRunner, RenderRunner};
647    use crate::scheduler;
648    use crate::virtual_dom::Collectable;
649
650    impl<COMP> Scope<COMP>
651    where
652        COMP: BaseComponent,
653    {
654        /// Hydrates the component.
655        ///
656        /// Returns a pending NodeRef of the next sibling.
657        ///
658        /// # Note
659        ///
660        /// This method is expected to collect all the elements belongs to the current component
661        /// immediately.
662        pub(crate) fn hydrate_in_place(
663            &self,
664            root: BSubtree,
665            parent: Element,
666            fragment: &mut Fragment,
667            internal_ref: DynamicDomSlot,
668            props: Rc<COMP::Properties>,
669        ) {
670            // This is very helpful to see which component is failing during hydration
671            // which means this component may not having a stable layout / differs between
672            // client-side and server-side.
673            tracing::trace!(
674                component.id = self.id,
675                "hydration(type = {})",
676                std::any::type_name::<COMP>()
677            );
678
679            let collectable = Collectable::for_component::<COMP>();
680
681            let mut fragment = Fragment::collect_between(fragment, &collectable, &parent);
682
683            let prepared_state = match fragment
684                .back()
685                .cloned()
686                .and_then(|m| m.dyn_into::<HtmlScriptElement>().ok())
687            {
688                Some(m) if m.type_() == "application/x-yew-comp-state" => {
689                    fragment.pop_back();
690                    parent.remove_child(&m).unwrap();
691                    Some(m.text().unwrap())
692                }
693                _ => None,
694            };
695
696            let state = ComponentRenderState::Hydration {
697                parent,
698                root,
699                own_slot: internal_ref,
700                sibling_slot: DynamicDomSlot::new_debug_trapped(),
701                fragment,
702            };
703
704            scheduler::push_component_create(
705                self.id,
706                Box::new(CreateRunner {
707                    initial_render_state: state,
708                    props,
709                    scope: self.clone(),
710                    prepared_state,
711                }),
712                Box::new(RenderRunner {
713                    state: self.state.clone(),
714                }),
715            );
716
717            // Not guaranteed to already have the scheduler started
718            scheduler::start();
719        }
720    }
721}
722
723/// Defines a message type that can be sent to a component.
724/// Used for the return value of closure given to
725/// [Scope::batch_callback](struct.Scope.html#method.batch_callback).
726pub trait SendAsMessage<COMP: BaseComponent> {
727    /// Sends the message to the given component's scope.
728    /// See [Scope::batch_callback](struct.Scope.html#method.batch_callback).
729    fn send(self, scope: &Scope<COMP>);
730}
731
732impl<COMP> SendAsMessage<COMP> for Option<COMP::Message>
733where
734    COMP: BaseComponent,
735{
736    fn send(self, scope: &Scope<COMP>) {
737        if let Some(msg) = self {
738            scope.send_message(msg);
739        }
740    }
741}
742
743impl<COMP> SendAsMessage<COMP> for Vec<COMP::Message>
744where
745    COMP: BaseComponent,
746{
747    fn send(self, scope: &Scope<COMP>) {
748        scope.send_message_batch(self);
749    }
750}