Developing a Netlify Contact Form using Alpine.js
Tags: javascript, html, webdev, netlify
Published: 2021-03-07 18:09:23

The most common request to a simple landing or a simple company web page is to provide a simple contact form. A simple feature to add, isn't it? Well, not so much. For a simple static web deployment, you need to provide a backend infrastructure with email support. Fortunately, there is an easy solution for it. 

## What is the Netlify?

[Netlify](https://www.netlify.com/) is a service platform that provides static deployment to your website with many features such as custom domains, https, git deployment, serverless functions, and for us, a form service.

> How does it work?

For a simple HTML form, append the `netlify` attribute to its form tag:

```html
<form name="contact" netlify>
....

</form>
```

Then, every time a user submits the form, it will be intercepted from Netlify, the form info will appear in your site dashboard, and the user will be directed to a success page. Furthermore, it may be configured to notify yourself of the submissions using email or any webhook and add three-level spam filtering. 

## Spam Prevention

The spam prevention systems consist of:

* a spam detection system,
* an optional *honeypot* field, which is a hidden form field that lures bot users into completing a field that human users can't see, and 
* an optional reCAPTCHA spam prevention.

  If you do not want to use any javascript, the following HTML code will be working when it will be deployed:

{% gist https://gist.github.com/wittyprogramming/cb65b7f93e7e85fa4767abd1c02ff020 %}

The first takeout from the above code is how to compose the `<form>` tag:

```html
<form name="contact" method="POST" data-netlify="true"
        netlify-honeypot="bot-field" data-netlify-recaptcha="true">
```

We named the form (`name="contact"`), we defined it as netlify form with the `data-netlify="true"` attribute and add two optional spam-prevention layers: the *honeypot* (`netlify-honeypot="bot-field"`) and the *Google reCAPTCHA 2* (`data-netlify-recaptcha="true"`).

The `netlify-honeypot` attribute is working by adding a hidden input form element, which will be submitted as empty:

```html
<p hidden><label>ignore: <input name="bot-field" /></label></p>
```

The idea behind this spam-prevention schema is that a bot will see the input element, and it will fill it. Therefore, if the form is submitted with a non-empty value, then it will be rejected. Take note that the name of the honeypot `bot-field` can be anything; just defined it in the `<form>` tag.

The *Google reCAPTCHA 2* will be showing up by adding the following inside the `<form>` tag:

```html
<div data-netlify-recaptcha="true"></div>
```

The above code will work on any HTML page that is deploying in the netlify server. In fact, by using the html5 form validation attributes, it can provide client-based form validation, too.

However, if the client-based form validation is desired to be done using javascript, how the submission is working? And, especially, using the [Alpine.js](https://github.com/alpinejs/alpine)?

## Alpine.js

[Alpine.js](https://github.com/alpinejs/alpine) is a reactive framework, very similar to Vue, with two very distinct differences. The first is the lower cost, as it does not need a  bundler. Just adding a script tag (~8KB gzip and minified) is sufficient.  The second one involves ignoring the famous virtual DOM strategy and adopts for sprinkling the code directly on your HTML. In that respect, it is very similar to Tailwind but for javascript instead of styling. 

The installation is simple enough just add a script to the end of your `<head>` section:

```html

<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.min.js" defer></script>
```

These distinct characteristics make it more suitable for jQuery replacement for smaller tasks than Vue.

For more information, you may check the introductory article about Alpine.js: {% link https://dev.to/kzagoris/introducing-alpinejs-the-ideal-jquery-replacement-jfa %}

Initially, we recreate the HTML-only form code and hide it. The hidden form acts as a stand-in for the Alpine.js form component, so the netlify bot understands what to expect to receive from the fetch submission.

```html
 <form
      hidden
      name="contact"
      method="POST"
      data-netlify="true"
      netlify-honeypot="bot-field"
      data-netlify-recaptcha="true"
    >
      <label><input type="text" name="name" /></label>
      <label> <input type="email" name="email" /></label>
      <label><textarea name="message"></textarea></label>
</form>
```

Next, we initialize our component using `x-data` with the required data object for form validation.
The `x-data` attribute plays the same role as the `data` Vue component property. The data object properties, as in Vue, are reactive.

```html
<form
      x-data="{
                name : '',
                nameDirty: false,
                message: '',
                messageDirty: false,
                email: '',
                emailDirty: false,
               }"
      x-on:submit.prevent="
                  nameDirty = true;
                  emailDirty = true;
                  messageDirty = true;
                  submitMessage(name, email, message);"
      id="contact"
    >
```

The `x-on` attaches an event listener to the form `submit` event.

Next, to validate and check the value of an input form element component, the following code is used:

```html
<p><label>Full Name: <input x-model="name" x-on:blur="nameDirty = true" type="text" name="name"/></label></p>
<p x-show.transition="nameDirty && name.length == 0" style="color: red" x-cloak>
        Please fill out your full name.
</p>
```

The `x-model` attribute adds a "two-way data binding" to the input element. Consequently, the input element's value will be in sync with the name variable in the data object. When the input lost focus, the `nameDirty` variable is updated as `true.`

If `nameDirty` is true and the name variable is empty, a warning message is shown using the `x-show` attribute. The `x-show` attribute toggles the `display: none;` style attribute on the element depending on the expression output.

The `transition` directive is a convenience API offered by Alpine.js, which applies CSS transitions to the element. From the [documentation](https://github.com/alpinejs/alpine#x-show), the `transition` directive is a simultaneous fade and scale: `opacity, scale: 0.95, timing-function: cubic-bezier(0.4, 0.0, 0.2, 1), duration-in: 150ms, duration-out: 75ms`

## Form Submission

The function `submitMessage` takes as input the contents of  `name`, `email` and `message` variables, validate them, and if it is correct, it submits the form contents using the [fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API).

Note that the AJAX request's content must be URL-encoded, as the Netlify forms do not support JSON form data currently. The URL-encoded form can be easily created by using the [URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) utility method:

```javascript
let formElement = document.getElementById("contact");
let body = new URLSearchParams(new FormData(formElement)).toString();
```

The final complete `submitMessage` function is:

```javascript
function submitMessage(name, email, message) {
    if (!name || name.length == 0 || !email || !isEmail || !message || message.length == 0) {
      return;
    }
    let formElement = document.getElementById("contact");
    let body = new URLSearchParams(new FormData(formElement)).toString();
    return fetch("/", {
      method: "POST",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
      },
      body: body,
    })
      .then((response) => {
        if (response.ok) {
          formElement.reset();
          alert("Thank you for your message!");
        } else {
          throw new Error(`Something went wrong: ${response.statusText}`);
        }
      })
      .catch((error) => console.error(error));
  }
```

The final code using  Alpine.js for client-based form input validation and sending the form to netlify using the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) is:

{% gist https://gist.github.com/wittyprogramming/d248fca0ad336ecd66366d0a49ca298d %}

Altogether, I think the Netlify form simplifies the usage of form in your web page. Of course, under the assumption to host your web app in their hosting service.