Setting Up a Contact Form in Vue.js and Using Drupal 9 as the Storage

This is the 3rd part in a series that looks at using Drupal 9 as the back-end and Vue.js v2.x as the frontend. We have looked at same-origin POST requests and then looked at what you need to do to do cross-origin POST requests.

In this part, we will pull it together and use what we have learnt in parts 1 and 2 for a real-world use case. That being posting data from a Vue.js frontend to a Drupal contact form that is using the contact storage contributed module.

If you want to learn more about the contact form and contact storage I have written about that. You can check out the Drupal contact form article. The main point to be aware of is that you need to add the contact storage module to save and access the emails in the backend of your Drupal site.

So from part 2, we have data posting to our Drupal site from a form to create an article and we use relationships in our post request data to post to a different author. To post our data to our contact form as stated we need to install the contact storage module and in doing so we now have a JSON:API endpoint to post too, so let's first look at the endpoint.

Looking at the Endpoint

To find the endpoint for contact storage we can look at the JSON:API URL on our site. We will see a few different endpoints for the contact form. Some of these are related to the configuration settings of the contact forms so we need to find the correct one. The one we want is the endpoint that is related to the contact form we want to post too. The URL pattern will be http://example.com/jsonapi/contact_message/product_enquiry, where the last parameter is the form you want to use. In this case, the form is the product enquiry form.
We can now change the URL in our Vue.js app.

