In part 7 we wrote the code for creating, previewing and saving a blog post, but we haven’t yet added any validation or even let the user know if the request was successful, so that’s the next thing we need to do. We’ll start by adding Sweet Alert 2 so we can return a success or error message, I’ve previously written an article on Adding Sweet Alert to Your Vue.js Application using Mixins, so we’re going to use that code as our base and adjust it slightly for Sweet Alert 2’s Promise based system, but first let’s add Sweet Alert 2 to our project:

npm install sweetalert2 --save-dev

Now let’s import that in resources/assets/js/admin/admin.js:

import SweetAlert from 'sweetalert2';
window.swal = SweetAlert;

And let’s import the styles in resources/assets/sass/app.scss above our semantic-ui styles:

@import "node_modules/sweetalert2/dist/sweetalert2.min";

And now run:

gulp sass

Now let’s create a new folder in resources/assets/js called mixins and create a new file in that folder called ‘SweetAlert.js’ and add our adjusted SweetAlert code:

var SweetAlert = {
    methods: {
        alert(options) {
            return swal(options);
        },
        alertSuccess({ title = "Success!", text = "That's all done!", timer = 1000, showConfirmationButton = false } = {}) {
            return new Promise((resolve, reject) => {
                this.alert({
                    title: title,
                    text: text,
                    timer: timer,
                    showConfirmButton: showConfirmationButton,
                    type: 'success'
                }).then(() => {
                    resolve();
                }, dismiss => {
                    resolve();
                })
            });
        },
        alertError({ title = "Error!", text = "Oops...Something went wrong", html = "" } = {}) {
            return new Promise((resolve, reject) => {
                this.alert({
                    title: title,
                    html: html,
                    text: text,
                    type: 'error'
                }).then(() => {
                    resolve();
                }, dismiss => {
                    resolve();
                });
            });
        },
        confirm(options = {}) {
            options = Object.assign({
                title: "Are you sure?",
                text: "Are you sure you want to do that?",
                type: "warning",
                showCancelButton: true,
                confirmButtonColor: "#DD6B55",
                confirmButtonText: "Yes",
            }, options);

            return this.alert(options);
        }
    }
}

export default SweetAlert;

Here you will notice that I’m also using promises. Sweet Alert 2 uses the Promise.reject() method when the dialog box is dismissed without confirmation from the user (i.e. when the user doesn’t click the “OK” button), which means alerts using a timer to close the dialog will fire the reject() method, and those with confirmButtons will resolve. I’m not interested in keeping that for success and error alerts because I’m not bothered how they were closed, I just want to know that the have been closed, so I’m resolving the promise regardless which means we can perform an action after the alert has shown. The confirm box uses Sweet Alert 2’s Promise system, because I do care if the user clicked “OK” or “Cancel” in this case.

With that done, we can now add Sweet Alert 2 to our CreatePost.vue file by importing the SweetAlert mixin and adding it to the mixin array. We can then call the alertSuccess method when the ajax call in our saveArticle() method returns a success response, later we will use the resolved Promise from the alertSuccess() method to push our router to the edit page, but we haven’t got that setup yet so I’ll just add a comment for now.

import ArticleForm from '@/admin/components/partials/ArticleForm.vue';
import SweetAlert from '@/mixins/SweetAlert.js';

export default {
    components: {
        ArticleForm
    },
    mixins: [SweetAlert],
    methods: {
        setArticle(title, article) {
            this.title = title;
            this.article = article;
        },
        saveArticle() {
            axios.post('/api/articles', {
                title: this.title,
                article: this.article
            }).then(response => {
                // Alert that the article was saved succesfully
                this.alertSuccess().then(() => {
                  // Push router to edit page
                });
            });
        },
    },
    data() {
        return {
            title: '',
            article: ''
        }
    }
}

Setting Up Form Request Validation

With that done we want to validate our form and return any errors, and to do that we are going to use Laravel’s Form Request Validation, so let’s create a new Form Request for validating an Article by doing:

php artisan make:request ArticleRequest

Which will create a file called ArticleRequest.php in app\Http\Requests, so let’s add some validation rules for our article, and make sure we return true from the authorize() method, so our Form Request now looks like this:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class ArticleRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'title' => 'required|unique',
            'article' => 'required'
        ];
    }
}

This simply makes sure we have a unique “title” and that the “title” and “article” fields aren’t empty. I don’t want to go over the top with the validation because later we will want to save drafts, which may mean just having a title and a short snippet of what the article is about. So now all we have to do to use our ArticleRequest is type hint it in the created method in our ArticlesController like so (note the use App\Http\Requests\ArticleRequest; at the top of the class which):

<?php

namespace App\Http\Controllers\Api;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use cebe\markdown\GithubMarkdown;
use App\Http\Services\ArticleService;
use App\Article;
// Use ArticleRequest
use App\Http\Requests\ArticleRequest;

