acton-htmx 1.0.0-beta.7

Opinionated Rust web framework for HTMX applications
Documentation
{# Form Component Macros #}
{# Declarative form building with HTMX support and validation #}

{# Start a form with automatic CSRF protection #}
{% macro form_start(action, method="post", htmx=true, target="", swap="innerHTML", class="") %}
<form action="{{ action }}"
      method="post"
      {% if class %}class="{{ class }}"{% endif %}
      {% if htmx %}
          {% if method == "post" %}hx-post="{{ action }}"
          {% elif method == "put" %}hx-put="{{ action }}"
          {% elif method == "delete" %}hx-delete="{{ action }}"
          {% endif %}
          {% if target %}hx-target="{{ target }}"{% endif %}
          hx-swap="{{ swap }}"
      {% endif %}>
    {# CSRF token will be injected by middleware #}
    <input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
    {% if method != "post" and method != "get" %}
    <input type="hidden" name="_method" value="{{ method }}">
    {% endif %}
{% endmacro %}

{% macro form_end() %}
</form>
{% endmacro %}

{# Text input field with label and validation #}
{% macro text_field(name, label="", value="", placeholder="", required=false, errors=[]) %}
<div class="form-group {% if errors %}has-error{% endif %}">
    {% if label %}
    <label for="{{ name }}" class="form-label">
        {{ label }}
        {% if required %}<span class="required">*</span>{% endif %}
    </label>
    {% endif %}
    <input type="text"
           id="{{ name }}"
           name="{{ name }}"
           class="form-control {% if errors %}is-invalid{% endif %}"
           {% if value %}value="{{ value }}"{% endif %}
           {% if placeholder %}placeholder="{{ placeholder }}"{% endif %}
           {% if required %}required{% endif %}>
    {% if errors %}
    <div class="invalid-feedback">
        {% for error in errors %}
        <div>{{ error }}</div>
        {% endfor %}
    </div>
    {% endif %}
</div>
{% endmacro %}

{# Email input field #}
{% macro email_field(name, label="Email", value="", required=true, errors=[]) %}
<div class="form-group {% if errors %}has-error{% endif %}">
    {% if label %}
    <label for="{{ name }}" class="form-label">
        {{ label }}
        {% if required %}<span class="required">*</span>{% endif %}
    </label>
    {% endif %}
    <input type="email"
           id="{{ name }}"
           name="{{ name }}"
           class="form-control {% if errors %}is-invalid{% endif %}"
           {% if value %}value="{{ value }}"{% endif %}
           {% if required %}required{% endif %}
           autocomplete="email">
    {% if errors %}
    <div class="invalid-feedback">
        {% for error in errors %}
        <div>{{ error }}</div>
        {% endfor %}
    </div>
    {% endif %}
</div>
{% endmacro %}

{# Password input field #}
{% macro password_field(name, label="Password", required=true, errors=[], autocomplete="current-password") %}
<div class="form-group {% if errors %}has-error{% endif %}">
    {% if label %}
    <label for="{{ name }}" class="form-label">
        {{ label }}
        {% if required %}<span class="required">*</span>{% endif %}
    </label>
    {% endif %}
    <input type="password"
           id="{{ name }}"
           name="{{ name }}"
           class="form-control {% if errors %}is-invalid{% endif %}"
           {% if required %}required{% endif %}
           autocomplete="{{ autocomplete }}">
    {% if errors %}
    <div class="invalid-feedback">
        {% for error in errors %}
        <div>{{ error }}</div>
        {% endfor %}
    </div>
    {% endif %}
</div>
{% endmacro %}

{# Textarea field #}
{% macro textarea_field(name, label="", value="", placeholder="", rows=4, required=false, errors=[]) %}
<div class="form-group {% if errors %}has-error{% endif %}">
    {% if label %}
    <label for="{{ name }}" class="form-label">
        {{ label }}
        {% if required %}<span class="required">*</span>{% endif %}
    </label>
    {% endif %}
    <textarea id="{{ name }}"
              name="{{ name }}"
              class="form-control {% if errors %}is-invalid{% endif %}"
              rows="{{ rows }}"
              {% if placeholder %}placeholder="{{ placeholder }}"{% endif %}
              {% if required %}required{% endif %}>{% if value %}{{ value }}{% endif %}</textarea>
    {% if errors %}
    <div class="invalid-feedback">
        {% for error in errors %}
        <div>{{ error }}</div>
        {% endfor %}
    </div>
    {% endif %}
</div>
{% endmacro %}

{# Select dropdown field #}
{% macro select_field(name, label="", options=[], selected="", required=false, errors=[]) %}
<div class="form-group {% if errors %}has-error{% endif %}">
    {% if label %}
    <label for="{{ name }}" class="form-label">
        {{ label }}
        {% if required %}<span class="required">*</span>{% endif %}
    </label>
    {% endif %}
    <select id="{{ name }}"
            name="{{ name }}"
            class="form-control {% if errors %}is-invalid{% endif %}"
            {% if required %}required{% endif %}>
        {% for option in options %}
        <option value="{{ option.value }}"
                {% if option.value == selected %}selected{% endif %}>
            {{ option.label }}
        </option>
        {% endfor %}
    </select>
    {% if errors %}
    <div class="invalid-feedback">
        {% for error in errors %}
        <div>{{ error }}</div>
        {% endfor %}
    </div>
    {% endif %}
</div>
{% endmacro %}

{# Checkbox field #}
{% macro checkbox_field(name, label="", checked=false, value="1", errors=[]) %}
<div class="form-check {% if errors %}has-error{% endif %}">
    <input type="checkbox"
           id="{{ name }}"
           name="{{ name }}"
           class="form-check-input {% if errors %}is-invalid{% endif %}"
           value="{{ value }}"
           {% if checked %}checked{% endif %}>
    {% if label %}
    <label for="{{ name }}" class="form-check-label">
        {{ label }}
    </label>
    {% endif %}
    {% if errors %}
    <div class="invalid-feedback">
        {% for error in errors %}
        <div>{{ error }}</div>
        {% endfor %}
    </div>
    {% endif %}
</div>
{% endmacro %}

{# Radio button field #}
{% macro radio_field(name, label="", options=[], selected="", errors=[]) %}
<div class="form-group {% if errors %}has-error{% endif %}">
    {% if label %}
    <label class="form-label">{{ label }}</label>
    {% endif %}
    {% for option in options %}
    <div class="form-check">
        <input type="radio"
               id="{{ name }}_{{ option.value }}"
               name="{{ name }}"
               class="form-check-input {% if errors %}is-invalid{% endif %}"
               value="{{ option.value }}"
               {% if option.value == selected %}checked{% endif %}>
        <label for="{{ name }}_{{ option.value }}" class="form-check-label">
            {{ option.label }}
        </label>
    </div>
    {% endfor %}
    {% if errors %}
    <div class="invalid-feedback">
        {% for error in errors %}
        <div>{{ error }}</div>
        {% endfor %}
    </div>
    {% endif %}
</div>
{% endmacro %}

{# Submit button #}
{% macro submit_button(text="Submit", class="btn btn-primary", loading_text="Submitting...") %}
<button type="submit"
        class="{{ class }}"
        hx-disabled-elt="this"
        hx-indicator="#submit-indicator">
    <span class="button-text">{{ text }}</span>
    <span id="submit-indicator" class="htmx-indicator">{{ loading_text }}</span>
</button>
{% endmacro %}

{# Button with HTMX action #}
{% macro action_button(text, url, method="post", target="", confirm="", class="btn btn-secondary") %}
<button type="button"
        class="{{ class }}"
        {% if method == "post" %}hx-post="{{ url }}"
        {% elif method == "get" %}hx-get="{{ url }}"
        {% elif method == "put" %}hx-put="{{ url }}"
        {% elif method == "delete" %}hx-delete="{{ url }}"
        {% endif %}
        {% if target %}hx-target="{{ target }}"{% endif %}
        {% if confirm %}hx-confirm="{{ confirm }}"{% endif %}
        hx-disabled-elt="this">
    {{ text }}
</button>
{% endmacro %}

{# Hidden field #}
{% macro hidden_field(name, value) %}
<input type="hidden" name="{{ name }}" value="{{ value }}">
{% endmacro %}

{# Form styles #}
<style>
    .form-group {
        margin-bottom: 1rem;
    }

    .form-label {
        display: block;
        margin-bottom: 0.5rem;
        font-weight: 500;
    }

    .form-label .required {
        color: #dc3545;
        margin-left: 0.25rem;
    }

    .form-control {
        display: block;
        width: 100%;
        padding: 0.5rem 0.75rem;
        font-size: 1rem;
        line-height: 1.5;
        color: #495057;
        background-color: #fff;
        border: 1px solid #ced4da;
        border-radius: 0.375rem;
        transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
    }

    .form-control:focus {
        color: #495057;
        background-color: #fff;
        border-color: #80bdff;
        outline: 0;
        box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
    }

    .form-control.is-invalid {
        border-color: #dc3545;
    }

    .form-control.is-invalid:focus {
        border-color: #dc3545;
        box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
    }

    .form-check {
        margin-bottom: 0.5rem;
    }

    .form-check-input {
        margin-top: 0.25rem;
        margin-right: 0.5rem;
    }

    .invalid-feedback {
        display: block;
        margin-top: 0.25rem;
        font-size: 0.875rem;
        color: #dc3545;
    }

    .btn {
        display: inline-block;
        padding: 0.5rem 1rem;
        font-size: 1rem;
        font-weight: 400;
        line-height: 1.5;
        text-align: center;
        text-decoration: none;
        cursor: pointer;
        border: 1px solid transparent;
        border-radius: 0.375rem;
        transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out;
    }

    .btn-primary {
        color: #fff;
        background-color: #007bff;
        border-color: #007bff;
    }

    .btn-primary:hover {
        background-color: #0069d9;
        border-color: #0062cc;
    }

    .btn-secondary {
        color: #fff;
        background-color: #6c757d;
        border-color: #6c757d;
    }

    .btn-secondary:hover {
        background-color: #5a6268;
        border-color: #545b62;
    }

    .btn:disabled {
        opacity: 0.65;
        cursor: not-allowed;
    }
</style>
{% endmacro %}