Praxisprojekt - Todo-App - Schritt-für-Schritt Anleitung
Diese Anleitung führt dich durch den kompletten Aufbau einer Todo-Liste Applikation. Du kannst in deinem eigenen Tempo arbeiten und jeden Schritt verstehen, bevor du zum nächsten übergehst.
Übersicht der Entwicklungsschritte
Abschnitt betitelt „Übersicht der Entwicklungsschritte“- Version 1: Grundlegende HTML-Struktur und einfaches Hinzufügen von Todos
- Version 2: Styling, Icons und Interaktivität (Löschen & Abhaken)
- Version 3: Filter-Funktionalität hinzufügen
- Version 4: LocalStorage - Todos speichern und laden
- Version 5: Status-Verwaltung - Completed/Uncompleted Status speichern
Version 1: Grundlegende Funktionalität
Abschnitt betitelt „Version 1: Grundlegende Funktionalität“Eine einfache Todo-Liste erstellen, bei der du neue Todos hinzufügen kannst.
Schritt 1.1: HTML-Grundstruktur erstellen
Abschnitt betitelt „Schritt 1.1: HTML-Grundstruktur erstellen“Erstelle eine Datei index.html mit folgendem Inhalt:
<!DOCTYPE html><html lang="de"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Todo-Liste</title> </head> <body> <header> <h1>Todo-Liste</h1> </header>
<form> <input type="text" class="todo-input" /> <button class="todo-button" type="submit">+</button> </form>
<div class="todo-container"> <ul class="todo-list"></ul> </div>
<footer></footer>
<script src="app.js"></script> </body></html>Erklärung:
- Das
<form>Element enthält ein Eingabefeld (.todo-input) und einen Submit-Button (.todo-button) - Die
<ul class="todo-list">Liste ist zunächst leer und wird später dynamisch mit Todos gefüllt - Das
app.jsScript wird am Ende eingebunden
Schritt 1.2: JavaScript - Selectors und Event Listener
Abschnitt betitelt „Schritt 1.2: JavaScript - Selectors und Event Listener“Erstelle eine Datei app.js:
// Selectorsconst todoInput = document.querySelector(".todo-input");const todoButton = document.querySelector(".todo-button");const todoList = document.querySelector(".todo-list");
// Event ListenerstodoButton.addEventListener("click", addTodo);
// FunctionsErklärung:
- Selectors: Wir speichern Referenzen zu den HTML-Elementen in Konstanten, damit wir sie später verwenden können
- Event Listener: Wenn auf den Button geklickt wird, soll die Funktion
addTodoausgeführt werden
Schritt 1.3: Todo hinzufügen - Funktion implementieren
Abschnitt betitelt „Schritt 1.3: Todo hinzufügen - Funktion implementieren“Füge die addTodo Funktion hinzu:
function addTodo(event) { event.preventDefault();
const todoDiv = document.createElement("div"); todoDiv.classList.add("todo");
const newTodo = document.createElement("li"); newTodo.innerText = todoInput.value; newTodo.classList.add("todo-item");
todoDiv.appendChild(newTodo); const completedButton = document.createElement("button"); completedButton.innerHTML = "<strong>check</strong>"; completedButton.classList.add("complete-btn");
todoDiv.appendChild(completedButton);
todoList.appendChild(todoDiv);}Erklärung:
event.preventDefault()verhindert, dass das Formular sich selbst abschickt (Standard-Verhalten)- Wir erstellen ein neues
<div>Element für jedes Todo - Das Todo-Text wird in einem
<li>Element gespeichert - Ein Button wird erstellt (funktioniert noch nicht, wird in Version 2 implementiert)
- Alles wird zur Todo-Liste hinzugefügt
Teste es: Öffne die index.html im Browser und füge ein Todo hinzu!
Version 2: Styling und Interaktivität
Abschnitt betitelt „Version 2: Styling und Interaktivität“Die App schöner gestalten und Funktionen zum Löschen und Abhaken von Todos hinzufügen.
Schritt 2.1: CSS-Styling hinzufügen
Abschnitt betitelt „Schritt 2.1: CSS-Styling hinzufügen“Erstelle eine Datei style.css und verlinke sie im HTML:
In index.html im <head> hinzufügen:
<link rel="stylesheet" href="style.css" /><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"/>Inhalt von style.css:
body { display: flex; min-height: 100vh; background: linear-gradient(45deg, #aac5ca, #7aa2ca); font-family: Arial;}
form input,form button { background: white; border: none; padding: 0.5rem; font-size: 2rem;}
form button { color: darkblue;}
form { display: flex;}
.trash-btn,.complete-btn { cursor: pointer; border: none; padding: 0.5rem; color: white;}
.trash-btn i,.complete-btn i { pointer-events: none;}
.trash-btn { background: red;}.complete-btn { background: green;}
.todo-app { margin: auto;}
.todo-list { padding: 0;}
.todo { list-style: none; display: flex; justify-content: space-between; align-items: center; width: 100%; background: white; margin-bottom: 1rem; transition: all 0.5s ease;}
.todo li { flex: 1; padding: 0.4rem 1rem;}
input { border: 0; padding: 0.5rem;}
.completed { text-decoration: line-through; opacity: 0.5;}
.fall { transform: translateY(8rem) rotateZ(20deg); opacity: 0;}Erklärung:
display: flexzentriert die App auf der Seite- Der Gradient-Hintergrund gibt der App ein modernes Aussehen
.completedKlasse für durchgestrichene, transparente abgeschlossene Todos.fallKlasse für eine Animation beim Löschen
Schritt 2.2: HTML anpassen - Icons verwenden
Abschnitt betitelt „Schritt 2.2: HTML anpassen - Icons verwenden“Aktualisiere das Button-Element im HTML:
<button class="todo-button" type="submit"> <i class="fa fa-plus-square"></i></button>Erklärung:
- Font Awesome Icons werden verwendet (bereits im
<head>verlinkt) fa fa-plus-squareist ein Plus-Icon
Schritt 2.3: JavaScript - Delete und Complete Funktionen
Abschnitt betitelt „Schritt 2.3: JavaScript - Delete und Complete Funktionen“Aktualisiere app.js:
// Selectorsconst todoInput = document.querySelector(".todo-input");const todoButton = document.querySelector(".todo-button");const todoList = document.querySelector(".todo-list");
// Event ListenerstodoButton.addEventListener("click", addTodo);todoList.addEventListener("click", deleteTodo);
// Functions
function addTodo(event) { event.preventDefault();
const todoDiv = document.createElement("div"); todoDiv.classList.add("todo");
const newTodo = document.createElement("li"); newTodo.innerText = todoInput.value; newTodo.classList.add("todo-item");
todoDiv.appendChild(newTodo);
const completedButton = document.createElement("button"); completedButton.innerHTML = "<i class='fa fa-check'></i>"; completedButton.classList.add("complete-btn");
todoDiv.appendChild(completedButton);
const trashButton = document.createElement("button"); trashButton.innerHTML = "<i class='fa fa-trash'></i>"; trashButton.classList.add("trash-btn"); todoDiv.appendChild(trashButton);
todoList.prepend(todoDiv);
// clear input value todoInput.value = "";}
function deleteTodo(e) { const item = e.target;
if (item.classList[0] === "trash-btn") { const todo = item.parentElement; todo.classList.add("fall"); todo.addEventListener("transitionend", function () { todo.remove(); }); }
if (item.classList[0] === "complete-btn") { const todo = item.parentElement; todo.classList.toggle("completed"); }}Erklärung:
- Event Delegation: Wir hören auf Klicks auf der gesamten
todoList, nicht auf jedem einzelnen Button - Löschen: Wenn auf den Trash-Button geklickt wird, wird die
fallAnimation ausgelöst und das Todo nach der Animation entfernt - Abhaken: Wenn auf den Complete-Button geklickt wird, wird die
completedKlasse hinzugefügt/entfernt (toggle) todoInput.value = ""leert das Eingabefeld nach dem Hinzufügen
Wichtig: prepend() fügt neue Todos oben hinzu, appendChild() würde sie unten hinzufügen.
Version 3: Filter-Funktionalität
Abschnitt betitelt „Version 3: Filter-Funktionalität“Ein Filter hinzufügen, um Todos nach Status (Alle/Abgeschlossen/Nicht abgeschlossen) anzuzeigen.
Schritt 3.1: HTML - Select-Element hinzufügen
Abschnitt betitelt „Schritt 3.1: HTML - Select-Element hinzufügen“Füge im <form> Element ein Select-Element hinzu:
<form> <input type="text" class="todo-input" /> <button class="todo-button" type="submit"> <i class="fa fa-plus-square"></i> </button> <div class="select"> <select name="todos" class="filter-todo"> <option value="all">Alle</option> <option value="completed">Abgeschlossen</option> <option value="uncompleted">Nicht abgeschlossen</option> </select> </div></form>Erklärung:
- Das
<select>Element bietet drei Optionen zum Filtern - Die
.selectDiv wird für das Styling benötigt
Schritt 3.2: CSS für Select-Element
Abschnitt betitelt „Schritt 3.2: CSS für Select-Element“Füge folgendes CSS hinzu:
select { border: none; outline: none; padding: 0.5rem; appearance: none; -webkit-appearance: none; -moz-appearance: none; padding: 1.1rem; cursor: pointer; width: 13rem;}
.select { position: relative; overflow: hidden; margin-left: 0.5rem;}
.select::after { content: "\25BC"; position: absolute; background: #7aa2ca; padding: 1.2rem; top: 0; right: 0; pointer-events: none; transition: all 0.3s ease;}
.select:hover::after { background: white; color: #4a6e92;}
.hidden { display: none;}Erklärung:
appearance: noneentfernt das Standard-Dropdown-Pfeil.select::afterfügt einen eigenen Pfeil hinzu (Unicode-Zeichen\25BC).hiddenKlasse versteckt Todos beim Filtern
Schritt 3.3: JavaScript - Filter-Funktion
Abschnitt betitelt „Schritt 3.3: JavaScript - Filter-Funktion“Aktualisiere app.js:
// Selectorsconst todoInput = document.querySelector(".todo-input");const todoButton = document.querySelector(".todo-button");const todoList = document.querySelector(".todo-list");const todoFilter = document.querySelector(".filter-todo");
// Event ListenerstodoButton.addEventListener("click", addTodo);todoList.addEventListener("click", deleteTodo);todoFilter.addEventListener("change", filterTodo);
// ... (addTodo und deleteTodo Funktionen bleiben gleich)
function filterTodo(e) { const todos = todoList.childNodes;
todos.forEach(function (todo) { switch (e.target.value) { case "all": todo.classList.remove("hidden"); break; case "completed": if (todo.classList.contains("completed")) { todo.classList.remove("hidden"); } else { todo.classList.add("hidden"); } break; case "uncompleted": if (todo.classList.contains("completed")) { todo.classList.add("hidden"); } else { todo.classList.remove("hidden"); } break; } });}Erklärung:
todoFilter.addEventListener("change", filterTodo)hört auf Änderungen im Select-Element- Die Funktion durchläuft alle Todos und zeigt/versteckt sie je nach Filter-Option
switchStatement macht den Code lesbarer als mehrere if-else Statements
Hinweis: childNodes kann auch Text-Knoten enthalten. In einer produktiven App würde man children verwenden, aber für diese Anleitung funktioniert es.
Version 4: LocalStorage - Daten speichern
Abschnitt betitelt „Version 4: LocalStorage - Daten speichern“Todos im Browser speichern, damit sie nach dem Neuladen der Seite noch vorhanden sind.
Schritt 4.1: JavaScript - LocalStorage Funktionen
Abschnitt betitelt „Schritt 4.1: JavaScript - LocalStorage Funktionen“Füge folgende Funktionen zu app.js hinzu:
// Event ListenerstodoButton.addEventListener("click", addTodo);todoList.addEventListener("click", deleteTodo);todoFilter.addEventListener("change", filterTodo);
document.addEventListener("DOMContentLoaded", getTodos);
// ... (vorherige Funktionen)
function addTodo(event) { event.preventDefault();
saveLocalTodos(todoInput.value);
// clear input value todoInput.value = "";}
function deleteTodo(e) { const item = e.target;
if (item.classList[0] === "trash-btn") { const todo = item.parentElement; removeLocalTodos(todo);
todo.classList.add("fall"); todo.addEventListener("transitionend", function () { todo.remove(); }); }
if (item.classList[0] === "complete-btn") { const todo = item.parentElement; todo.classList.toggle("completed"); }}
// ... (filterTodo bleibt gleich)
function saveLocalTodos(todo) { let todos; if (localStorage.getItem("todos") === null) { todos = []; } else { todos = JSON.parse(localStorage.getItem("todos")); } todos.push(todo); localStorage.setItem("todos", JSON.stringify(todos));
createTodo(todo);}
function getTodos() { let todos; if (localStorage.getItem("todos") === null) { todos = []; } else { todos = JSON.parse(localStorage.getItem("todos")); }
todos.forEach(function (todo) { createTodo(todo); });}
function removeLocalTodos(todo) { let todos; if (localStorage.getItem("todos") === null) { todos = []; } else { todos = JSON.parse(localStorage.getItem("todos")); }
var index = todos.indexOf(todo.innerText);
todos.splice(index, 1); localStorage.setItem("todos", JSON.stringify(todos));}
function createTodo(todo) { const todoDiv = document.createElement("div"); todoDiv.classList.add("todo");
const newTodo = document.createElement("li"); newTodo.innerText = todo; newTodo.classList.add("todo-item"); todoDiv.appendChild(newTodo);
const completedButton = document.createElement("button"); completedButton.innerHTML = "<i class='fa fa-check'></i>"; completedButton.classList.add("complete-btn");
todoDiv.appendChild(completedButton);
const trashButton = document.createElement("button"); trashButton.innerHTML = "<i class='fa fa-trash'></i>"; trashButton.classList.add("trash-btn"); todoDiv.appendChild(trashButton);
todoList.appendChild(todoDiv);}Erklärung:
localStorage: Ein Browser-API zum Speichern von Daten lokal im BrowserJSON.stringify(): Konvertiert JavaScript-Objekte/Arrays zu Strings (für localStorage)JSON.parse(): Konvertiert Strings zurück zu JavaScript-Objekten/ArrayssaveLocalTodos():- Lädt vorhandene Todos oder erstellt ein leeres Array
- Fügt das neue Todo hinzu
- Speichert alles zurück in localStorage
- Erstellt das Todo-Element im DOM
getTodos():- Wird beim Laden der Seite ausgeführt (
DOMContentLoaded) - Lädt alle gespeicherten Todos und zeigt sie an
- Wird beim Laden der Seite ausgeführt (
removeLocalTodos():- Findet das Todo im Array und entfernt es
- Speichert das aktualisierte Array zurück
createTodo():- Wurde aus
addTodoextrahiert, damit wir es auch zum Laden verwenden können - Erstellt das HTML-Element für ein Todo
- Wurde aus
Wichtig: Der Status (completed/uncompleted) wird noch nicht gespeichert - das kommt in Version 5!
Version 5: Status-Verwaltung
Abschnitt betitelt „Version 5: Status-Verwaltung“Den Status (abgeschlossen/nicht abgeschlossen) jedes Todos speichern und beim Laden wiederherstellen.
Schritt 5.1: JavaScript - Todos als Objekte speichern
Abschnitt betitelt „Schritt 5.1: JavaScript - Todos als Objekte speichern“Aktualisiere app.js komplett:
// Selectorsconst todoInput = document.querySelector(".todo-input");const todoButton = document.querySelector(".todo-button");const todoList = document.querySelector(".todo-list");const todoFilter = document.querySelector(".filter-todo");
// Event ListenerstodoButton.addEventListener("click", addTodo);todoList.addEventListener("click", clickTodo);todoFilter.addEventListener("change", filterTodo);
document.addEventListener("DOMContentLoaded", getTodos);
// Functions
function addTodo(event) { event.preventDefault();
saveLocalTodos(todoInput.value);
// clear input value todoInput.value = "";}
function clickTodo(e) { const item = e.target;
if (item.classList[0] === "trash-btn") { const todo = item.parentElement; removeLocalTodos(todo);
todo.classList.add("fall"); todo.addEventListener("transitionend", function () { todo.remove(); }); }
if (item.classList[0] === "complete-btn") { const todo = item.parentElement; updateTodo(todo); todo.classList.toggle("completed"); }}
function filterTodo(e) { const todos = todoList.childNodes; console.log(todos);
todos.forEach(function (todo) { console.log(todo); switch (e.target.value) { case "all": todo.classList.remove("hidden"); break; case "completed": if (todo.classList.contains("completed")) { todo.classList.remove("hidden"); } else { todo.classList.add("hidden"); } break; case "uncompleted": if (todo.classList.contains("completed")) { todo.classList.add("hidden"); } else { todo.classList.remove("hidden"); } break; } });}
function updateTodo(value) { const todos = loadStoredTodos(); const todoItem = value.querySelector(".todo-item"); const index = todos.findIndex(element => element.name === todoItem.innerText);
if (todos[index].status === "completed") { todos[index].status = "uncompleted"; } else { todos[index].status = "completed"; }
localStorage.setItem("todos", JSON.stringify(todos));}
function saveLocalTodos(value) { const todos = loadStoredTodos();
const todo = new Object(); todo.name = value; todo.status = "uncompleted";
todos.push(todo); localStorage.setItem("todos", JSON.stringify(todos)); createTodo(todo);}
function getTodos() { const todos = loadStoredTodos();
todos.forEach(function (todo) { createTodo(todo); });}
function loadStoredTodos() { let todos; if (localStorage.getItem("todos") === null) { todos = []; } else { todos = JSON.parse(localStorage.getItem("todos")); } return todos;}
function removeLocalTodos(todo) { const todos = loadStoredTodos(); const todoItem = todo.querySelector(".todo-item"); const index = todos.findIndex(element => element.name === todoItem.innerText); todos.splice(index, 1); localStorage.setItem("todos", JSON.stringify(todos));}
function createTodo(todo) { const todoDiv = document.createElement("div"); todoDiv.classList.add("todo");
if (todo.status === "completed") { todoDiv.classList.add("completed"); }
const newTodo = document.createElement("li"); newTodo.innerText = todo.name; newTodo.classList.add("todo-item"); todoDiv.appendChild(newTodo);
const completedButton = document.createElement("button"); completedButton.innerHTML = "<i class='fa fa-check'></i>"; completedButton.classList.add("complete-btn");
todoDiv.appendChild(completedButton);
const trashButton = document.createElement("button"); trashButton.innerHTML = "<i class='fa fa-trash'></i>"; trashButton.classList.add("trash-btn"); todoDiv.appendChild(trashButton);
todoList.appendChild(todoDiv);}Wichtige Änderungen:
-
Todos als Objekte: Jedes Todo ist jetzt ein Objekt mit
nameundstatus:{name: "Einkaufen gehen",status: "uncompleted"} -
loadStoredTodos(): Zentrale Funktion zum Laden der Todos (DRY-Prinzip - Don’t Repeat Yourself) -
updateTodo():- Findet das Todo im Array über
findIndex() - Ändert den Status zwischen “completed” und “uncompleted”
- Speichert zurück in localStorage
- Findet das Todo im Array über
-
createTodo():- Prüft beim Erstellen, ob das Todo bereits “completed” ist
- Fügt die
completedKlasse hinzu, wenn nötig - Verwendet
todo.namestatt nurtodo
-
removeLocalTodos():- Verwendet
querySelector(".todo-item")um den Text zu finden - Sucht im Array nach dem Todo mit
findIndex()
- Verwendet
Erklärung zu findIndex():
const index = todos.findIndex(element => element.name === todoItem.innerText);Diese Methode durchsucht das Array und gibt den Index des ersten Elements zurück, das die Bedingung erfüllt. Wenn nichts gefunden wird, gibt sie -1 zurück.
Zusammenfassung
Abschnitt betitelt „Zusammenfassung“Du hast jetzt eine vollständige Todo-Liste Applikation erstellt mit:
✅ Grundfunktionalität: Todos hinzufügen
✅ Interaktivität: Todos löschen und abhaken
✅ Styling: Schönes, modernes Design
✅ Filter: Todos nach Status filtern
✅ Persistenz: Todos werden im Browser gespeichert
✅ Status-Verwaltung: Completed/Uncompleted Status wird gespeichert
Nächste Schritte (optional)
Abschnitt betitelt „Nächste Schritte (optional)“Du könntest die App noch erweitern mit:
- Edit-Funktion (Todos bearbeiten)
- Prioritäten (hoch/mittel/niedrig)
- Fälligkeitsdaten
- Kategorien/Tags
- Drag & Drop zum Sortieren
Viel Erfolg beim Programmieren! 🚀