Learn Firestore Building a To-do List Appliaction

FireStore is one of the latest addition to Firebase feature as a No-SQL DB backend support, Like Firebase Realtime Database, it keeps your data in sync across client apps through realtime listeners and offers offline support for mobile and web so you can build responsive apps that work regardless of network latency or Internet connectivity.

It is still Beta, however it's getting some real attraction. So I thought writing an article would be a good idea where I'd illustrate how to build a to-do list app with JavaScript, integrated with FireStore for the storage.

The app demo

As you can see it basically add new todos, mark, and also delete from the list.

Getting Started

Firestore setup

In your Firebase console, go to Database, and create a Cloud FireStore. You'll get a pop up message asking you to choose your security rule, make sure it is set to Start in test mode, so you don't get errors when trying to read or write from the database.

After that, make a Collection with the name todo Then create a Document with the "field" activity and category "type" set as string then the "value" what ever you want to write. Something as below:

When you click the save button you should see something like this

After that click on Project Overview at the top let corner. Then you'll be taking to a "Get started here" page. There are three options 1. Add Firebase to your iOS app 2. Add Firebase to your Android app 3. Add Firebase to your web app

Choose the third option. When you clicked on it should see a modal pop up, copy the first script tag and paste at the bottom of your index.html page, but make sure it is above the script tag of your main.js

Do thesame with the other script tag, but this time only copy the code inside of it stating from // Initialize Firebase comment, down to the firebase.initializeApp(config);

Paste it in your main.js file at the very top

Now we can start working in our main.js file

Creating a reference to our Firebase and storing it in a const also configure some settings to avoid warning in the browser console:

  
      const db = firebase.firestore(); //db reference
      db.settings({ timestampsInSnapshots: true }); //avoid warning in the console
  

Getting our documents from firebase

Remember when we created our databse we inserted some values, now let's get them from FireStore.

    
     //Getting Data
    db.collection('todo').get().then((snapshot) => {
      snapshot.docs.forEach(doc => {
        console.log(doc.data());
      })
    })
    
  

Now if you check your console in your browser you should see the document.

But this method will be removed at the last part of this article as it will be replaced with a more dynamic method, and why is that? the above method doesn't reflect instant changes in the browser, meaning it's not in real-time.

In the demo of the app, you see how it inserts and deletes items all happening in real-time, but with the method above we'll have to refresh the browser before we can see the changes. But it's still good that you know this method.

The HTML markup & CSS

