DEV Community

Cover image for Interactive forms with Alpine.js
Jairus Joer for Aggregata

Posted on • Updated on • Originally published at aggregata.de

Interactive forms with Alpine.js

The texts in this article were generated in parts by OpenAI's ChatGPT and corrected and revised by us.

Starting point: The form

A simple contact form serves as the starting point for the intended dynamization. The next step is to define the necessary HTML and JavaScript.

<form class="form">
  <input class="form-input" name="name" type="text" />
  <input class="form-input" name="email" type="email" />
  <textarea class="form-input" name="message"></textarea>
  <button type="submit">Submit</button>
</form>
Enter fullscreen mode Exit fullscreen mode

Integrate Alpine.js into the form

Alpine.js installation and setup is required for this section. Read more in our post: Include Alpine.js in a production environment.

Inside the Alpine script, register the useForm context and define the asynchronous post function as follows. Note also the offloading of the data formatting to its own data function, which allows a more flexible inclusion of the data within the context.

The data function returns an object of the stored input values, while the post function sends data as JSON as POST to our (still) fictitious backend.

document.addEventListener("alpine:init", () => {
  Alpine.data("useForm", () => ({
    data() {
      const inputs = Array.from(this.$el.querySelectorAll("input, textarea"));
      const data = inputs.reduce(
        (object, key) => ({ ...object, [key.name]: key.value }),
        {}
      );
      return data;
    },

    async post() {
      return await (
        await fetch("/", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify(this.data()),
        })
      ).json();
    },
  }));
});
Enter fullscreen mode Exit fullscreen mode

In the <form> element we register the previously defined useForm context, along with the post function to handle data collection with Alpine. x-on:submit.prevent is Alpine's counterpart to Event.preventDefault(), interrupts the form submission and triggers the post function instead.

<form class="form" x-data="useForm" x-on:submit.prevent="post">
  <input class="form-input" name="name" type="text" />
  <input class="form-input" name="email" type="email" />
  <textarea class="form-input" name="message"></textarea>
  <button type="submit">Submit</button>
</form>
Enter fullscreen mode Exit fullscreen mode

Display responses dynamically

Thanks to Alpine's reactivity, responses to submitted forms can be used to provide users with immediate feedback on their requests. In the following example a simple PHP script serves as backend, which provides the form data as JSON via the variable $json and also returns a JSON object via $response.

Via $response->state the state of the query is defined, which is returned independently of the data.
The included information is used to identify, display and communicate the response in the frontend.

header("Content-type: application/json; charset=utf-8");

$json = json_decode(file_get_contents('php://input'));

$response->data= [
  // ...
];

$response->state = [
  'code'=> 200,
  'type' => 'success',
  'message'=> 'Your request was sent successfully.'
];

http_response_code($response->state['code']);
exit(json_encode($response));
Enter fullscreen mode Exit fullscreen mode

In the Alpine object response the response of the POST request is stored and made accessible to the HTML frontend via the useForm context.

document.addEventListener("alpine:init", () => {
  Alpine.data("useForm", () => ({
    response: false,

    data() {
      const inputs = Array.from(this.$el.querySelectorAll("input, textarea"));
      const data = inputs.reduce(
        (object, key) => ({ ...object, [key.name]: key.value }),
        {}
      );
      return data;
    },

    async post() {
      this.response = await (
        await fetch("/", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify(this.data()),
        })
      ).json();
    },
  }));
});
Enter fullscreen mode Exit fullscreen mode

Via x-show the element .form-response is faded in by the response of the backend - here in form of a simple banner. The returned status type success is registered as a new class .is-success to influence the coloring of the banner.

<form class="form" x-data="useForm" x-on:submit.prevent="post">
  <div
    class="form-response"
    style="display: none"
    x-show="response"
    x-bind:class="`is-${response?.state?.type}`"
    x-text="response?.state?.message"
    x-transition
  ></div>
  <input class="form-input" name="name" type="text" />
  <input class="form-input" name="email" type="email" />
  <textarea class="form-input" name="message"></textarea>
  <button type="submit">Submit</button>
</form>
Enter fullscreen mode Exit fullscreen mode

Thus, answers from the backend can be displayed dynamically via Alpine. In a further step, this functionality could also be used to display information for the validation of individual input fields.


TL;DR

Alpine adds reactive features to a static form that make it easier to send, receive, and visualize information from the backend for input - using Alpine's context.

Top comments (0)