Vue User Manager - Part 1
Part 1 - Setting Up the App
This tutorial is best suited for someone who has dabbled with Vue, but maybe not yet built a working app. Learning a robust framework like Vue can be overwhelming because there is so much to it. This tutorial won't explain all the ins and outs of Vue, instead it will expose you to many features of the framework. But hopefully you'll start to see how you might build a working application with Vue.
Vue makes use of modern JavaScript features, so hopefully you are comfortable with many of the syntax features that were added in ES6.
If you've never worked with Vue before, then I suggest you watch this excellent free course to help you get started.
Create a folder for this project (you can call it vue-user-manager) and then create these three files in it:
index.html
main.js
main.css
user-data-access.js
Add this code to main.css:
form label, input{
display:block;
}
form input[type=submit], input[type=button]{
display: inline;
}
form .validation{
color: red;
}
The Data Access Component
The user-data-access.js file will be used to define the data access component for this project. This is the same data access component that you created in Component Based Design Part 2:
class UserDataAccess{
///////////////////////////////////////////////
// PRIVATE INSTANCE VARIABLES (start with #)
///////////////////////////////////////////////
// We'll use the dummyData to populate the localStorage database
#dummyData = [
{id:1, firstName:"Jane", lastName:"Doe", email:"jdoe@acme.com"},
{id:2, firstName:"Tony", lastName:"Thompsom", email:"tony@acme.com"},
{id:3, firstName:"Jesse", lastName:"Jones", email:"jesse@acme.com"}
];
//////////////////////////////////
// CONSTRUCTOR
//////////////////////////////////
constructor(){
// check to see if the localStorage db already exists.
// if not, then create it an populate it with the dummy data
if(!localStorage.getItem("userData")){
localStorage.setItem("userData", JSON.stringify(this.#dummyData));
}
}
//////////////////////////////////
// PUBLIC METHODS
//////////////////////////////////
getAllUsers(){
const str = localStorage.getItem("userData");
const users = JSON.parse(str);
return users;
}
getUserById(id){
const str = localStorage.getItem("userData");
const users = JSON.parse(str);
const user = users.find(u => u.id == id);
return user;
}
insertUser(newUser){
// Set the new user's id:
newUser.id = this.#getMaxId() + 1;
const str = localStorage.getItem("userData");
const users = JSON.parse(str);
users.push(newUser); // we really should validate newUser before adding it!
localStorage.setItem("userData", JSON.stringify(users));
}
updateUser(updatedUser){
// again, we should validate updatedUser before putting it in the database
const str = localStorage.getItem("userData");
const users = JSON.parse(str);
const indexOfUserToUpdate = users.findIndex(u => updatedUser.id == u.id);
users[indexOfUserToUpdate] = updatedUser;
localStorage.setItem("userData", JSON.stringify(users));
}
deleteUser(id){
const str = localStorage.getItem("userData");
const users = JSON.parse(str);
const indexOfUserToRemove = users.findIndex(u => id == u.id);
users.splice(indexOfUserToRemove,1);
localStorage.setItem("userData", JSON.stringify(users));
}
//////////////////////////////////
// PRIVATE METHODS
//////////////////////////////////
#getMaxId(){
const str = localStorage.getItem("userData");
const users = JSON.parse(str);
let maxId = 0;
for(let x = 0; x < users.length; x++){
if(users[x].id > maxId){
maxId = users[x].id;
}
}
return maxId;
}
}
We'll make use of a 'user data access' component in the Vue app that we are about to create.
Add this code to index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue User Manager</title>
<link rel="stylesheet" href="main.css">
<script src="user-data-access.js"></script>
</head>
<body>
<!-- The Vue app will get 'mounted' into this div -->
<div id="app"></div>
<!-- imports the Vue library -->
<script src="https://unpkg.com/vue@next"></script>
<!-- Make sure to include Vue before main.js, this file will contain our JS code-->
<script src="main.js"></script>
</body>
</html>
Note the following about index.html:
- We link to the stylesheet (main.css)
- We import (link to) the UserDataAccess class, which is defined in user-data-access.js
- We have a DIV that will act as the container for our Vue app
- We import/link to the Vue framework
- We import/link to the main.js file, which will contain the JS code that we add for this project (the code in main.js should run after Vue has been loaded)
Add this code (to main.js):
const uda = new UserDataAccess();
const rootComponent = {
template: `<div>
<h1>User Manager</h1>
<p>Number of users: {{users.length}}</p>
<br>
<button @click="addUser">Add User</button>
<!--We'll add a few Vue components here later-->
</div>`,
data(){
return {
users: uda.getAllUsers()
}
},
methods:{
addUser(){
alert("TODO: Add new user");
// Uncomment the line below to see how Vue is 'reactive':
//this.users.push({id:4, firstName:"Foo", lastName:"Bar"})
}
}
};
const app = Vue.createApp(rootComponent);
// We'll define a few more Vue components here
app.mount('#app');
There's A LOT going in this code! And we'll go over all of it. But first, load the index.html page in a browser to see what the app looks like. You should see the number of users in the database and a button that doesn't do much right now.
Now let's discuss the code in main.js. The first line of code instantiates our user data access component and stores it in a constant named uda. The Vue framework is a UI framework, and it does not specify how your app interacts with a database. So the UserDataAccess class allows us to control how the data is stored and gives us an API that we can use do to CRUD operations on the data.
The rootComponent variable is an object that defines the root component for our app (this is the component that contains all the other components in the app). To define a component in Vue, you create an object that has specific properties in it. This is known as the options object. We'll talk about the properties that have been added to the object in just a minute. But I'd like to jump down to the code that comes after the object first.
Once you define the root component for your app, you pass it into the createApp method that Vue provides. This will return an instance of a Vue app, which we have aptly named 'app'.
Once you have the Vue app created, you need to attach it to an element in the DOM. You can do this by calling the mount() method of the Vue app (note that you pass in a CSS selector as the parameter to mount()).
Now let's back up and discuss the properties of the rootComponent options object. Since Vue components are UI components, they need to know what HTML elements should be displayed when the component appears in the app. This is the purpose of the template property. If you look at the value of the template property, you'll see that there are a few things that do not exist in the HTML language. These are features that the Vue framework has added and we'll learn about a lot of them as we continue to explore Vue.
First, you'll notice the double curly braces, which allow you to display data in the UI (this is one way of data-binding in Vue). The data being displayed is the number of users in our database. The double curly braces actually allow you to incorporate JavaScript code into your templates (we have a JS expression that evaluates to the length of the users array inside the curlies). Other frameworks, such as React, make use of a similar way of mixing JS code into HTML templates.
Also notice the @click attribute that has been added to the button element. This is how you can hook up event handler functions in Vue. In this case, when the button gets clicked, the addUser() method will be invoked (which is defined below the template, in the methods property).
The things that appear to be attributes added to elements in Vue are known as directives. For beginners of Vue, they are complicated by the fact that there are alternative ways of using many of the directives in Vue. For example, the @click directive could also be written like this:
<button v-on:click="addUser">Add User</button>
The @ symbol is a short-cut for the v-on directive.
If you want to see all the directives that are available in Vue (as well as the alternative syntax), you can find them here. Do not feel obligated to go over them right now, we'll cover the most important ones as we move through the course. Another popular framework called Angular also makes heavy use of directives.
The next property in the rootComponent object is actually a method named data(). This is using the ES6 syntax for adding methods to objects in JavaScript. You might be more familiar with the 'classic' approach to putting methods in objects, like so:
const someObj = {
someProperty: "Foo!",
someMethod: function(){
// code for method goes here
}
}
But as of ES6, you can also use this syntax:
const someObj = {
someProperty: "Foo!",
someMethod(){
// code for method goes here
}
}
I don't believe that there is any real difference when using one or the other. It's purely syntax sugar.
The data() method in a Vue component is used to define the data that the component will work with. It must return an object that includes a property for each piece of data that the component works with. The fact that it must return an object that defines the data felt very strange to me when I was learning Vue. There is a reason for this, but you needn't worry about it.
We are defining a users data member and we are initializing it by getting all the users from our database.
Note that we are displaying the length of the users data member (an array) in the template. In other words, the length of the users data member is bound to the user interface, inside the paragraph element. In a moment, you'll see the magic of Vue and how if bound data changes, the UI will automatically update to reflect the change.
You can add functionality to a Vue component by adding a methods property to the options object. The methods property is an object that contains methods only (note that it's using the ES6 syntax for adding methods to the object). You can add code to call these methods from inside the template, or from other parts of your component. You'll often add methods as event handlers (which is what we are doing with the addUser() method - it will be triggered when the button in the UI is clicked).
There are lots of other properties and methods that you can add to the options object when you are defining a Vue component, and we'll be learning about quite a few of them as we continue our journey into Vue.
Now I want to demonstrate one of the driving features of the Vue framework, which is known as reactivity. Inside the addUser() method, go ahead and remove the comment that 'pushes' a new object onto the 'users' data member (as a side note, if you want to refer to a data member from within a method, Vue requires that you prefix it with this.). After removing the comment, run the app and notice what happens when you press the button. The UI will automatically update to reflect that the data has changed (as a side note - we did not insert the user into the database, we'll do that later). Vue automatically attempts to take care of the data-binding. In other words, it 'reacts' to data changes by updating the UI.
This is a BIG DEAL.
Make sure to comment out this line of code after seeing the 'reactive' nature of Vue because we don't really want it there. We'll set up the app to create users later.
As you learned in Int Web Dev, writing code for data-binding in pure JavaScript is tedious! The difficulty in writing data-binding code in JavaScript has been one of the driving problems that frameworks (such as Vue and React) seek to alleviate.
As you'll see at times, Vue (and React) are not completely 'reactive'. There are cases when the automatic data-binding does not work properly, and you have to know some tricks to get around this. But Vue 3 did a good job of fixing many of these problems.
Finally, I should point out that when Vue 3 was released, it offered a different approach to creating components. This was known as the composition api. We have been using the options api (remember that you create an 'options' object that defines your component). We will continue to use the options api throughout this course because it is easier to learn and understand.
We have covered many fundamental concepts in Vue in this activity. Please be prepared to ask questions about any of them when we meet in class.