Before we dive fully into the JavaScript let's setup the HTML & CSS

  
      @import url('https://fonts.googleapis.com/css?family=Montserrat:300,400,500');

      body{
          background: linear-gradient(320deg, #4682B4, #483D8B);
          font-family: 'Montserrat', sans-serif;
      }

      h1{
          color: #fff;
          font-size: 40px;
          letter-spacing: 2px;
          margin-top: 80px;
          text-align: center;
      }
      .wrapper{
        width: 90%;
        margin: 0 auto;
      }
      .content{
          background: #fff;
          max-width: 960px;
          margin: 30px auto;
          padding:  20px 30px;
          border-radius: 5px;
          box-shadow: 1px 3px 5px rgba(0,0,0,0.1)
      }

      ul{
          list-style-type: none;
          padding: 0;
      }

      li{
          padding: 20px 30px;
          font-size: 1.2rem;
          color: #333;
          position: relative;
          border-bottom: 1px solid #e6e6e6;
          height: 46px;
      }



      li span{
          display: block;
          padding-left: 10px;
      }

      li:before{
        position: absolute;
        font-family: 'FontAwesome';
        content: '\f22d';
        left: 0;
        top: 20%;
        color: #ddd;
        font-size: 1.9rem;
      }
      li.special:before{
        position: absolute;
        font-family: 'FontAwesome';
        content: '\f22d';
        left: 0;
        top: 20%;
        color: #2dc997;
        font-size: 1.9rem;
      }

      li span:nth-child(2){
          font-size: 16px;
          margin-top: 6px;
          color: #999;
      }

      li div{
          position: absolute;
          top: 0;
          right: 0px;
          background: rgba(255,255,255,0.6);
          width: 40px;
          text-align: center;
          padding: 16px 0;
          font-weight:  bold;
          cursor:  pointer;
      }

      form input{
          float: left;
          width: 38%;
          margin: 0;
          border: 0;
          border-bottom: 1px solid #eee;
          margin: 0 1%;
          padding: 10px;
          display: block;
          box-sizing: border-box;
          font-size: 18px;
          font-family: 'Montserrat', sans-serif;
      }

      form input:focus{
          outline: none;
          border-bottom: 3px solid #4682B4;
          padding-bottom: 8px;
          transition: all ease 0.2s;
      }

      form:after{
          content: '';
          clear: both;
          display: block;
      }

      button{
          padding: 13px;
          margin-left: 20px;
          font-weight: bold;
          color: #fff;
          cursor: pointer;
          border: none;
          background: #fff;
      }
      button .fa-plus-circle{
        color: #4682B4;
        font-size: 2em;
      }

      .marked{
        text-decoration: line-through;
        color: #7B7D7D;
      }

  

Don't get overwhelmed by the CSS You'll understand it as you go on.

Inserting Documents

You've known how to get the document from FireStore now let's insert more docuents with JavaScript, without going to our Firebase console.

  
      const form = document.querySelector('#add-todo-form'); //form

      form.addEventListener('submit', (e) => {
        e.preventDefault(); //preventing browser default behaviour

        //add to the docs
        db.collection('todo').add({
          activity: form.activity.value,
          category: form.category.value
        });

        //clear form fields after submitting
        form.activity.value = '';
        form.category.value = '';
      })

  

I case you're wondering form.activity.value | form.category.value we're refering to the inputs in the form remember the name of the inputs are set to name="activity" & name="category". The value is the value that's typed into the input fields (maybe you know that alreadly)

Rendering & deleting the documents

    
        const todoList = document.querySelector('#todo-list'); //ul tag

        function renderTodos(doc){

          //CREATING ELEMENTS WITH JAVASCRIPT
          let li = document.createElement('li'); //create li tag
          let activity = document.createElement('span');
          let category = document.createElement('span');
          let cross = document.createElement('div'); //create delete button


        //SETTING THE TEXT CONTENTS
        li.setAttribute('data-id', doc.id);
        activity.textContent = doc.data().activity;
        category.textContent = doc.data().category;
        cross.textContent = 'x';

        //APPENDING CHILDREN
        li.appendChild(activity);
        li.appendChild(category);
        li.appendChild(cross);

        todoList.appendChild(li);

        //DELETE FUNCTION
        cross.addEventListener('click', (e) => {
          e.stopPropagation();
          let id = e.target.parentElement.getAttribute('data-id');
          db.collection('todo').doc(id).delete();
        })


        //MARKED FUNCTION
        li.addEventListener('click', () => {
        let circle = document.querySelector('li .fa'); //the round icon - from fontawesome
        li.className = ' special';
        li.className += ' marked';
        })

     }
    
  

Let me explain the two nexted funtions inside the renderTodos() function

1. The delete function: we assigned a variable called cross to create a div. Then we set the textContent to "x", that's the delete button. We then added an event listener to it, we stop the "Propagation" and then we targeted the parentElement, remember at the "SETTING THE TEXT CONTENTS" above we made a setAttribute, the li tag is the parentElement, after we've gotten the id of the document then we called the delete() method.

2. The marked function: I did this just for visuality purpose when an item is clicked there is a line that runs through it, the color lighten and also the circle icon turn green. This doesn't reflect in the database it doesn't saves. So you can just ignore it if you want.

Getting Documents in Real-time (Real-time Method)

At the beginning of the article we were able to get the the document from our FireStore. But I said that wasn't the way we want to do it, the method below is the replacement for that first method we did.

  
      db.collection('todo').orderBy('activity').onSnapshot(snapshot => {
        let changes = snapshot.docChanges();
        changes.forEach(change => {
          if(change.type == 'added'){
            renderTodos(change.doc);
          }else if(change.type == 'removed'){
            let li = todoList.querySelector('[data-id=' + change.doc.id + ']');
            todoList.removeChild(li);
          }
        })
      })
  

Wait! let me explain

Starting from the top, we got the document from our collecton we then sorted them by activity [orderBy('activity')]. We then call the onSnapshot method which takes a call back snapshot (see the .onSnapshot() method as an event listener, listening for changes in the collection).

Detecting the changes: We detect a change on the document through a methond called .docChanges()

The type: For simplicity sake, when an item is added to the collection the type is added. If an item is deleted it is a removed type

So basically if the type is added then we want the renderTodos(change.doc) function to fire, else we want to remove from the document todoList.removeChild(li);

Conclusion

I know it's not a fully CRUD application, we create, read and deletebut we didn't update. However if you wish to know how to update here's the way to do it.

  
    db.collection('todo').doc('id-of-the-item').update({
      activity: 'Go to the gym',
      category: 'Fitness'
    });
  

what do I mean by id-of-the-item? Every item in the collection as their unique identity. So lets say you inspect an item on the list you shoud see some the like this "jshfojr3hfkek9sg" that's the id

Bring it together, it should be something like this:

    
      db.collection('todo').doc('shfojr3hfkek9sg').update({
        activity: 'Go to the gym',
        category: 'Fitness'
      });
    
  

If you run it, the item you target should be updated. Here is the repo for this article, maybe you'll like to refer to it later.