Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 98 additions & 1 deletion css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ button {
border: 0px;
color: #888;
font-size: 15px;
width: 60px;
width: auto;
margin: 10px 0 0;
font-family: Lato, sans-serif;
cursor: pointer;
transition: color 0.2s ease;
padding: 0 10px;
}
button:hover {
color: #333;
Expand Down Expand Up @@ -117,4 +119,99 @@ ul li.editMode input[type=text] {

ul li.editMode label {
display:none;
}

/* Stats and Filters */
.stats {
display: flex;
justify-content: space-between;
align-items: center;
margin: 20px 0;
padding: 10px;
background: #f5f5f5;
border-radius: 6px;
font-size: 14px;
color: #666;
}

#task-count {
font-weight: 300;
}

.clear-completed {
background: #fff;
border: 1px solid #ddd;
border-radius: 4px;
padding: 5px 10px;
font-size: 13px;
color: #888;
cursor: pointer;
transition: all 0.2s ease;
}

.clear-completed:hover {
background: #0FC57C;
color: #fff;
border-color: #0FC57C;
}

.filters {
display: flex;
justify-content: center;
gap: 10px;
margin: 20px 0;
padding: 10px;
background: #f5f5f5;
border-radius: 6px;
}

.filter-btn {
background: #fff;
border: 1px solid #ddd;
border-radius: 4px;
padding: 8px 16px;
font-size: 14px;
font-weight: 500;
color: #888;
cursor: pointer;
transition: all 0.2s ease;
}

.filter-btn:hover {
background: #0FC57C;
color: #fff;
border-color: #0FC57C;
}

.filter-btn.active {
background: #0FC57C;
color: #fff;
border-color: #0FC57C;
}

/* Responsive Design */
@media (max-width: 480px) {
.container {
width: 95%;
margin: 50px auto 0;
}

.stats {
flex-direction: column;
gap: 10px;
align-items: stretch;
}

.filters {
flex-direction: column;
gap: 8px;
}

.filter-btn {
padding: 10px;
}

input#new-task {
width: calc(100% - 80px);
}
}
13 changes: 13 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,19 @@
<label for="new-task">Add Item</label><input id="new-task" type="text"><button>Add</button>
</p>

<!-- 统计区域 -->
<div class="stats">
<span id="task-count">0 tasks · 0 completed</span>
<button id="clear-completed" class="clear-completed">Clear Completed</button>
</div>

<!-- 过滤按钮 -->
<div class="filters">
<button class="filter-btn active" data-filter="all">All</button>
<button class="filter-btn" data-filter="active">Active</button>
<button class="filter-btn" data-filter="completed">Completed</button>
</div>

<h3>Todo</h3>
<ul id="incomplete-tasks">
<li><input type="checkbox"><label>Pay Bills</label><input type="text"><button class="edit">Edit</button><button class="delete">Delete</button></li>
Expand Down
169 changes: 166 additions & 3 deletions js/app.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
// Vanila JavaScript todo list app
// Vanila JavaScript todo list app
var taskInput = document.getElementById("new-task"); // New-task
var addButton = document.getElementsByTagName("button")[0]; // First button
var incompleteTasksHolder = document.getElementById("incomplete-tasks"); // Incomplete-tasks
var completedTasksHolder = document.getElementById("completed-tasks"); // Completed-tasks

// New elements for filters and stats
var filterButtons = document.querySelectorAll(".filter-btn");
var taskCountSpan = document.getElementById("task-count");
var clearCompletedButton = document.getElementById("clear-completed");
var currentFilter = "all";

