# 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 %}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://tabbli.gitbook.io/tabbli/working-with-sites/working-with-forms.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
