Building a Contact Form with Model

This guide will help you understand the process of building a contact form backed up with Model. Whenever you need to build a simple contact form, blog functionality, or complex API endpoint, you can define any data structure you need with Models.

Requirements

Steps

Building a Contact Form with Model is a nine-step process:

Step 1: Define Contact – Model Schema

To define a new Model Schema, use YAML files placed in the model_schemas directory. In the key-value format, you can define a name for your Model, and an array of Properties for the Model object. To build a Contact Form, you'll need email, name, and description attributes on the Model that you can call a contact.

model_schemas/contact.yml

name: contact
properties:
- name: name
  type: string
- name: email
  type: string
- name: description
  type: string

The basic Configuration of the Model consists of a name and the list of Properties. In the example above, you have defined three attributes of type string.

For more information on Properties please see Properties Documentation.

Step 2: Define Contact Form – Form Object

Now that you have the definition of a Contact in place, you can create a Form that will configure the server to accept requests with data in the desired format. Data passed through the form will be stored in the database as a Contact object.

forms/contact_form.liquid

---
name: contact_form
resource: modules/contacts/contact
resource_owner: anyone
fields:
  properties:
    name:
      validation:
        presence: true
    email:
      validation:
        presence: true
        email: true
    description:
      validation:
        presence: true
---

The above definition whitelists name, email, and description attributes and defines validation for each attribute.

Step 3: Fetch saved data with GraphQL

Now that you have created the Contact custom model type in the previous step, you can proceed to fetch this data with GraphQL. To define get_contacts query, create file get_contacts.graphql file in graphql directory.

query get_contacts($id: ID) {
  models(filter: { name: { exact: "modules/contacts/contact" }, id: { value: $id } }, per_page: 100) {
    results {
      id
      name: property(name: "name")
      email: property(name: "email")
      description: property(name: "description")
    }
  }
}

To see if the above query works properly you can head to the next section to learn how to embed query results with page view or use pos-cli gui

Step 4: Render Models within page

Display contacts as a list with the possibility to get to the contact details view.
As a first step, you need a page that will respond to given paths:

  • GET /contacts for list view
  • GET /contacts/[ID] for detail view

Page slug for the above URL map will simply be contacts as the ID passed in the URL is dynamically assigned to the context.params.slug2 variable. For more information, see Pages documentation.


---
slug: contacts
---
{%- assign id = context.params.slug2 -%}
{%- graphql g = "modules/contacts/get_contacts", id: id -%}

{%- if id == blank -%}
  {% include 'modules/contacts/contacts/list', contacts: g.models.results %}
{%- else -%}
  {% include 'modules/contacts/contacts/show', contact: g.models.results.first %}
{%- endif -%}

views/partials/contacts/list.liquid


<h1>List View</h1>
<table>
  <tr>
    <th>ID</th>
    <th>Email</th>
    <th></th>
  </tr>
  {% for contact in contacts %}
  <tr>
    <td>{{ contact.id }}</td>
    <td>{{ contact.email }}</td>
    <td><a href="/contacts/{{ contact.id }}">Details</a></td>
  </tr>
  {% endfor %}
</table>

views/partials/contacts/show.liquid


<h1>Detail view</h1>
<p>{{ contact.name }}</p>
<p>{{ contact.email }}</p>
<p>{{ contact.description }}</p>

Step 5: Build Contact Form with HTML

In Step 2: Define Contact Form-Data Creation, you have created a form file called contact_form, that configures the form object. This was a minimal example to allow sending requests to our API endpoint, and create Model objects. Now you can extend this file with HTML, that renders the form to our users. The HTML part of the file comes after the configuration part, i.e. after the closing --- tag.

---
name: contact_form
resource: modules/contacts/contact
resource_owner: anyone
redirect_to: /contacts
fields:
  properties:
    name:
      validation:
        presence: true
    email:
      validation:
        presence: true
        email: true
    description:
      validation:
        presence: true
---

{% form %}
  <label for="name">Name</label>
  <input name="{{ form.fields.properties.name.name }}" value="{{ form.fields.properties.name.value }}" id="name" type="text">
  {% if form.errors['properties.name'] %}
    <p>{{ form.errors['properties.name'] }}</p>
  {% endif %}

  <label for="email">Email</label>
  <input name="{{ form.fields.properties.email.name }}" value="{{ form.fields.properties.email.value }}" id="email" type="email">
  {% if form.errors['properties.email'] %}
    <p>{{ form.errors['properties.email'] }}</p>
  {% endif %}

  <label for="description">Description</label>
  <textarea name="{{ form.fields.properties.description.name }}" id="description">{{ form.fields.properties.description.value }}</textarea>
  {% if form.errors['properties.description'] %}
    <p>{{ form.errors['properties.description'] }}</p>
  {% endif %}

  <button>Submit</button>
{% endform %}

The redirect_to configuration option defines where to redirect the user after successful form submission. The predefined form tag allows you to create context for submitted data.

For more information of advanced form configuration please visit Form Documentation.

To display the form on the previously created contacts page you have to include it with:


{% include_form 'modules/contacts/contact_form' %}

As a result, you should be able to create Model objects, called Contact with simple form submission, and list them after fetching with GraphQL.

Step 6: Delete contact

In order to remove record from the database, create delete_contact_form:

---
name: delete_contact_form
resource: modules/contacts/contact
resource_owner: anyone
redirect_to: /contacts
fields: {}
---

{% form method: "delete" %}
  <button type="submit">Delete</button>
{% endform %}


To display delete button on the list of contacts, include the form as follows:

<td>
  {% include_form "modules/contacts/delete_contact_form", id: contact.id %}
</td>

Step 7: Edit Contact

As you already have the form to create contacts, you can simply reuse it for the purpose of record update. In order to prefill the form with proper data, add id to include_form tag.

---
slug: contacts/edit
---
{% include_form "modules/contacts/contact_edit_form", id: context.params.slug3 %}

Step 8: Add Email Notification

To send an Email Notification to the email that was sent with form submission, create an Email Notification file that will define email configuration and message template.

File should be located in the notifications/email_notifications, name it contact_notification.liquid:


---
name: contact_notification
delay: 0
enabled: true
from: 'Example Site <info@example.com>'
subject: Contact
to: '{{ form.properties.email }}'
trigger_condition: true
---
<h2>Hi {{ form.properties.name }}</h2>
<p>Thank you for your request, we will contact you shortly!</p>

As you can see, all request parameters are now accessible in form.model object, so you can simply compose any email you want. Next step is linking contact_notification in the contact_form by adding to form configuration inside forms/contact_form.liquid file.

email_notifications:
- contact_notification

Step 9: Secure the form

At this point, you might want to restrict access to the forms and views you have created. You can do that using Authorization Policies.

Live example and source code

To play with a live example (in much fuller form and with additional information) go to the contact example page.

Source code can be found on GitHub.

Next steps

Congratulations! You have learned how to build forms with Model. You can now explore our reference documentation for Properties and Model Schemas:

Questions?

We are always happy to help with any questions you may have.