Crate domafic [] [src]

Domafic - Safe, high-performance, universal web applications

Domafic is a library for building templates and interactive web applications. Applications built in Domafic can be rendered server-side and used in front-end web servers, but they can also be deployed directly to the client using asm.js and WebAssembly.

A simple template:

use domafic::tags::{div, h1};
use std::marker::PhantomData;

type Msg = ();

// Create a function `render` from `birthday: &'static str` to `DomNode<Message=Msg>`
let render = |birthday: &'static str| div((
    h1((
        "Hello, world! Your birthday is: ", birthday,
    )),

    // Since we don't publish any messages, we need to create an empty node with our
    // message type. This tells the compiler that our message type is `Msg`. This would
    // be unnecessary if we published any messages or if we specified the return type of
    // the `render` function.
    PhantomData::<Msg>,
));

assert_eq!(
    "<div><h1>Hello, world&#33; Your birthday is: Christmas</h1></div>".to_string(),
    render("Christmas").to_string()
);

If you've used HTML or JSX, the syntax should look familiar. Note that we didn't need to use any macros or interpreters-- the template above is just pure, allocation-free Rust. The template itself is just a function that returns a DomNode. The DomNode trait lets us use the result of render as an HTML node. We can write DomNodes to HTML, render them to a live web page using Javascript, or use them as children of other DomNodes.

Domafic's design is similar to that of popular single-state frontend frameworks such as Elm or Redux. An application consists of state, an updater, and a renderer.

The application state holds all of the information needed by the renderer to draw the page. The renderer is a function that takes the current state as input and produces the current UI as output. Finally, the updater is responsible for recieving messages generated by event listeners and updating the application state accordingly.

For example, here is a simple example showing a counter and +/- buttons:

use domafic::tags::{button, div, h1};
use domafic::listener::on;

// If rendering client-side with asm.js or WebAssembly:
#[cfg(target_os = "emscripten")]
use domafic::web_render::{run, JsIo};
#[cfg(target_os = "emscripten")]
use domafic::KeyIter;

type State = isize;

enum Msg {
    Increment,
    Decrement,
}

#[cfg(target_os = "emscripten")]
let update = |state: &mut State, msg: Msg, _: KeyIter, _: &JsIo<Msg>| {
    *state = match msg {
        Msg::Increment => *state + 1,
        Msg::Decrement => *state - 1,
    }
};

let render = |state: &State| {
    div ((
        h1("Hello from rust!"),
        button ((
            on("click", |_| Msg::Decrement),
            "-",
        )),
        state.to_string(),
        button ((
            on("click", |_| Msg::Increment),
            "+",
        )),
    ))
};

// If rendering server-side:
#[cfg(not(target_os = "emscripten"))]
println!("HTML: {}", render(&0));

// If rendering client-side with asm.js or WebAssembly:
#[cfg(target_os = "emscripten")]
run("body", update, render, 0);

Check out more examples like this one in the Github repository.

The above example, if compiled for an emscripten target (via cargo build --target=asmjs-unknown-emscripten or similar) will produce a Javascript file that, when included on a webpage, will replace the contents of "body" with the message "Hello from rust!", +/- buttons, and a number.

So how does this all work? When the call to run occurs, Domafic gives the initial state (0) to the renderer (our "render" function) which returns the initial page to display to the user.

This page includes buttons with listeners for on("click", ...), so when a button is clicked, the appropriate message is generated (either Msg::Increment or Msg::Decrement). This message is then passed into the updater (our update function) and used to update the state.

Once the state is successfully updated, render is called once more to redraw the page. When run in the browser, Domafic keeps an internal DOM (tree-based representation of the UI) and uses it to minimize the changes that need to be made on-screen. This prevents unnecessary re-drawing of UI components.

One last thing you may have noticed: we've been writing our render functions as closures, rather than named functions. The reason for this is that the return type of the render method is long and hard to write out. If you must use named functions, consider using the nightly conservative_impl_trait feature, which will allow you to write the function signature of render like fn render(state: &State) -> impl DomNode<Message=Msg>.

Reexports

pub use dom_node::DomNode;
pub use dom_node::DomValue;
pub use listener::Listener;
pub use listener::Event;
pub use listener::on;
pub use processors::DomNodes;
pub use processors::Listeners;

Modules

dom_node

Trait for elements that can be drawn as to HTML DOM nodes

html_writer

Types, traits and functions for writing a DomNode to HTML

listener

Types, traits, and functions for creating event handlers

processors

Traits for processing collections of DomNodes or Listeners

tags

Types and functions for creating tag elements such as divs or spans

Structs

KeyIter

An iterator over keys into a DomNode tree.

Enums

AttributeValue

A value of a DomNode attribute.

Type Definitions

KeyValue

A mapping between an attribute key and value. Example: ("key", AttributeValue::Str("value"))