var createNewTaskElement = function(taskString) { // New Task List Item
var listItem = document.createElement("li"); // Create List Item
var checkBox = document.createElement("input"); // Input (checkbox)
Expand Down Expand Up @@ -35,18 +41,22 @@ var addTask = function() { // Add a new task
incompleteTasksHolder.appendChild(listItem); // Append listItem to incompleteTasksHolder
bindTaskEvents(listItem, taskCompleted); // We bind it to the incomplete holder
taskInput.value = ""; // Resets the field
updateStats(); // Update stats after adding task
applyFilter(); // Apply current filter
saveToLocalStorage(); // Save to localStorage
};

var editTask = function() { // Edit an existing task
var listItem = this.parentNode; // Create List Item
var editInput = listItem.querySelector("input[type=text"); // Input (text)
var editInput = listItem.querySelector("input[type=text]"); // Input (text)
var label = listItem.querySelector("label"); // Label
var button = listItem.getElementsByTagName("button")[0]; // Button

var containsClass = listItem.classList.contains("editMode"); // We check for .editMode and assign it a variable
if(containsClass) { // Switch from .editMode
label.innerText = editInput.value; // Label text become the input's value
button.innerText = "Edit"; // Buttons name modified to Edit
saveToLocalStorage(); // Save to localStorage after edit
} else { // Switch to .editMode
editInput.value = label.innerText; // Input value becomes the label's text
button.innerText = "Save"; // Button name modified to Save
Expand All @@ -58,18 +68,26 @@ var deleteTask = function() { // Delete an existing task
var listItem = this.parentNode; // We use parentNode to target the object containing the delete button
var ul = listItem.parentNode; // We use parentNode again to target the list containing the task
ul.removeChild(listItem); // Remove the parent list item from the ul
updateStats(); // Update stats after deleting task
saveToLocalStorage(); // Save to localStorage
};

var taskCompleted = function() { // Mark a task as complete
var listItem = this.parentNode; // We assign it for readability
completedTasksHolder.appendChild(listItem); // Append the task list item to the #completed-tasks
bindTaskEvents(listItem, taskIncomplete); // We bind it to the opposite holder
updateStats(); // Update stats after completing task
applyFilter(); // Apply current filter
saveToLocalStorage(); // Save to localStorage
};

var taskIncomplete = function() { // Mark a task as incomplete
var listItem = this.parentNode; // We assign it for readability
incompleteTasksHolder.appendChild(listItem); // Append the task list item to the #incomplete-tasks
bindTaskEvents(listItem, taskCompleted); // We bind it to the opposite holder
updateStats(); // Update stats after incompleting task
applyFilter(); // Apply current filter
saveToLocalStorage(); // Save to localStorage
};

var bindTaskEvents = function(taskListItem, checkBoxEventHandler) { // Select it's children
Expand All @@ -81,17 +99,162 @@ var bindTaskEvents = function(taskListItem, checkBoxEventHandler) { // Select
checkBox.onchange = checkBoxEventHandler; // Bind checkBoxEventHandler to checkbox
};

// Filter functions
var applyFilter = function() {
var incompleteTasks = incompleteTasksHolder.children;
var completedTasks = completedTasksHolder.children;

// Show/hide incomplete tasks based on filter
for(var i = 0; i < incompleteTasks.length; i++) {
var task = incompleteTasks[i];
if(currentFilter === "all" || currentFilter === "active") {
task.style.display = "list-item";
} else if(currentFilter === "completed") {
task.style.display = "none";
}
}

// Show/hide completed tasks based on filter
for(var i = 0; i < completedTasks.length; i++) {
var task = completedTasks[i];
if(currentFilter === "all" || currentFilter === "completed") {
task.style.display = "list-item";
} else if(currentFilter === "active") {
task.style.display = "none";
}
}

// Update h3 visibility based on filter and task counts
var hasIncomplete = incompleteTasksHolder.children.length > 0 && (currentFilter === "all" || currentFilter === "active");
var hasCompleted = completedTasksHolder.children.length > 0 && (currentFilter === "all" || currentFilter === "completed");

document.querySelector("h3:nth-of-type(1)").style.display = hasIncomplete ? "block" : "none";
document.querySelector("h3:nth-of-type(2)").style.display = hasCompleted ? "block" : "none";
};

var setFilter = function(filter) {
currentFilter = filter;

// Update button states
filterButtons.forEach(function(button) {
if(button.dataset.filter === filter) {
button.classList.add("active");
} else {
button.classList.remove("active");
}
});

applyFilter();
};

// Stats functions
var updateStats = function() {
var totalTasks = incompleteTasksHolder.children.length + completedTasksHolder.children.length;
var completedTasks = completedTasksHolder.children.length;
var activeTasks = incompleteTasksHolder.children.length;

taskCountSpan.innerText = totalTasks + " tasks · " + completedTasks + " completed · " + activeTasks + " active";
};

// Clear completed tasks
var clearCompleted = function() {
while(completedTasksHolder.firstChild) {
completedTasksHolder.removeChild(completedTasksHolder.firstChild);
}
updateStats();
applyFilter();
saveToLocalStorage();
};

// LocalStorage functions
var saveToLocalStorage = function() {
var tasks = {
incomplete: [],
completed: []
};

// Save incomplete tasks
for(var i = 0; i < incompleteTasksHolder.children.length; i++) {
var task = incompleteTasksHolder.children[i];
var label = task.querySelector("label");
tasks.incomplete.push(label.innerText);
}

// Save completed tasks
for(var i = 0; i < completedTasksHolder.children.length; i++) {
var task = completedTasksHolder.children[i];
var label = task.querySelector("label");
tasks.completed.push(label.innerText);
}

localStorage.setItem("todoTasks", JSON.stringify(tasks));
};

var loadFromLocalStorage = function() {
var tasks = JSON.parse(localStorage.getItem("todoTasks"));

if(tasks) {
// Clear existing tasks
while(incompleteTasksHolder.firstChild) {
incompleteTasksHolder.removeChild(incompleteTasksHolder.firstChild);
}
while(completedTasksHolder.firstChild) {
completedTasksHolder.removeChild(completedTasksHolder.firstChild);
}

// Load incomplete tasks
for(var i = 0; i < tasks.incomplete.length; i++) {
var taskString = tasks.incomplete[i];
var listItem = createNewTaskElement(taskString);
incompleteTasksHolder.appendChild(listItem);
bindTaskEvents(listItem, taskCompleted);
}

// Load completed tasks
for(var i = 0; i < tasks.completed.length; i++) {
var taskString = tasks.completed[i];
var listItem = createNewTaskElement(taskString);
var checkBox = listItem.querySelector("input[type=checkbox]");
checkBox.checked = true;
completedTasksHolder.appendChild(listItem);
bindTaskEvents(listItem, taskIncomplete);
}

updateStats();
applyFilter();
}
};

var ajaxRequest = function() {
console.log("AJAX request");
};

// Event listeners
addButton.addEventListener("click", addTask); // Adds event listener for the click handler to the addTask function
addButton.addEventListener("click", ajaxRequest); // Adds an event listener for AJAX

// Filter button event listeners
filterButtons.forEach(function(button) {
button.addEventListener("click", function() {
setFilter(this.dataset.filter);
});
});

// Clear completed button event listener
clearCompletedButton.addEventListener("click", clearCompleted);

// Load tasks from localStorage on page load
window.addEventListener("load", function() {
loadFromLocalStorage();
});

for(var i = 0; i < incompleteTasksHolder.children.length; i++) { // Cycle over incompleteTasksHolder ul list items
bindTaskEvents(incompleteTasksHolder.children[i], taskCompleted); // Bind events to list item's children (taskCompleted)
}

for(var i = 0; i < completedTasksHolder.children.length; i++) { // Cycle over completedTasksHolder ul list items
bindTaskEvents(completedTasksHolder.children[i], taskIncomplete); // Bind events to list item's children (taskIncomplete)
}
}

// Initial stats update
updateStats();