Todo App Tutorial - Front-end Development

Hi Folks,
This tutorial is all about how to create a simple todo list using web technologies - HTML, CSS, and JavaScript…

Demo:

Live:

https://www.surender.net/todo-app

Source code:

https://github.com/SurenderLohia/todo-app

Targeted audience:

One who looking to learn front-end development.
One who looking to build some cool stuff using web technology.
If you are new to the front-end, I believe this one will be a good starting point.

Yeah, will start…

Pro tip:

If you stuck somewhere in code feel free to check it out that part from source code

Task breakdown:

  1. Create required files and folders.
  2. Create a basic structure using HTML and CSS.
  3. Add JavaScript functionality.
  4. Deploy our code to live, using Github pages.


1. Create required files and folders

First will create the required directories and files. Run the below commands one by one in the terminal.
Note: Don’t copy $ sign while coping command, it’s just a placeholder to indicate terminal cursor pointer.

  
    $ mkdir todo-app
    $ cd todo-app
    $ touch index.html
    $ mkdir css
    $ mkdir js
    $ touch css/main.css
    $ touch js/main.js
  


2. Create basic structure using HTML and CSS

Open the created directory (todo-app) in your favorite editor (I am using VS Code).

Will add a basic HTML skeleton first. Open index.html and paste the below HTML code in that file.

  
    <!-- index.html -->
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Todo App</title>
    </head>
    <body>
      <h2>Todo App</h1>
    </body>
    </html>
  

Run the local server to view this HTML in the browser…

To start the local python server (or you run any other server) go to terminal and navigate to the todo-app directory. And past the below command. It will start the local python server.

  
    $ python -m SimpleHTTPServer
  

Once server started open Chrome browser and enter the below URL

  
    http://localhost:8000
  

(Python is available in Mac OS by default, in case if you don’t have python or not aware of how to start a local server, then goto todo-app directory right-click the index.html file and click open with -> Google Chrome)

You will see below HTML

Base html

We have not added any actual content yet, but this is our starting point.

Now, will add the required CSS styles. Open the css/main.css file in the editor and copy-paste the below code.

  
    /* css/main.css */
    body {
      margin: 0;
    }

    .wrap {
      max-width: 600px;
      margin-left: auto;
      margin-right: auto;
      padding-left: 20px;
      padding-right: 20px;
    }

    /* Flex */
    .flex {
      display: flex;
    }

    .align-center {
      align-items: center;
    }


    /* Typo */
    .font-weight-normal {
      font-weight: normal;
    }

    /* Margins */
    .mr1 {
      margin-right: 10px;
    }

    .mt0 {
      margin-top: 0;
    }

    .mb0 {
      margin-bottom: 0;
    }

    .mla {
      margin-left: auto;
    }

    .text-btn {
      background-color: transparent;
      padding: 0;
      border: 0;
      cursor: pointer;
    }

    .text-btn:focus {
      outline: 0;
    }

    .danger {
      color: red;
    }


    /* Todo List */
    .add-todo {
      padding-top: 15px;
      padding-bottom: 15px;
      
      margin-top: 15px;
      margin-bottom: 15px;
    }

    .todo-list {
      padding-left: 0;
    }

    .todo-list-item {
      padding: 10px 10px 10px 0;
      border-bottom: 1px solid #ccc;
    }

    .todo-list-item input:checked + label {
    text-decoration: line-through;
    }

    .delete-btn {
      padding: 5px;
    }

  

Now will update our index.html.
First, we have to include main.css in index.html.
Open index.html in an editor and before closing head tag (</head>) add the below line

  
    <link href="css/main.css" rel="stylesheet">
  

Now we have to add required HTML for todo-list In index.html copy and paste the below code in between the body tags (<body>….</body>)

