3 unstable releases
0.2.1 | Jun 2, 2020 |
---|---|
0.2.0 | Jun 1, 2020 |
0.1.2 | Dec 20, 2019 |
#19 in #full-stack
Used in 2 crates
130KB
3.5K
SLoC
Reign View is a component based HTML templating library for Rust inspired by Vue.js templates.
This library makes using templates as easy as pie. It uses HTML based template syntax that are valid and can be parsed by spec-compliant browsers and HTML parsers [1][2]. It has been developed foremost with ease of use in mind followed by future extensibility, modularization and customization.
This library also provides multiple helpers and feature gates which an user can use to customize, allowing the library to be used directly with or without reign_router.
Please refer to API documentation for more details.
NOTE: Minimum supported Rust version is 1.45.0
Table of contents
Quickstart
-
Add reign to your code base with default features ommitted and
view
feature enabled[dependencies] reign = { version = "*", features = ["view"], default-features = false }
-
Initiate the templates in your
main.rs
use reign::prelude::*; // If your templates live under `src/views` folder views!("src", "views");
-
Write a template in
src/views/pages/about.html
<p> {{ name }} <sub>aged {{ age: u8 }}</sub> </p>
-
Render the template
use reign::prelude::*; let (name, age) = ("John", 28); // The macro automatically captures all the // variables it needs, builds the view to display // and returns a `String` // // `pages::about` is the unique path of the above template render!(pages::about)
How it works
There are multiple steps that goes into this templating library.
- Building a view out of the HTML template.
- Rendering the view into a String.
Building
Let's assume that you have written the template described in the previous section at the respective path.
Now, when you initiate the templating library by writing the following:
use reign::prelude::*;
views!("src", "views");
The library expands the views!
macro to something like the following:
// It will create a module which is always called `views`
mod views {
// Then it will create a module for `pages` subdirectory
pub mod pages {
// Then it will create a struct for the template file
pub struct About<'a> {
// It will add a raw string field for every variable
// found which does not have a type described
pub name: &'a str,
// It will add a typed field for every variable found
// that was given a type (once in a template is enough)
pub age: u8,
}
use std::fmt::{Display, Formatter, Result};
// Then it will implement std::fmt::Display for it
impl Display for About<'_> {
fn fmt(&self, f: &mut Formatter) -> Result {
write!(f, "{}{}{}{}{}",
"<p>\n ", self.name, "\n <sub>aged ", self.age, "</sub>\n</p>"
)
}
}
}
}
The above expansion is approximate. There might be small changes in the way they were expanded or other hidden things that are for internal use.
You can read more about template syntax below here
Rendering
When the plain view feature is enabled and when you try to render a template like the following:
use reign::prelude::*;
let (name, age) = ("John", 28);
render!(pages::about);
The library expands the render!
macro to something like the following:
format!("{}", crate::views::pages::About {
name: name.as_ref(),
age,
});
Which returns the following String:
<p>
John
<sub>aged 28</sub>
<p>
Template Syntax
Before we start talking about the template syntax, let's agree on a few terms for this section so that it will be easier to refer to them later on.
An expression is a custom subset of all the types of expressions available in Rust language. You can read about them here.
A pattern is a custom rust pattern syntax where the expressions allowed are the only ones defined in the above paragraph. You can read more about them here.
A field refers to a field of the struct that is built for the template by
the views!
macro when initiating the template library.
All html style tags that are used in the template should be closed either by a self closing syntax or an end tag. The only exception are the tags which are allowed by HTML spec to be self closing by default called void elements.
Text
The most basic form of templating is "interpolation" using the "mustache" syntax (double curly braces).
<span>Message: {{ msg }}</span>
The mustache tag will be replaced with the value of the msg
field. You can
also use an expression inside the mustache tags. Any type that has
std::fmt::Display
implemented can be the final result of the expression
defined by the mustache tags.
<span>Word Count: {{ msg.len() }}</span>
Attributes
Interpolation can also be used in values of attributes.
<div title="Application - {{ page_name }}"></div>
If you want to use "
inside the attribute value for an expression, you can
follow the HTML spec and surround the value with '
.
<div title='Application - {{ "Welcome" }}'></div>
Variable Attributes
If you want to have an attribute that is completely interpolated with just one mustache tag and nothing else, you can do this.
<div :title="page_name"></div>
The value of the attribute title
will be the value of page_name
field.
Control Attributes
The library doesn't allow if
conditions and for
loops as expressions
inside the mustache tags. You can use a control attribute on html tags to do this.
<div !if='page_name == "home"'>Welcome</div>
<div !else>{{ page_name }}</div>
The above template prints either the first or the second div
depending
on the expression provided to the !if
control attribute.
!else-if
is also supported as you would expect.
for
loops also make use of control attribute syntax like shown below.
<div !for="char in page_name.chars()">{{ char }}</div>
The above template prints div
tags for all the characters in page_name
field. Other than the name difference in the control attribute, !for
needs
pattern in expression syntax.
Grouping Elements
Because !if
and !for
are attributes, they need to be attached to a single
tag. But sometimes, you might need to render more than one element. In that
case, you can use the template
tag which works like an invisible wrapper.
<template v-if="condition">
<h1>{{ title }}</h1>
<p>{{ content }}</p>
</template>
A template doesn't allow multiple elements to be defined at the root. Which
means, you can use the template
tag to group elements at the root of
the template.
<template>
<!DOCTYPE html>
<html></html>
</template>
Class & Style bindings
To be implemented
Components
Every template can be used as a component by default and thus is reusable. Let us
assume we have a template src/views/shared/button.html
with the following:
<button :href="href">{{text}}</button>
The generated view for this would look like the following:
struct Button<'a> {
href: &'a str,
text: &'a str,
}
The above template can be used in other templates using the following syntax:
<navbar>
<shared:button href="/" text="Dashboard" />
<shared:button href="/settings" text="Account" />
</navbar>
The attributes on a component work just like the attirbutes on a normal HTML element described above.
Any template can be used as a component. We can refer to the template by using it's
tag reference. Tag reference can be achieved by joining all the parts in the path of the
component with :
after converting them to kebab case. A template that lives at
src/views/users/avatar.html
can be used with users:avatar
, and similarily a template
that lives at src/views/common/simple/small_icon.html
can be used with common:simple:small-icon
.
Slots
Just like with HTML elements, it’s often useful to be able to pass content to a component, like this:
<div>
<shared:button href="/">
<span class="icon"></span>
Dashboard
</shared:button>
</div>
Fortunately, this is possible by using our custom slot
element in the button.html
.
<button :href="href">
<slot></slot>
</button>
The <slot></slot>
above will be replaced by the elements we described inside
shared:button
element when it's used.
Scope
When we use a variable inside a component's slot, such as:
<div>
<shared:button href="/">{{ link_text }}</shared:button>
</div>
That slot tries to access this templates fields (i.e. the same scope
). The slot does
not have access to <shared:button>
's fields. For example, trying to access href
would not work:
<div>
<shared:button href="/">{{ href }}</shared:button>
</div>
It would instead create a href
field on the template itself.
Fallback
To be implemented
Named Slots
There are times when it’s useful to have multiple slots. For example, in a <base-layout>
component with the following template:
<div class="container">
<header>
<!-- We want header content here -->
</header>
<main>
<!-- We want main content here -->
</main>
<footer>
<!-- We want footer content here -->
</footer>
</div>
For these cases, the <slot>
element has a special attribute, name
, which can be
used to define additional slots:
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
A <slot>
outlet without name
implicitly has the name "default"
.
To provide content to named slots, we can use a special attribute on a <template>
,
providing the name of the slot:
<layout:base>
<template #header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template #footer>
<p>Here's some contact info</p>
</template>
</layout:base>
Now everything inside the <template>
elements will be passed to the corresponding
slots. Any content not wrapped in a <template>
is assumed to be for the default slot.
However, we can still wrap default slot content in a <template>
if you wish to be explicit.
Helpers & Feature Gates
There are multiple feature gates on Reign to help the user select what he wants from the library.
Please refer to examples to see how they are used.
Please refer to reign_derive for more information about the usage of macros.
view
views!
can be used to build the views.render!
can be used to render a view into a string.
view-backend
views!
can be used to build the views.- Enables
render
helper andrender!
renders a view into response for reign_router backend handler. - Enables
redirect
helper for reign_router backend handler. - Enables
json
helper andjson!
builds response for reign_router backend handler
Appendix
Expressions
Allowed expressions are described below with bop
being a binary operator and
uop
being an unary operator. ...
represents possible repetitions.
literal
ident
[expr, ...]
expr bop expr
uop expr
expr(expr, ...)
expr.ident
expr.number
expr[expr]
(expr)
[expr; expr]
(expr, ...)
& expr
expr as type
expr: type
expr..expr
type { ident: expr, ..expr, ... }
Patterns
Allowed patterns are described below with expr
represents the above mentioned
expression. ...
represents possible repetitions.
ident
_
& pat
type { pat, .., ... }
(pat, ...)
type(pat, .., ...)
Annotations
- Tag names can contain
:
which is not completely supported by pure HTML5 spec but most of the parsers support it. - We also assume the parsers are made with Web Components specification in mind.
Dependencies
~3.5–6.5MB
~125K SLoC