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
190
191
192
193
194
195
196
197
198
199
200
//! Reactive component trees using two way model and view message passing.
//!
//! Sometimes an application can get so entangled that it's hard to follow the
//! path of messages through `Transmitter`s, `Receiver`s and fold functions. For
//! situations like these where complexity is unavoidable, Mogwai provides the
//! [Component] trait and the helper struct [`Gizmo`].
//!
//! Many rust web app libraries use a message passing pattern made famous by
//! the Elm architecture to wrangle complexity. Mogwai is similar, but different
//! - Like other libraries, messages come out of the DOM into your component's
//!   model by way of the [Component::update] function.
//! - The model is updated according to the value of the model message.
//! - _Unlike_ Elm-like libraries, view updates are sent out of the update
//!   function by hand! This sounds tedious but it's actually no big deal. You'll
//!   soon understand how easy this is in practice.
//!
//! Mogwai lacks a virtual DOM implementation. One might think that this is a
//! disadvantage but to the contrary this is a strength, as it obviates the
//! entire diffing phase of rendering DOM. This is where Mogwai gets its speed
//! advantage.
//!
//! Instead of a virtual DOM Mogwai uses channels to patch the DOM from afar. The
//! [`Component::update`] method is given a [`Transmitter<Self::ViewMsg>`] with which
//! to send _view patching messages_. Messages sent on this transmitter will
//! be sent out to the view to update the DOM (if that view chooses to). This forms a
//! cycle:
//! 1. Messages come into the update function from the view which processes the message,
//! updates the state, and may send messages out to the view
//! 2. Message come into the view from the update function where they are used to patch
//! the DOM.
//!
//! In this way DOM updates are obvious. You know exactly where, when and
//! why updates are made - both to the model and the view.
//!
//! Here is a minimal example of a [`Component`] that counts its own clicks.
//!
//! ```rust
//! # extern crate mogwai;
//! use mogwai::prelude::*;
//!
//! #[derive(Clone)]
//! enum In {
//!     Click
//! }
//!
//! #[derive(Clone)]
//! enum Out {
//!     DrawClicks(i32)
//! }
//!
//! struct App {
//!     num_clicks: i32
//! }
//!
//! impl Component for App {
//!     type ModelMsg = In;
//!     type ViewMsg = Out;
//!     type DomNode = HtmlElement;
//!
//!     fn view(&self, tx: &Transmitter<In>, rx: &Receiver<Out>) -> ViewBuilder<HtmlElement> {
//!         builder! {
//!             <button on:click=tx.contra_map(|_| In::Click)>
//!                 {(
//!                     "clicks = 0",
//!                     rx.branch_map(|msg| match msg {
//!                         Out::DrawClicks(n) => {
//!                             format!("clicks = {}", n)
//!                         }
//!                     })
//!                 )}
//!             </button>
//!         }
//!     }
//!
//!     fn update(&mut self, msg: &In, tx_view: &Transmitter<Out>, _sub: &Subscriber<In>) {
//!         match msg {
//!             In::Click => {
//!                 self.num_clicks += 1;
//!                 tx_view.send(&Out::DrawClicks(self.num_clicks));
//!             }
//!         }
//!     }
//! }
//!
//!
//! pub fn main() -> Result<(), JsValue> {
//!     let app = Gizmo::from(App{ num_clicks: 0 });
//!
//!     if cfg!(target_arch = "wasm32") {
//!         View::from(app).run()
//!     } else {
//!         Ok(())
//!     }
//! }
//! ```
//!
//! As shown above, the first step is to define the incoming messages that will update the model.
//! Next we define the outgoing messages that will update our view. The [`Component::view`]
//! trait method uses these message types to build the view. It does this by
//! consuming a `Transmitter<Self::ModelMsg>` and a `Receiver<Self::ViewMsg>` and returning
//! a [`ViewBuilder`].
//! These terminals represent the inputs and the outputs of your component. Roughly,
//! `Self::ModelMsg` comes into the [`Component::update`] function and `Self::ViewMsg`s go out
//! of the `update` function.
//!
//! ## Creating a component
//!
//! To use a component after writing its `Component` trait implementation we turn it into a
//! [`Gizmo`]:
//!
//! ```rust, ignore
//!     let app: Gizmo<App> = Gizmo::from(App{ num_clicks: 0 });
//! ```
//!
//! [`Gizmo`]s can then be used to spawn a view, or can be converted into a view.
//!
//! ```rust, ignore
//!     let view = View::from(app.view_builder());
//! ```
//!
//! ```rust, ignore
//!     let view = View::from(app);
//! ```
//!
//! ## Communicating to components
//!
//! If your component is owned by another, the parent component can communicate to
//! the child through its messages, either by calling [`Gizmo::send`]
//! on the child component within its own update function or by subscribing to
//! the child component's messages when the child component is created (see
//! [`Subscriber`]).
//!
//! ## Placing components
//!
//! A parent component may nest an in-scope component by placing a [`ViewBuilder`]
//! or [`View`] inside the parent component's RSX:
//! ```rust, ignore
//! let child = builder! { <blockquote>"Fairies live"</blockquote> };
//! let parent = builder! {
//!     <div id="fairy_quote">{child}</div>
//! };
//! ```
use wasm_bindgen::JsCast;
use web_sys::Node;

#[allow(unused_imports)]
use crate::prelude::{Gizmo, ParentView, Receiver, Transmitter, View, ViewBuilder};

pub mod subscriber;
use subscriber::Subscriber;

/// Defines a component with distinct input (model update) and output
/// (view update) messages.
///
/// See the [module level documentation](super::component) for more details.
pub trait Component
where
    Self: Sized + 'static,
    Self::ModelMsg: Clone,
    Self::ViewMsg: Clone,
    Self::DomNode: JsCast + AsRef<Node> + Clone,
{
    /// Message type used to drive component state updates.
    type ModelMsg;

    /// Message type used to drive view DOM patching.
    type ViewMsg;

    /// The type of [`web_sys::Node`] that represents the root of this component.
    /// ie HtmlElement, HtmlInputElement, etc.
    type DomNode;

    /// Used to perform any one-time binding from in scope [`Gizmo`]s or [`Model`]s to this component's subscribers.
    ///
    /// This function will be called only once, after a [`Gizmo`] is converted from the
    /// type implementing `Component`.
    #[allow(unused_variables)]
    fn bind(&self, input_sub: &Subscriber<Self::ModelMsg>, output_sub: &Subscriber<Self::ViewMsg>) {
    }

    /// Update this component in response to any received model messages.
    /// This is essentially the component's fold function.
    fn update(
        &mut self,
        msg: &Self::ModelMsg,
        tx_view: &Transmitter<Self::ViewMsg>,
        sub: &Subscriber<Self::ModelMsg>,
    );

    /// Produce this component's view using a `Transmitter` of model input messages
    /// and a `Receiver` of view output messages.
    ///
    /// Model messages flow from the view into the update function. View messages
    /// flow from the update function to the view.
    fn view(
        &self,
        tx: &Transmitter<Self::ModelMsg>,
        rx: &Receiver<Self::ViewMsg>,
    ) -> ViewBuilder<Self::DomNode>;
}