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::{DomNode, IntoNode}; use domafic::tags::{div, h1}; use domafic::empty::empty; 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: ".into_node(), birthday.into_node(), )), // 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. empty::<Msg>(), )); assert_eq!( "<div><h1>Hello, world! 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 DomNode
s to HTML, render them to a live
web page using Javascript, or use them as children of other DomNode
s.
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::IntoNode; 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; #[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, _keys: KeyIter| { *state = match msg { Msg::Increment => *state + 1, Msg::Decrement => *state - 1, } }; let render = |state: &State| { div (( h1("Hello from rust!".into_node()), button (( on("click", |_| Msg::Decrement), "-".into_node(), )), state.to_string().into_node(), button (( on("click", |_| Msg::Increment), "+".into_node(), )), )) }; // 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 dom_node::IntoNode; |
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 |
empty |
Types and functions for creating |
html_writer |
Types, traits and functions for writing a |
listener |
Types, traits, and functions for creating event handlers |
processors |
Traits for processing collections of |
tags |
Types and functions for creating tag elements such as |
Structs
KeyIter |
An iterator over keys into a |
Enums
AttributeValue |
A value of a |
Type Definitions
KeyValue |
A mapping between an attribute key and value.
Example: |