Spaces:
Running
Running
| const todoForm = document.getElementById('todo-form'); | |
| const todoInput = document.getElementById('todo-input'); | |
| const todoList = document.getElementById('todo-list'); | |
| // Initialize todos from Local Storage, defaulting to an empty array | |
| let todos = JSON.parse(localStorage.getItem('todos')) || []; | |
| // Function to save todos to Local Storage | |
| const saveTodos = () => { | |
| localStorage.setItem('todos', JSON.stringify(todos)); | |
| }; | |
| // Function to render the entire list | |
| const renderTodos = () => { | |
| todoList.innerHTML = ''; // Clear existing list items | |
| if (todos.length === 0) { | |
| const emptyMessage = document.createElement('li'); | |
| emptyMessage.textContent = "Great job! No tasks currently pending."; | |
| emptyMessage.className = "todo-item empty-state"; | |
| todoList.appendChild(emptyMessage); | |
| return; | |
| } | |
| todos.forEach((todo, index) => { | |
| const listItem = document.createElement('li'); | |
| listItem.className = `todo-item ${todo.completed ? 'completed' : ''}`; | |
| listItem.setAttribute('role', 'listitem'); | |
| listItem.setAttribute('aria-checked', todo.completed); | |
| // Task Text (Clickable to toggle completion) | |
| const taskText = document.createElement('span'); | |
| taskText.className = 'task-text'; | |
| taskText.textContent = todo.text; | |
| taskText.setAttribute('tabindex', '0'); // Make text focusable for keyboard users | |
| taskText.addEventListener('click', () => toggleComplete(index)); | |
| taskText.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter' || e.key === ' ') { | |
| e.preventDefault(); | |
| toggleComplete(index); | |
| } | |
| }); | |
| // Actions container | |
| const actions = document.createElement('div'); | |
| actions.className = 'todo-actions'; | |
| // Delete Button | |
| const deleteBtn = document.createElement('button'); | |
| deleteBtn.textContent = 'Delete'; | |
| deleteBtn.className = 'delete-btn'; | |
| deleteBtn.setAttribute('aria-label', `Delete task: ${todo.text}`); | |
| deleteBtn.addEventListener('click', () => deleteTodo(index)); | |
| listItem.appendChild(taskText); | |
| actions.appendChild(deleteBtn); | |
| listItem.appendChild(actions); | |
| todoList.appendChild(listItem); | |
| }); | |
| }; | |
| // 1. Add Functionality | |
| const addTodo = (e) => { | |
| e.preventDefault(); | |
| const taskText = todoInput.value.trim(); | |
| if (taskText === "") { | |
| // Use standard constraint validation feedback if input is required, | |
| // but since we prevent default and trim, a simple focus is enough | |
| alert("Please enter a task."); | |
| todoInput.focus(); | |
| return; | |
| } | |
| const newTodo = { | |
| text: taskText, | |
| completed: false, | |
| id: Date.now() // Simple unique ID for future scalability if needed | |
| }; | |
| todos.push(newTodo); | |
| saveTodos(); | |
| renderTodos(); | |
| todoInput.value = ''; // Clear input | |
| todoInput.focus(); | |
| }; | |
| // 2. Delete Functionality | |
| const deleteTodo = (index) => { | |
| // Optional: add a visual confirmation dialog | |
| if (confirm(`Are you sure you want to delete "${todos[index].text}"?`)) { | |
| // Remove 1 element starting at the given index | |
| todos.splice(index, 1); | |
| saveTodos(); | |
| renderTodos(); | |
| } | |
| }; | |
| // 3. Mark as Complete Functionality (Toggle) | |
| const toggleComplete = (index) => { | |
| todos[index].completed = !todos[index].completed; | |
| saveTodos(); | |
| renderTodos(); // Re-render to apply the completed class and ARIA state | |
| }; | |
| // Event Listeners | |
| document.addEventListener('DOMContentLoaded', renderTodos); | |
| todoForm.addEventListener('submit', addTodo); |