> For the complete documentation index, see [llms.txt](https://tabbli.gitbook.io/tabbli/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tabbli.gitbook.io/tabbli/working-with-sites/working-with-forms.md).

# Working with forms

## Basic usage

Usually almost each site require to interact with a user, so you need different types of forms for working with data. Tabbli supports a system for automatic form rendering, validation and submission. In most cases you can use method `get_record_form(...)` from `DB` interface. Please see [Working with the database](/tabbli/working-with-sites/querying-the-data-from-the-database.md#db-interface-reference) section for understand principles for working with data. The basic scenario for forms is like this:

* Create a form object for the collection
* Check the request method and try to validate the form if it is POST
* If validation is passed then save a data. Do not forget to execute scenarios for a proper trigger with `execute_scenario(...)` method.
* Render the form if you need it (for GET request or if validation has not been passed)
* Make a redirect to another page after successful form submission

Look at this example:

```python
{% from "BASE:macroses/forms.html" import form_fields with context %}
{% set form = DB(request)
    .get_collection('message')
    .get_record_form() %}

{% if request.method == 'POST' %}
    {% if form.is_valid() %}
        {% if form.is_valid() %}
            {% set record = form.save() %}
            {% do record.execute_scenario('added') %}
            {% do redirect_to('/') %}
        {% endif %}
    {% endif %}
{% endif %}

<form method="post">
    {{ form_fields(form) }}
    <button type="submit" class="btn btn-primary">Submit</button>
</form>
```

If you need to edit existing record you need to specify `record` attribute for `get_record_form`:

```python
{% set job = DB().get_collection('jobs').get_record(key=key) %}
{% set form = DB(request)
    .get_collection('jobs')
    .get_record_form(record=job) %}
```

For cases when you use File properties do not forge to use enctype="multipart/form-data" attribute for the form tag:

```python
<form method="post" enctype="multipart/form-data">
    {{ form_fields(form) }}
    <div class="form-group mt-4 pt-4">
        <button type="submit" class="btn btn-success">Submit</button>
    </div>
</form>
```

## Modify submitted data before saving to the database

If a form returns only a part of required property values you need for successful saving a record to the database, you can use form.save(commit=False) for creating a record object but without saving to the database. Modify the record and then use its save() method for update the database.

```python
{% set record = form.save(commit=False) %}
{% do record.properties.update({'user': request.user.email}) %}
{% do record.save() %}
{% do record.execute_scenario('added') %}
```

As you see you should use `update(...)` method for change the properties, because `{% set ... %}` tag allows to modify variables only inside the template context.

If you need to change some system properties, like `name` for example, you should use `setattr` function:

```python
{% do setattr(record, 'name', {'en':'Lorem ipsum'}) %}
```

## Hiding "Name" field

If you want to generate a value for Name field automatically or using computation options for it, you can use attribute ignore\_name:

```python
{% set form = DB(request)
    .get_collection('profile')
    .get_record_form(record=profile, ignore_name=True) %}
```

## Show only specified fields

If you want to create a form with only specified fields (for example, if you want to fill the rest of properties automatically) you should specify `property_keys` attribute:

```python
{% set form = DB(request)
    .get_collection('profile')
    .get_record_form(
        record=profile, ignore_name=True,
        property_keys=['bio', 'avatar', 'gender', 'website']) %}
```

## Widgets overriding

Some types of properties (like relations) support alternate widgets. You can such code for change it:

```python
{% set form = DB(request)
    .get_collection('profile')
    .get_record_form(
        record=profile, ignore_name=True,
        property_keys=['bio', 'avatar', 'gender', 'website'],
        override_widgets={'gender': 'radio_select'}) %}
```

The next alternate widgets are available:

* For single item selection:
  * **default** - autocomplete widget;
  * **select** - just a regular select box;
  * **radio\_select** - radio buttons.
* For multiple items selection:
  * **default** - autocomplete widget;
  * **select\_multiple** - regular selectbox with multiple selection support;
  * **checkbox\_select** - a list of checkboxes.

## Submitting forms via AJAX

In some cases it might sense to work with forms via AJAX, without reloading a whole page in the browser. Tabbli provides a simple JS framework for work with such kind of behavior which is called **TabbliJS** which is included to a standard site template. First of all, you need to add a container to any page where you need to show the form:

```python
<div class="__action--ajax-load" 
    id="form-container" 
    data-src="/submit-contact/" 
    style="height:300px;">Loading...</div>
```

Here the class `__actiom--ajax-load` is using to tell the Tabbli to load the page with URL specified in the attribute `data-src`.  After that you should create additional controller and a template which renders the form itself.

```python
{% from "BASE:macroses/forms.html" import form_fields with context %}
{% set form = DB(request)
    .get_collection('contacts').get_record_form() %}
    
{% if request.method == "POST" %}
    {% if form.is_valid() %}
        {% set record = form.save() %}
        {% do record.execute_scenario('added') %}
        {% set form = None %}
    {% endif %}
{% endif %}

{% if form %}
<form class="__action--ajax-submit" method="post" 
        action="/submit-contact/" data-target="#form-container">
    {{ form_fields(form) }}
    <div class="form-group mt-4">
        <button type="submit" class="btn btn-success">Submit</button>
    </div>
</form>
{% else %}
    <div class="alert alert-success">
        Thank you for your interest!
    </div>
{% endif %}

```

In that code, class `__action--ajax-submit` means that after pressing Submit button the form data will be submitted via AJAX. When server will generate a result it will loaded into the container with id `form-container` (it is specified in attribute `data-target`). You can see that it is a container in our main page where we are loading the form into. If form validation is passed the result will contain an alert message about successful submission.

## Changing standard forms look and feel

Sometimes you could be interested in overriding basic Tabbli templates for form rendering. If you use [Bootstrap 4](https://getbootstrap.com) as your CSS framework you rarely will want to do it. But, if you really need it you can override the next templates by adding your own to the directory `snippets`:

* `snippets/form_fields.html` - renders all form widgets and block of global form errors;
* `snippets/form_field.html` - render a single form widget.

If you do not override them, macros `{{ form_fields(form) }}` will use predefined templates. Look at the sources here:

{% code title="snippets/form\_fields.html" %}

```markup
{% if form.errors.__all__ %}
    <ul class="list-unstyled">
        {% for error in form.errors.__all__ %}
            <li class="text-danger">
                <small><i class="fa fa-exclamation-circle fa-fw"></i> 
                    {{error}}
                </small>
            </li>
        {% endfor %}
    </ul>
{% endif %}
{% for field in form %}
    {% include [
        site.key+":snippets/form_field.html", 
        "BASE:snippets/form_field.html"] %}
{% endfor %}
```

{% endcode %}

{% code title="snippets/form\_field.html" %}

```markup
{% set required_text %}<span class="text-danger">*</span>{% endset %}
{% if field.field.widget.input_type == "hidden" %}
    {{ field }}
{% else %}
<div class="{% if field.field.widget.__class__.__name__ == "CheckboxInput" %}form-check{% else %}form-group{% endif %}">
{% if field.field.widget.input_type == "checkbox" and 
        field.field.widget.allow_multiple_selected is not defined %}
    {{ field.as_widget(attrs={'class':'form-check-input'}) }}
    <label class="form-check-label" 
        for="{{ field.id_for_label }}">{{ field.label }}</label>
{% else %}
    <label for="{{ field.id_for_label }}">{{ field.label }}
        {% if field.field.required and 
            not hide_required_badges %}{{ required_text }}{% endif %}
    </label>
    {% if field.field.widget.__class__.__name__ in 
            ['SummernoteInplaceWidget', 'ClearableFileInput'] %}
        {{ field }}
    {% elif field.field.widget.__class__.__name__ == 
            "CheckboxSelectMultiple"  %}
        {% for item in field %}
            <div class="form-check">
                {{ item.tag() }}
                <label for="{{ item.id_for_label }}">
                    {{ item.choice_label }}
                </label>
            </div>
        {% endfor %}
    {% elif field.field.widget.input_type == "radio" %}
        {% for radio in field %}
            <div class="form-check">
                {{ radio.tag() }}
                <label for="{{ radio.id_for_label }}">
                    {{ radio.choice_label }}
                </label>
            </div>
        {% endfor %}
    {% else %}
        {{ field.as_widget(attrs={'class':'form-control'}) }}
    {% endif %}
{% endif %}
{% if field.help_text %}
<small class="form-text text-muted">{{field.help_text|safe}}</small>
{% endif %}
{% if field.errors %}
<ul class="list-unstyled">
    {% for error in field.errors %}
        <li class="text-danger"><small>
            <i class="fa fa-exclamation-circle fa-fw"></i> {{error}}
        </small></li>
    {% endfor %}
</ul>
{% endif %}
</div>
{% endif %}

```

{% endcode %}

So, if you need to change look and feel of your forms you can override these templates.

If you need to change the look of only a few forms and do not want to change it globally, you can create separate templates with other names (you can use generic templates as an example). Like `snippets/my_custom_form.html` and `snippets/my_custom_form_field.html`, for example. And than you can define your own macro for form rendering:

```python
{% macro my_custom_form(form) %}
    {% include site.key+":snippets/my_custom_form.html" %}
{% endmacro %}
```

Now, you can use your new macro like generic one:

```python
<form method="post">
    {{ my_custom_form(form) }}
    
    <button type="submit">Submit</button>
</form>
```

If you need to use the same macro in multiple templates you can put to the separate template and than import where you need it:

```python
{% from site.key+":macroses/my_custom_forms.html" 
    import my_custom_form with context %}
```
