Monday, September 11, 2017

How to Validate and Submit Form Using VueJs and PHP?

Client-side validation using JavaScript enhances user experience by giving feedback immediately to the user rather than having them complete a round-trip through the server. Before we begin, you must understand that this type of data input validation is no substitute for server-side validation. Without server-side validation, you are trusting the user and whatever tools they are using to make sure input is valid. Client-side validation with Vue.js (or any other JavaScript library/framework) should be used in addition to, not instead of it.

I'll also mention that many modern browsers have client-side validation built-in by using HTML attributes such as (but not limited to) required and maxlength="10". However, not all browsers support this and all of them behave differently. Let’s look at how to build our own form validation using Vue.js so it behaves the same in all modern browsers.

Scenario
We want to build a form with 4 inputs:
• Name
• Number between 1 and 10
• Email
• Message

All inputs are required. The number input should only allow numbers between 1 and 10 & email input should have a valid format.

Setup
Start with a basic HTML file with the Bootstrap 3 stylesheet and Vue.js.
<!DOCTYPE html>
<html>
 <head>
    <meta charset="utf-8">
    <title>Form Validation</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"/>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" integrity="sha256-eZrrJcwDc/3uDhsdt61sL2oOBY362qM3lon1gyExkL0=" crossorigin="anonymous" />
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
</body>
</html>
Our form will go in the content area. Just to get a prettier layout, we’ll put it inside a single col, inside a single row, inside a single container. Each of these elements have classes that position them nicely.
<!div class="container">
  <!div class="row justify-content-center">
    <!div class="col-md-6">
      <!form>
       <!!-- form elements go here -->
      <!/form>
    <!/div><!!-- /col -->
  <!/div><!!-- /row -->
<!/div><!!-- /container -->
Now, our 4 input elements and submit button will go inside the form. Bootstrap provides validation classes. Let’s add those as well.
  <div class="form-group">
      <label class="control-label" for="name">Name </label>
      <input id="name" name="name" class="form-control" type="text" v-model="name">
      <span id="helpBlock" class="help-block">This field is required. </span>
    </div>
    
    <div class="form-group">
      <label class="control-label" for="number">Enter a number between 1 and 10 </label>
      <input id="number" name="number" class="form-control" type="text" v-model="number">
      <span id="helpBlock" class="help-block">Make sure this is a number between 1 and 10. </span>
    </div>

    <div class="form-group">
      <label class="control-label" for="name"Email</label>
      <input id="email" name="email" class="form-control" type="text" v-model="email">
      <span id="helpBlock" class="help-block">Email is invalid. </span>
    </div>

    <div class="form-group">
      <label class="control-label" for="message">Enter a Message </label>
      <textarea  name="message" class="form-control" type="text" v-model="message"> </textarea>
      <span id="helpBlock" class="help-block">This field is required. </span>
    </div>                 
    
    <button class="btn btn-primary">Submit </button>

Right now, the feedback elements will always be displayed. Later, we'll make sure they’re only shown if validation fails.

Building the Vue App
With the HTML in place, now we'll jump into main.js and start coding the logic for our form.
var form = new Vue({
  el: '#form',
  data: {
    name: '',
    email: '',
    message: '',
    number: '',
    attemptSubmit: false,
    postStatus: false
  },
  computed: {
    missingName: function () {},
    wrongNumber: function () {},
    missingEmail: function () {},
    missingMessage: function () {}
  },
  methods: {
    isNumeric: function () {},
    isEmail: function () {},
    validateForm: function () {},
    onSubmit () {}
  },
});


In our data object, there are 6 variables:
1. name — Will hold the value of the name input
2. email — Will hold the value of the email input
3. message — Will hold the value of the message input
4. number — Will hold the value of the number input
5. attemptSubmit — A boolean that indicates whether the user has attempted to submit the form. We can use this to hide validation warnings until after the user has tried to submit the form.
6. postStatus - A boolean that indicates whether the form has been submitted. We can use this to hide the form after the submission.

There are also a 4 computed variables here:
1. missingName — Will return true if name is empty
2. wrongNumber — Will return true if number is not between 1 and 10
3. missingEmail — Will return true if email is empty or invalid
4. missingMessage — Will return true if message is empty

Finally, there are a 4 methods:
1. isNumeric — A utility function that checks to see if an input is a number
2. isEmail — A utility function that checks to see if an input has valid email
3. validateForm — The main function that will be called when trying to submit
4. onSubmit - If the validation is passed, that will be called

I mentioned above that there are 4 variables that hold the value of the inputs. We can wire those up with Vue models. Back in the HTML…
  <input id="name" name="name" class="form-control" type="text" v-model="name">
  <input id="number" name="number" class="form-control" type="text" v-model="number">
  <input id="email" name="email" class="form-control" type="text" v-model="email">
  <textarea  name="message" class="form-control" type="text" v-model="message"></textarea>
