Renderujemy każde zadanie za pomocą własnej funkcji renderTasks() oraz metody tablicy forEach()


Super! Możemy dodawać pojedyncze nowe elementy do listy za pomocą appendTaskToTheTasksList(), ale... musimy teraz stworzyć funkcję renderTasks(), która "przejdzie się" po wszystkich pobranych zadaniach z localStorage i każde z nich doda do HTMLowej listy używając appendTaskToTheTasksList().

Żeby funkcja renderTasks() "przeszła się" po wszystkich zadaniach z naszej listy tasks, która jest tablicą, wykorzystamy metodę tablicy forEach().

Metoda forEach() przyjmuje jako argument dowolną funkcję, którą wywoła na każdym elemencie tablicy. Jednocześnie forEach() przekazuje tej funkcji 2 argumenty, z których tamta funkcja może zrobić użytek: sam element tablicy oraz jego indeks. Zobacz poniższy przykład i wypróbuj go w konsoli (nie w pliku!):

let elements = ["a", "b", "c"];

// funkcja logElement() potrzebuje dwóch argumentów:
// elementu oraz jego indeksu, by wyświetlić to w konsoli:
function logElementAndIndex(element, index) {
  console.log("Element:", element, "ma indeks:", index);
}

// iterujemy po wszystkich elementach tablicy elements,
// wywołując na każdym elemencie funkcję logElementAndIndex(),
// która otrzyma od forEach 2 argumenty: element i indeks elementu:
elements.forEach(logElementAndIndex);

// UWAGA! Zwróć uwagę, że przekazaliśmy samą funkcję, bez nawiasów,
// ponieważ gdybyśmy dodali nawiasy, to automatycznie wywołalibyśmy tę funkcję bez argumentów!

// Możemy też wykorzystać tylko pierwszy argument,
// przekazany przez forEach() => sam element:
function logElementOnly(element) {
  console.log("Element:", element);
}

elements.forEach(logElementOnly);

// w tym przypadku drugi argument (indeks)
// przekazany przez forEach() będzie po prostu pominięty

Mam nadzieję, że zrozumiałeś/aś, jak działa metoda forEach(). Spróbujmy teraz jej przekazać funkcję appendTaskToTheTasksList():

function renderTasks() {
  // wyczyść listę/ usuń wszystkie elementy listy,
  // żeby później wyrenderować/ dodać ich aktualną wartość na nowo:
  // (pamiętaj, że HTMLowa lista jest wcześniej zadeklarowaną zmienną globalną,
  // więc mamy do niej dostęp)
  tasksList.innerHTML = "";

  // przeiterujmy teraz wszystkie pobrane zadania
  // i wywołajmy na każdym z nich funkcję appendTaskToTheTasksList(),
  // która po kolei wyrenderuje każde z nich jako element listy:
  tasks.forEach(appendTaskToTheTasksList);
}

Uff... Teraz musimy dopilnować, żeby funkcja renderTasks() była wywoływana za każdym razem, kiedy dojdzie do jakiejkolwiek modyfikacji zmiennej tasks, czyli w funkcjach addTask(), addTaskAtIndex(), deleteTask() i deleteAllTasks(), aby lista, wyświetlana na ekranie zawsze była zsynchronizowana ze stanem naszej aplikacji i localStorage.

Możemy to zrobić, wywołując renderTasks() wewnątrz funkcji showTasks(), dlatego że jest ona wywoływana w każdej z wymienionych funkcji oraz jest wywołana na końcu naszego kodu, dzięki czemu, kiedy odpalamy aplikację, pokazuje nam zadania w konsoli. Teraz także wyrenderuje nam zadania w przeglądarce!

// zmodyfikuj funkcję showTasks()
// dodając do niej renderTasks():
function showTasks() {
  console.log("Twoje zapisane zadania:", tasks);
  renderTasks(); // <== nowy kod idzie tutaj
}

Brawo! Wiem, że łatwo jest w tym wszystkim się pogubić, ale taki rozrost programu i odpowiednio kodu jest czymś normalnym. Zresztą, nie dodaliśmy jeszcze nawet wszystkich funkcjonalności! A na dodatek jest to tylko mała aplikacja do zarządzania zadaniami, od której zaczyna większość programistów...

Niemniej jednak, na tym etapie, możesz odpalić jednocześnie plik HTML todo-app.html w przeglądarce, przejść do konsoli do zadkładki snippets i otworzyć tam zaktualizowany plik todo-app-dom, uruchomić go klikając w ikonkę trójkąta lub stosując skrót klawiszowy Ctrl+Enter i w konsoli, która się otworzy, będziesz mógł/a na razie dodawać i usuwać zadania w konsoli (tak, jak wcześniej), ale wynik tych działań (zmiany w liście zadań) będą wyświetlały się także w przeglądarce!

Spróbuj pododawać kilka zadań, usunąć jakieś itd., ale najpierw upewnij się, że kod Twojej aplikacji wygląda następująco:

console.clear();

function getTasksFromLocalStorage() {
  const storedTasksStringified = localStorage.getItem("tasks");

  const storedTasks = JSON.parse(storedTasksStringified);

  return storedTasks;
}

let tasks = getTasksFromLocalStorage() || [];

//================= nowy kod DOM: ==========================//

// zadeklaruj globalną zmienną przechowującą HTMLową listę zadań:
const tasksList = document.getElementById("tasks-list");

