Skip to content

Commit

Permalink
save
Browse files Browse the repository at this point in the history
  • Loading branch information
levkk committed Oct 18, 2024
1 parent a0498fc commit 95ffb40
Show file tree
Hide file tree
Showing 11 changed files with 346 additions and 5 deletions.
4 changes: 3 additions & 1 deletion docs/docs/controllers/sessions.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,6 @@ let response = Response::new()

## Renew sessions

Sessions are automatically renewed on each request. Expired sessions are renewed as well, unless session [authentication](../authentication) is enabled.
Sessions are automatically renewed on each request. This allows your active users to remain "logged in", while inactive ones would be redirected to a login page if session [authentication](../authentication) is enabled.

Expired sessions are not renewed, so a user holding an expired session will need to use an authentication controller to get a new valid session.
6 changes: 3 additions & 3 deletions docs/docs/views/index.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# Views basics

Rwf comes with a [templating](templates/) library that can generate dynamic pages. Dynamic templates allow you to generate unique HTML pages on the fly, and libraries like [Turbo](turbo) can use it in a way that feels like a native frontend application.
Rwf comes with a [templating](templates/) library that can generate dynamic pages. Dynamic templates allow you to create unique HTML pages on the fly, and libraries like [Turbo](turbo) can use it in a way that feels like a native frontend application.

## What are views?

Views are the **V** in MVC: they control what your users see and how they experience your web app. Separating views from [controllers](../controllers/) allows them to reuse similar parts of your web app on different pages.
Views are the **V** in MVC: they control what your users see and how they experience your web app. Separating views from [controllers](../controllers/) allows controllers to reuse similar parts of your web app on different pages without code duplication.


## Using JavaScript frontends

If you prefer to build your frontend with JavaScript libraries like React or Vue, take a look at Rwf's [REST](../controllers/REST/) API documentation. While useful, Rwf templates are not required to build web applications.
If you prefer to build your frontend with JavaScript libraries like React or Vue, take a look at Rwf's [REST](../controllers/REST/) API documentation. Rwf templates are not required to build web applications.

## Learn more

Expand Down
7 changes: 7 additions & 0 deletions docs/docs/views/templates/.pages
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
nav:
- 'index.md'
- 'variables.md'
- 'if-statements.md'
- 'for-loops.md'
- 'functions.md'
- '...'
19 changes: 19 additions & 0 deletions docs/docs/views/templates/caching.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Template cache

Templates are compiled and evaluated on the fly. This is handy for local development, allowing you to modify the template without recompiling the Rust app or restarting the web server, but in production could be an unnecessary performance hindrance.

The template cache makes sure a template is compiled only once. All subsequent executions of the template will use an internal representation and are much faster to run.

## Using the cache