Below HTML contains:

  1. Todo-list title
  2. Add-todo section
  3. Todo-list Header section which contains All, Completed, Active filters, and delete all button.
  4. And Todo-list
  
    <div class="wrap">
      <h2 class="font-weight-normal">Todo App</h1>
      <section class="todo-list-container">
        <section class="add-todo flex">
          <input id="js-add-todo-input" class="mr1" type="text" value="" placeholder="Task" />
          <button id="js-add-todo-btn">Add</button>
        </section>
        <header class="todo-header flex">
          <div class="flex align-center mr1">
            <input class="mt0 js-filter-todo-radio-btn" type="radio" value="all" id="all" name="todo-filter" checked="checked" />
            <label for="all">All</label>
          </div>
          <div class="flex align-center mr1">
            <input class="mt0 js-filter-todo-radio-btn" type="radio" value="active" id="active" name="todo-filter" />
            <label for="active">Active</label>
          </div>
          <div class="flex align-center mr1">
            <input class="mt0 js-filter-todo-radio-btn" type="radio" value="completed" id="completed" name="todo-filter" />
            <label for="completed">Completed</label>
          </div>
          <div class="flex align-center mla">
            <button id="js-delete-all-btn">Delete All</button>
          </div>
        </header>
        <ul class="todo-list" id="js-todo-list">
          <li class="flex align-center todo-list-item">
            <input class="mt0 mb0 mr1" type="checkbox" value="todo-id-1" id="todo-id-1" />
            <label for="todo-id-1">HTML</label>
          </li>
          <li class="flex align-center todo-list-item">
            <input class="mt0 mb0 mr1" type="checkbox" value="todo-id-2" id="todo-id-2" />
            <label for="todo-id-2">CSS</label>
          </li>
        
        </ul>
      </section>
    </div>
  

Now, the todo-app structure is ready goto browser and refresh it, you will see like below HTML page.

Todo-app basic structure


3. Add JavaScript functionality

