Setting Up Ajax Validation with Laravel & VueJS in No Time

Setting Up Ajax Validation with Laravel & VueJS in No Time

It's never been easier to implement form validation in your applications like now with Laravel. All you have to do is to call $this->validate($request, []) from your controller and feed it the rules you want to validate with.

But sometimes we want to use Ajax validation so we can validate the form without reloading the page. And it's usually not very exciting to do this for every form in your application. Very quickly your code gets messy and it doesn't feel good.

Since this is a repeatable task, I thought it would be very helpful to find a way to make it easy to set up and use.

Recently, VueJS has been my main choice for every front-end task I want to accomplish. So this is what we'll use in this tutorial. Don't worry if it's new to you. It's so easy to learn. Even if you've never used it before, you can follow this tutorial easily.

After this tutorial you should be able to use Ajax validation in any form you want by just adding a few characters!

Registering routes

Let's start first by registering two end-points in our routes file – I'm here dealing with a fresh install of Laravel 5.2.

One route is for showing a form for creating a new article (this will be our example). The other one is to take the request and do the validation then create the article.

Route::group(['middleware' => ['web']], function () {
    Route::get('/article/create', 'ArticleController@showArticleCreationForm');
    Route::post('/article', 'ArticleController@publish');
});

Here's the initial implementation of both (in ArticleController).

public function showArticleCreationForm()
{
    return view('article.create');
}

public function publish(Request $request)
{
    $this->validate($request, [
        'title' => 'required|min:3',
        'body' => 'required|min:10'
    ]);

    return 'publish';
}

Creating the view

From the preceding code, we know that we need to create a view to show the creation form. So in the views directory create article/create.blade.php. Then put in the following.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Create Article</title>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" integrity="sha256-7s5uDGW3AHqw6xtJmNNtr+OBRJUlgkNJEo78P4b0yRw= sha512-nNo+yCHEyn0smMxSswnf/OnX6/KwJuZTlNZBjauKhTK0c+zT+q5JOCx0UFhXQ6rJR9jg6Es8gPuD2uZcYDLqSw==" crossorigin="anonymous">
    <style>
        .body {
            height: 200px !important;
        }

        .error {
            color: darkred;
            margin-top: 5px;
            display: block;
        }
   </style>
</head>
<body>
    <form class="col-md-4 col-md-offset-4" action="/article" method="post">
        <h1>Create New Article</h1>
        <hr>

        {!! csrf_field() !!}

        <div class="form-group">
            <input class="form-control title" type="text" name="title" placeholder="Title">
        </div>

        <div class="form-group">
            <textarea class="form-control body" name="body" placeholder="Content"></textarea>
        </div>

        <button class="btn btn-primary" type="submit">Publish</button>
    </form>
</body>
</html>

To make things easier, we'll do all of our work in this single file. When you know how things work, you can organize the views and scripts the way you want.

As you can see here, nothing is too fancy; it's a typical form. Also note that I'm using twitter bootstrap here to make things prettier.

Validating the form

At this point the validation works but nothing is displayed when the form is invalid. Let's do this now the normal way (without Ajax).

All we have to do is to check the $errors object to see if it has anything to display. We'll do that bellow each input.

<form class="col-md-4 col-md-offset-4" action="/article" method="post">
    <h1>Create New Article</h1>
    <hr>

    {!! csrf_field() !!}

    <div class="form-group">
        <input class="form-control title" type="text" name="title" placeholder="Title">
        @if ($errors->has('title'))
            <span class="error">{{ $errors->first('title') }}</span>
        @endIf
    </div>

    <div class="form-group">
        <textarea class="form-control body" name="body" placeholder="Content"></textarea>
        @if ($errors->has('body'))
            <span class="error">{{ $errors->first('body') }}</span>
        @endIf
    </div>

    <button class="btn btn-primary" type="submit">Publish</button>
</form>

If you try to submit this form with invalid data, you will get an error below the invalid input telling you what the problem is – normal stuff, right? Now let's do the Ajax thing.

The way we'll do it

Before we start, let me just give you a quick illustration of the way we'll accomplish this thing.

Since Laravel handles Ajax validation out of the box, we don't have to do anything in the back-end side. The controller will the remain the same.

What our work will be is to check when the user submits the form. And when he does, we'll prevent the defaults and send an Ajax request to the specified url (from the action property). In this case it will be the publish on the ArticleController.

If the form is valid, we'll allow the form to continue submitting – by calling .submit() on the form, as you'll see.

