yew/app_handle.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
//! [AppHandle] contains the state Yew keeps to bootstrap a component in an isolated scope.
use std::ops::Deref;
use std::rc::Rc;
use web_sys::Element;
use crate::dom_bundle::{BSubtree, DomSlot, DynamicDomSlot};
use crate::html::{BaseComponent, Scope, Scoped};
/// An instance of an application.
#[derive(Debug)]
pub struct AppHandle<COMP: BaseComponent> {
/// `Scope` holder
pub(crate) scope: Scope<COMP>,
}
impl<COMP> AppHandle<COMP>
where
COMP: BaseComponent,
{
/// The main entry point of a Yew program which also allows passing properties. It works
/// similarly to the `program` function in Elm. You should provide an initial model, `update`
/// function which will update the state of the model and a `view` function which
/// will render the model to a virtual DOM tree.
#[tracing::instrument(
level = tracing::Level::DEBUG,
name = "mount",
skip(props),
)]
pub(crate) fn mount_with_props(host: Element, props: Rc<COMP::Properties>) -> Self {
clear_element(&host);
let app = Self {
scope: Scope::new(None),
};
let hosting_root = BSubtree::create_root(&host);
app.scope.mount_in_place(
hosting_root,
host,
DomSlot::at_end(),
DynamicDomSlot::new_debug_trapped(),
props,
);
app
}
/// Update the properties of the app's root component.
///
/// This can be an alternative to sending and handling messages. The existing component will be
/// reused and have its properties updates. This will presumably trigger a re-render, refer to
/// the [`changed`] lifecycle for details.
///
/// [`changed`]: crate::Component::changed
#[tracing::instrument(
level = tracing::Level::DEBUG,
skip_all,
)]
pub fn update(&mut self, new_props: COMP::Properties) {
self.scope.reuse(Rc::new(new_props), DomSlot::at_end())
}
/// Schedule the app for destruction
#[tracing::instrument(
level = tracing::Level::DEBUG,
skip_all,
)]
pub fn destroy(self) {
self.scope.destroy(false)
}
}
impl<COMP> Deref for AppHandle<COMP>
where
COMP: BaseComponent,
{
type Target = Scope<COMP>;
fn deref(&self) -> &Self::Target {
&self.scope
}
}
/// Removes anything from the given element.
fn clear_element(host: &Element) {
while let Some(child) = host.last_child() {
host.remove_child(&child).expect("can't remove a child");
}
}
#[cfg(feature = "hydration")]
mod feat_hydration {
use super::*;
use crate::dom_bundle::Fragment;
impl<COMP> AppHandle<COMP>
where
COMP: BaseComponent,
{
#[tracing::instrument(
level = tracing::Level::DEBUG,
name = "hydrate",
skip(props),
)]
pub(crate) fn hydrate_with_props(host: Element, props: Rc<COMP::Properties>) -> Self {
let app = Self {
scope: Scope::new(None),
};
let mut fragment = Fragment::collect_children(&host);
let hosting_root = BSubtree::create_root(&host);
app.scope.hydrate_in_place(
hosting_root,
host.clone(),
&mut fragment,
DynamicDomSlot::new_debug_trapped(),
Rc::clone(&props),
);
#[cfg(debug_assertions)] // Fix trapped next_sibling at the root
app.scope.reuse(props, DomSlot::at_end());
// We remove all remaining nodes, this mimics the clear_element behaviour in
// mount_with_props.
for node in fragment.iter() {
host.remove_child(node).unwrap();
}
app
}
}
}