Introduction
To-do list applications are foundational projects for developers. They involve essential concepts like DOM manipulation, event handling, and data storage. In this guide, we’ll build a fully functional to-do list application using Vanilla JavaScript and progressively enhance it by integrating Firebase for real-time database functionality, user authentication, and advanced features like cross-device synchronization.
Table of Contents
- Getting Started
- Setting Up the Project
- Basic Features of the To-Do List Application
- Enhancing the To-Do List
- Edit, Delete, and Mark as Complete
- Filtering and Sorting Tasks
- Styling the Application
- Firebase Integration
- Setting Up Firebase
- Adding Real-Time Database Functionality
- User Authentication and Multi-User Task Management
- Syncing Data Across Devices
- Complete Codebase
- Real-World Use Cases and Benefits
- FAQs
- Conclusion
1. Getting Started
Setting Up the Project
Create the folder structure for your project:
/todo-app
├── index.html
├── style.css
├── script.js
HTML for the To-Do App
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>To-Do List App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="app">
<h1>To-Do List</h1>
<div class="input-container">
<input type="text" id="taskInput" placeholder="Enter a new task" />
<button id="addTaskBtn">Add Task</button>
</div>
<ul id="taskList"></ul>
</div>
<script src="https://www.gstatic.com/firebasejs/9.21.0/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/9.21.0/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/9.21.0/firebase-firestore.js"></script>
<script src="script.js"></script>
</body>
</html>
Basic JavaScript for Adding Tasks
document.getElementById('addTaskBtn').addEventListener('click', function () {
const taskInput = document.getElementById('taskInput');
const taskList = document.getElementById('taskList');
if (taskInput.value.trim() !== '') {
const li = document.createElement('li');
li.textContent = taskInput.value;
taskList.appendChild(li);
taskInput.value = '';
} else {
alert('Please enter a valid task.');
}
});
2. Enhancing the To-Do List
Adding Edit, Delete, and Mark as Complete
Enhance the task list to include buttons for editing, deleting, and marking tasks as completed.
document.getElementById('addTaskBtn').addEventListener('click', function () {
const taskInput = document.getElementById('taskInput');
const taskList = document.getElementById('taskList');
if (taskInput.value.trim() !== '') {
const li = document.createElement('li');
li.innerHTML = `
<span class="task">${taskInput.value}</span>
<button class="complete">Complete</button>
<button class="edit">Edit</button>
<button class="delete">Delete</button>
`;
taskList.appendChild(li);
taskInput.value = '';
}
taskList.addEventListener('click', function (event) {
if (event.target.classList.contains('delete')) {
event.target.parentElement.remove();
} else if (event.target.classList.contains('edit')) {
const taskText = event.target.parentElement.querySelector('.task');
const newTask = prompt('Edit your task:', taskText.textContent);
if (newTask) taskText.textContent = newTask;
} else if (event.target.classList.contains('complete')) {
event.target.parentElement.classList.toggle('completed');
}
});
});
Filtering and Sorting Tasks
Add a dropdown to filter tasks by their status:
<select id="filter">
<option value="all">All</option>
<option value="completed">Completed</option>
<option value="incomplete">Incomplete</option>
</select>
Update the task filtering logic in JavaScript:
document.getElementById('filter').addEventListener('change', function () {
const filterValue = this.value;
const tasks = document.querySelectorAll('#taskList li');
tasks.forEach(task => {
const isCompleted = task.classList.contains('completed');
if (filterValue === 'all' ||
(filterValue === 'completed' && isCompleted) ||
(filterValue === 'incomplete' && !isCompleted)) {
task.style.display = '';
} else {
task.style.display = 'none';
}
});
});
3. Styling the Application
Add styling for a polished look:
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
#app {
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
width: 300px;
text-align: center;
}
.completed {
text-decoration: line-through;
color: gray;
}
4. Firebase Integration
Setting Up Firebase
- Go to the Firebase Console and create a project.
- Set up a Firestore database and enable Authentication.
Connecting Firebase
Initialize Firebase in script.js:
import { initializeApp } from "firebase/app";
import { getFirestore, collection, addDoc, onSnapshot, deleteDoc, doc } from "firebase/firestore";
const firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_AUTH_DOMAIN",
projectId: "YOUR_PROJECT_ID",
storageBucket: "YOUR_STORAGE_BUCKET",
messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
appId: "YOUR_APP_ID"
};
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
Adding Real-Time Database Functionality
Store tasks in Firebase:
document.getElementById('addTaskBtn').addEventListener('click', async function () {
const taskInput = document.getElementById('taskInput');
if (taskInput.value.trim() !== '') {
await addDoc(collection(db, "tasks"), { task: taskInput.value, completed: false });
taskInput.value = '';
}
});
onSnapshot(collection(db, "tasks"), (snapshot) => {
const taskList = document.getElementById('taskList');
taskList.innerHTML = '';
snapshot.docs.forEach(doc => {
const task = doc.data();
const li = document.createElement('li');
li.innerHTML = `
<span class="task">${task.task}</span>
<button class="delete" data-id="${doc.id}">Delete</button>
`;
taskList.appendChild(li);
li.querySelector('.delete').addEventListener('click', async () => {
await deleteDoc(doc(db, "tasks", li.getAttribute('data-id')));
});
});
});
5. Complete Codebase
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>To-Do List App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="app">
<h1>To-Do List</h1>
<div class="input-container">
<input type="text" id="taskInput" placeholder="Enter a new task" />
<button id="addTaskBtn">Add Task</button>
</div>
<ul id="taskList"></ul>
</div>
<!-- Firebase SDK -->
<script src="https://www.gstatic.com/firebasejs/9.21.0/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/9.21.0/firebase-firestore.js"></script>
<script src="script.js"></script>
</body>
</html>
style.css
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
#app {
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
width: 300px;
text-align: center;
}
.input-container {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
}
input {
flex: 1;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
margin-right: 10px;
}
button {
padding: 10px 15px;
background-color: #007BFF;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
ul {
list-style-type: none;
padding: 0;
}
li {
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
margin-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
.completed {
text-decoration: line-through;
color: gray;
}
.delete {
background-color: #FF5C5C;
color: #fff;
border: none;
padding: 5px 10px;
cursor: pointer;
border-radius: 4px;
}
.delete:hover {
background-color: #FF0000;
}
Script.js
// Import Firebase modules
import { initializeApp } from "firebase/app";
import {
getFirestore,
collection,
addDoc,
onSnapshot,
deleteDoc,
doc,
updateDoc,
} from "firebase/firestore";
// Firebase configuration
const firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_AUTH_DOMAIN",
projectId: "YOUR_PROJECT_ID",
storageBucket: "YOUR_STORAGE_BUCKET",
messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
appId: "YOUR_APP_ID",
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
// DOM Elements
const taskInput = document.getElementById("taskInput");
const addTaskBtn = document.getElementById("addTaskBtn");
const taskList = document.getElementById("taskList");
// Add Task to Firestore
addTaskBtn.addEventListener("click", async () => {
const task = taskInput.value.trim();
if (task) {
await addDoc(collection(db, "tasks"), {
task: task,
completed: false,
});
taskInput.value = "";
} else {
alert("Please enter a valid task.");
}
});
// Real-time Listener for Tasks
onSnapshot(collection(db, "tasks"), (snapshot) => {
taskList.innerHTML = ""; // Clear current tasks
snapshot.docs.forEach((doc) => {
const taskData = doc.data();
const taskId = doc.id;
// Create task list item
const li = document.createElement("li");
li.innerHTML = `
<span class="task ${taskData.completed ? "completed" : ""}">
${taskData.task}
</span>
<button class="complete" data-id="${taskId}">Complete</button>
<button class="delete" data-id="${taskId}">Delete</button>
`;
taskList.appendChild(li);
// Mark Task as Completed
li.querySelector(".complete").addEventListener("click", async () => {
await updateDoc(doc(db, "tasks", taskId), {
completed: !taskData.completed,
});
});
// Delete Task
li.querySelector(".delete").addEventListener("click", async () => {
await deleteDoc(doc(db, "tasks", taskId));
});
});
});
How to Use This Code
- Replace "YOUR_API_KEY" and other Firebase configuration values with those from your Firebase project.
- Copy all three files into a project directory.
- Open the index.html file in a browser.
- Add, complete, and delete tasks while the changes sync with Firebase in real-time.
6. Real-World Use Cases and Benefits
Use Cases:
- Task management apps.
- Collaboration tools with real-time updates.
Benefits:
- Learn JavaScript and Firebase integration.
- Understand real-time database implementation.
7. FAQs
Q: Can I use Firebase Authentication for multiple users?
A: Yes, Firebase Authentication supports multi-user sign-ins using methods like email, Google, and Facebook.
Q: Is Firebase free?
A: Firebase offers a free plan with sufficient limits for small-scale applications.
Now your to-do list app is feature-rich and ready for deployment!
8. Conclusion
Building a To-Do List Application with Vanilla JavaScript and Firebase provides a strong foundation for understanding core web development concepts and real-time database integration. This project demonstrates how simple functionalities like adding, updating, and deleting tasks can evolve into a dynamic, feature-rich application with the addition of Firebase. By following this guide, you’ve learned how to create a fully functional app, write clean and modular code, and use Firebase to sync data in real time.
This project can serve as a starting point for more advanced applications, such as collaborative task managers, note-taking apps, or project management tools. Keep exploring and iterating to add more features, like authentication or cloud storage, to further enhance your skills and portfolio!
Exploring JavaScript's Array Methods with Real-World Use Cases
Learn how to use JavaScript Destructuring with Practical Examples
About Muhaymin Bin Mehmood
Front-end Developer skilled in the MERN stack, experienced in web and mobile development. Proficient in React.js, Node.js, and Express.js, with a focus on client interactions, sales support, and high-performance applications.