To use the template cache, templates must be stored on disk, for example in a `templates` directory. Loading a template should use the [`Template::load`](https://docs.rs/rwf/latest/rwf/view/template/struct.Template.html#method.load) function:

```rust
let template = Template::load("templates/index.html")?;
```

The first time the template is loaded, it will be fetched from disk and compiled. Once compiled, it will be stored in the cache to be reused by all subsequent calls to [`Template::load`](https://docs.rs/rwf/latest/rwf/view/template/struct.Template.html#method.load).

## Enable the cache

The template cache is disabled by default. To enable it, toggle the `cache_templates` setting in [configuration](../../../configuration).
28 changes: 28 additions & 0 deletions docs/docs/views/templates/for-loops.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# For loops

Rwf templates have only one kind of for loop: for each. This allows writing more reliable templates, and to avoid common bugs like infinite loops, which will stall a web app in production.

A for loop can iterate over a list of values, for example:

```erb
<ul>
<% for value in list %>
<li><%= value %></li>
<% end %>
</ul>
```

Template lists, unlike Rust's `Vec`, can hold [variables](../variables) of different data types, and are dynamically evaluted at runtime:

=== "Template"
```erb
<% for value in ["one", 2 * 5, 3/1.5] %>
<%= value %>
<% end %>
```
=== "Output"
```
one
10
2.0
```
1 change: 1 addition & 0 deletions docs/docs/views/templates/functions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Functions
45 changes: 45 additions & 0 deletions docs/docs/views/templates/if-statements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# If statements

If statements allow you to control the flow of templates, conditionally displaying some elements while hiding others. For example, if a [variable](../variables) is "falsy", you can hide entire sections of your website:

```erb
<% if logged_in %>
<!-- profile page -->
<% else %>
<!-- login page -->
<% end %>
```

If statements start with `if` and must always finish with `end`.

## Expressions

If statements support evaluating large expressions for truthiness, for example:

```erb
<% if var.lower + "_" + bar.upper == "lo_HI" %>
<!-- do something -->
<% end %>
```

While it's advisable to write simple if statements and delegate complex logic to views where the Rust compiler can be more helpful, Rwf template language is almost [Turing-complete](https://en.wikipedia.org/wiki/Turing_completeness) and can be used to write arbitrarily complex templates.

### Operator precendence

Templates respect operator precedence, e.g., multiplication is performed before addition, unless parentheses are specified (which are also supported).

## Else If

If statements support else if blocks (written as `elsif`), evaluating multiple expressions and executing the first one which evaluates to true:

```erb
<% if one %>
<!-- one -->
<% elsif two %>
<!-- two -->
<% elsif three %>
<!-- three -->
<% else %>
<!-- I guess it's four? --->
<% end %>
```
48 changes: 47 additions & 1 deletion docs/docs/views/templates/index.md
Original file line number Diff line number Diff line change
@@ -1 +1,47 @@
# Templates overview
# Templates overview

Dynamic templates are a mix of HTML and a programming language which directs how the HTML is displayed. For example, if you have a profile page for your web app users, you would want each of your users to have a page unique to them. To achieve this, you would write only one template and substitute unique aspects of each using template variables, for example:

```erb
<div class="profile">
<h2><%= username %></h2>
<p><%= bio %></p>
</div>
```

The variables `username` and `bio` can be substituded for values unique to each of your users, for example:

=== "Rust"
```rust
use rwf::prelude::*;

let template = Template::from_str(r#"
<div class="profile">
<h2><%= username %></h2>
<p><%= bio %></p>
</div>
"#)?;

let html = template.render([
("username", "Alice"),
("bio", "I like turtles")
])?;

println!("{}", html);
```
=== "Output"
```html
<div class="profile">
<h2>Alice</h2>
<p>I like turtles</p>
</div>
```

Templates help reuse HTML (and CSS, JavaScript) just like regular functions and structs help
reuse code.

## Learn more

- [Variables](variables)
- [For loops](for-loops)
- [If statements](if-statements)
1 change: 1 addition & 0 deletions docs/docs/views/templates/nomenclature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Nomenclature
162 changes: 162 additions & 0 deletions docs/docs/views/templates/variables.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# Variables

Template variables are used to substitute unique information into a reusable template. Rwf supports variables of different kinds, like strings, numbers, lists, and hashes. Complex variables like hashes can be iterated through using [for loops](../for-loops).

## Using variables

Using variables in your templates is typically done by "printing" them, or outputting them, into the template text, by placing them between `<%=` and `%>` tags:

```erb
<%= variable %>
```

The `<%=` tag indicates what follows is an [expression](../nomenclature), which should be evaluated and converted to text for displaying purposes.

The `%>` tag is not specific to printing variables, and indicates the end of a code block inside a template. What follows that tag is just regular text which has no special meaning.

## Defining variables

A variable is defined when a template is rendered. Using one of many possible ways to define a [context](../context), the variable is given a value at runtime:

=== "Rust"
```rust
let template = Template::from_str("<%= variable %>")?;
let string = template.render([
("variable", "I love pancakes for dinner."),
])?;
println!("{}", string);
```
=== "Output"
```
I love pancakes for dinner.
```

### Missing variables

It's not uncommon to forget to define variables, epecially if a template is large, or used in multiple places in the app where some variables don't have a known value.

If an undefined variable is used in a template, Rwf will throw a runtime error. This is good for debugging issues when variables are unintentionally forgotten by the developer. However, if the variable is not always available, you can check if it's defined first:

```erb
<% if variable %>
<p><%= variable %></p>
<% end %>
```

Due to the nature of [if statements](../if-statements), if the variable is defined and evaluates to a "falsy" value, e.g. `0`, `""` (empty string), `null`, etc., the if statement will not be executed either. This is helpful for handling many similar cases without having to write complex statements.

## Supported data types

Rwf variables support most Rust data types. The conversion between Rust and the template language happens automatically.

### Number

Rwf supports two kinds of numbers: integers and floating points.

An integer is any whole number, negative or positive (including zero). Rust has many integer types, e.g. `i8`, `i32`, `u64`, etc., but the template language converts all of them to an 64-bit singed integer:

=== "Template"
```erb
<%= 500 %>
```
=== "Output"
```
500
```

Rust's `f32` and `f64` are converted to 64-bit double precision floating point. Operations between integers and floating points are supported, the final result being a float:

=== "Template"
```erb
<%= 500 + 1.5 %>
```
=== "Output"
```
501.5
```

Numbers can be [converted](../functions) to strings, floored, ceiled and rounded, for example:

=== "Template"
```erb
<%= 123.45.round.to_s %>
```
=== "Output"
```
123
```

### Strings

Using strings in templates can be used in two ways: with the `<%=` (print) operator, which outputs the string, escaping any dangerous HTML characters, e.g. `<` becomes `&lt;`, or using `<%-` which performs no conversions and prints the string as-is.

#### String security

Escaping HTML characters is a good idea in cases where your users are the ones supplying the value of the string. This prevents script injection attacks, e.g. users placing malicious code on your website.

Unless you're sure about the provinance of a string, use `<%=` to output it in templates.

### Boolean

Boolean variables can either be `true` or `false`. They map directly to Rust's `bool` data type.

### Lists

Lists are arrays of other template variables, including other lists, strings, numbers, and hashes. In templates, lists can be defined by using square brackets, and iterated on using [for loops](../for-loops), for example:

=== "Template"
```erb
<% for item in [1, 2, "three"] %>
<%= item %>
<% end %>
```
=== "Output"
```
1
2
three
```

Rwf lists are flexible and can contain multiple data types. This separates them from Rust's `Vec` and slices which can only hold one kind of data.

You can also access a specific element in a list by indexing into it with the dot(`.`) notation:

```erb
<%= list.1 %>
```

Lists are 0-indexed, so the above example accesses the second element in the list.

### Hashes

Hashes, also known as dicts or hash tables, are a key/value storage data type. Unlike a list, it contains a mapping between a value (the key), and a value. Values can be accessed by knowing a key, or by iterating through the entire hash with a [for loop](../for-loops):

```erb
<p><%= user.name %></p>
<p><%= user.email %></p>
```

Rwf hashes use the dot (`.`) notation to access values in a hash. In this example, the `user` is a hash, and `name` and `email` are keys.

## Truthy vs. falsy

Variables are often used in [if statements](../if-statements) to decide whether to execute some code or not. To make the template language less verbose, variables can be evaluted for truthiness without calling explicit functions depending on their data type.

The following variables and data types evaluate to false:

| Data type | Value |
|-----------|----------|
| Integer | `0` |
| Float | `0.0` |
| Boolean | `false` |
| String | `""` (empty) |
| List | `[]` (emtpy) |
| Hash | `{}` (empty) |

All other variables evaluate to true.

## Learn more

- [If statements](../if-statements)
- [For loops](../for-loops)
- [Functions](../functions)
30 changes: 30 additions & 0 deletions examples/dynamic-templates/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,33 @@ async fn main() {
.await
.expect("error shutting down server");
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_turtles() -> Result<(), Error> {
let template = Template::from_str(
r#"
<div class="profile">
<h2><%= username %></h2>
<p><%= bio %></p>
</div>
"#,
)?;

let html = template.render([("username", "Alice"), ("bio", "I like turtles")])?;

assert_eq!(
html,
r#"
<div class="profile">
<h2>Alice</h2>
<p>I like turtles</p>
</div>
"#
);
Ok(())
}
}

0 comments on commit 95ffb40

Please sign in to comment.