Components

A "component", "widget", "gizmo" in mogwai is a view and zero or more async tasks that modify that view. To create a view we use a ViewBuilder:

Components may have asynchronous tasks appended to them. When the view is built its tasks will be spawned until they are complete, or when the view implementation decides to drop and cancel them.

A view talks to its task loops using sinks and streams. This is the mode of mogwai.

Here is an example of a click counter:

fn counter(recv_parent_msg: impl Stream<Item = CounterMsg> + Send + 'static) -> ViewBuilder {
    let clicked = Output::<CounterMsg>::default();
    let mut num_clicks = Input::<u32>::default();
    let click_stream = clicked.get_stream();

    rsx! (
        button(on:click = clicked.sink().contra_map(|_: JsDomEvent| CounterMsg::Click)) {
            {(
                "clicks = 0",
                num_clicks.stream().unwrap().map(|n| format!("clicks = {}", n))
            )}
        }
    )
    .with_task(async move {
        let mut msg = click_stream.boxed().or(recv_parent_msg.boxed());
        let mut clicks: u32 = 0;
        loop {
            match msg.next().await {
                Some(CounterMsg::Click) => {
                    clicks += 1;
                }
                Some(CounterMsg::Reset) => {
                    clicks = 0;
                }
                None => break,
            }

            num_clicks.set(clicks).await.unwrap();
        }
    })
}

We can nest the counter component in another component:

fn app() -> ViewBuilder {
    let reset_clicked = Output::<CounterMsg>::default();

    rsx! {
        div() {
            "Application"
            br(){}
            {counter(reset_clicked.get_stream())}
            button(on:click = reset_clicked.sink().contra_map(|_:JsDomEvent| CounterMsg::Reset)){
                "Click to reset"
            }
        }
    }
}

And then build it all into one view:

    let view = JsDom::try_from(app()).unwrap();