Component Based Design - Part 2 - Data Access Components

The components that we have experimented with so far have been UI components (user interface), which means that they are components that users can interact with. For example, by filling out forms and clicking on buttons.

There are other types of components in an application that are not seen, but they provide some sort of functionality. You might have a 'messaging' component that allows the system/application to send texts and emails. You might have a component that logs the activity that takes place within the system.

Data access components are used to manage the information stored by a system. A data access component allows the application to interact with some type of database, and it usually includes methods/functions that allow for CRUD operations. CRUD stands for create, read, update, and delete.

In many business applications, information is stored in a relational (SQL) database, but there are other ways of storing information. For this exercise, we'll build a data access component works with a localStorage database. In a real-world application, you probablly wouldn't use localStorage because it has some drawbacks (we can discuss them in class). But localStorage is great for prototyping apps because you don't have to take the time to design a SQL database, and writing all the complicated queries to interact with it (you'll be learning about SQL databases next semester).

Start by creating a folder named user-data-access and put it inside the component-based-design folder that you created in the previous activity. You can put all the files we create throughout this activity in the user-data-access folder.

Then create a file named user-data-access.js and put the following code in it:


class UserDataAccess{

	///////////////////////////////////////////////
	// PROPERTIES
	///////////////////////////////////////////////
	
	// 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 'userData' key is already in the localStorage database
		// if not, then create it, and populate it with the dummy data
		if(!localStorage.getItem("userData")){
		    localStorage.setItem("userData", JSON.stringify(this.dummyData));
		}
		
	}

	
	//////////////////////////////////
	// METHODS
	//////////////////////////////////




}

In this case we've used a class to create our component. And I added comments to help you keep the properties, constructor, and methods organized. Right now, the class has a single property (dummyData) and a constructor (we'll add more to it soon). The dummyData variable just provides some initial data to load into the browser's localStorage, so that we have some data to work with.

The code in the constructor function checks to see if a key named 'userData' exists in the localStorage database. If this key does not already exist, then it is created and populated with the dummyData.

Now we'll create a web page to test out our data access component. Create a file named user-data-access-tests.html and put this code in it:

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<title>User Data Access Tests</title>
	<script src="user-data-access.js"></script>
	<script>
		
		const uda = new UserDataAccess();

	</script>
</head>
<body>

</body>
</html>

Notice we link to the UserDataAccess class in the first SCRIPT element. The code in the second SCRIPT element calls the constructor to create an instance of the component (a UserDataAccess object), which is stored in the uda variable.

Load the .html page in the browser and then use the Developer Tools to inspect the localStorage. To do this, press F12 to open the Developer Tools, then click on the Application tab (note that some keyboards require you to hold down the 'fn' key while pressing F12). From there you should be able to see the inital (dummy) data assigned to the 'userData' key.

Now we'll add some methods to the UserDataAccess class that will allow us to interact with the data. Let's start by adding a method that fetches all the users - add this method to the UserDataAccess class (under the comment for METHODS):

getAllUsers(){
	const str = localStorage.getItem("userData");
	const users = JSON.parse(str);
	return users;
}

The first line of code in the method gets the data stored in the 'userData' key of the localStorage database. Key/value databases can only store strings, but we can convert the string into an array of users by calling the parse() method of the JSON object. The final line returns the array of users.

Next we'll test out this method by calling it in the .html file. Add this code just, under the call to the constructor function in user-data-access-tests.html:

// Test getAllUsers()
console.log(uda.getAllUsers());

If you reload the page and look at the console log, you should see the intial data. If so, then it appears that our first test passes by successfully fetching all users in the database.

Now we'll add a method that gets a user by their user ID. Add this method to the UserDataAccess class (put it under the getAllUsers() method):

getUserById(id){
	const str = localStorage.getItem("userData");
	const users = JSON.parse(str);
	const user = users.find((u) => u.id == id);
	return user;
}

Test this method by adding the following code to the .html file (just after the previous test):

// Test getUserById()
console.log(uda.getUserById(3));

When you reload the page, you should see the information for Jesse Jones in the console log.

Next we'll add a method to create a new user, but before we do we'll add a method that can be used to assign IDs to users when they are inserted. If we were using a SQL database we would not have to assign IDs when users are inserted because it would be done by the database. But a localStorage database is just a simple key/value store that doesn't do much, so we'll need to generate unique IDs when users are inserted.

First we'll add a method named getMaxId(). This method gets the users array from localStorage and then and then returns the highest ID in the array.

Add this code to the METHODS section in user-data-access.js:

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;
}

When we insert a new user, we can use this method to get the highest user id in the database, and then increment by 1 to get a unique id for the user we are about to insert.

And now add the following method, which will insert a new user into the database. Add this code to the METHODS section of the user-data-access.js file:

insertUser(newUser){
	// We really should validate newUser before inserting it!
	// Set the new user's id:
	newUser.id = this.getMaxId() + 1;
	const str = localStorage.getItem("userData");
	const users = JSON.parse(str);
	users.push(newUser); 
	localStorage.setItem("userData", JSON.stringify(users));
}

Note that this method has a parameter for the new user object, this object will be added to the database.

In the body of the method, we first generate a unique ID by calling the getMaxId() method. We then pull the data from localStorage and convert the string into an array of user objects before 'pushing' the new user onto the array. Finally, we save the changes to localStorage.

Also note the comment about validating the new user before inserting it. We should really make sure that the newUser object contains valid data, so that we don't corrupt the database. But we'll skip the validation code for now, just to keep things simple.

To test the insertUser() method, add this code to the .html page:

// Test insertUser()
uda.insertUser({firstName:"Marty", lastName:"Tanner", email:"marty@acme.com"});
uda.insertUser({firstName:"Phen", lastName:"Van", email:"phen@acme.com"});
console.log(uda.getAllUsers());

If you refresh the page, you should see the console log messages that include Marty and Phen. Make sure to check the IDs that were assigned to each user, this will indicate to us that the getMaxId() method is working properly.

Now refresh the page, and you'll see that there are duplicates of Marty and Phen. Each time you refresh the page, another duplicate of Marty and Phen is added to localStorage. To prevent this, we can clear out the localStorage before running our tests. Add this code before the call to the constructor function in user-data-access-tests.html, it simply wipes our your browsers localStorage database:

//Wipe out the database before testing:
localStorage.clear();

Now we'll add a method that allows us to update a user in the database. Add this method to the UserDataAccess class:

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));
}

See if you can follow each line of code in the body of the method. After fetching the data and parsing it into an array of user objects, it uses the findIndex array method to find the position of the user object that needs to be updated. It then replaces that object with the one passed in as a parameter.

To test the method, add this to the .html page:

// Test updateUser()
const marty = uda.getUserById(4); // first, get a user to update
marty.lastName = "THOMPSON"; // make an update
uda.updateUser(marty);
console.log(uda.getAllUsers());

When you refresh the page, the last console log entry should show that the change has been saved to the database.

Let's add a method that deletes a user, add this code to the UserDataAccess class:

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));
}

The method takes a parameter that represents the id of the user to delete.

Add this code to the .html page to test out the deleteUser() method:

// Test deleteUser()
uda.deleteUser(4);
console.log(uda.getAllUsers());

When you refresh the page, the final console log should show that Marty has been removed from the database.

What we have just done is created a data access component that interacts with users stored in a localStorage database. The component allows us to do CRUD operations with the users. Again CRUD stands for 'create', 'read', 'update', and 'delete'.

Now that you have seen how a simple data access component can be build, you may want to read this article about the data access layer in a software system.