2 releases

0.5.1 May 9, 2020
0.5.0 May 9, 2020

#27 in #spa


Used in savory-html

MIT/Apache

16KB
88 lines

Library for building user interface

TODO: add README content


lib.rs:

Savory is library for building user interface.

master docs · crate info · pipeline · rustc version · unsafe forbidden

Features

  • Views: Views can be any type implement View trait or any standalone function that returns Node, views can be trait object which make them very composable.
  • Elements: Savory uses elements as core building unit when building stateful UI. Elements owns thier state and handle user inputs via messages.
  • Collection of UI elements: Savory ships with collection of resuable and themeable UI elements.
  • Theme: UI elements can be themed by any type that implement ThemeImpl trait, themes have full control on the element appearance.
  • Typed HTML: Use typed CSS and HTML attributes, Savory try hard not to rely on strings when creating CSS and HTML attributes since these can produce hard to debug bugs.
  • Enhance Seed API: Enhancement on Seed API that makes working with Node, Orders fun.

Savory tries to make writing UI elements fun and boilerplate free.

Savory crates:

Core Concept

Savory have two main types View and Element, View types produce static HTML, while Element types produce interactive HTML, as simple as that.

Elements types must implemente Element and View traits, which would make them interactive.

View types must implemente View trait, that would produce the static HTML.

Counter Example

Here is very simple counter, that doesn't use all Savory features, but it's good as starting point for newcomers.

use savory_core::prelude::*;
use savory_html::prelude::*;
use wasm_bindgen::prelude::*;

// app element (the model)
pub struct Counter(i32);

// app message
pub enum Msg {
    Increment,
    Decrement,
}

impl Element for Counter {
    type Message = Msg;
    type Config = Url;

    // initialize the app in this function
    fn init(_: Url, _: &mut impl Orders<Msg>) -> Self {
        Self(0)
    }

    // handle app messages
    fn update(&mut self, msg: Msg, _: &mut impl Orders<Msg>) {
        match msg {
            Msg::Increment => self.0 += 1,
            Msg::Decrement => self.0 -= 1,
        }
    }
}

impl View<Node<Msg>> for Counter {
    // view the app
    fn view(&self) -> Node<Msg> {
        let inc_btn = html::button().add("Increment").on_click(|_| Msg::Increment);
        let dec_btn = html::button().add("Decrement").on_click(|_| Msg::Decrement);

        html::div()
            .add(inc_btn)
            .add(self.0.to_string())
            .add(dec_btn)
    }
}

#[wasm_bindgen(start)]
pub fn view() {
    // mount and start the app at `app` element
    Counter::start();
}

Dependencies

~19MB
~327K SLoC