// dodaj teraz pomocniczą funkcję `appendTaskToTheTasksList()`
// tworząca nowy element listy,
// pobierającą zadanie jako argument i dodającą zadanie do listy:    
function appendTaskToTheTasksList(task) {
  // utwórz nowy element listy li:
  const li = document.createElement("li");
  // i przypisz do niego wartość zadania:
  li.textContent = task;

  // dodaj element listy do listy:
  // (pamiętaj, że HTMLowa lista jest zmienną globalną, więc mamy do niej dostęp)
  tasksList.appendChild(li);
}

function renderTasks() {
  // wyczyść listę/ usuń wszystkie elementy listy,
  // żeby później wyrenderować/ dodać ich aktualną wartość na nowo:
  // (pamiętaj, że HTMLowa lista jest wcześniej zadeklarowaną zmienną globalną,
  // więc mamy do niej dostęp)
  tasksList.innerHTML = "";

  // przeiterujmy teraz wszystkie pobrane zadania
  // i wywołajmy na każdym z nich funkcję appendTaskToTheTasksList(),
  // która po kolei wyrenderuje każde z nich jako element listy:
  tasks.forEach(appendTaskToTheTasksList);
}

function showTasks() {
  console.log("Twoje zapisane zadania:", tasks);
  renderTasks(); // <== nowy kod
}
//=========== koniec nowego kodu DOM =============//

function updateTasksInLocalStorage() {
  localStorage.setItem("tasks", JSON.stringify(tasks));
}

function addTask(newTask) {
  tasks.push(newTask);

  updateTasksInLocalStorage();

  console.log("Do Twoich zadań zostało dodane nowe zadanie:", newTask);

  showTasks();
}

function addTaskAtIndex(index, newTask) {
  tasks.splice(index, 0, newTask);

  console.log("Do Twoich zadań zostało dodane nowe zadanie:", newTask);

  updateTasksInLocalStorage();

  showTasks();
}

function deleteTask(index) {
  tasks.splice(index, 1);

  updateTasksInLocalStorage();

  showTasks();
}

function deleteAllTasks() {
  tasks = [];

  console.log("Wszystkie zadania zostały usunięte...");

  updateTasksInLocalStorage(tasks);

  showTasks();
}

// odpalając program, wyświetlamy zapisane zadania na start
// w konsoli, ale też w przeglądarce (renderujemy je):
showTasks();

Teraz dodawaj i usuwaj zadania w konsoli, przypisuj je do indeksu lub też usuń wszystko, obserwując, co się dzieje na ekranie (tylko nie wklejaj od razu całego poniższego kodu do konsoli, bo na ekranie wyświetli Ci się... nic, ponieważ ostatnim poleceniem jest deleteAllTasks(), aczkolwiek w konsoli zobaczysz każdą operację):

addTask("zrobić kawę");
addTask("zjeść naleśniki");

addTaskAtIndex(1, "zanim zjesz naleśniki, zadzwoń do sekretariatu");

deleteTask(1); // jednak nie dzwoń do sekretariatu

deleteAllTasks();

Wszystko działa super, natomiast moglibyśmy usprawnić (zrefaktorować) nasz kod w następujący sposób: funckja showTasks() de facto nie spełnia już w pewnym sensie swojej funkcji. Potrzebowaliśmy jej wcześniej, by wyświetlić zadania w konsoli, natomiast teraz naszym priorytetem jest wyrenderowanie zadań w przeglądarce i teraz mamy 2 mylące funkcje: showTasks() i renderTasks(), gdzie ta druga funkcja powinna wieść prym.

W związku z tym proponuję usunąć funkcję showTasks() i zastąpić ją całkowicie funkcją renderTasks(), która dodatkowo będzie wyświetlała zaktualizowane zadania w konsoli (tak na wszelki wypadek). Zróbmy to, w związku z czym nasz kod teraz będzie teraz wyglądał następująco (usunąłem wczesniejsze komentarze z kodu):

console.clear();

function getTasksFromLocalStorage() {
  const storedTasksStringified = localStorage.getItem("tasks");

  const storedTasks = JSON.parse(storedTasksStringified);

  return storedTasks;
}

let tasks = getTasksFromLocalStorage() || [];

const tasksList = document.getElementById("tasks-list");
  
function appendTaskToTheTasksList(task) {
  const li = document.createElement("li");
  li.textContent = task;

  tasksList.appendChild(li);
}

function renderTasks() {
  tasksList.innerHTML = "";

  tasks.forEach(appendTaskToTheTasksList);

  // oprócz wyrenderowania zadań w przeglądarce
  // wypisujemy je także w konsoli:
  console.log("Twoje zapisane zadania:", tasks)
}

function updateTasksInLocalStorage() {
  localStorage.setItem("tasks", JSON.stringify(tasks));
}

function addTask(newTask) {
  tasks.push(newTask);

  updateTasksInLocalStorage();

  console.log("Do Twoich zadań zostało dodane nowe zadanie:", newTask);

  renderTasks(); // <= zamieniamy showTasks() na renderTasks()
}

function addTaskAtIndex(index, newTask) {
  tasks.splice(index, 0, newTask);

  console.log("Do Twoich zadań zostało dodane nowe zadanie:", newTask);

  updateTasksInLocalStorage();

  renderTasks(); // <= zamieniamy showTasks() na renderTasks()
}

function deleteTask(index) {
  tasks.splice(index, 1);

  updateTasksInLocalStorage();

  renderTasks(); // <= zamieniamy showTasks() na renderTasks()
}

function deleteAllTasks() {
  tasks = [];

  console.log("Wszystkie zadania zostały usunięte...");

  updateTasksInLocalStorage(tasks);

  renderTasks(); // <= zamieniamy showTasks() na renderTasks()
}

renderTasks(); // <= zamieniamy showTasks() na renderTasks()

Teraz nasz kod jest trochę bardziej czytelny!