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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
//! Flexible generic virtual dom representation.
//!
//! The types here can be used to generically represent a virtual dom tree. This generic
//! representation can be used to plug various concrete virtual dom representations into the
//! [`diff`] and [`patch`] algorithms implemented in this crate.
//!
//! [`diff`]: ../diff/fn.diff.html
//! [`patch`]: ../patch/enum.Patch.html

use std::fmt;
use std::mem;
use wasm_bindgen::prelude::*;
pub use crate::component::Component;
pub use crate::app::Dispatcher;

/// This represents an event handler. The handler can either always map to a specific message, or a
/// function can be provided that will transform the given [`web_sys::Event`] into a message. This
/// function must be a plain fn pointer and cannot capture any state from the environment.
///
/// [`web_sys::Event`]: https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Event.html
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum EventHandler<'a, Message> {
    /// A message that will be generated when this event associated with this handler fires.
    Msg(&'a Message),

    /// A callback that will convert a [`web_sys::Event`] into a message.
    ///
    /// [`web_sys::Event`]: https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Event.html
    Fn(fn(web_sys::Event) -> Option<Message>),

    /// A callback that will convert a [`web_sys::Event`] into a message.
    ///
    /// This variation accepts a message to pass data into the callback.
    ///
    /// [`web_sys::Event`]: https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Event.html
    FnMsg(&'a Message, fn(Message, web_sys::Event) -> Option<Message>),

    /// This callback will recieve the value of a form input and convert it to a message.
    InputValue(fn(String) -> Option<Message>),

    /// A function that will convert a [`web_sys::InputEvent`] event to a Message.
    ///
    /// [`web_sys::InputEvent`]: https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.InputEvent.html
    InputEvent(fn(web_sys::InputEvent) -> Option<Message>),
}

/// A DOM node or JS closure created when applying a patch.
pub enum WebItem<Message> {
    /// A DOM element.
    Element(web_sys::Element),
    /// A DOM text node.
    Text(web_sys::Text),
    /// A JS closure.
    Closure(Closure<dyn FnMut(web_sys::Event)>),
    /// A component.
    Component(Box<dyn Component<Message>>),
    /// A previously occupied, now empty storage entry.
    Taken,
    /// The end of a node.
    ///
    /// Used for tracking the depth in the tree. We need this so we can find the top level elements
    /// in the storage vec.
    Up,
}

impl<Message> WebItem<Message> {
    /// Swap this WebItem with WebItem::Taken and return the item.
    pub fn take(&mut self) -> Self {
        let mut taken = WebItem::Taken;
        mem::swap(self, &mut taken);
        taken
    }

    /// Possibly get a reference to the web_sys::Element in this WebItem.
    pub fn as_element(&self) -> Option<&web_sys::Element> {
        match self {
            WebItem::Element(node) => Some(node),
            _ =>  None,
        }
    }

    /// Possibly get a reference to the web_sys::Text in this WebItem.
    pub fn as_text(&self) -> Option<&web_sys::Text> {
        match self {
            WebItem::Text(node) => Some(node),
            _ =>  None,
        }
    }

    /// Possibly get a reference to the js_sys::Closure in this WebItem.
    pub fn as_closure(&self) -> Option<&Closure<dyn FnMut(web_sys::Event)>> {
        match self {
            WebItem::Closure(closure) => Some(closure),
            _ =>  None,
        }
    }

    /// Possibly get a reference to the Component in this WebItem.
    pub fn as_component(&self) -> Option<&Box<dyn Component<Message>>> {
        match self {
            WebItem::Component(c) => Some(c),
            _ =>  None,
        }
    }
}

impl<Message> fmt::Debug for WebItem<Message> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            WebItem::Element(node) => write!(f, "Element({:?})", node),
            WebItem::Text(text) => write!(f, "Text({:?})", text),
            WebItem::Closure(_) => write!(f, "Closure(_)"),
            WebItem::Component(_) => write!(f, "Component(_)"),
            WebItem::Taken => write!(f, "Taken"),
            WebItem::Up => write!(f, "Up"),
        }
    }
}

/// A list of [`WebItem`]s.
///
/// The list should match the traversal order of the vDOM tree we are operating on.
///
/// [`WebItem`]: enum.WebItem.html
pub type Storage<Message> = Vec<WebItem<Message>>;

/// Items representing all of the data in the DOM tree.
///
/// This is the struct emitted from the `Iterator` passed to our `diff` function. The items emitted
/// should always be in the same order, given the same input. Each entry in the enum represents
/// some aspect of a DOM node. The idea here is the sequence of items will be the same sequence of
/// things seen if we were to walk the DOM tree depth first going through all nodes and their
/// various attributes and events.
#[derive(Debug, PartialEq)]
pub enum DomItem<'a, Message, Command, K> {
    /// An element in the tree.
    Element {
        /// The element name.
        name: &'a str,
        /// An optional key for this element. Should have been generated from a type implementing
        /// [`Hash`] using a [`Hasher`].
        ///
        /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html
        /// [`Hasher`]: https://doc.rust-lang.org/std/hash/trait.Hasher.html
        key: Option<&'a K>,
    },
    /// A text node in the tree.
    Text(&'a str),
    /// Raw HTML code to be rendered using innerHTML. Use with caution as this can be used as an
    /// attack vector to execute arbitrary code in the client's browser.
    UnsafeInnerHtml(&'a str),
    /// An attribute of the last node we saw.
    Attr {
        /// The attribute name.
        name: &'a str,
        /// The attribute value.
        value: &'a str,
    },
    /// An event handler from the last node we saw.
    Event {
        /// The trigger for this event.
        trigger: &'a str,
        /// The handler for this event.
        handler: EventHandler<'a, Message>,
    },
    /// We are finished processing children nodes, the next node is a sibling.
    Up,
    /// A component.
    Component {
        /// An optional key for this component.
        ///
        /// This is necessary if a component has internal state that must be maintained between dom
        /// updates.
        key: Option<&'a K>,
        /// A message to send to the component.
        // XXX msg: &'a Message,
        msg: Message,
        /// A function to create the component if necessary.
        create: fn(Dispatcher<Message, Command>) -> Box<dyn Component<Message>>,
    },
    /// For internal use. This is a reference to a keyed item.
    Key(&'a K),
}

/// This trait provides a way to iterate over a virtual dom representation.
pub trait DomIter<Message: Clone, Command, K> {
    /// Return an iterator over the virtual dom.
    fn dom_iter<'a>(&'a self) -> Box<dyn Iterator<Item = DomItem<'a, Message, Command, K>> + 'a>;
}