How To Create a To-Do List Application in JavaScript, HTML and CSS
In this JavaScript Tutorial, we will see how to create a task management application using JavaScript, HTML5, CSS3.
This Todo app include functionalities like filtering and sorting capabilities, pagination, data persistence, import/export, Priority levels, due dates, and completion tracking .
What We Are Gonna Use In This Project:
- JavaScript Programming Language.- HTML and CSS.
- Font-awesome.
- Visual Studio Editor.
Project Source Code:
- Save Tasks
Saves the current tasks array to browser's localStorage to preserve data.
// Save tasks to the browser's memory
// so they don't disappear when you close the page
function saveTodos() {
// localStorage is like a mini database in your browser
// We convert our tasks array
// to a string format (JSON) that can be stored
localStorage.setItem('todos', JSON.stringify(todos));
}
- Display Engine
Filters, sorts, paginates, and displays tasks on the screen with styling and controls.
// Show tasks on the screen
function renderTodos() {
// Step 1: Filter tasks based on whether they're done or not
let filteredTodos = todos; // Start with all tasks
// Check which filter option is selected (all, active, or completed)
if (filterOption.value === 'active') {
// Only keep tasks that are NOT completed
// Only show unfinished tasks
filteredTodos = todos.filter(todo => !todo.completed);
} else if (filterOption.value === 'completed') {
// Only keep tasks that ARE completed
// Only show finished tasks
filteredTodos = todos.filter(todo => todo.completed);
}
// If 'all' is selected, we keep all tasks (no filter needed)
// Step 2: Only show tasks that match what you typed in the search box
// Convert search to lowercase for easier matching
const searchTerm = searchBar.value.toLowerCase();
// Keep only tasks where the text contains what you searched for
filteredTodos = filteredTodos.filter(todo =>
// Check if task contains search term
todo.text.toLowerCase().includes(searchTerm)
);
// Step 3: Put tasks in order based on what sorting option you picked
filteredTodos.sort((a, b) => {
// sort() compares two items at a time to decide their order
if (sortOption.value === 'due-date') {
// Put earliest due dates first by comparing date values
// Smaller numbers (earlier dates) come first
return new Date(a.dueDate) - new Date(b.dueDate);
} else if (sortOption.value === 'priority') {
// Put highest priority tasks first
// We assign number values to priorities: high=3, medium=2, low=1
const priorityOrder = { high: 3, medium: 2, low: 1 };
// Compare priority numbers (higher number comes first)
return priorityOrder[b.priority] - priorityOrder[a.priority];
}
// If no special sorting, show oldest tasks first (by ID/creation time)
return a.id - b.id; // Smaller IDs (created earlier) come first
});
// Step 4: Only show tasks for the current page (not all at once)
// Calculate which tasks should appear on current page
// Where to start (e.g., page 1 starts at 0)
const startIndex = (currentPage - 1) * todosPerPage;
// Where to end (e.g., page 1 ends at 5)
const endIndex = startIndex + todosPerPage;
// Get just the chunk of tasks for this page
const currentTodos = filteredTodos.slice(startIndex, endIndex);
// Step 5: Clear the current list before showing new tasks
todoList.innerHTML = ''; // Remove all existing tasks from screen
// Step 6: Add each task to the page
currentTodos.forEach((todo) => {
// Create a new list item for each task
const li = document.createElement('li');
// Set classes for styling (different colors for completed tasks and priorities)
li.className = `todo-item ${todo.completed ? 'completed' : ''}
priority-${todo.priority}`;
// Create colorful tag showing priority level (Low/Medium/High)
// Make the first letter uppercase for nicer display
const priorityBadge = `<span class="priority-badge ${todo.priority}">
${todo.priority.charAt(0).toUpperCase() +
todo.priority.slice(1)}</span>`;
// Create HTML for each task with its text, due date, and buttons
li.innerHTML = `
<div class="task-info">
<span class="task-text">${priorityBadge}${todo.text}</span>
<span class="due-date">Due: ${todo.dueDate}</span>
</div>
<div class="todo-actions">
<button class="complete-btn" onclick="toggleComplete(${todo.id})">
<i class="fas ${todo.completed ? 'fa-undo' : 'fa-check'}"></i>
<span class="button-text">${todo.completed ? ' Undo' : ' Complete'}</span>
</button>
<button class="edit-btn" onclick="editTodo(${todo.id})">
<i class="fas fa-edit"></i>
<span class="button-text"> Edit</span>
</button>
<button class="delete-btn" onclick="deleteTodo(${todo.id})">
<i class="fas fa-trash"></i>
<span class="button-text"> Delete</span>
</button>
</div>
`;
// Add the finished task item to our list
todoList.appendChild(li);
});
// Step 7: Update page buttons and stats at the bottom
updatePaginationButtons(filteredTodos.length); // Enable/disable page buttons
updateStats(); // Update the counts of tasks (total, active, completed)
}
- Page Navigation Controller.
Enables/disables Previous and Next buttons based on current page and total task count.
// Turn on/off Previous and Next buttons depending on which page you're on
function updatePaginationButtons(totalTodos) {
// Calculate how many pages we need based on total tasks
// Round up to include partial pages
const totalPages = Math.ceil(totalTodos / todosPerPage);
// Disable Previous button if we're on the first page
// Can't go back if on first page
prevPageBtn.disabled = currentPage === 1;
// Disable Next button if we're on the last page or have no tasks
// Can't go forward if on last page
nextPageBtn.disabled = currentPage === totalPages || totalPages === 0;
}
- Task Creation.
Creates a new task from form input with unique ID, priority, due date, and adds it to the list.
// Add a new task to the list
function addTodo(e) {
// Stop the form from refreshing the page (default behavior)
e.preventDefault();
// Get the task text and remove any extra spaces
const todoText = todoInput.value.trim();
// Get the selected due date and priority
const dueDate = dueDateInput.value;
const priority = priorityInput.value;
// Only add task if there's actual text (not empty)
if (todoText) {
// Create a new task object with all its details
const newTodo = {
id: Date.now(), // Use current time as a unique ID number (milliseconds since 1970)
text: todoText, // The actual task description
completed: false, // New tasks always start as not completed
// Format the date nicely, or use "No due date" if none selected
dueDate: dueDate ? new Date(dueDate).toLocaleDateString() : 'No due date',
priority: priority, // The importance level (low/medium/high)
dateAdded: new Date().toISOString() // Store when this task was created
};
// Add task to our list of tasks
todos.push(newTodo);
// Save to browser storage so tasks remain after page refresh
saveTodos();
// Clear the form fields after adding
todoInput.value = ''; // Empty the task input
dueDateInput.value = ''; // Reset due date
priorityInput.value = 'low'; // Reset priority to default
// Jump to the page where the new task will appear (the last page)
currentPage = Math.ceil(todos.length / todosPerPage);
// Refresh the task display to show the new task
renderTodos();
}
}
- Status Toggle.
Switches a task between completed and active status by finding it by ID and flipping its boolean.
// Mark a task as done or not done
function toggleComplete(id) {
// Find the specific task with this ID
const todo = todos.find(t => t.id === id);
// If we found it, update its status
if (todo) {
// Switch between done and not done (true/false)
todo.completed = !todo.completed;
saveTodos(); // Save the updated task list
renderTodos(); // Refresh the display
}
}
- In-Place Task Editor.
Allows editing of task text, due date, and priority using browser prompt dialogs.
// Change the text or details of a task
function editTodo(id) {
// Find the specific task with this ID
const todo = todos.find(t => t.id === id);
// If we found it, show prompts to edit it
if (todo) {
// Ask for new task text in a popup
const newText = prompt('Edit task:', todo.text);
// Only proceed if user didn't click Cancel
if (newText !== null) {
// Update the task text (removing any extra spaces)
todo.text = newText.trim();
// Ask for new due date in a popup
const newDueDate = prompt('Edit due date (YYYY-MM-DD):', todo.dueDate);
// Only proceed if user didn't click Cancel
if (newDueDate !== null) {
// Update due date or use "No due date" if empty
todo.dueDate = newDueDate || 'No due date';
}
// Ask for new priority in a popup
const newPriority = prompt('Edit priority (low/medium/high):', todo.priority);
// Only update priority if valid and user didn't click Cancel
if (newPriority !== null && ['low', 'medium', 'high'].includes(newPriority)) {
todo.priority = newPriority;
}
saveTodos(); // Save changes to browser storage
renderTodos(); // Refresh the display
}
}
}
- Task Removal.
Removes a task from the list after user confirmation and handles page adjustment if needed.
// Remove a task from the list
function deleteTodo(id) {
// Ask user to confirm deletion to prevent accidents
if (confirm('Are you sure you want to delete this task?')) {
// Keep all tasks EXCEPT the one with this ID (effectively removing it)
todos = todos.filter(t => t.id !== id);
// Save the updated list
saveTodos();
// If we deleted the last task on this page, go back a page
// (This happens when tasks per page divides evenly into total tasks)
if (todos.length % todosPerPage === 0 && currentPage > 1) {
currentPage--;
}
// Refresh the display
renderTodos();
}
}
- Pagination Navigation.
Moves between pages of tasks by incrementing or decrementing the current page number.
// Move between pages of tasks
function changePage(direction) {
// Increase or decrease page number
currentPage += direction; // Add 1 to go forward, subtract 1 to go back
// Refresh display to show the new page
renderTodos();
}
- Real-time Statistics.
Calculates and displays total, active, and completed task counts in the stats section.
// Show how many tasks are total, active, and done
function updateStats() {
// Count all tasks
const totalTasks = todos.length;
// Count how many are marked as done
const completedTasks = todos.filter(t => t.completed).length;
// Calculate how many are still active (not done)
const activeTasks = totalTasks - completedTasks;
// Update the stats display with these numbers
statsDisplay.innerHTML = `
<strong>${totalTasks}</strong> Total Tasks |
<strong>${activeTasks}</strong> Active |
<strong>${completedTasks}</strong> Completed
`;
}
- Data Export.
Creates and downloads a JSON file containing all tasks for backup purposes.
// Save tasks to a file you can download
function exportTodos() {
// Convert the tasks array to a string format (JSON)
const tasksJSON = JSON.stringify(todos);
// Create a special URL that represents this data as a file
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(tasksJSON);
// Create a fake download button in memory (not visible on screen)
const downloadLink = document.createElement('a');
downloadLink.setAttribute("href", dataStr); // Set the file data
downloadLink.setAttribute("download", "tasks.json"); // Set the filename
document.body.appendChild(downloadLink); // Add to page temporarily
// Click this fake button to start the download
downloadLink.click();
// Remove the fake button after download starts
downloadLink.remove();
}
- Data Import
Reads an uploaded JSON file and replaces current tasks with the imported data.
// Load tasks from a file on your computer
function importTodos(event) {
// Get the file the user selected
const file = event.target.files[0];
// Only proceed if a file was actually selected
if (file) {
// Create a tool to read the file contents
const reader = new FileReader();
// Set up what happens when the file is loaded
reader.onload = function(e) {
try {
// Try to convert the file content to a tasks array
const importedTodos = JSON.parse(e.target.result);
// Replace our current tasks with the imported ones
todos = importedTodos;
// Save to browser storage
saveTodos();
// Refresh the display
renderTodos();
// Let the user know it worked
alert("Tasks imported successfully!");
} catch (error) {
// If anything goes wrong (like invalid file format)
alert("Error importing tasks. Please make sure the file is valid.");
}
};
// Start reading the file content
reader.readAsText(file);
}
}