Learn How To Make CRUD Pages With Laravel And Vuejs

Learn How To Make CRUD Pages With Laravel And Vuejs Part 2

In this part of this series we continue completing the CRUD operations and we will finish it with Create and update posts.

 

 

Series Topics:

 

In the previous part we setup the project and setup all the required assets, added the listing page, sorting, search, and delete. In this part we will cover (C) create and (U) update and view operations.

 

Creating Posts

Open assets/js/components/Crud/CreatePost.vue and add the below contents:

<template>
    <div>
        <h2>Create Post</h2>

        <ul v-if="this.errors.length > 0" class="ui negative message">
            <li v-for="val in this.errors">{{ val }}</li>
        </ul>

        <div v-if="this.success!=''" class="ui positive message">
            {{ this.success }}
        </div>

        <div class="ui active dimmer" v-if="showLoader">
            <div class="ui loader"></div>
        </div>

        <form class="ui form" method="post" v-on:submit.prevent="submit" enctype="multipart/form-data">
            <div class="field" v-bind:class="{error: this.titleClass}">
                <label>Title</label>
                <input type="text" name="title" placeholder="Title" v-model="title">
            </div>
            <div class="field" v-bind:class="{error: this.bodyClass}">
                <label>Body</label>
                <textarea name="body" placeholder="Body" v-model="body"></textarea>
            </div>
            <div class="field">
                <label>Photo</label>
                <input type="file" name="photo" @change="onFileChanged">
            </div>
            <button class="ui button primary" type="submit">Submit</button>
            <router-link to="/list" class="ui button">Close</router-link>
        </form>
    </div>
</template>

<script>

    import axios from 'axios'

    export default {
        data() {
          return {
              errors: [],
              titleClass: false,
              bodyClass: false,
              success: "",
              showLoader: false
          }
        },
        methods: {
            submit(event) {

                let self = this;

                this.errors = [];

                if(this.$store.state.title == "") {
                    this.errors.push("Title required");
                }

                if(this.$store.state.body == "") {
                    this.errors.push("Body required");
                }

                if(this.$store.state.photoObj == "") {
                    this.errors.push("Photo required");
                }

                if(this.$store.state.photoObj != "") {
                    var extension = this.$store.state.photoObj.name.split(".")[1];
                    if(extension != "jpg" && extension != "jpeg" && extension != "png" && extension != "gif") {
                        this.errors.push("Photo extension invalid");
                    }
                }

                if(this.errors.length > 0) {
                    return false;
                }

                self.showLoader = true;

                const formData = new FormData();
                formData.append('title', self.$store.state.title);
                formData.append('body', self.$store.state.body);
                formData.append('photo', self.$store.state.photoObj, self.$store.state.photoObj.name)

                axios.post(BaseUrl + '/posts/create', formData, { withCredentials: true })
                    .then((result) => {
                        self.success = result.data.msg;

                        self.showLoader = false;

                        self.$store.commit('setTitle', '');
                        self.$store.commit('setBody', '');
                        self.$store.commit('setPhotoObj', '');
                        self.$store.commit('setPhoto', '');

                        setTimeout(()=>{
                            self.$router.push({ path: '/list' })
                        }, 1000);
                    });
            },
            onFileChanged(event) {
                const file = event.target.files[0];

                this.$store.commit('setPhotoObj', file);
            }
        },
        computed: {
            title: {
                get() {
                    return this.$store.state.title
                },
                set(value) {
                    this.$store.commit('setTitle', value)

                    if(value == "") {
                        this.titleClass = true;
                    } else {
                        this.titleClass = false;
                    }
                }
            },
            body: {
                get() {
                    return this.$store.state.body;
                },
                set(value) {
                    this.$store.commit('setBody', value);

                    if(value == "") {
                        this.bodyClass = true;
                    } else {
                        this.bodyClass = false;
                    }
                }
            }
        }
    }
</script>

In this code we added the create form which has three fields (title, body, photo) and we used the two way model binding to bind the fields to the values attached to the store and defined the values as a computed property like this:

