Vue Final Project - Part 3 - The UserDetails component

In this step we'll add functionality to the UserDetails component.

Update api.js to get a user by id

The first step is to update our data access component so that we can fetch a user by their id. In the api.js file, add the following function:

export function getUserById(id){
    return ax.get("users/" + id).then(resp => (resp.data)).catch((error) => errorHandler("Error Getting User By Id:" + error));
}

The getUserById() function takes a user id as a parameter, and uses it to make a GET request for the user from our JSON server.

Updating the UserDetails Component

This is a long an tedious component to build. We'll try to break it down into small steps. Please make sure you ask questions with every little step we take!

Replace the code in UserDetails.vue with this:

<template>
    <div v-if="user">
        <form @submit.prevent="onSubmit">
            <div>
                <label>First Name:</label>
                <input v-model="user.firstName" />
            </div>
            <div>
                <label>Last Name:</label>
                <input v-model="user.lastName" />
            </div>
            <div>
                <label>Email:</label>
                <input v-model="user.email" />
            </div>
            <div>
                <label>Password:</label>
                <input type="password" v-model="user.password" />
            </div>
            <div>
                <label>Role:</label>
                <select v-if="roles.length > 0" v-model="user.roleId">
                	<option v-for="r in roles" :key="r.id" :value="r.id">{{r.name}}</option>
                </select>
            </div>
            <div>
                <label>Active:</label>
                <input type="checkbox" v-model="user.active">
            </div>
            <div>
                <input type="submit" id="btnSubmit" name="submit button">
                <input type="button" @click="$router.push({name:'UserList'})" value="Cancel">
            </div>
        </form>
    </div>
</template>

<script>
import {getUserById, getAllRoles} from "@/api"

export default {
    props:["userId"],
    data(){
        return{
            user:null,
            roles:[]
        }
    },
    mounted(){
        getAllRoles().then(roles => this.roles = roles);
        getUserById(this.userId).then(user => this.user = user);
    },
    methods:{
        onSubmit() {
        	alert("TODO: validate the input before handling the form submit!");
        }
    }
}
</script>

<style scoped>
label{ display: block; }
</style>

There's a lot going on here, and yet there are still some missing pieces. Note the following, we'll start with the options object and then address the template:

  1. We are importing both the getUserById() and getAllRoles() functions from the api.js file/module.
  2. We've added two data members, the user one will store details about a specific user. The roles array will store information for all the user roles and will be bound to the select box in the template.
  3. The mounted() method will call the functions we've imported (from api.js) to fetch all roles, and to fetch a user by their id. Note that we are passing this.userId as a param when calling getUserById(). this.userId refers to the userId prop. Remember from the previous step that this prop is getting passed into the component by the router. In Vue, if you need to refer to a prop or a data member from somewhere else inside the options object, then you need to prefix it with this.. But you don't need to use this. when you are adding code inside the template (I don't know why).
  4. The arrow function inside the then() takes the user object that is fetched from the JSON server and assigns it to the user data member.
  5. We've added an onSubmit() method, which we'll fill in with code later.

Here are some notes about the code in the template:

  1. We are using a v-if directive in two places. In the first place, it's the DIV that includes everything else in the component. We will only render this DIV IF the user data member is truthy. We first initialized it as null (falsey). It will get assigned an object (truthy) onced the component has mounted AND we have finished fetching the user from the JSON server. At that time we will have a user (and all of it's details), and we'll be ready to display the UI. The SELECT element is using a v-if and will only be displayed once the roles have been fetched.
  2. We are using the v-model directive to bind properties of the user data member to the INPUT elements.
  3. We are using the v-for directive to loop through the roles and create an OPTION element for each one. We bind the role id to the value attribute, and we display the role name inside the OPTION element.

Run the app and navigate to the UserList component, then click one of the Edit buttons. You should see the form in the UserDetails component populated with the details of the user you selected.

At this time, the changes to a user are not being sent to the server. We'll get to that soon, but first we need to discuss validating the input.

Validating User Input

For your final project, you will have to incorporate input validation code for all of your forms. As you know, there are various approaches you could take. Let me know if you need help implementing validation.