Open js/main.js file in the editor and copy-paste the below js code

  
    // IIFE to create local scope and to avoid global namespacing collisions
    (function () {
      let todoCollection = {};
      let todoIds = [];

      /* 
      Data Structure:

      const todoCollection = {
        "1": {
          id: 1,
          task: "HTML",
          isCompleted: false,
        },
        "2": {
          id: 2,
          task: "CSS",
          isCompleted: false,
        },
      };

      const todoIds = [1, 2];

      */

      // Localstorage
      function persistData(key, value) {
        const valueStirng = JSON.stringify(value);
        window.localStorage.setItem(key, valueStirng);
      }

      function getPersistedData(key) {
        const value = window.localStorage.getItem(key);
        return JSON.parse(value);
      }

      function init() {
        const _todoCollection = getPersistedData('todoCollection');
        const _todoIds = getPersistedData('todoIds');

        if(_todoCollection) {
          todoCollection = _todoCollection;
        }

        if(_todoIds) {
          todoIds = _todoIds;
        }

        if(todoIds.length) {
          renderTodoList(todoListEl, todoIds, todoCollection);
        }
      }

      // Dom Elements
      const todoListEl = document.getElementById("js-todo-list");
      const addTodoBtn = document.getElementById('js-add-todo-btn');
      const addTodoInput = document.getElementById('js-add-todo-input');
      const filterTodoRadioBtns =  document.querySelectorAll('.js-filter-todo-radio-btn');
      const deleteAllBtn = document.getElementById('js-delete-all-btn');

      function createListItemEl(todoItem) {
        return `
  • <input class="mt0 mb0 mr1" type="checkbox" value="${todoItem.id}" id="${todoItem.id}" ${todoItem.isCompleted ? 'checked': ''} />
  • `; } function renderTodoList(container, todoIds, todoCollection) { let todoListHTML = ""; todoIds.forEach((id) => { todoListHTML += createListItemEl(todoCollection[id]); }); container.innerHTML = todoListHTML; } function addTodo(task) { const todoId = todoIds.length + 1; const todoItem = { id: todoId, task, isCompleted: false, }; todoCollection[todoId] = todoItem; todoIds.push(todoItem.id); addTodoInput.value = ''; persistData('todoCollection', todoCollection); persistData('todoIds', todoIds); renderTodoList(todoListEl, todoIds, todoCollection); } function toggleTodoItem(id) { todoCollection[id].isCompleted = !todoCollection[id].isCompleted; persistData('todoCollection', todoCollection); } function filterTodoitems(category) { console.log('category', category); if(category === 'all') { renderTodoList(todoListEl, todoIds, todoCollection); return; } let filteredTodoCollection = {} let filteredTodoIds = []; if(category === 'active') { todoIds.forEach((id) => { const todoItem = todoCollection[id]; console.log(todoItem); if(!todoItem.isCompleted) { filteredTodoCollection[id] = todoItem; filteredTodoIds.push(id); } }); } else if(category === 'completed') { todoIds.forEach((id) => { const todoItem = todoCollection[id]; if(todoItem.isCompleted) { filteredTodoCollection[id] = todoItem; filteredTodoIds.push(id); } }); } else { filteredTodoCollection = todoCollection; } renderTodoList(todoListEl, filteredTodoIds, filteredTodoCollection); } function deleteAll() { todoCollection = {}; todoIds = []; persistData('todoCollection', {}); persistData('todoIds', []); renderTodoList(todoListEl, [], {}); } function deleteTodoItem(id) { const todoItemIndex = todoIds.indexOf(Number(id)); delete todoCollection[id]; todoIds.splice(todoItemIndex, 1); persistData('todoCollection', todoCollection); persistData('todoIds', todoIds); renderTodoList(todoListEl, todoIds, todoCollection); } function onAddTodo() { const task = addTodoInput.value; addTodo(task); } // Using EventDelegation: to keep our code simple and performant function onClickTodoItem(e) { const target = e.target; if(target.tagName === 'INPUT') { e.stopPropagation(); const id = target.id; toggleTodoItem(id); } if(target.tagName === 'BUTTON' && target.classList.contains('js-delete-btn')) { e.stopPropagation(); const id = target.getAttribute('data-id'); deleteTodoItem(id); } } function onFilterTodoItems(e) { e.stopPropagation(); const category = e.target.value; filterTodoitems(category); } function onDeleteAll() { deleteAll(); } init(); // AttachEvents addTodoBtn.addEventListener('click', onAddTodo, false); todoListEl.addEventListener('click', onClickTodoItem, false); filterTodoRadioBtns.forEach((item) => { item.addEventListener('input', onFilterTodoItems, false); }); deleteAllBtn.addEventListener('click', onDeleteAll, true); })();

    Add main.js file to index.html file. Add below line before body closing tag (</body>)

      
        <script src="js/main.js"></script>
      
    

    Now remove the placeholder todo-list items from index.html.
    Remove the below code from index.html.

      
        <li class="flex align-center todo-list-item">
          <input class="mt0 mb0 mr1" type="checkbox" value="todo-id-1" id="todo-id-1" />
          <label for="todo-id-1">HTML</label>
        </li>
        <li class="flex align-center todo-list-item">
          <input class="mt0 mb0 mr1" type="checkbox" value="todo-id-2" id="todo-id-2" />
          <label for="todo-id-2">CSS</label>
        </li>
      
    

    Refresh the page you will see the below todo-app with empty todo-list

    Todo-app app

    Now, go ahead add, remove todo-items. After a refresh also data will be available, because we stored our data on local storage, so it’s persisting…

    That’s all we are done. Now will deploy our app.


    4. Deploy our code to live, using Github Pages.

    First, we have to do git init. Go to the terminal, navigate to the todo-app directory, and enter the below command.

      
        git init
      
    

    After that need to add all files to git. For that run the below command in terminal

      
        git add .
      
    

    Then do git commit. Run the below command in the terminal.

      
        git commit -m "Create todo-app"
      
    

    Now, go to your Github account [https://github.com/]

    In the right-hand side corner, there will be a + icon click on that icon and select “New repository”. It looks like below screenshot

    Create new repository

    Give a Repository name to “todo-app” and click create repository button.

    To add remote origin, run the below command in terminal
    Note: update your Github <username>

      
        git remote add origin [email protected]:<username>/todo-app.git
      
    

    To push your code to the respective remote repository, run the below command in terminal

      
        git push -u origin master
      
    

    Refresh the Github repository. Now, your code will be there…

    Now we have to deploy via Github-pages

    In that Github repository, click settings -> (scroll) go to GitHub Pages section. Click the select box under the source section, select master and click save.

    You will get a notification like “GitHub Pages source saved.”

    After that, Under the GitHub Pages section, you can find your published app URL. Like below…

    “Your site is ready to be published at http://www.surender.net/todo-app