<div class="field" v-bind:class="{error: this.titleClass}">
     <label>Title</label>
     <input type="text" name="title" placeholder="Title" v-model="title">
</div>

computed: {
            title: {
                get() {
                    return this.$store.state.title
                },
                set(value) {
                    this.$store.commit('setTitle', value)

                    if(value == "") {
                        this.titleClass = true;
                    } else {
                        this.titleClass = false;
                    }
                }
            }
.....

Here the title will be updated and read from the vuex store using the get() and set() functions, the get() will be called to retrieve data and set() will be called on change the input.

 

Then we added a method to submit the form, at first the method validates the inputs and in case there is an error it will be rendered in top of the form using v-for like this:

<ul v-if="this.errors.length > 0" class="ui negative message">
            <li v-for="val in this.errors">{{ val }}</li>
        </ul>

On successful submission we send an ajax request with form fields so we constructed a formData object and fill it with the data like this:

const formData = new FormData();
                formData.append('title', self.$store.state.title);
                formData.append('body', self.$store.state.body);
                formData.append('photo', self.$store.state.photoObj, self.$store.state.photoObj.name)

                axios.post(BaseUrl + '/posts/create', formData, { withCredentials: true })
                    .then((result) => {
                        self.success = result.data.msg;

                        self.showLoader = false;

                        self.$store.commit('setTitle', '');
                        self.$store.commit('setBody', '');
                        self.$store.commit('setPhotoObj', '');
                        self.$store.commit('setPhoto', '');

                        setTimeout(()=>{
                            self.$router.push({ path: '/list' })
                        }, 1000);
                    });

After the data saved successfully we return back to the listing page using $router.push({path: ‘/list’})

Important note: sometimes laravel blocks post requests through ajax and shows error 419 and this because cross domain to solve this install laravel package called laravel cors:

composer require barryvdh/laravel-cors



Updating Posts

Open assets/js/components/Crud/EditPost.vue and add the below contents:

<template>
    <div>
        <h2>Edit Post #{{this.$route.params.id}}</h2>

        <ul v-if="this.errors.length > 0" class="ui negative message">
            <li v-for="val in this.errors">{{ val }}</li>
        </ul>

        <div v-if="this.success!=''" class="ui positive message">
            {{ this.success }}
        </div>

        <div class="ui active dimmer" v-if="showLoader">
            <div class="ui loader"></div>
        </div>

        <form class="ui form" method="post" v-on:submit.prevent="submit" enctype="multipart/form-data">
            <div class="field" v-bind:class="{error: this.titleClass}">
                <label>Title</label>
                <input type="text" name="title" placeholder="Title" v-model="title">
            </div>
            <div class="field" v-bind:class="{error: this.bodyClass}">
                <label>Body</label>
                <textarea name="body" placeholder="Body" v-model="body"></textarea>
            </div>
            <div class="field">
                <img v-if="this.$store.state.photo!=''" :src="photoUrl" width="100" height="80" />
                <label>Photo</label>
                <input type="file" name="photo" @change="onFileChanged">
            </div>
            <button class="ui button primary" type="submit">Submit</button>
            <router-link to="/list" class="ui button">Close</router-link>
        </form>
    </div>
</template>

<script>

    import axios from 'axios'

    export default {
        data() {
          return {
              errors: [],
              titleClass: false,
              bodyClass: false,
              success: "",
              showLoader: false,
              photoUrl: ""
          }
        },
        methods: {
            submit(event) {

                let self = this;

                this.errors = [];

                if(this.$store.state.title == "") {
                    this.errors.push("Title required");
                }

                if(this.$store.state.body == "") {
                    this.errors.push("Body required");
                }

                if(this.$store.state.photoObj != "") {
                    var extension = this.$store.state.photoObj.name.split(".")[1];
                    if(extension != "jpg" && extension != "jpeg" && extension != "png" && extension != "gif") {
                        this.errors.push("Photo extension invalid");
                    }
                }

                if(this.errors.length > 0) {
                    return false;
                }

                self.showLoader = true;

                let token = document.head.querySelector('meta[name="csrf-token"]');

                const formData = new FormData();
                formData.append('title', self.$store.state.title);
                formData.append('body', self.$store.state.body);
                formData.append('id', self.$route.params.id);
                formData.append('_token', token.content);

                if(self.$store.state.photoObj != "") {
                    formData.append('photo', self.$store.state.photoObj, self.$store.state.photoObj.name);
                }

                setTimeout(() => {
                    axios.post(BaseUrl + '/posts/update', formData, { withCredentials: true })
                        .then((result) => {
                            self.success = result.data.msg;

                            self.showLoader = false;

                            self.$store.commit('setTitle', '');
                            self.$store.commit('setBody', '');
                            self.$store.commit('setPhotoObj', '');
                            self.$store.commit('setPhoto', '');

                            setTimeout(()=>{
                                self.$router.push({ path: '/list' })
                            }, 1000);
                    });
                }, 2000);
            },
            onFileChanged(event) {
                const file = event.target.files[0];

                this.$store.commit('setPhotoObj', file);
            }
        },
        computed: {
            title: {
                get() {
                    return this.$store.state.title
                },
                set(value) {
                    this.$store.commit('setTitle', value)

                    if(value == "") {
                        this.titleClass = true;
                    } else {
                        this.titleClass = false;
                    }
                }
            },
            body: {
                get() {
                    return this.$store.state.body;
                },
                set(value) {
                    this.$store.commit('setBody', value);

                    if(value == "") {
                        this.bodyClass = true;
                    } else {
                        this.bodyClass = false;
                    }
                }
            }
        },
        mounted() {
        	let self = this;

        	self.showLoader = true;

            axios.get(BaseUrl + '/posts/view/' + this.$route.params.id)
                .then((result) => {
                    let post = result.data.data;

                    self.$store.commit('setTitle', post.title);
                    self.$store.commit('setBody', post.body);
                    self.$store.commit('setPhoto', post.photo);

                    self.photoUrl = this.$store.state.photo;

                    self.showLoader = false;
                });
        }
    }
</script>

The update form as the same as the create form except that we need to fetch the post data on first page load and populate them with form fields so in the mounted() hook we add this code:

mounted() {
        	let self = this;

        	self.showLoader = true;

            axios.get(BaseUrl + '/posts/view/' + this.$route.params.id)
                .then((result) => {
                    let post = result.data.data;

                    self.$store.commit('setTitle', post.title);
                    self.$store.commit('setBody', post.body);
                    self.$store.commit('setPhoto', post.photo);

                    self.photoUrl = this.$store.state.photo;

                    self.showLoader = false;
                });
        }

The form values gets automatically binded as we already update the store and this is the advantage of using vuex store which acts as a central repository of data.

 

Viewing Posts

Open assets/js/components/Crud/ViewPost.vue and add the below contents:

<template>
    <div>
        <h2>View Post #{{this.$route.params.id}}</h2>
        <br/>
        <div class="ui active dimmer" v-if="showLoader">
            <div class="ui loader"></div>
        </div>

        <div class="ui grid">
            <div class="">
                <p>
                    <strong>Title: </strong><span>{{ this.post.title}}</span>
                </p>
            </div>
            <br/>
            <div>
                <p>
                    <strong>Body: </strong><span>{{ this.post.body}}</span>
                </p>
            </div>
            <br/>
            <div v-if="this.post.photo!=''">
                <p>
                    <strong>Photo: </strong><span><img :src="this.post.photo" width="200" height="180" /></span>
                </p>
            </div>
        </div>
    </div>
</template>

<script>

    import axios from 'axios';

    export default {

        data() {
            return {
                showLoader: true,
                post: {}
            }
        },
        mounted() {

            let self = this;

            axios.get(BaseUrl + '/posts/view/' + this.$route.params.id)
                .then((result) => {
                    self.post = result.data.data;

                    self.showLoader = false;
                });
        }
    }
</script>

Here we are doing the same scenario we fetch the post data using axios and binded them a variable that we define into data() method.

0 0 vote
Article Rating
Share this: