Our first DOMINATOR UI

It's time to go through the basics of making a simple gui application using DOMINATOR! Let's briefly go over the project setup of the first example application, and then we'll get started.

Program structure

A dominator project is a rust project that compiles to wasm. This means we can use a fairly recognisable rust project structure.

To build the application for the browser, there are a few options available. Throughout this tutorial, we will use rollup with the @wasm-tool/rollup-plugin-rust.

We will not cover the details of setting up a project with rollup here, but you can take a look at the tutorials/hello-world project for a simple example. Feel free to modify the rollup.config.js file to suit your needs.

In the dist folder, we have the index.html file. It is the entry point for our application, and it loads the js bootstrap file that will be generated by rollup. This js file will set up the memory for and load the wasm module, and take care of any bindings we may need between v8 and the wasm world.

The code

We start out with a very simple, yet obligatory, hello world example:

use dominator::{append_dom, body, html};
use wasm_bindgen::prelude::*;

#[wasm_bindgen(start)]
fn main() {
    append_dom(&body(), html!("h1", {
        .text("Hello, world!")
    }));
}

Although simple, there are a lot of things to unpack here!

The main function is annotated with #[wasm_bindgen(start)]. This instructs wasm_bindgen to generate code such that the function will be called when the wasm module is loaded, much like the main function in a binary crate.

The append_dom function from dominator lets us attach a dom node to an existing DOM node. In this case, we use the body utility function to get a reference to the body node, and attach the h1 node we create to it.

When executed in the browser, the hello world example above inserts the following DOM structure into the body element:

<h1>Hello,world!</h1>

Get used to the html! macro

The html! macro from dominator will be our main tool for constructing dom HTML nodes.

The first parameter is the tag name to construct.

The second parameter is a list of chained method calls that will be applied to the DomBuilder<HtmlElement> instance used to construct the node. Don't worry about understanding all the details of this just yet! Let's just agree on a name for referencing the block containing chained method calls; the method_block.

For now, be content to know that calling .text() within the block we pass to the macro will create a text node with the provided static text content!

Adding child elements

To create any useful dom structures, we have to be able to create child elements on our nodes. There are a few methods for doing this on the DomBuilder:

html!("div", {
    .child(html!("span", { .text("A child element") }))
    .children([
        html!("span", { .text("Another child") }),
        html!("span", { .text("Another child") }),
        html!("span", { .text("Another child") }),
    ])
})

How it works

Dominator does not hold any internal representation of the DOM tree in memory. When we create dom nodes using the html! macro, we simply use standard browser APIs to create and manipulate them.

What we get back from the DomBuilder is a Dom instance, which acts as a handle to the real, actual in-the-browser DOM node. This means we can perform any action on the DOM (and the rest of the browser environment for that matter!) via the generated js_sys and web_sys bindings.

We also need to be aware that there is no VDOM, diffing or other helper in place to alleviate dom updates/style recalculations. This sounds problematic, but as we will discuss later, using futures-signals idiomatically actually makes it super easy to make near-optimal DOM updates!

The rest of the tutorials

To avoid having too many tutorial applications (and their corresponding node_modules occupying ludicrous amounts of drive space), the rest of the tutorial code will reside in the tutorials/all_the_others folder.

Inside of this application there is a sub-folder named tutorials, which in turn contains one sub folder for each tutorial we will be referencing.

When you run this application, the web page contains a tabbed view of all the tutorials, so you can switch between them on the fly.