class ArticlesController extends Controller
{
   // Other methods omitted 

    // Type Hint ArticleRequest
    public function store(ArticleRequest $request){
        $request['formatted_article'] = $this->articleService->renderArticle($request->article);
        $request['slug'] = str_slug($request['title']);
        Article::create($request->all());
    }
}

Now if we try to submit our form with an empty “title” or “article” field we will receive a “422 unprocessable entity” error along with our Laravel error messages, so let’s deal with the response in our CreatePost.vue file by adding a catch method to our ajax call and throwing up an error alert with the validation errors:

saveArticle() {
    axios.post('/api/articles', {
        title: this.title,
        article: this.article
    }).then(response => {
        // Alert that the article was saved succesfully
        this.alertSuccess().then(() => {
            // Push router to edit page
        });
    }).catch(error => {
        let response = error.response;
        if(response.status === 422){
            let message = '';
            // Loop through each error message and add it to our message variable
            Object.keys(response.data).forEach(item => {
                let field = response.data[item];
            	for(let i = 0; i < field.length; i++){
                  message += field[i] + "<br />"; 	
                }
            });
            this.alertError({html: message});
        }
    });
}

Now we get an error alert with our laravel validation error messages, however, that piece of code looks like something I might be needing again, so lets refactor this code and use a mixin instead. So in resources\assets\mixins we will create a new file called ValidationHandler.js and add the following code:

var ValidationHandler = {
    methods: {
        getHtmlValidationErrors(response) {
            let message = '';
            // Loop through each error message and add it to our message variable
            Object.keys(response.data).forEach(item => {
            	let field = response.data[item];
            	for(let i = 0; i < field.length; i++){
                  message += field[i] + "<br />"; 		
            	}
            });

            return message;
        }
    }
}

export default ValidationHandler;

Now in CreatePosts.vue we can use the getHtmlValidationErrors() method from the mixin, all we need to do is import the mixin and add it to our mixin array, we can then replace our error response code:

import ArticleForm from '@/admin/components/partials/ArticleForm.vue';
import SweetAlert from '@/mixins/SweetAlert.js';
import ValidationHandler from '@/mixins/ValidationHandler.js';

export default {
    components: {
        ArticleForm
    },
    mixins: [SweetAlert, ValidationHandler],
    methods: {
        setArticle(title, article) {
            this.title = title;
            this.article = article;
        },
        saveArticle() {
            axios.post('/api/articles', {
                title: this.title,
                article: this.article
            }).then(response => {
                // Alert that the article was saved succesfully
                this.alertSuccess().then(() => {
                   // Push router to edit page
                });
            }).catch(error => {
                let response = error.response;
                if (response.status === 422) {
                    this.alertError({
                        // Call getHtmlValidationErrors from the ValidationHandler mixin
                        html: this.getHtmlValidationErrors(response)
                    });
                }
            });
        },
    },
    data() {
        return {
            title: '',
            article: ''
        }
    }
}

Finally, let’s use our Loader component to place a ‘saving article’ loader over the page while the save is in progress, so our final component looks like this:

<template>
    <div>
        <loader :loading="saving" loading-text="Saving Article"></loader>
        <article-form @input="setArticle"></article-form>
        <div class="ui divider"></div>
        <button class="ui primary button" type="submit" @click="saveArticle">Publish</button>
    </div>
</template>

<script type="text/javascript">
import ArticleForm from '@/admin/components/partials/ArticleForm.vue';
import Loader from '@/components/core/Loader.vue';
import SweetAlert from '@/mixins/SweetAlert.js';
import ValidationHandler from '@/mixins/ValidationHandler.js';

export default {
    components: {
        ArticleForm,
        Loader
    },
    mixins: [SweetAlert, ValidationHandler],
    methods: {
        setArticle(title, article) {
            this.title = title;
            this.article = article;
        },
        saveArticle() {
            this.saving = true;
            axios.post('/api/articles', {
                title: this.title,
                article: this.article
            }).then(response => {
                // Alert that the article was saved succesfully
                this.alertSuccess().then(() => {
                   // Push router to edit page
                });
            }).catch(error => {
                let response = error.response;
                if (response.status === 422) {
                    this.alertError({
                        html: this.getHtmlValidationErrors(response)
                    });
                }
            }).then(() => {
                this.saving = false;
            });
        },
    },
    data() {
        return {
            title: '',
            article: '',
            saving: false
        }
    }
}
</script>

And that’s it for part 8, we now have SweetAlert and a Validation Handler added and everything’s coming along nicely. In Part 9 we will look at editing our saved articles.

As always, you can find all the code we have written here on the Vuetiful Project’s Github Page

Advertisements