If it's not, on the other hand, we'll fetch all the errors sent by Laravel and display them as before, except this time using VueJS.

Getting VueJS

So the first step here is to pull in Vue and create an instance of it. To make things simpler, we'll reference a CDN version. So before add these two lines:

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.14/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-resource/0.6.1/vue-resource.min.js"></script>

One for Vue itself, and the other for performing ajax requests, called vue-resource.

Note that vue-resource is optional here. You can instead use jQuery to perform the Ajax requests if you want; It's a matter of preference.

After those two lines, open a new script tag and instantiate a new instance and bind it to the body of id #app – or any other wrapper, it just must contain the form.

<script>
new Vue({
    el: '#app',

    data: {},

    methods: {}
});
</script>

To check if everything is bound correctly, add a ready() method to the Vue instance and alert some message.

<script>
new Vue({
    el: '#app',

    data: {},

    methods: {},

    ready: function() {
      alert('Everything is working fine!');
    }
});
</script>

Preparing data holders

Even if you're not familiar with VueJS, you might have noticed that the Vue instance takes some data and methods to perform.

In the data object, we'll need two objects: formInputs and formErrors. The formInputs will have the current values of the form inputs. So this means we need to bind each form input to a property within the formInputs object.

The second one is formErrors which will have the errors returned by Laravel (if any) of each invalid form input.

To have those objects, add them to the data of the Vue instance.

data: {
  formInputs: {},
  formErrors: {}
}

Binding the form inputs

Now let's update our form to bind its inputs to the data of our Vue instance.

<form @submit.prevent="submitForm" class="col-md-4 col-md-offset-4" action="/article" method="post">
    <h1>Create New Article</h1>
    <hr>

    {!! csrf_field() !!}

    <div class="form-group">
        <input class="form-control title" type="text" name="title" placeholder="Title" v-model="formInputs.title">
        <span v-if="formErrors['title']" class="error">@{{ formErrors['title'] }}</span>
    </div>

    <div class="form-group">
        <textarea class="form-control body" name="body" placeholder="Content" v-model="formInputs.body"></textarea>
        <span v-if="formErrors['body']" class="error">@{{ formErrors['body'] }}</span>
    </div>

    <button class="btn btn-primary" type="submit">Publish</button>
</form>

Nothing complex. Normal Vue stuff. One thing I want you to notice, though, is the event we bind to the form: @submit.prevent="submitForm". This event is responsible to fire the Ajax request and see if it can continue submitting the form or not – we've already talked about this thing.

Implementing the event handler

The only remaining thing is to implement that event handler. In the methods object add the following.

submitForm: function(e) {
  var form = e.srcElement;
  var action = form.action;
  var csrfToken = form.querySelector('input[name="_token"]').value;

  this.$http.post(action, this.formInputs, {
    headers: {
        'X-CSRF-TOKEN': csrfToken
    }
  })
  .then(function() {
        form.submit();
  })
  .catch(function (data, status, request) {
    var errors = data.data;
    this.formErrors = errors;
  });
}

At the beginning of the function, we pull in some necessary data. Like the target url of the form (action) and the CSRF token associated with the form.

After that we fire the Ajax request and see what kind of response we get back. If it's successful, then we fire the callback inside the then part. All it does is submitting the form.

However, if it's not a successful request, the error handler (in the catch) fetches all the errors and assign them to the formErrors object of our Vue instance. Note that the errors are found inside the data property of the data response: data.data.

One little caveat

Great! Everything should be working now. You can test this by yourself and see the result.

However, there's a little problem you may haven't noticed yet. And that is the code inside ArticleController@publish will be executed twice when the form is valid: once by the ajax request and once by the normal request.

The way I solve this problem is by preventing the Ajax request from continuing.

public function publish(Request $request)
{
    $this->validate($request, [
        'title' => 'required|min:3',
        'body' => 'required|min:10'
    ]);

    if ($request->ajax()) return;

    return 'publish';
}

Actually, I couldn't figure out a better solution than this one. So please guys if you have a better solution, let me know in the comments.

It's a reusable solution

The great thing about this solution, in my mind, is that it's reusable throughout your application. You only have to attach the submitForm event to the form. Then bind its inputs to the two objects: formInputs & errorInputs.

Other than that, there's nothing to do!

You can get the final Vue code from this gist.

Laravel Vue
Taha Shashtari

About Taha Shashtari

I'm a freelance web developer. Laravel & VueJS are my main tools these days and I love building stuff using them. I write constantly on this blog to share my knowledge and thoughts on things related to web development... Let's be friends on twitter.