Skip to main content

docs/getting-started/basic-concepts.md

# Basic Concepts

Understand the core ideas behind Mau.

## What is Mau?

Mau is a **template engine** for Elixir. It takes a template string with special syntax and renders it to final output based on a context (a map of variables).

**Simple equation:**
```
Template + Context = Output
```

Example:
```
Template: "Hello {{ name }}!"
Context: %{"name" => "World"}
Output: "Hello World!"
```

## The Three Types of Content

Mau templates contain three types of content:

### 1. Plain Text
Regular text that appears as-is in the output:

```
Welcome to Mau!
```

This renders exactly as written.

### 2. Expressions (with `{{ }}`)
Expressions evaluate code and insert the result:

```
{{ user.name }}
{{ items | length }}
{{ 5 + 3 }}
```

Expressions:
- Access variables with `{{ variable_name }}`
- Access properties with `{{ object.property }}`
- Use filters with `{{ value | filter }}`
- Use expressions with `{{ 5 + 3 }}`

### 3. Tags (with `{% %}`)
Tags control logic but don't produce output themselves:

```
{% if condition %}
  This appears if true
{% endif %}

{% for item in items %}
  Process each item
{% endfor %}
```

Tags:
- `{% if %}` - Conditionals
- `{% for %}` - Loops
- `{% assign %}` - Variables
- Comments: `{# This is a comment #}`

## Context: Your Data

The **context** is a map that provides data to your template:

```elixir
context = %{
  "user" => %{
    "name" => "Alice",
    "email" => "alice@example.com",
    "age" => 30
  },
  "settings" => %{
    "theme" => "dark",
    "notifications" => true
  }
}
```

Access context data in templates:
- `{{ user.name }}` → "Alice"
- `{{ user.age }}` → 30
- `{{ settings.theme }}` → "dark"

## Variables

Variables store data that you reference in templates.

### Simple Variables

```elixir
context = %{"greeting" => "Hello"}
template = "{{ greeting }} World"
# Output: "Hello World"
```

### Nested Variables

```elixir
context = %{
  "user" => %{
    "profile" => %{
      "name" => "Bob"
    }
  }
}
template = "{{ user.profile.name }}"
# Output: "Bob"
```

### Array/List Access

```elixir
context = %{"items" => ["apple", "banana", "cherry"]}
template = "First: {{ items[0] }}, Last: {{ items[2] }}"
# Output: "First: apple, Last: cherry"
```

### Variable Assignment

Create variables in your template:

```elixir
template = """
{% assign greeting = "Hello" %}
{{ greeting }} World
"""
# Output: "Hello World"
```

## Filters: Transform Data

Filters modify values. Use the pipe `|` to apply them:

```
{{ value | filter_name }}
```

Common filters:

```elixir
{{ "hello" | capitalize }}      # "Hello"
{{ "HELLO" | lower_case }}      # "hello"
{{ [1, 2, 3] | length }}        # 3
{{ [3, 1, 2] | sort }}          # [1, 2, 3]
{{ "a,b,c" | split(",") }}      # ["a", "b", "c"]
{{ items | first }}             # First item
{{ items | last }}              # Last item
```

Chain multiple filters:

```
{{ text | strip | capitalize | upper_case }}
```

This applies filters left to right.

## Expressions: Calculate Values

Perform calculations and comparisons in `{{ }}`:

### Arithmetic

```elixir
{{ 5 + 3 }}              # 8
{{ 10 - 4 }}             # 6
{{ 3 * 4 }}              # 12
{{ 10 / 2 }}             # 5.0
```

### Comparisons

```elixir
{{ 5 > 3 }}              # true
{{ 5 < 3 }}              # false
{{ 5 == 5 }}             # true
{{ 5 != 3 }}             # true
{{ 5 >= 5 }}             # true
{{ 5 <= 10 }}            # true
```

### Logical Operations

```elixir
{{ true and false }}     # false
{{ true or false }}      # true
{{ not false }}          # true
```

### String Concatenation

