Todo-List 2.0 Lab


Let's build a Todo-List with Google Cloud Firestore!

Goals:

  1. Setting Up Google Cloud Credits
  2. Understanding the Firestore Document Structure
  3. Saving and Loading Items from a Firestore Database




Step 0: Understanding Cloud Firestore

What is a database and why do we need one?

Right now, we have a todo-list app that works -- we can add items and we can even remove them! However, if you ever have to close your browser or reload the page, you'll lose everything on your list! Databases can fix that!

The topic we're discussing is called "Persistent State." Basically, data can either be persistent or not. Right now, our data is NOT persistent because it does not live on after our page reloads. We want to make our data persistent and to do so, we need to store it in a database which will save your data between sessions.

So what is a database? Well the name kind of speaks for itself: A database is a structured set of data held in a computer OR a place to store data!


How is data stored and stuctured in Cloud Firestore?

So how do these things work? Right now, we know that variables store information -- is that the same thing? Nope.

Databases store data in a structured way. For Firestore, the data is structured into Collections and Documents.

A document is a single unit of storage. So if you wanted to store information about a user -- like a username, a First and Last name, and a password -- you'd store all of that info in a single document.

A collection is just a list of documents. So we could have a collection called "Users" inside of which we store all our documents that have specific data for each user.


So how will we structure our data?

For our app, we have Notes and our notes have text associated with them. As such, we'll have a collection called "Notes." Inside of that, we'll create a single document for every sticky-note where we'll save the text associated with the sticky-note.


How do you SAVE data to a Cloud Firestore Database?

Let's use our "User" example for this. So we want to add a user -- here's the Javascript code that would do it.

db.collection("users").add({
    username: "arman22",
    firstname: "Arman",
    lastname: "Hezarkhani",
    password: "armanrocks",
})
.then(function(docRef) {
    console.log("You created a document! Here's its unique ID: ", docRef.id);
})

That's it! What just happened?
On the first line, we're declaring that we want to create a new document inside of the "users" collection.
On lines 2-5, we're declaring the values that should be saved inside of this new document.
On line 7, we have a .then() statement. What's that? Let's take a step back.

Lines 1-6 are saving data to our Firestore database. To do that, we're literally sending that data over the internet to our database, then our database has to go through the trouble of saving it, all of which takes time. As such, we have a .then() statement to say the following: First, I want to store this data to our database. THEN, once it's done, I want to run this function.

So what's line 7 doing? It's telling our code what to do after the database is done saving our data! On line 8, we're simply printing out what the database returned to us, which is an ID for this document.
Hint, Hint: It's a unique ID, JUST for this document. We'll be using that later on!


How do you READ data from a Cloud Firestore Database?

Imagine we've saved a bunch of users. Now, we want to get every user and simply log the first and last names to the console.

db.collection("users").get().then((querySnapshot) => {
    querySnapshot.forEach((doc) => {
        console.log("First Name: " + doc.data().firstname);
        console.log("Last Name: " + doc.data().lastname);
    });
});

This is also a bunch of new stuff -- so what's going on?
On line 1, we're saying: Hey Database, in that collection called "users," get me every value and save that data inside of 'querySnapshot.'You don't have to know exactly what 'querySnapshot' is or means -- just know that it has the values that we asked our database for.
On line 2, we're basically saying: Thanks for sending me every doc I asked for. Now, for each document that you sent me in querySnapshot, I'm going to do some stuff.
Lines 3 and 4 are just logging specific information from each document.




Step 1: Getting Started with Firestore

1) Get your free credits

I emailed you with a free cloud credit. Click on "Student Coupon Retrieval Link" and fill out the form. It'll then ask you to verify your email. Once you do, you'll receive another email with a Coupon, looking something like "XXXX-XXXX-XXXX-XXXX." That coupon holds $50 of free Google Cloud Credit.


2) Redeem the Credits

  1. Go to console.cloud.google.com/edu.
  2. MAKE SURE YOU'RE LOGGED IN TO YOUR @GMAIL.COM ADDRESS -- NOT YOUR @ANDREW.CMU.EDU ADDRESS
  3. Input the code
  4. Press "Accept and Continue."

3) Open your Firestore Project

  1. Go to console.firebase.google.com.
  2. Click on "Create a Project"
  3. Under "Project Name," Click on the dropdown and select the project that was created for you.
  4. Select all checkboxes and click "Add Firebase."

4) "Add Firestore to your Webapp"

Now that you've done this, you should be on the Firebase "Getting Started" homepage. On there, you should see four icons -- click on the third one, which looks like HTML code, indicating a "Web" project.
This'll open up a new page to "Add Firestore to your Webapp." Under Register App, put "todo_list" for the app nickname and click "Register App."
Now, you'll see something like this:

  // Your web app's Firebase configuration
  var firebaseConfig = {
    apiKey: "",
    authDomain: "",
    databaseURL: "",
    projectId: "",
    storageBucket: "",
    messagingSenderId: "",
    appId: ""
  };
  // Initialize Firebase
  firebase.initializeApp(firebaseConfig);
