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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
//! This module contains the `App` struct, which is used to bootstrap
//! a component in an isolated scope.

use crate::html::{Component, ComponentLink, NodeRef, Scope};
use crate::utils::document;
use cfg_if::cfg_if;
cfg_if! {
    if #[cfg(feature = "std_web")] {
        use stdweb::web::{Element, INode, IParentNode};
    } else if #[cfg(feature = "web_sys")] {
        use web_sys::Element;
    }
}

/// An instance of an application.
#[derive(Debug)]
pub struct App<COMP: Component> {
    /// `Scope` holder
    scope: Scope<COMP>,
}

impl<COMP> Default for App<COMP>
where
    COMP: Component,
{
    fn default() -> Self {
        App::new()
    }
}

impl<COMP> App<COMP>
where
    COMP: Component,
    COMP::Properties: Default,
{
    /// The main entry point of a Yew program. 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. If you would like to pass props,
    /// use the `mount_with_props` method.
    pub fn mount(self, element: Element) -> ComponentLink<COMP> {
        clear_element(&element);
        self.scope.mount_in_place(
            element,
            NodeRef::default(),
            None,
            NodeRef::default(),
            COMP::Properties::default(),
        )
    }

    /// Alias to `mount("body", ...)`.
    pub fn mount_to_body(self) -> ComponentLink<COMP> {
        // Bootstrap the component for `Window` environment only (not for `Worker`)
        let element = document()
            .query_selector("body")
            .expect("can't get body node for rendering")
            .expect("can't unwrap body node");
        self.mount(element)
    }

    /// Alternative to `mount` which replaces the body element with a component which has a body
    /// element at the root of the HTML generated by its `view` method. Use this method when you
    /// need to manipulate the body element. For example, adding/removing app-wide
    /// CSS classes of the body element.
    pub fn mount_as_body(self) -> ComponentLink<COMP> {
        let html_element = document()
            .query_selector("html")
            .expect("can't get html node for rendering")
            .expect("can't unwrap html node");
        let body_element = document()
            .query_selector("body")
            .expect("can't get body node for rendering")
            .expect("can't unwrap body node");
        html_element
            .remove_child(&body_element)
            .expect("can't remove body child");
        self.scope.mount_in_place(
            html_element,
            NodeRef::default(),
            None,
            NodeRef::default(),
            COMP::Properties::default(),
        )
    }
}

impl<COMP> App<COMP>
where
    COMP: Component,
{
    /// Creates a new `App` with a component in a context.
    pub fn new() -> Self {
        let scope = Scope::new(None);
        App { scope }
    }

    /// 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.
    pub fn mount_with_props(
        self,
        element: Element,
        props: COMP::Properties,
    ) -> ComponentLink<COMP> {
        clear_element(&element);
        self.scope
            .mount_in_place(element, NodeRef::default(), None, NodeRef::default(), props)
    }

    /// Alias to `mount_with_props("body", ...)`.
    pub fn mount_to_body_with_props(self, props: COMP::Properties) -> ComponentLink<COMP> {
        // Bootstrap the component for `Window` environment only (not for `Worker`)
        let element = document()
            .query_selector("body")
            .expect("can't get body node for rendering")
            .expect("can't unwrap body node");
        self.mount_with_props(element, props)
    }

    /// Alternative to `mount_with_props` which replaces the body element with a component which
    /// has a body element at the root of the HTML generated by its `view` method. Use this method
    /// when you need to manipulate the body element. For example, adding/removing app-wide
    /// CSS classes of the body element.
    pub fn mount_as_body_with_props(self, props: COMP::Properties) -> ComponentLink<COMP> {
        let html_element = document()
            .query_selector("html")
            .expect("can't get html node for rendering")
            .expect("can't unwrap html node");
        let body_element = document()
            .query_selector("body")
            .expect("can't get body node for rendering")
            .expect("can't unwrap body node");
        html_element
            .remove_child(&body_element)
            .expect("can't remove body child");
        self.scope.mount_in_place(
            html_element,
            NodeRef::default(),
            None,
            NodeRef::default(),
            props,
        )
    }
}

/// Removes anything from the given element.
fn clear_element(element: &Element) {
    while let Some(child) = element.last_child() {
        element.remove_child(&child).expect("can't remove a child");
    }
}