Projekt Końcowy

Witaj z powrotem.
To ostatnia lekcja kursu JavaScript.
Tak.
Final boss.
Ale spokojnie.
Ten boss nie zieje ogniem.
Przeważnie.
W poprzednich lekcjach poznałeś wiele ważnych rzeczy w JavaScript:
- zmienne;
- warunki;
- funkcje;
- tablice;
- obiekty;
- DOM;
- zdarzenia;
- formularze;
- walidację;
- local storage;
- fetch.
Dzisiaj połączymy to wszystko.
Zbudujemy mały task manager.
Nie ogromną aplikację.
Nie startup.
Nie imperium produktywności z motywacyjnym dashboardem i siedmioma planami subskrypcji.
Po prostu czysty projekt dla początkujących.
Aplikacja będzie:
- ładować startowe zadania z pliku JSON;
- pokazywać zadania na stronie;
- pozwalać użytkownikowi dodać nowe zadanie;
- sprawdzać puste inputy;
- oznaczać zadania jako ukończone;
- usuwać zadania;
- zapisywać zadania w local storage;
- pamiętać zadania po odświeżeniu strony.
To prawdziwy projekt frontendowy.
Mały.
Użyteczny.
Bardzo JavaScript.
Co Zbudujesz
Zbudujesz task manager.
Użytkownik będzie mógł:
- zobaczyć listę zadań;
- dodać nowe zadania;
- oznaczyć zadania jako wykonane;
- usunąć zadania;
- wyczyścić wszystkie zadania.
Aplikacja będzie też zapisywać zadania w local storage.
Więc jeśli użytkownik odświeży stronę, zadania zostaną na miejscu.
To znaczy, że połączymy:
- strukturę HTML;
- stylowanie CSS;
- dane JavaScript;
- renderowanie w DOM;
- event listenery;
- obsługę formularza;
- walidację;
- local storage;
- fetch;
- JSON.
Krótko mówiąc, JavaScript zakłada dziś wszystkie swoje ubrania naraz.
Stylowo?
Może.
Edukacyjnie?
Absolutnie.
Utwórz Projekt
Utwórz folder dla tej lekcji:
mkdir javascript-lesson12
cd javascript-lesson12
touch index.html
touch script.js
touch tasks.json
Twój projekt powinien wyglądać tak:
javascript-lesson12/
index.html
script.js
tasks.json
Ważne:
Ponieważ użyjemy fetch(), uruchom projekt przez lokalny serwer.
Na przykład z Caddy:
caddy file-server --listen :8080
Potem otwórz:
http://localhost:8080
Nie otwieraj po prostu index.html przez podwójne kliknięcie.
Przeglądarka może nie pozwolić fetch() ładować lokalnych plików JSON z file://.
Przeglądarki mają zasady.
Dużo zasad.
Niektóre są przydatne.
Niektóre wyglądają, jakby napisał je smok od biurokracji.
Utwórz Dane Startowe
Otwórz tasks.json i dodaj:
[
{
"id": 1,
"title": "Review JavaScript variables",
"completed": false
},
{
"id": 2,
"title": "Practice DOM manipulation",
"completed": false
},
{
"id": 3,
"title": "Build the final project",
"completed": false
}
]
To jest tablica obiektów.
Każde zadanie ma:
id;title;completed.
To bardzo częste w prawdziwych projektach.
Zadania.
Produkty.
Użytkownicy.
Wpisy blogowe.
Zamówienia.
Bardzo często dane są tablicą obiektów.
JavaScript patrzy na tablice obiektów i mówi:
Ach tak, dom.
Napisz HTML
Otwórz index.html i dodaj:
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Projekt Końcowy</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 60px auto;
padding: 0 24px;
background-color: #f3f4f6;
color: #111827;
}
.card {
background-color: white;
padding: 24px;
border: 2px solid #e5e7eb;
border-radius: 18px;
}
h1 {
font-size: 42px;
margin-bottom: 8px;
}
p {
font-size: 18px;
line-height: 1.6;
}
form {
display: flex;
gap: 10px;
margin-top: 20px;
}
input {
flex: 1;
padding: 12px;
border: 2px solid #d1d5db;
border-radius: 12px;
font-size: 18px;
}
button {
background-color: #2563eb;
color: white;
border: none;
padding: 12px 18px;
border-radius: 999px;
font-weight: 700;
cursor: pointer;
}
button:hover {
background-color: #1d4ed8;
}
.secondary {
background-color: #6b7280;
}
.secondary:hover {
background-color: #4b5563;
}
.danger {
background-color: #dc2626;
}
.danger:hover {
background-color: #b91c1c;
}
.message {
margin-top: 18px;
font-weight: 700;
}
.task-list {
list-style: none;
padding: 0;
margin-top: 22px;
display: grid;
gap: 12px;
}
.task {
display: flex;
justify-content: space-between;
gap: 12px;
align-items: center;
padding: 16px;
border: 2px solid #e5e7eb;
border-radius: 14px;
background-color: #f9fafb;
}
.task-title {
font-size: 20px;
font-weight: 700;
}
.completed .task-title {
text-decoration: line-through;
color: #6b7280;
}
.actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
@media (max-width: 600px) {
form {
flex-direction: column;
}
.task {
align-items: flex-start;
flex-direction: column;
}
}
</style>
</head>
<body>
<h1>Projekt Końcowy</h1>
<div class="card">
<p>
Dodawaj zadania, oznaczaj je jako wykonane, usuwaj je i odśwież stronę.
Twoje zadania zostaną zapisane w przeglądarce.
</p>
<form id="taskForm">
<input id="taskInput" type="text" placeholder="Wpisz nowe zadanie">
<button type="submit">Dodaj Zadanie</button>
</form>
<p id="message" class="message">Ładowanie zadań...</p>
<ul id="taskList" class="task-list"></ul>
<button id="clearButton" class="danger">Wyczyść Wszystkie Zadania</button>
</div>
<script src="script.js"></script>
</body>
</html>
Ten HTML daje nam:
- formularz;
- input;
- listę zadań;
- miejsce na komunikat;
- przycisk czyszczenia.
CSS sprawia, że wygląda to jak mała prawdziwa aplikacja.
A nie szara strona z muzeum starożytnego internetu.
Dobrze.
Mamy teraz standardy.
Przeważnie.
Zaplanuj JavaScript
Zanim napiszemy kod, zrozummy, co aplikacja musi robić.
Musimy:
- wybrać elementy HTML;
- przechowywać zadania w tablicy;
- ładować zadania z local storage;
- jeśli nie ma zapisanych zadań, załadować startowe zadania z
tasks.json; - renderować zadania na stronie;
- dodać nowe zadanie z formularza;
- sprawdzić pusty input;
- oznaczyć zadanie jako wykonane;
- usunąć zadanie;
- wyczyścić wszystkie zadania;
- zapisać zmiany w local storage.
Brzmi jak dużo.
Ale to tylko małe kawałki.
Projekty JavaScript często właśnie tak wyglądają.
Jedna wielka straszna rzecz zamienia się w wiele małych normalnych rzeczy.
Jak sprzątanie pokoju.
Straszne, jeśli myślisz o całym pokoju.
Możliwe, jeśli zaczniesz od jednej skarpetki.
Wybierz Elementy
Otwórz script.js i dodaj:
const taskForm = document.getElementById("taskForm");
const taskInput = document.getElementById("taskInput");
const taskListElement = document.getElementById("taskList");
const messageElement = document.getElementById("message");
const clearButton = document.getElementById("clearButton");
Teraz JavaScript może pracować ze stroną.
Te zmienne łączą JavaScript z DOM.
DOM jest mostem.
Bez tego mostu JavaScript mówi sam do siebie w kącie.
Co czasem jest użyteczne.
Ale nie dzisiaj.
Utwórz Tablicę Zadań
Dodaj to:
let tasks = [];
Ta tablica będzie przechowywać wszystkie zadania, gdy aplikacja działa.
Każde zadanie będzie obiektem takim jak ten:
{
id: 1,
title: "Learn JavaScript",
completed: false
}
Mamy więc:
- tablicę dla listy;
- obiekty dla pojedynczych zadań.
Tablice i obiekty razem.
Klasyczna drużyna.
Jak kawa i debugowanie.
Zapisz Zadania w Local Storage
Dodaj tę funkcję:
function saveTasks() {
localStorage.setItem("tasks", JSON.stringify(tasks));
}
Ta funkcja zapisuje tablicę tasks w local storage.
Ponieważ local storage zapisuje stringi, używamy:
JSON.stringify(tasks)
To zamienia tablicę na string JSON.
Bez JSON.stringify JavaScript nie zapisze tablicy poprawnie.
Zrobi się dziwnie.
A JavaScript jest już wystarczająco dziwny bez pomocy.
Wczytaj Zadania z Local Storage
Dodaj tę funkcję:
function loadTasksFromStorage() {
const savedTasks = localStorage.getItem("tasks");
if (savedTasks === null) {
return false;
}
tasks = JSON.parse(savedTasks);
return true;
}
Ta funkcja sprawdza, czy zadania już istnieją w local storage.
Jeśli nie ma zapisanych zadań, zwraca false.
Jeśli są zapisane zadania, ładuje je do tablicy tasks i zwraca true.
To pomaga nam zdecydować:
Użyj zapisanych zadań, jeśli istnieją.
W przeciwnym razie załaduj startowe zadania z JSON.
Bardzo uporządkowane.
Podejrzanie profesjonalne.
Wczytaj Startowe Zadania przez Fetch
Dodaj tę funkcję:
async function loadDefaultTasks() {
try {
const response = await fetch("tasks.json");
if (!response.ok) {
throw new Error("Nie udało się wczytać startowych zadań.");
}
tasks = await response.json();
saveTasks();
} catch (error) {
messageElement.textContent = "Nie udało się wczytać startowych zadań.";
console.log(error);
}
}
Ta funkcja ładuje zadania z tasks.json.
Używa:
fetch;await;response.ok;response.json;try;catch.
To dużo poprzednich lekcji w jednej funkcji.
Spójrz na siebie.
Używasz wiedzy.
Niebezpieczne.
Ale piękne.
Renderuj Zadania
Teraz musimy pokazać zadania na stronie.
Dodaj tę funkcję:
function renderTasks() {
taskListElement.innerHTML = "";
if (tasks.length === 0) {
messageElement.textContent = "Brak zadań. Dodaj jedno powyżej.";
return;
}
messageElement.textContent = `Masz ${tasks.length} zadanie/zadania.`;
for (const task of tasks) {
const listItem = document.createElement("li");
listItem.className = task.completed ? "task completed" : "task";
listItem.innerHTML = `
<span class="task-title">${task.title}</span>
<div class="actions">
<button data-id="${task.id}" class="secondary complete-button">
${task.completed ? "Cofnij" : "Gotowe"}
</button>
<button data-id="${task.id}" class="danger delete-button">
Usuń
</button>
</div>
`;
taskListElement.appendChild(listItem);
}
}
Ta funkcja:
- czyści listę;
- sprawdza, czy są zadania;
- tworzy
<li>dla każdego zadania; - dodaje przyciski do ukończenia i usunięcia;
- wkłada wszystko na stronę.
To jest renderowanie DOM.
Dane stają się HTML.
HTML staje się widoczny.
Użytkownik jest pod wrażeniem.
Może.
Dodaj Nowe Zadanie
Teraz dodaj tę funkcję:
function addTask(event) {
event.preventDefault();
const title = taskInput.value.trim();
if (title === "") {
messageElement.textContent = "Najpierw wpisz zadanie.";
return;
}
const newTask = {
id: Date.now(),
title: title,
completed: false
};
tasks.push(newTask);
saveTasks();
renderTasks();
taskInput.value = "";
}
Ta funkcja:
- zatrzymuje reload formularza przez
preventDefault; - czyta wartość inputa;
- sprawdza pusty input;
- tworzy nowy obiekt zadania;
- dodaje go do tablicy;
- zapisuje zadania;
- renderuje zadania ponownie;
- czyści input.
To pełny workflow.
Input.
Walidacja.
Aktualizacja danych.
Storage.
Aktualizacja DOM.
Bardzo frontendowo.
Bardzo ładnie.
Bardzo “chyba naprawdę zaczynam rozumieć JavaScript.”
Oznacz Zadanie jako Wykonane
Teraz potrzebujemy funkcji, która oznacza zadanie jako wykonane.
Dodaj to:
function toggleTask(id) {
for (const task of tasks) {
if (task.id === id) {
task.completed = !task.completed;
}
}
saveTasks();
renderTasks();
}
Ta funkcja otrzymuje id.
Potem znajduje pasujące zadanie.
Potem odwraca completed.
Jeśli było false, staje się true.
Jeśli było true, staje się false.
To robi ten fragment:
task.completed = !task.completed;
! oznacza “nie”.
Prosto.
Potężnie.
Mały symbol.
Duży charakter.
Usuń Zadanie
Dodaj tę funkcję:
function deleteTask(id) {
tasks = tasks.filter(function (task) {
return task.id !== id;
});
saveTasks();
renderTasks();
}
To używa filter.
Tworzy nową tablicę ze wszystkimi zadaniami oprócz tego, które chcemy usunąć.
Ta linia:
return task.id !== id;
oznacza:
Zachowaj każde zadanie, którego id nie jest id usuwanego zadania.
Zadanie z pasującym id znika.
Bardzo czysto.
Bez dramatu.
Zadanie odchodzi po cichu.
W przeciwieństwie do niektórych bugów.
Obsłuż Kliknięcia Przycisków Zadań
Przyciski Gotowe i Usuń są tworzone dynamicznie.
Dlatego będziemy nasłuchiwać kliknięć na całej liście zadań.
Dodaj to:
function handleTaskListClick(event) {
const id = Number(event.target.dataset.id);
if (event.target.classList.contains("complete-button")) {
toggleTask(id);
}
if (event.target.classList.contains("delete-button")) {
deleteTask(id);
}
}
To używa:
event.target
żeby wiedzieć, który przycisk został kliknięty.
A to:
event.target.dataset.id
czyta data-id z przycisku.
Ponieważ wartości dataset są stringami, zamieniamy je na liczbę:
Number(event.target.dataset.id)
To nazywa się event delegation.
Elegancka nazwa.
Prosta idea.
Nasłuchuj na rodzicu.
Reaguj na kliknięcia dzieci.
Jak nauczyciel, który patrzy na całą klasę zamiast stać przy jednym uczniu.
Wyczyść Wszystkie Zadania
Dodaj tę funkcję:
function clearTasks() {
tasks = [];
saveTasks();
renderTasks();
}
To usuwa wszystkie zadania z tablicy.
Potem zapisuje pustą tablicę.
Potem aktualizuje stronę.
Prosto.
Potężnie.
Lekko niebezpiecznie.
Dlatego przycisk jest czerwony.
Czerwone przyciski znaczą:
Pomyśl, zanim klikniesz.
Zwykle.
Zainicjalizuj Aplikację
Teraz musimy uruchomić aplikację.
Dodaj tę funkcję:
async function startApp() {
const hasSavedTasks = loadTasksFromStorage();
if (!hasSavedTasks) {
await loadDefaultTasks();
}
renderTasks();
}
To robi:
- próbuje wczytać zadania z local storage;
- jeśli ich nie ma, ładuje startowe zadania z JSON;
- renderuje zadania.
To daje aplikacji pamięć.
Pierwsza wizyta?
Ładuje domyślne zadania.
Potem?
Używa zapisanych zadań.
Bardzo praktyczne.
Bardzo prawdziwe.
Dodaj Event Listenery
Dodaj to:
taskForm.addEventListener("submit", addTask);
taskListElement.addEventListener("click", handleTaskListClick);
clearButton.addEventListener("click", clearTasks);
startApp();
Teraz aplikacja może reagować na:
- submit formularza;
- kliknięcia przycisków zadań;
- kliknięcie przycisku czyszczenia.
I na końcu:
startApp();
uruchamia wszystko.
Aplikacja się budzi.
Oby w dobrym humorze.
Pełny Kod JavaScript
Oto pełny script.js:
const taskForm = document.getElementById("taskForm");
const taskInput = document.getElementById("taskInput");
const taskListElement = document.getElementById("taskList");
const messageElement = document.getElementById("message");
const clearButton = document.getElementById("clearButton");
let tasks = [];
function saveTasks() {
localStorage.setItem("tasks", JSON.stringify(tasks));
}
function loadTasksFromStorage() {
const savedTasks = localStorage.getItem("tasks");
if (savedTasks === null) {
return false;
}
tasks = JSON.parse(savedTasks);
return true;
}
async function loadDefaultTasks() {
try {
const response = await fetch("tasks.json");
if (!response.ok) {
throw new Error("Nie udało się wczytać startowych zadań.");
}
tasks = await response.json();
saveTasks();
} catch (error) {
messageElement.textContent = "Nie udało się wczytać startowych zadań.";
console.log(error);
}
}
function renderTasks() {
taskListElement.innerHTML = "";
if (tasks.length === 0) {
messageElement.textContent = "Brak zadań. Dodaj jedno powyżej.";
return;
}
messageElement.textContent = `Masz ${tasks.length} zadanie/zadania.`;
for (const task of tasks) {
const listItem = document.createElement("li");
listItem.className = task.completed ? "task completed" : "task";
listItem.innerHTML = `
<span class="task-title">${task.title}</span>
<div class="actions">
<button data-id="${task.id}" class="secondary complete-button">
${task.completed ? "Cofnij" : "Gotowe"}
</button>
<button data-id="${task.id}" class="danger delete-button">
Usuń
</button>
</div>
`;
taskListElement.appendChild(listItem);
}
}
function addTask(event) {
event.preventDefault();
const title = taskInput.value.trim();
if (title === "") {
messageElement.textContent = "Najpierw wpisz zadanie.";
return;
}
const newTask = {
id: Date.now(),
title: title,
completed: false
};
tasks.push(newTask);
saveTasks();
renderTasks();
taskInput.value = "";
}
function toggleTask(id) {
for (const task of tasks) {
if (task.id === id) {
task.completed = !task.completed;
}
}
saveTasks();
renderTasks();
}
function deleteTask(id) {
tasks = tasks.filter(function (task) {
return task.id !== id;
});
saveTasks();
renderTasks();
}
function handleTaskListClick(event) {
const id = Number(event.target.dataset.id);
if (event.target.classList.contains("complete-button")) {
toggleTask(id);
}
if (event.target.classList.contains("delete-button")) {
deleteTask(id);
}
}
function clearTasks() {
tasks = [];
saveTasks();
renderTasks();
}
async function startApp() {
const hasSavedTasks = loadTasksFromStorage();
if (!hasSavedTasks) {
await loadDefaultTasks();
}
renderTasks();
}
taskForm.addEventListener("submit", addTask);
taskListElement.addEventListener("click", handleTaskListClick);
clearButton.addEventListener("click", clearTasks);
startApp();
To jest pełny projekt.
To już nie jest zabawkowy kod.
Nadal przyjazny dla początkujących.
Ale prawdziwy.
Masz teraz state, renderowanie, zdarzenia, storage, walidację i fetch.
JavaScript to już nie tylko console.log.
On buduje rzeczy.
Niebezpiecznie.
W dobry sposób.
Przetestuj Projekt
Uruchom lokalny serwer:
caddy file-server --listen :8080
Otwórz:
http://localhost:8080
Teraz przetestuj:
- startowe zadania ładują się z
tasks.json; - dodanie pustego zadania pokazuje ostrzeżenie;
- dodanie prawdziwego zadania działa;
- kliknięcie Gotowe oznacza zadanie jako wykonane;
- kliknięcie Cofnij przywraca zadanie;
- kliknięcie Usuń usuwa zadanie;
- odświeżenie strony zostawia zadania zapisane;
- Wyczyść Wszystkie Zadania usuwa wszystkie zadania.
Jeśli wszystko działa, gratulacje.
Zbudowałeś kompletną małą aplikację JavaScript.
Jeśli coś się psuje, też gratulacje.
Robisz prawdziwe programowanie.
Prawdziwe programowanie to głównie:
Dlaczego to nie działa?
Aha.
To była jedna litera.
Typowe Błędy
Zapomnienie Lokalnego Serwera
Jeśli zadania się nie ładują, sprawdź, jak otworzyłeś stronę.
Źle:
file:///home/user/javascript-lesson12/index.html
Lepiej:
caddy file-server --listen :8080
Potem:
http://localhost:8080
Fetch chce serwera.
Nawet małego.
Przeglądarki są surowe.
Jak nauczyciele z certyfikatami bezpieczeństwa.
Zła Nazwa Pliku JSON
Jeśli kod mówi:
fetch("tasks.json")
to plik musi nazywać się:
tasks.json
Nie:
task.json
Nie:
Tasks.json
Nie:
final-boss-data.json
Nazwy plików mają znaczenie.
Komputery nie są emocjonalnie elastyczne.
Zapomnienie JSON.stringify
Źle:
localStorage.setItem("tasks", tasks);
Poprawnie:
localStorage.setItem("tasks", JSON.stringify(tasks));
Tablice muszą zostać zamienione na stringi przed zapisaniem.
Inaczej JavaScript da ci zupę.
Nie dobrą zupę.
Zapomnienie JSON.parse
Źle:
tasks = localStorage.getItem("tasks");
Poprawnie:
tasks = JSON.parse(savedTasks);
Kiedy czytasz JSON z local storage, zrób parse.
Stringify przy zapisie.
Parse przy ładowaniu.
Spakuj walizkę.
Rozpakuj walizkę.
Nie zakładaj walizki na siebie.
Praktyka
Dodaj licznik zadań, który pokazuje:
Ukończone: 2 / 5
Podpowiedź:
Możesz policzyć ukończone zadania tak:
const completedTasks = tasks.filter(function (task) {
return task.completed;
});
Potem użyj:
completedTasks.length
To da ci praktykę z:
- tablicami;
filter;- aktualizacją DOM;
- logiką renderowania.
Mała funkcja.
Użyteczne ulepszenie.
Miłe małe zwycięstwo.
Mini Wyzwanie
Dodaj priorytet zadań.
Każde zadanie powinno mieć:
- title;
- priority.
Na przykład:
{
id: Date.now(),
title: "Learn fetch",
priority: "high",
completed: false
}
Dodaj <select> w HTML:
<select id="priorityInput">
<option value="low">Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
</select>
Potem pokaż priorytet w każdej karcie zadania.
Bonus:
Użyj różnych tekstów dla różnych priorytetów.
Nie musi być idealnie.
Najpierw niech działa.
Potem ulepszaj.
Tak rosną projekty.
Najpierw ziemniak.
Potem wypolerowany ziemniak.
Potem produkt.
Podsumowanie
Dzisiaj zbudowałeś kompletny projekt JavaScript dla początkujących.
Użyłeś:
- zmiennych do przechowywania referencji i danych;
- tablic do przechowywania zadań;
- obiektów do reprezentowania zadań;
- funkcji do organizowania logiki;
- metod DOM do renderowania zadań;
- zdarzeń do reagowania na użytkownika;
- formularza do zbierania inputu;
- walidacji do blokowania pustych zadań;
- local storage do zapamiętywania zadań;
- fetch do ładowania danych startowych;
- JSON do przechowywania danych strukturalnych.
To ogromne osiągnięcie.
Nie nauczyłeś się tylko składni.
Zbudowałeś coś.
To jest różnica.
Składnia to słownictwo.
Projekty to rozmowa.
Teraz JavaScript nie jest już tylko teorią.
Jest narzędziem.
Dziwnym narzędziem.
Czasem dramatycznym.
Czasem mylącym.
Ale bardzo potężnym.
Kurs Ukończony
Ukończyłeś kurs JavaScript dla początkujących.
Teraz znasz główne idee potrzebne, żeby kontynuować z:
- TypeScript;
- React;
- Next.js;
- Astro;
- projektami frontendowymi;
- stronami opartymi o API;
- aplikacjami full-stack.
Nie spiesz się.
Ćwicz.
Buduj małe rzeczy.
Psuj je.
Naprawiaj.
Potem buduj trochę większe rzeczy.
To jest ścieżka.
Nie glamour.
Nie natychmiastowa.
Ale prawdziwa.
I teraz masz wystarczająco JavaScript, żeby zacząć nią iść.
Gratulacje.
Final boss został pokonany.
Na razie.
JavaScript zawsze ma gdzieś ukrytego kolejnego bossa.