← Back to course

Projekt Końcowy

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:

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:

To prawdziwy projekt frontendowy.

Mały.

Użyteczny.

Bardzo JavaScript.

Co Zbudujesz

Zbudujesz task manager.

Użytkownik będzie mógł:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

Mała funkcja.

Użyteczne ulepszenie.

Miłe małe zwycięstwo.

Mini Wyzwanie

Dodaj priorytet zadań.

Każde zadanie powinno mieć:

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ś:

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:

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.