With our models wired up, we can use the computed variables to determine the status of each input. missingName and missingMessage are easy. Return true if value is empty:
  missingName: function () { return this.name === ''; },
  missingMessage: function () { return this.message === ''; },
wrongNumber is a little more complex, but not bad at all. Return true if the value is not a number or it’s less than 1 or it’s greater than 10. missingEmail return true if the value has not a valid format or null.
 wrongNumber: function () {
  return (
    this.isNumeric(this.number) === false ||
    this.number < 1 ||
    this.number > 10
  )
},

missingEmail: function () {
   return (
     this.isEmail(this.email) === null)
},
Oops, we’re calling isNumeric() and isEmail() but we haven’t coded it, yet. Here’s what it looks like:
isNumeric: function (n) {
  return !isNaN(parseFloat(n)) && isFinite(n);
},
isEmail: ( str ) => {      
   let regexp = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/     
   return str.match(regexp);
}
We're actually done with the JavaScript except for the actual validateForm() and onSubmit function. For that one, we just want to tell our app the user attempted to submit the form and prevent it from submitting if missingName, missingEmail, missingMessage or wrongNumber are true. If all the values are validated, we call onSubmit method.
validateForm: function (event) {
   this.attemptSubmit = true;
   if (this.missingName || this.wrongNumber || this.missingMessage || this.missingEmail) {
       event.preventDefault();
   } else {
       this.onSubmit();
   }       
Done with JavaScript. Back in the HTML, we need to fire the validateForm() function when the form is submitted.

  <form id="form" method="post" v-on:submit.prevent="validateForm">


Then, add the (Bootstrap) has-warning class for each input. We only want to add this class if BOTH:
1. The user has attempted to submit the form, and 2. The value is not valid
<div class="form-group" v-bind:class="{ 'has-warning': attemptSubmit && missingName }">
...
<div class="form-group" v-bind:class="{ 'has-warning': attemptSubmit && wrongNumber }">
...
<div class="form-group" v-bind:class="{ 'has-warning': attemptSubmit && missingEmail }">
...
<div class="form-group" v-bind:class="{ 'has-warning': attemptSubmit && missingMessage }">

Finally, we only want the feedback elements to appear under those same circumstances:
 <span id="helpBlock" class="help-block" v-if="attemptSubmit && missingName">This field is required.</span>
.....
 <span id="helpBlock" class="help-block" v-if="attemptSubmit && wrongNumber">Make sure this is a number between 1 and 10.</span>
......
 <span id="helpBlock" class="help-block" v-if="attemptSubmit && missingEmail">Email is invalid.</span>
......
 <span id="helpBlock" class="help-block" v-if="attemptSubmit && missingMessage">This field is required.</span>
That’s it! On load, the form looks like…
If you try to submit with an empty fields, a warning appears.
The nice part is that, the way we’ve coded it, when the user corrects their mistake, the warning goes away immediately.

Form Submission
After the validation is successful, we will submit using PHP. In order to submit the POST values we are going to use. Axios. Axios is a promise-based HTTP client that works both in the browser and in a node.js environment. It basically provides a single API for dealing with XMLHttpRequests and node’s http interface.

Before using axios, you first need to install it. Adding Axios to your project is easy. There are two options:
1. Install Axios with Node Package Manager as follows:
  $ npm install axios
2. The easiest way is to include Axios by using a Content Delivery Network, e.g. by including the following <script> tag in your index.html file:
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

Simply import axios or require it as you do with any other dependency in node as below (if you are using nodeJs and webpack only):

import axios from 'axios';

or

var axios = require('axios');

A frequently overlooked but very useful capability Axios provides is the ability to create a base instance that allows you to share a common base URL and configuration across all calls to the instance.

onSubmit () {
    
     if (process.env.NODE_ENV === 'development') {
            axios.defaults.baseURL = 'http://localhost:8888/your-project-name/php'
      }

      axios.post('post.php', {
                'name': this.name,
                'email': this.email,
                'message': this.message,
                'number': this.number               
        }).then(response => {
            if (response.data.error) {
                    console.log('error', response.data.error)
           } else {
                this.postStatus = true
                console.log('success', response.data.message)                   
           }
        }).catch(error => {
              console.log(error.response)
     }); 
}

Create a folder 'php' and create a file 'post.php' under it.
/* php/post.php */

<?php
header('Content-type: application/json');
header('Access-Control-Allow-Headers: Content-Type');
header("Access-Control-Allow-Origin: *");

$inputJSON = file_get_contents('php://input');
$input = json_decode($inputJSON, TRUE);

$name = $input['name'];
$email = $input['email'];
$message = $input['message'];
$number = $input['number'];


$result['message'] = '';
$result['error']  = false;

if($name){
  $result['message']  = "Posted Values => ".$name."-".$email."-".$message."-".$number;
  $result['error']  = false;
}
else {
  $result['error']  = 'Form submission failed.';
}


echo json_encode($result);

file_get_contents(php://input) - gets the raw POST data and this is useful if the POSTed data is a JSON encoded structure, which is often the case for an AJAX POST request. The PHP superglobal $_POST, only is supposed to wrap data that is either
1. application/x-www-form-urlencoded (standard content type for simple form-posts) or
2. multipart/form-data-encoded (mostly used for file uploads)

This is because these are the only content types that must be supported by user agents. So the server and PHP traditionally don't expect to receive any other content type (which doesn't mean they couldn't). But if you are working with Ajax a lot, this probably also includes exchanging more complex data with types (string, int, bool) and structures (arrays, objects), so in most cases JSON is the best choice. The content would now be 'application/json' (or at least none of the above mentioned), so PHP's $_POST-wrapper doesn't know how to handle that (yet). The data is still there, you just can't access it through the wrapper. So you need to fetch it yourself in raw format with file_get_contents('php://input') (as long as it's not multipart/form-data-encoded).

After the form is submitted, you will see confirmation message as below.
I have created a component called 'Form.Vue' with above pieces. Here is the complete source code.

//Form.Vue

<template>  
    <div id="app" class="container">
      <div class="row justify-content-center">
        <div class="col-md-6" v-if="!postStatus">
          <form id="form" method="post" v-on:submit.prevent="validateForm">
            <div class="form-group" v-bind:class="{ 'has-warning': attemptSubmit && missingName }">
              <label class="control-label" for="name">Name</label>
              <input id="name" name="name" class="form-control" type="text" v-model="name">
              <span id="helpBlock" class="help-block" v-if="attemptSubmit && missingName">This field is required.</span>
            </div>
            
            <div class="form-group" v-bind:class="{ 'has-warning': attemptSubmit && wrongNumber }">
              <label class="control-label" for="number">Enter a number between 1 and 10</label>
              <input id="number" name="number" class="form-control" type="text" v-model="number">
              <span id="helpBlock" class="help-block" v-if="attemptSubmit && wrongNumber">Make sure this is a number between 1 and 10.</span>
            </div>

             <div class="form-group" v-bind:class="{ 'has-warning': attemptSubmit && missingEmail }">
              <label class="control-label" for="name">Email</label>
              <input id="email" name="email" class="form-control" type="text" v-model="email">
              <span id="helpBlock" class="help-block" v-if="attemptSubmit && missingEmail">Email is invalid.</span>
            </div>

             <div class="form-group" v-bind:class="{ 'has-warning': attemptSubmit && missingMessage }">
              <label class="control-label" for="message">Enter a Message</label>
              <textarea  name="message" class="form-control" type="text" v-model="message"></textarea>
              <span id="helpBlock" class="help-block" v-if="attemptSubmit && missingMessage">This field is required.</span>
            </div>                 
            
            <button class="btn btn-primary">Submit</button>
          </form>
        </div><!-- /col -->

         <div class="col-md-8" v-if="postStatus">

           <h1>The form is submitted successfully.</h1>

         </div> 
      </div><!-- /row -->

    </div><!-- /container -->
</template>

<script>

import axios from 'axios'
  
  export default {   
    data () {
      return {
        name: '',
        email: '',
        message: '',
        number: '',
        attemptSubmit: false,
        postStatus: false
      }
    },
    computed: {
        missingName: function () { return this.name === '' },
        missingMessage: function () { return this.message === '' },
        missingEmail: function () {
          return (
            this.isEmail(this.email) === null)
        },
        wrongNumber: function () {
          return (
            this.isNumeric(this.number) === false ||
            this.number < 1 ||
            this.number > 10
          )
      },
  },
  methods: {
     isNumeric: function (n) {
      return !isNaN(parseFloat(n)) && isFinite(n);
    },
    isEmail: ( str ) => {      
      let regexp = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/     
      return str.match(regexp);
    },       
    validateForm: function (event) {
      this.attemptSubmit = true;
      if (this.missingName || this.wrongNumber || this.missingMessage || this.missingEmail) {
          event.preventDefault();
      } else {
         this.onSubmit();
      }       
    },
    onSubmit () {
    
       if (process.env.NODE_ENV === 'development') {
                axios.defaults.baseURL = 'http://localhost:8888/your-project-name/php'
        }

       axios.post('post.php', {
                'name': this.name,
                'email': this.email,
                'message': this.message,
                'number': this.number               
            }).then(response => {
                if (response.data.error) {
                    console.log('error', response.data.error)
                } else {
                    this.postStatus = true
                    console.log('success', response.data.message)                   
                }
            }).catch(error => {
                console.log(error.response)
            }); 
      }
  }   
}
</script>

Source: Form Validation with Vue.js

1 comment:

  1. Thank you for this tutorial, worked like a charm!

    ReplyDelete