Expand description

Zero-cost ultra-high-performance declarative DOM library using FRP signals for Rust!

Getting Started

Can be used as a standalone DOM rendering library but comes into its own when paired with futures-signals. The best place to start is the Signals tutorial and then become acquainted with DomBuilder<A> and html!. Idiomatic use is:

  • Create struct components that own data
  • Wrap any data that can change in a Mutable<T>, MutableVec<T>, or MutableBTreeMap<K, V>
  • Create render functions for each struct component
  • Avoid local state in the DOM: all flags, triggers, data etc. for rendering should be stored in the struct
  • Call the render functions for each component in a chain all the way down

dominator handles any necessary DOM updates on changes to the data. A basic app looks something like the below, but refer to the examples for a comprehensive set of best practices.

use dominator::{events::Click, html, Dom};
use futures_signals::signal::Mutable;
use wasm_bindgen::prelude::*;

// Top level App component
struct App {
    name: String,
    info: Info, // Info sub-component
}

impl App {
    fn new(msg: &str) -> Self {
        Self {
            name: String::from("Dominator App"),
            info: Info::new(msg),
        }
    }
    fn render(app: Self) -> Dom {
        html!("div", {
            .text(&app.name)
            // Call sub-component rendering functions
            .child(Info::render(&app.info))
        })
    }
}

// Info sub-component
struct Info {
    msg: Mutable<String>, // String value that can change over time
}

impl Info {
    fn new(msg: &str) -> Self {
        Self {
            msg: Mutable::new(String::from(msg)),
        }
    }
    fn render(info: &Self) -> Dom {
        html!("button", {
            // Text will automatically update on any changes to `msg`
            .text_signal(info.msg.signal_cloned())
            .event({
                // Clone due to move + 'static
                let msg = info.msg.clone();
                // Update `msg` when clicked
                move |_: Click| msg.set(String::from("Clicked"))
            })
        })
    }
}

#[wasm_bindgen(start)]
pub fn main_js() -> Result<(), JsValue> {
    let app = App::new("Hello world");
    dominator::append_dom(&dominator::body(), App::render(app));
    Ok(())
}

Handling 'static web apis

There are lots of 'static lifetime requirements for web apis and therefore calls to move and clone: so Arc<T> and Rc<T> quickly come in handy. Which to use? As a general rule Arc<T> for rust types unless they !Send: currently on WASM rust uses single threaded primitives so there is no cost to using atomics and you will be ready for multi-threaded wasm; use Rc<T> for any JS values which are not thread safe and likely never will be.

There is the clone! macro which is a nice shorthand for the many calls to .clone().

MutableVec<T> and MutableBTreeMap<K, V> do not impl Clone so if you want to make them cloneable you will need to wrap them in an Arc<T> or Rc<T>.

Clone and Mutable<T>

Mutable<T> uses Arc<T> internally so cloning it calls Arc::clone and will create another pointer to the same allocation. This can have subtle consequences when cloning any structs with Mutable<T> in.

#[derive(Clone)]
struct Info {
    msg: Mutable<String>,
}

let item = Info {
    msg: Mutable::new(String::from("original")),
};

let item_clone = item.clone();

// All updates to `msg` on the clone will update it on the original
item_clone.msg.set(String::from("both changed"));

assert_eq!(item.msg.get_cloned(), item_clone.msg.get_cloned());

// All updates to `msg` on the original will update it on the clone
item.msg.set(String::from("changed again"));

assert_eq!(item.msg.get_cloned(), item_clone.msg.get_cloned());

Mixins

dominator has a great way of creating reusable functionalities and components: create a function with the signature DomBuilder<A> -> DomBuilder<A>.

fn mixin<A>(builder: DomBuilder<A>) -> DomBuilder<A> {
    // Do some stuff with the builder and then return it
    builder
}

It can then be called from within the html! macro using the apply or apply_if methods:

html!("div", {
    .apply(mixin)
    .apply_if(true, mixin)
})

js Bundler

dominator works really well when paired with the rollup.js and the rust rollup plugin. See the examples folders for how to get setup.

Modules

Macros

Allows the application of methods to a type using the standard dominator macro syntax. Used internally by most of the other macros html!, with_node!, with_cfg!, shadow_root!, dom_builder!, stylesheet!, class!. It puts each method call in a separate statement: this is to ensure that a lock created inside one method call will not extend to the rest of the method calls. Since Mutable<T>, MutableVec<T>, or MutableBTreeMap<K, V> all use RwLock internally this is important for subsequent method calls that may want to access the same data. It also accepts some of the other macros with_node!, with_cfg!, shadow_root!, pseudo! and will elide the first this:expr parameter in the macro calls.

Creates a unique auto-generated CSS classname and injects it into document.styleSheets. It wraps ClassBuilder and takes any of its methods.

Used as a shorthand syntax for calling .clone() when parsing variables into functions. Due to 'static requirements of many of the web apis move and clone are frequently required.

Used to wrap an existing DOM node in a DomBuilder<A> where the node being wrapped must impl AsRef<web_sys::Element>.

Used to build a Dom and is a wrapper over DomBuilder<A>. By default the macro is internally typed to HtmlElement. Note if you want to generate SVG make sure to use the svg! macro.

Used to generate pseudo classes and elements. Usually called from within the class! macro:

Attaches DOM elements to the ShadowRoot of a DOM element. The internal element type of the macro is ShadowRoot and it requires ShadowRootMode as a parameter.

Creates a global CSS stylesheet: similar to creating a .css file. It wraps StylesheetBuilder, the first argument is the element selector(s) that needs to impl MultiStr and then any of the StylesheetBuilder methods.

Used to create SVG elements. Exactly like the html! macro in that it wraps DomBuilder<A> but it creates elements correctly namespaced to SVG. The default internal element type is SvgElement

Used to apply methods to a builder based upon a standard rust #[cfg(predicate)]. Typically used within one of the other builder macros where the first builder term is elided. If used with the rollup-plugin-rust the required cargo build arguments are added using cargoArgs: ["--features", "foo"].

Provides owned access to the internal element of a dom_builder! and is used for passing the element into other builder methods. Runs before the element is inserted into the DOM. Typically used within html! where the first builder term is elided.

Structs

Enums

The ShadowRootMode enum.

Constants

Functions