Copy that somewhere (in an empty file somewhere), as we'll need it in a moment.
Click "Continue to Console."

5) Create a Database

  1. On the left navbar, click on "Database."
  2. Click "Create Database"
  3. In the pop-up, choose "Start in test mode" and click "Next"
  4. Leave the default location and click "Done"



AAAAAAAND that's all the set-up you need to do!!! Let's recap:

Wooooo -- now let's code!!!




Step 2: Save Items to the Database

Now that we have our database set up, let's code!
Start by downloading the starter-code here here
Right now, the javascript simply runs some code when a form is submitted.
Let's add to that!

First, we'll need to add some configuration code -- this is basically code that's required by Firestore in order for the code to work.
At the top of the index.js, add the following code:

firebaseConfig = {
    apiKey: "",
    authDomain: "",
    databaseURL: "",
    projectId: "",
    storageBucket: "",
    messagingSenderId: "",
    appId: ""
};

firebase.initializeApp(firebaseConfig);

var db = firebase.firestore();
Look familiar? Good! Make sure that firebaseConfig has the values that you copied from earlier.

Now that we have the configuration, let's outline what we'll be doing in this code, every time our user submits a new Todo item:

  1. Get the user input
  2. Save that input to the database
  3. Add the sticky-note to the page

In the previous lab, we did steps 1 and 3. Now, though, we'll be saving to the database before we add a sticky-note to the page.
Add some code to read the user's input and pass it to a function called saveItemToDatabase.
That should look something like this:

$("#form1").submit(function(e) {
  e.preventDefault();

  var todo_input = document.getElementById("todo-input");
  var todo_text = todo_input.value;

  saveItemToDatabase(todo_text);

  todo_input.value = "";
});

function saveItemToDatabase(todo_text){
  // Your Code Here!
}
So now, every time a user types a todo item, we pass the user's input -- the todo_text -- to a function. In that function, we want to save the todo_text to our firestore database.
Try it on your own!

Your code should now look something like this:
function saveItemToDatabase(todo_text){
  // Save element to database
  doc = db.collection("notes").add({
    noteText: todo_text
  })
}
So...what is this code doing? It's creating a Firestore Document with one element -- noteText -- and it's adding that Document to the Collection called "notes".
Wanna see if it worked? Check back in the Firestore console to see for yourself!

Okay, now we're saving elements to our database, so we're done with steps 1 and 2. Step 3 is to add our element to a page -- lets add a .then() statement to do that now!
After doing so, your code should look something like this:

function saveItemToDatabase(todo_text){
  // Save element to database
  doc = db.collection("notes").add({
    noteText: todo_text
  })
  .then(function(docRef){
    docRef.get().then(function(doc) {
      addNewItem(doc);
    });
  });
}

function addNewItem(doc){
  // Create new todo div to store all todo content
  var todo_card = document.createElement("div");
  todo_card.classList.add("todo_card");

  // Put the todo item in a p
  var todo_text_elem = document.createElement("p");
  todo_text_elem.innerHTML = doc.data().noteText;

  // Construct the entire element
  todo_card.appendChild(todo_text_elem)

  // Add it to the DOM
  document.getElementById("container").appendChild(todo_card);
}
Now, every time a user submits a todo item, we're saving the text to the database. When that save is complete, we're passing the document object that the database returns to our addNewItem function to add the text to the page.

Now, let's write some code to delete the sticky-notes!
First, we'll need to give the sticky-notes a unique ID. Luckily, Firebase automatically generates a unique ID for every document you create, and it's stored inside of doc.id.
So, the following code should allow us to give our sticky note a unique ID:

var todo_card_id = doc.id
todo_card.id = todo_card_id
Lastly, we can add the following code to remove the element from the page AND delete it from the database:
// Add a click listener to the todo card
todo_card.addEventListener("click", function(){
  // Remove Element from the DOM
  document.getElementById(todo_card_id).remove()
  // Delete from Database
  db.collection("notes").doc(doc.id).delete();
});

Yay!!! Now, we can delete items, too!
There's an issue, though... Try refreshing the page :(
This is happening because we're not loading elements from the database when we first load the page. If you add the following code to the bottom of the javascript document, that'll work, too:

function loadNotes(){
  db.collection("notes").get().then(function(querySnapshot) {
    querySnapshot.forEach(function(doc) {
        addNewItem(doc);
    });
  });
};

$(document).ready(function(){
  loadNotes()
});

aaaaand that's it :)