data: function() {
    return {
-     postUrl: 'https://example.com/jsonapi/node/article',
+     postUrl: 'https://example.com/jsonapi/contact_message/product_enquiry',
      titleHolder: '',
      formData: {

Related: Drupal 9 JSON:API endpoint

This endpoint is the one we can post to and have the data saved to the database. If you look at the JSON:API you can see the schema and see all the properties. The properties that we are interested in are name, email, messages, and we all so need the relationship of uid with the type and id properties. You can add any fields you like in Drupal but I want to keep it simple so name, email, and message is perfect.

A note on the id: we need to get the id from the data object of the contact form object in the relationship object as when submitting the form from Drupal it adds a missing value to the uid data object. When posting from a remote we need to include it in the request. This can be hardcoded into our Vue.js data object and is illustrated below.

Image
contact form JSON:API schema drupal

So now we have a schema and properties we can change the data object in our Vue.js application to match the endpoint and then adjust the mapping to our form.

Our new data object will look like this.

formData: {
        "data": {
          "type": "contact_message--product_enquiry",
          "attributes":{
            "name":"",
            "mail":"",
            "subject":"From API",
            "message":""
          },
          "relationships": {
            "uid": {
              "data": {
                "type": "contact_form--contact_form",
                "id": "a968e45a-"
              }
            }
          }
        }
       }

Changing the mappings

If you have been following along or are familiar with Vue.js then this should be a trivial matter of updating the v-model directives in the form to match the new data object schema.

We now have our data object matching what the Drupal JSON:API is expecting so we can submit the form and the record should show up in the database. Good job! We will now look at validation in Vue.js and then redirecting the successful POST request.

Submitting the form Cross-origin or Same-origin

As such this doesn't change, if you skipped parts one and two I suggest you go and read them as you will find out what needs to be done to submit same-origin and cross-origin respectively. However, presently nothing happens whether there is a success or error, except a console.log, so we will now fix that.   

Validation

In Part 2 we did some basic validation as only the title needed to be populated.

Validation can be done either on the front-end or on the back-end. I am going to do basic validation on the front-end for the fields I have set required and also check that the email is formatted correctly. In both cases, we will first check for empty form inputs and then check that the email field is formatted in the correct way.

Drupal validates submissions and returns an error if the format is wrong. However, after trying to work with the error response it seemed it was going to need a lot of logic to make it work. In the end, I felt that dealing with it on the front-end is sufficient.

Prepare the necessary data properties

First, we need to add some empty properties to our Vue.js data object and set them to empty. We only need to check that the email is not empty and is a valid email and that a message is added. You could make it that the name is required if you like but I am happy that most people will add a name and if they don't I can still contact them anyhow. We will also add some error properties that we can use to inform our users of errors. So 4 new properties in all.

Add 4 properties like shown below.

emailHolder: '',
messageHolder: '', 
errorEmail: '',
errorMessage: '',

At this point, we should also change the mappings of the form elements to these new empty properties. Essentially what we are doing is setting the form values to these holder properties and then checking the holders validate on submit and if they do populate the data object that we then pass to our fetch.

Check for empty values

Next, we are going to want to check that the fields aren't empty on submit. I am going to make sure that the message is of some minimum set length as I think at least a sentence would be good. This is only a frontend check however and not set in the Drupal contact form. We do this in a new method and if the checks pass we then run the submit method. So for this to happen we need to submit the form to a new method. Let's set that up.

- <form @submit.prevent="createPost">
+ <form @submit.prevent="validateInputs">

Now we will look at what we need to do in our new validateInputs method.

To check that the inputs are not empty we can use the .length method and make sure they are greater than 0 or in the case of the message check that it is greater than a certain number of characters. If the length is not correct then we can set the error message properties to the string.

You could also use ! to check it is not empty.

validateInputs(event) {
      
      if (this.messageHolder.length <= 20) {
        this.errorMessage = 'Please add Message of at least 20 characters';
      }

      if (!this.emailHolder) {
        this.errorEmail = 'Please add your email';
      }

      event.preventDefault();
    },

We also need to include the error message properties in the HTML. If you are familiar with Vue.js this should be easy for you to work out. The following code will be perfect.

<form @submit.prevent="validateInputs">
  <label for="title">Name</label>
  <input type="text" id="title" v-model="formData.data.attributes.name">
  <label for="email">Email</label>
+ <div>{{ errorEmail }}</div>
  <input type="text" id="email" v-model="emailHolder">
  <label for="body">Message</label>
+ <div>{{ errorMessage }}</div>
  <textarea v-model="messageHolder" placeholder="How can I help?" rows="5"></textarea>
  <button>Contact Us</button>
</form>

Okay, now we need to check that the email is a correct email. To do this we need to check it again. We can do this by passing the submitted value into a second function if it passes the first check. In our second function, we can use Regex to make sure the value submitted matches an email pattern. We can do this in an if-else statement.

if (!this.emailHolder) {
  this.errorEmail = 'Please add your email';
+ } else if (!this.checkEmailRegex(this.emailHolder)) {
+ this.errorEmail = 'Please add an email';
}

The code sequence is effectively 2 walls, the first wall checks the value is not empty, if it is empty it returns the message. If it is not empty then it goes around the wall and hits the second wall to check that the email is correctly formatted.

Now we need to add a couple more things to the method, namely, we need to:

  1. Clear the error holders each time the function is run.
  2. Set the fromData object properties to the values collected that are stored in the holder properties.
  3. We need to call the submit form function if all the checks return empty.

This is the complete validation method.

validateInputs(event) {
      this.errorEmail = '';
      this.errorMessage = '';

      if (this.messageHolder.length <= 20) {
        this.errorMessage = 'Please add Message of at least 20 characters';
      }

      if (!this.emailHolder) {
        this.errorEmail = 'Please add your email';
      }

      if (!this.errorEmail.length && this.messageHolder.length >= 20) {
        this.formData.data.attributes.mail = this.emailHolder;
        this.formData.data.attributes.message = this.messageHolder;
        this.clearPost();
      }

      event.preventDefault();
    },

So that's it, if the fields are validated then the submit function is called. Let's change the submit method so that it redirects to a new page on success.
Note that since we have validation on the submission that the response from Drupal should be a success and therefore our fetch async await function should run correctly.

Redirect on Success

Once the form is submitted successfully we want to make a message that lets the person know the form has been successfully submitted and they can expect to hear from us. You can also make an offer or guide them to another page on your website if you like. We will do this by redirecting them to a new page but you could also set the form to hidden and make a message appear.

As our code already deals with clearing the from on success, from Part 1 or 2, all we need to do is add a redirect. I am going to use a JavaScript method that is run on the window.

Add this to your clearPost() method.

window.location.href = 'about';

It's that simple now when the form is submitted with correct validation the user is directed to the about page.

If you would prefer to add a different page then you can learn about how to create a new page in the Vue.js Router article.

So there we have it, we can submit our form to our Drupal site from a cross-origin site.
In a follow up I will look at a few minor additions that will top this off nicely. Be sure to sign up for the email newsletter to be notified when it goes live. I also write about a bunch of other useful and interesting front-end technologies and share my learning experiences. Thanks for reading and have a great day!