But for the moment, we will create very crude and minimal validation code (you will have to replace this with your own solution).

Add this method to UserDetails.vue, put it just under the onSubmit() method:

isValid(){
    if(!this.user.firstName || !this.user.lastName || !this.user.email || !this.user.password){
        return false;
    }
    return true;
}

And now update the onSubmit() method to look like this:

onSubmit() {
    if(this.isValid()){
        alert("Input has been validated! So go ahead and PUT/POST the user!");
    }
}

Remember that when you are adding code to the options object, and referring to a prop, data member, or method, you must prefix it with this..

Now run the app. You'll see the alert appear if the input is (somewhat) valid, otherwise you'll see nothing.

Updating the user on the server

In the api.js file, add this function:

export function updateUser(user){
  return ax.put("users/" + user.id, user).catch((error) => errorHandler("Error Updating User:" + error));
}

Note that we are calling the put() method on our Axios instance, which will do a PUT request. PUT requests are for updating resources on the server.

Add the updateUser() method to the imports in UserDetails.vue, the import statement should look like this:

import {getUserById, getAllRoles, updateUser} from "@/api"

Now update the onSubmit() method in UserDetails.vue to look like this:

onSubmit() {
    if(this.isValid()){
        if(this.userId > 0){
            updateUser(this.user).then(resp => {
                this.$router.push({name: 'UserList'});
            });
        }else{
            alert("TODO: INSERT A NEW USER");
        }
    }
}

If the form is valid, we call updateUser() (which we imported from the api.js file). This sends an ajax call to the server, updating the user. When the response comes back, we navigate back to the UserList component by calling this.$router.push() and passing in an object that specifies the name of the route we want to go to.

Run the app and update a user, then check the database file (db.json) to see if it worked.

Adding New Users

Update the mounted() method in UserDetails.vue to look like this:

mounted(){
	
	getAllRoles().then(roles => this.roles = roles);
  	
  	if(this.userId > 0){
    	getUserById(this.userId).then(user => this.user = user);
  	}else{
    	// if the userId prop was not passed in, then we are creating a new empty user
    	this.user = {id:0, firstName:"", lastName:"", email:"", password:"", roleId:1, active: true};
  	}
}

Notice that what we've done is added and IF statement that checks the userId prop to see if it's greater than 0 If not, then we create a new user object.

Now we'll make some updates so that you can navigate to the UserDetails component when you want to add a new user.

Add this object to the routes array in the router file (router/index.js):

{ path: '/users/add', name: 'AddUser', component: UserDetails }

Unlike the other route that navigates to the UserDetails component, this one does not pass in the userId prop. Note that we've given this route a name of 'AddUser', you'll see that again in just a minute.

Add this button to the template of UserList.vue (put it above the table element):

<button @click="$router.push({name: 'AddUser' })">Add New User</button>

When the button is pressed, we use the router's push() method to navigate to the route named 'AddUser'.

Try it out. You should be able to press the 'Add New User' button and navigate to an empty and form in the UserDetails component. If you enter some input into the form (just enough to pass our validation code), then you'll see an alert that says 'TODO: INSERT A NEW USER'. We'll take care of that 'todo' right now...

Add this function to the api.js file:

export function insertUser(user){
  return ax.post("users/", user).catch((error) => errorHandler("Error Inserting User:" + error));
}

Now import the function in UserDetails.vue by updating the import statement to look like this:

import {getUserById, getAllRoles, updateUser, insertUser} from "@/api"

Update the onSubmit() method in UserDetails.vue to look like this (we're just replacing the alert with a call to insertUser()):

onSubmit() {
	if(this.isValid()){
    	if(this.userId > 0){
      		updateUser(this.user).then(resp => this.$router.push({name: 'UserList'}));
    	}else{
      		insertUser(this.user).then(resp => this.$router.push({name: 'UserList'})); 
    	}
  	}
}

Run the app and try adding a new user, hopefully it will be added to db.json on by the JSON server.

Reminder: You'll will have to Bootstrap the UserDetails component for your final project Make it look consistent with what you did for the UserList. And make sure to use an icon or two!