```elixir
{{ "Hello " + "World" }} # "Hello World"
{{ first_name + " " + last_name }}
```

## Control Flow: If/Else

### Basic If

```elixir
{% if user.active %}
  User is active
{% endif %}
```

### If/Else

```elixir
{% if user.admin %}
  Admin Panel
{% else %}
  User Area
{% endif %}
```

### If/Elsif/Else

```elixir
{% if user.role == "admin" %}
  Admin
{% elsif user.role == "moderator" %}
  Moderator
{% else %}
  User
{% endif %}
```

## Loops: Iterate Collections

### For Loop

```elixir
{% for item in items %}
  {{ item }}
{% endfor %}
```

### Loop Variables

Access loop information with `forloop`:

```elixir
{% for item in items %}
  {{ forloop.index }}: {{ item }}
{% endfor %}
```

**Loop variables:**
- `forloop.index` - Current position (1-based)
- `forloop.index0` - Current position (0-based)
- `forloop.first` - true on first iteration
- `forloop.last` - true on last iteration
- `forloop.length` - Total count

### Nested Loops

```elixir
{% for category in categories %}
  {{ category.name }}:
  {% for item in category.items %}
    - {{ item }}
  {% endfor %}
{% endfor %}
```

## Truthy and Falsy

In conditionals, values are evaluated as truthy or falsy:

**Falsy values:**
- `false`
- `nil`
- `""` (empty string)
- `[]` (empty list)
- `{}` (empty map)

**Truthy values:**
- Everything else, including:
  - `true`
  - Non-empty strings: `"hello"`
  - Non-zero numbers: `5`, `-1`
  - Non-empty lists: `[1, 2, 3]`
  - Non-empty maps: `%{"key" => "value"}`

Example:

```elixir
{% if user.email %}
  Email: {{ user.email }}
{% endif %}
```

This renders only if `user.email` is not falsy.

## Comments

Comments are not rendered:

```elixir
{# This is a comment and won't appear in output #}

Template text

{# Comments can be multi-line
   and span across lines #}
```

## Whitespace Control

Control whitespace around expressions and tags:

```elixir
{{ name }}       {# Spaces around are preserved #}
{{- name }}      {# Trim left whitespace #}
{{ name -}}      {# Trim right whitespace #}
{{- name -}}     {# Trim both #}
```

Example:

```elixir
template = """
Items:
{% for item in items %}
  - {{ item }}
{% endfor %}
"""

# vs with whitespace control:
template = """
Items:
{%- for item in items %}
  - {{ item }}
{%- endfor %}
"""
```

## Type Preservation

By default, all template output is a string:

```elixir
Mau.render("{{ 42 }}", %{})
# {:ok, "42"}  <- string, not number
```

Enable type preservation to maintain types:

```elixir
Mau.render("{{ 42 }}", %{}, preserve_types: true)
# {:ok, 42}  <- number, not string

Mau.render("{{ active }}", %{"active" => true}, preserve_types: true)
# {:ok, true}  <- boolean preserved

Mau.render("{{ items }}", %{"items" => [1, 2, 3]}, preserve_types: true)
# {:ok, [1, 2, 3]}  <- list preserved
```

**Important:** Mixed content (text + expressions) always returns strings:

```elixir
Mau.render("Count: {{ 42 }}", %{}, preserve_types: true)
# {:ok, "Count: 42"}  <- string because it contains text
```

## Summary

| Concept | Syntax | Purpose |
|---------|--------|---------|
| **Text** | Plain text | Static content |
| **Expression** | `{{ value }}` | Insert dynamic data |
| **Filter** | `\| filter_name` | Transform data |
| **If** | `{% if condition %}` | Conditional rendering |
| **For** | `{% for item in list %}` | Iterate collections |
| **Assign** | `{% assign var = value %}` | Create variables |
| **Comment** | `{# comment #}` | Non-rendered text |

## Next Steps

- [Your First Template](first-template.md) - Build a complete example
- [Template Language Reference](../reference/template-language.md) - Detailed feature guides
- [Email Template Examples](../examples/email-templates.md) - Real-world use cases