Labor 11 - JavaScript alapok¶
Bevezetés¶
A laboron egy egyszerű "offline" To-Do alkalmazást készítünk. Az első feladatokat, ahol a kódvázat és legfontosabb funkciókat valósítjuk meg, laborvezetői segítséggel, majd további funkciókat önállóan kell elvégezni, a JavaScript tudás elsajátítás érdekében.
A JavaScriptről dióhéjban
A korábban megismert HTML és CSS adják a weboldalunk vázát, alapműködését és kinézetét, viszont a korai dokumentum-alapú weboldalaktól áttértünk a dinamikus weboldalakra, melyek futás időben módosítják az aktuális dokumentumot (a DOM-ot), így interakciót kezelhetünk, és a weboldalunkra (a kliens oldalra) önálló alkalmazásként tekintünk.
Az alkalmazásainkhoz dinamizmust (időbeni változást) szkripteléssel rendelünk, erre JavaScriptet használunk. A JavaScript egy dinamikusan típusos, interpretált szkriptnyelv, a hozzá tartozó futtatókörnyezetek végrehajtó egységei pedig alapvetően egyszálúak, így nincsen kölcsönös kizárási problémánk.
Érdemes továbbá megemlíteni a felhasználandó típusokat (function
, object
, string
, number
, undefined
, boolean
, symbol
), az ezek közötti szabad konverziót és a JavaScript eseményhurkot (event loop). Az event loop a JavaScriptet folyamatosan befejeződésig futtatja ("Run-to-completion"), amíg a futás be nem fejeződik, majd aszinkron eseményre vár. Az események bekövetkeztével az eseményhez regisztrált eseménykezelők lefutnak. Az események lehetnek:
- felhasználói interakció,
- időzítés,
- IO műveletek eredménye (pl. AJAX, Websocket).
A fontosabb kulcsgondolatok tehát röviden:
- interpretált futtatás,
- DOM dinamikus manipulációja,
- dinamikus típusosság és típuskonverzió,
- egyszálúság, event loop és aszinkronitás.
Említésre méltó még, hogy a JavaScript (klasszikus értelemben véve) nem objektum-orientált, az osztályok koncepciója a nyelvben később jelent meg; a nyelv a prototipikus öröklés módszerét alkalmazza az objektumorientált megközelítéshez. Ezen kívül különös sajátosságai vannak, a this
kulcsszó pl. nem az aktuális objektumra, hanem az aktuális függvényre mutat (kivétel az arrow syntax, ami a this
-t az eredeti értéken hagyja).
Az Internet Explorer elhíresült arról, hogy a fejlesztés rá jelentősen nehézkesebb, mint bármely alternatívára. Ma már a Microsoft is hivatalosan is az új, Chromium alapú Edge böngészőt támogatja, amely - a már nem is támogatott - Windows 7 OS-en is működik, így új weboldalakat Internet Explorer támogatással már nem kell készítenünk.
Git repository létrehozása és letöltése¶
A feladatok megoldása során ne felejtsd el követni a feladat beadás folyamatát Github.
- Moodle-ben keresd meg a laborhoz tartozó meghívó URL-jét és annak segítségével hozd létre a saját repository-dat.
- Várd meg, míg elkészül a repository, majd checkout-old ki.
- Egyetemi laborokban, ha a checkout során nem kér a rendszer felhasználónevet és jelszót, és nem sikerül a checkout, akkor valószínűleg a gépen korábban megjegyzett felhasználónévvel próbálkozott a rendszer. Először töröld ki a mentett belépési adatokat (lásd itt), és próbáld újra.
- Hozz létre egy új ágat
megoldas
néven, és ezen az ágon dolgozz. - A neptun.txt fájlba írd bele a Neptun kódodat. A fájlban semmi más ne szerepeljen, csak egyetlen sorban a Neptun kód 6 karaktere.
1. Feladat - Eseménykezelés¶
Az alkalmazás alapjaként egy egyszerű HTML oldal szolgál, amihez a saját JavaScriptünket írjuk.
A JS kódot HTML-ben is elhelyezhetnénk, viszont az nem karbantartható és alapvetően nem best practice, úgyhogy a saját todo.js
fájlba fogjuk tenni a kódot, amit behivatkozunk. A stílusozást Bootstrappel oldjuk meg.
- Nyissuk meg a Visual Studio Code-dal leklónozott repository
feladat
mappáját (File → Open Folder) és indítsuk el az alkalmazást a Live Serverrel (index.html
→ jobb klikk → Open with Live Server) - Ellenőrizzük, hogy megfelelően betöltődik-e az alkalmazás!
Warning
A böngésző különböző körülmények függvényében cache-elheti a fájljainkat, ezért a frissítést ilyenkor kézzel kell megoldanunk. Ne felejtsük el menteni a fájlt, ezután a böngészőben állítsuk be az F12 Developer Tools-ban a Network fülön az "Always refresh from server" / "Disable cache" elnevezésű beállítást!
Láthatjuk, hogy a statikus oldal az alábbiakból tevődik össze:
- cím,
- fülek az összes, aktív, inaktív és kész elemek szűrésére,
- a to-do elemek listája, az egyes elemek mellett az értelmezett műveletek,
- új elem hozzáadása panel, melyen az új to-do bejegyzés szövegét kell megadnunk egy űrlapon.
A <body>
végén egy <script>
a todo.js
fájlra hivatkozik! A szkript az oldal lényegi tartalmának betöltődése után fut le, így nem kell majd várakoznunk a dokumentum teljes betöltődésére (a gyakorlatban ez azt jelenti, hogy a szkript futásának kezdetén a DOM a HTML-nek megfelelő állapotban már létrejött). A gyakorlatban ez változó, szokás a <head>
elemben in betölteni JS fájlokat amikor kritikus, viszont az gátolja a HTML megjelenését, amíg a JS fájl le nem töltődik.
1.1 Todo osztály és állapotok¶
Az egyes Todo elemek modelljére érdemes saját osztályt definiálnunk.
Osztályok
Modern (ES6/ES2015 és magasabb) JavaScriptben konstruktor függvény helyett használhatunk valós osztályokat is, a gyakorlaton ezt fogjuk látni. JavaScriptben egy (nem osztály) függvény konstruktorfüggvény, ha a this
változón tulajdonságokat helyez el és nem tér vissza semmivel. Ekkor a new
kulcsszóval meghívva a függvényt az konstruktorként funkcionál és a this
(tulajdonságokkal "felaggatott") értékét kapjuk vissza. Ezen felül az instanceof
kulcsszóval megvizsgálhatjuk, hogy egy adott függvény konstruktora által készített objektumról van-e szó, tehát szinte már osztálypéldányként kezelhetjük az objektumot.
A fülek lehetséges állapotai az all
, active
, inactive
és done
. Az all
kivételével ezeket az állapotokat veheti fel egy Todo elem is.
A todo.js
elejére vegyük fel a Todo osztályt, annak konstruktorfüggvényét és a konkrét példányokat tároló (üres) tömböt, valamint a lehetséges állapotokat
class Todo {
constructor(name, state) {
this.name = name;
this.state = state;
}
}
const todos = []; // Az oldal betöltődésekor a TODO lista egy üres tömb.
const states = ["active", "inactive", "done"];
const tabs = ["all"].concat(states); // "all" + a state tömb elemei a fülek.
console.log(tabs);
JavaScript változók
JavaScriptben három kulcsszóval deklarálhatunk lokális változót:
var
: az "eredeti" módja a változó deklarációjának, modern JS-ben érdemes kerülni. Engedi ugyanazon változó újradeklarációját, sőt, nem köti scope-hoz a változót.let
: scope-hoz köti a változót, ezért nem deklarálható újra és nem is "szivárog" át a scope-ok között.const
: konstans értékű/referenciájú "változót" (érdekes változónak nevezni, ha nem változhat) tárol. A változó nem kaphat új értéket (tehát ha referenciatípus, pl. objektum vagy tömb, akkor nem lehet új objektumot/értéket adni a változónak), de ha objektumot tárol, annak tagjai (vagy a tömb elemei) értelemszerűen változhatnak.
A legnyilvánvalóbb (ha nem is a legrobusztusabb) módja a hibakeresésnek az, ha a konzolra írunk. Az F12 segítségével a Console fülön láthatjuk a kimenetet.
1.2 Új Todo felvétele¶
Iratkozzunk fel az űrlap submit
eseményére és valósítsuk meg az új Todo elem létrehozását. A feliratkozást megtehetjük HTML-ből és JavaScriptből is, most az utóbbit alkalmazzuk! Illesszük be az alábbi kódrészletet a korábbi kódrészletek után!
- Hozzunk létre egy
form
változót, amibe mentsük el az oldalon találhatónew-todo-form
azonosítójú form referenciáját. Tipp:document.getElementById("new-todo-form")
- Egy másik
input
nevű változóba pedig mentük el a szövegdoboz referenciáját, ahol a Todo elem nevét meg lehet. Ennek az azonosítója anew-todo-title
. - Iratkozzunk fel a
form
submit eseményére egy függvénnyel, aminek egyevent
bemenő paramétere legyen. - Tiltsuk le az űrlap postolásának alapértelmezett viselkedését, hogy ne töltse újra az oldalt. Tipp:
preventDefault()
. - Ha a szövegdobozban szerepel valamilyen érték, akkor azzal a névvel adjunk a
todos
tömbhöz egy új Todo elemet. - Az szövegdobozból töröljük ki a begépelt szöveget.
Megvalósítás: Todo elem felvétele
const form = document.getElementById("new-todo-form");
const input = document.getElementById("new-todo-title");
form.onsubmit = function(event) {
// Meggátoljuk az alapértelmezett működést, ami frissítené az oldalt.
event.preventDefault();
// Csak akkor adjuk a tömbhöz ha a szögegdobozban érvényes érték van.
// Ekvivalens ezzel: if (input && input.value && input.value.length)
// vagy if (input != null && input.value != null && input.value.length > 0)
if (input?.value?.length) {
// Az új todo-t aktív állapotban hozunk létre.
todos.push(new Todo(input.value, "active"));
// Kiürítjük az inputot
input.value = "";
// TODO: újrarajzolni a listát
}
}
Definiálnunk kell még egy Button
osztályt a gombok kezelésére, amiket a Todo elemekhez fogunk rendelni. Nem volna szükség a modellek definiálására, elvégre is a JS egy dinamikus nyelv, de struktúrát ad a kódnak, objektum-orientáltabban kezelhető.
A Button osztályon az alábbi tulajdonságok legyenek:
action
- Művelet, amit a gomb végezicon
- FontAwesome ikon neve (class="fas fa-*")type
- Bootstrapbeni típus ("secondary", "danger" stb.)title
- Tooltip szövege
Illetve készítünk egy tömböt buttons
névvel, amibe a következő négy gombot tegyük bele: Done, Active, Inactive, Remove. A gombok nevéből kiderül, hogy ezekkel tudjuk új állapotba tenni, vagy törölni az elemet.
Info
A VS Code-ban valószínűleg az IntelliSense nyomára tudunk bukkanni a JS kód írása közben. Ennek az oka nem a JavaScript, hanem a háttérben futó TypeScript fordító. Mivel minden JavaScript egyben TypeScript kód is, ezért a típusinformációk kinyerhetők a kódból. Ez a TypeScript nagy előnye a JS-sel szemben. Fordítási hibáink nem lesznek JavaScriptben, de az IntelliSense segítségét ki lehet így használni.
Megvalósítás: Gombok felvétele
class Button {
constructor(action, icon, type, title) {
this.action = action;
this.icon = icon;
this.type = type;
this.title = title;
}
}
// A gombokat reprezentáló modell objektumok tömbje
const buttons = [
new Button("done", "check", "success", "Mark as done"),
new Button("active", "plus", "secondary", "Mark as active"),
// Az objektumot dinamikusan is kezelhetjük, ekkor nem a konstruktorral példányosítjuk
{ action: "inactive", icon: "minus", type: "secondary", title: "Mark as inactive" },
new Button("remove", "trash", "danger", "Remove"),
];
Így már gyakorlatilag fel tudunk venni új elemet, viszont ez nem látszik a felületen, ugyanis csak memóriában dolgoztunk, és nem módosítottuk megfelelően a DOM-ot.
1.3 Todo lista kirajzolása¶
Készítenünk kell egy olyan függvényt, ami a todos tömbben lévő elemeket a felületen meg tudja jeleníteni.
Ehhez először készítsünk egy olyan függvényt createElementFromHTML
névvel, ami a paraméterként megkapott HTML sztringbőé elkészít a memóriában egy virtuális DOM elemet.
function createElementFromHTML(html) {
const virtualElement = document.createElement("div");
virtualElement.innerHTML = html;
return virtualElement.childElementCount == 1
? virtualElement.firstChild
: virtualElement.children;
}
Erre azért van szükség, mert nincs arra natív JavaScript API (jelenleg), hogy stringből HTML elemet hozzunk létre, viszont bármely (akár virtuális, tehát az aktuális DOM-ban nem, de memóriában létező) DOM elem innerHTML-jének beállításával a DOM elem(ek) ténylegesen létrejön(nek).
Alternatív megoldásként megtehetnénk, hogy létrehozzuk a createElement
segítségével az elemet, majd egyesével beállítjuk az attribútumait, de az jóval körülményesebb, kódolósabb megoldást eredményez.
Ezt követően készítük el a renderTodos
függvényt, ami az összes Todo elemet kirajzolja a felületre. Bemenő paraméter nem kell neki, mert a todos
tömböt elérjük.
- A lista elemet a
todo-list
azonosítójú DOM elemben találhatóak. Ezt először törölni kell. Tipp:innerHTML
-t kell üres sztringre állítani. - Iteráljunk végig a
todos
tömbön és egyrow
változóba készítsük el szükséges HTML template-et. A HTML template megtalálható az index.html oldalon, közvetlenül atodo-list
azonosítójú elem alatt. - A gombokhoz tartozó HTML tartalmat is érdemes egy ciklussal generálni. Ehhez a
buttons
tömbön kell végigiterálni és acreateElementFromHTML
függvény segítségével minden gombhoz létrehozni a szükséges DOM elemet- Itt kell figyelni arra, hogy azt a gombot tegyük
disabled
-re amelyik állapotban éppen a Todo elem van. - Majd az egyes gombokhoz generált DOM elemet adjuk hozzá az
action-buttons
CSS osztállyal ellátott elemhez, ami a sornak a templatejébenrow
változó kell, hogy szerepeljen. Tipp:.querySelector(" ").appendChild(domElem)
- Itt kell figyelni arra, hogy azt a gombot tegyük
- Végül az így összeállított sor template-et (
row
) hozzá kell adni atodo-list
-hez. - Ne feledd, hogy ezt a függvényt meg is kell hívni, hogy a kezdeti állapotot kirajzolja!
Selectorok
A querySelector()/querySelectorAll()
API-kkal egy CSS szelektort adhatunk meg a document-en vagy egy elemen, és az illeszkedő első/összes elemet kapjuk vissza.
Megvalósítás: HTML tartalom renderelése
function renderTodos() {
// Megkeressük a konténert, ahová az elemeket tesszük
const todoList = document.getElementById("todo-list");
// A jelenleg a DOM-ban levő to-do elemeket töröljük
todoList.innerHTML = "";
// Bejárjuk a jelenlegi todo elemeket.
for(let todo of todos)
{
const row = createElementFromHTML(
`<div class="row">
<div class="col d-flex p-0">
<a class="list-group-item flex-grow-1" href="#">
${todo.name}
</a>
<div class="btn-group action-buttons"></div>
</div>
</div>`);
// A gomb modellek alapján legyártjuk a DOM gombokat.
for(let button of buttons)
{
const btn = createElementFromHTML(
`<button class="btn btn-outline-${button.type} fas fa-${button.icon}" title="${button.title}"></button>`
);
if (todo.state === button.action) // azt a gombot letiljuk, amilyen állapotban van egy elem
btn.disabled = true;
// TODO: a gomb klikk eseményének kezelése
// A virtuális elem gomb konténerébe beletesszük a gombot
row.querySelector(".action-buttons").appendChild(btn);
}
// Az összeállított HTML-t a DOM-ban levő todo-list ID-jú elemhez fűzzük.
todoList.appendChild(row);
}
}
// Kezdeti állapot kirajzolása.
renderTodos();
Most már látjuk, hogy mi fog kerülni a // TODO: újrarajzolni a listát
komment helyére a form elküldésekor:
renderTodos();
Törlés
Ha abba a hibába esnénk, hogy a DOM elemeket egyesével szeretnénk eltávolítani a DOM-ból a #todo-list
elem children
tulajdonságának segítségével, vigyáznunk kell, ugyanis ez egy "élő" kollekció: miközben az elemeket töröljük, a kollekció length tulajdonsága is változik! Persze egy egyszerű for
ciklussal megoldható, de mindenképpen a végétől indulva járjuk be a kollekciót, amíg az ki nem ürül!
Beadandó¶
1. feladat beadandó (1 pont)
Illessz be egy képernyőképet néhány hozzáadott tennivalóról, amiből az egyiknek a neve a neptun kódod legyen! (f1.png
)
2. Feladat - Todo elemek állapotváltás¶
Az alkalmazásban több helyen is kell állapotváltozást kezelni. Egyfelől az egyes Todo elemek állapota változhat, másfelől a tabfülekre kattintva csak az adott állapotban lévő elemeket kell megjeleníteni, illetve jelölni kell az aktuálisan kiválasztott fület is.
Felhasználói interakciót úgy tudunk megvalósítani, hogy eseménykezelőt rendelünk egy tag megfelelő eseményéhez.
HTML-ben az on{eseménynév}
attribútumok megadásával tudunk eseménykezelőt kötni egy taghez. Például: `
JavaScriptben a DOM API-t használval
- az elem referenciáját megszerezve az
.addEventListener("eseménynév", callbackFüggvény)
függvény meghívásával - vagy a megfelelő feliratkoztató függvény beállításával. Például:
onclick = callbackFüggvény
2.1. Tabfül váltás¶
Készítsük el a kódot, amiben az egyes tabfülek között váltani tudunk. Ehhez a todo.js
fájlt kell kiegészíteni.
- Vegyünk fel egy változót
currentTab
névvel, amiben eltároljuk az aktuálisan kiválasztott fül típusát. - Készísünk egy függényt
selectTab
névvel, ami paraméterül megkapja, hogy melyik fület kell megjelenítenie és ez alapján atodo-tab
CSS osztállyal rendelkező tabfüleken az.active
CSS osztályt átteszi az újonnan kiválasztott fülre.
Megvalósítás: Tabfül váltás JavaScript
// Aktuálisan kiválasztott fül.
let currentTab;
function selectTab(type) {
// Beállítjuk az újonnan kiválasztott fület.
currentTab = type;
for (let tab of document.getElementsByClassName("todo-tab")) {
// Az összes fülről levesszük az `.active` osztályt.
tab.classList.remove("active");
// Ha éppen ez a fül a kiválasztott, visszatesszük az `.active` osztályt.
if (tab.getAttribute("data-tab-name") == type)
tab.classList.add("active");
}
// Újrarajzolunk mindent.
renderTodos();
}
// App indulásakor (oldal betöltéskor) kiválasztjuk az "all" fület.
selectTab("all");
self-invoking function declaration
A fenti minta, amikor egy függvényt a definiálása után közvetlenül meghívunk, egy "csúnyább", de elterjedt alternatívával szokták alkalmazni, ez az ún. self-invoking function declaration, aminek sok változata ismeretes, ez az egyik:
(const selectTab = function(type) { /* ... */})("all");
- A HTML-ben az egyes fülekhez a klikk esemény kezelőben hívjuk meg a
selectTab
függvényt. Tipp:onclick="selectTab('all')"
) - Ezen felül minden fülhöz vegyünk fel egy adattároló attribútumot is. Ezt az attribútumot a
data-
előtaggal láttuk el jelezvén, hogy az attribútum kizárólag adathordozásra szolgál. Tipp:data-tab-name="all"
Megvalósítás: Tabfül váltás HTML
<li class="nav-item">
<a class="todo-tab nav-link" data-tab-name="all" href="#all" onclick="selectTab('all')">
All <span class="badge bg-secondary">1</span>
</a>
</li>
<li class="nav-item">
<a class="todo-tab nav-link" data-tab-name="active" href="#active" onclick="selectTab('active')">
Active <span class="badge bg-secondary">1</span>
</a>
</li>
<li class="nav-item">
<a class="todo-tab nav-link" data-tab-name="inactive" href="#inactive" onclick="selectTab('inactive')">
Inactive <span class="badge bg-secondary"></span>
</a>
</li>
<li class="nav-item">
<a class="todo-tab nav-link" data-tab-name="done" href="#done" onclick="selectTab('done')">
Done <span class="badge bg-secondary"></span>
</a>
</li>
2.2. Todo elem állapot váltása¶
Az elemek állapotának változását hasonlóképpen kezelhetjük, mint a tabfülek váltását.
Amikor a gombokat gyártjuk a renderTodos()
függvényben, az eseménykezelőket ott helyben be tudjuk regisztrálni (a / TODO: a gomb klikk eseményének kezelése
komment helyére)
- Regisztráljuk be a kattintás eseménykezelőt a gombhoz.
- A
remove
akciót külön kell kezelni. Egy megerősítés után (tipp:confirm
) atodos
tömbből kell kivenni az elemet. - Az összes többi esetben csak az adott todo elem állapotát kell módosítani a gomb actionjére.
Megvalósítás: Gomb eseménykezelők állapot változáshoz
// Feliratkozás a kattints eseményre.
// A `btn.onclick = function() {` függvény helyett az arrow function-t használjuk.
btn.onclick = () => {
// Törlést külön kell kezelni.
if (button.action === "remove") {
// Megerősítés után kiveszünk a 'todo'-adik elemtől 1 elemet a todos tömbből.
if (confirm("Are you sure you want to delete the todo titled '" + todo.name + "'?")) {
todos.splice(todos.indexOf(todo), 1);
renderTodos();
}
}
else {
// Ha nem törlés, akkor átállítjuk a kiválasztott todo állapotát a gomb állapotára.
todo.state = button.action;
renderTodos();
}
}
Confirm
Érdekesség a confirm()
függvény, amely böngészőben natívan implementált: a felhasználónak egy egyszerű megerősítő ablakot dob fel a megadott szöveggel, és blokkolva várakozik a válaszra. A válasz egy boolean érték, így az if
törzse csak akkor fut le, ha a felhasználó OK-val válaszol. Hasonló az alert()
, az viszont csak egy OK-zható figyelmeztetést dob fel, ami nem tér vissza semmivel, ill. a prompt()
, amivel stringet kérhetünk be. Ezeket ritkán, lehetőleg soha nem használjuk, helyettük nem blokkoló, aszinkron egyedi megoldást készítünk.
Ha for (var ... in ...)
ciklust és function
-t használnánk a buttons
és todos
tömbökön, akkor a klikk eseménykezelő ebben a formában hibás lesz, mert az iterációk újrahasznosítanák a todo és button változókat. Az arrow function viszont ezeket a változókat ún. "closure"-be helyezi, technikailag a változókból egy lokális másolat készül, így a függvény helyesen fog lefutni.
2.3. Badge-ek frissítése¶
Egészítsük ki a renderTodos()
függvényt, hogy frissítse a fülek mellett található badge-ben megjelenő számértékeket:
Megvalósítás: badge-ek frissítése
document.querySelector(".todo-tab[data-tab-name='all'] .badge").innerHTML = todos.length || "";
for (let state of states)
document.querySelector(`.todo-tab[data-tab-name='${state}'] .badge`).innerHTML = todos.filter(t => t.state === state).length || "";
Filter
A filter()
függvénynek egy callbacket adunk át, ez fog kiértékelődni minden elemre: ha a feltétel igaz, akkor az elemet visszakapjuk, különben nem. Magyarul: azokra az elemekre szűrünk, amelyek állapota az aktuálisan bejárt állapot ("active", "inactive", "done"), tehát megszámoljuk, hány elem van az adott státuszban. Ezen felül, ha az érték falsey
, tehát esetünkben 0, helyette üres stringet adunk vissza, így nem fog megjelenni a badge.
2.4. Lista szűrése¶
Utolsó lépésként logikus, hogy az aktuális fül alapján szűrjük le az elemeket, ne mindig az összes látszódjon. Ezt a renderTodos()
apró módosításával tudjuk megtenni. Szűrjük le a todos
tömböt, majd a szűrés eredményén iteráljunk végig a for..of
ciklussal.
Tehát az eredeti todos
tömböt a filter
segítségével szűrjük úgy, hogy csak azok az elemek maradjanak a szűrt tömbben, ami az aktuális fülnek megfelel VAGY az "all" fülön vagyunk (tehát látszódjon minden).
Megvalósítás: lista szűrése
const filtered = todos.filter(function(todo){ return todo.state === currentTab || currentTab === "all"; });
for(let todo of filtered) { // ...
Alternatíva
Egy tömör megoldás, amit persze nem egyszerű megérteni, az alábbi:
const filtered = todos.filter(todo => ["all", todo.state].includes(currentTab);
Szintén alternatív megoldásként, ahol a fül vizsgálatot kiszerveztük:
const filtered = (currentTab === 'all' ? todos : todos.filter(todo => todo.state === currentTab));
Beadandó¶
BEADANDÓ (1 pont)
Illesszen be egy-egy képernyőképet (f2-1.png
, f2-2.png
, f2-3.png
) a tennivalók állapotainak változtatásáról, a különböző oldalakon történő megjelenésükről!
3. Feladat (Önálló) - Sorrendezés¶
Legyenek fel-le mozgathatók a to-do elemek az all
listában!
- Hozz létre két új gombot, amely a felfelé és lefelé mozgatást jelzik az elemnél! Használd a
fas fa-arrow-up
ésfas fa-arrow-down
osztályokat az ikonokhoz! A gombok csak azall
fülön legyenek láthatók! - A gomb legyen letiltva, ha nem mozgatható az elem a megadott irányba!
- A gombra kattintva az elem kerüljön előrébb/hátrébb az elemek listájában!
Beadandó¶
BEADANDÓ (1.5 pont)
Illessz be egy-egy képernyőképet néhány tennivalóról sorrendezés előtt és után! (f3-1.png
, f3-2.png
)
4. Feladat (Önálló) - Perzisztálás¶
Egy to-do listának nem sok értelme van, ha nem menthetők el az adataink. A mentésre egyértelmű lehetőséget biztosít a localStorage
és a sessionStorage
. Mindkettő kulcs-érték tároló, a kulcsok és értékek egyaránt string
típusúak. A különbség a kettő között az élettartamuk: míg a localStorage
- bár korlátos méretű - a böngészőt újraindítva is megtartja állapotát, a sessionStorage
a böngészőt/fület bezárva elvész. A sessionStorage
adatokat memóriában, a localStorage
adatokat viszont perzisztensen, fájlban tárolja a böngésző.
A tároláshoz minden renderelési ciklus elején volna érdemes mentenünk. Bár az alkalmazásunk renderTodos()
függvénye nevéből fakadóan a DOM-ot manipulálja, ez az a pont, ahol bármilyen változásról értesülünk. Fontos, hogy tartsuk be a separation of concerns elvet: mindenki a saját feladatával foglalkozzon! Ezért ne itt valósítsuk meg a perzisztálást, hanem egy saját függvényben, amit meghívunk minden változást indukáló ponton a kódban:
- elem állapotának változása,
- elem létrehozása,
- elem törlése.
Változásdetektálás
Komplexebb alkalmazásfejlesztő keretrendszerekben is problémát okoz a változásokról történő értesülés, a React, az AngularJS és az Angular mind más és más módszereket alkalmaznak a változások detektálására.
A tároláshoz a localStorage.setItem(key, value)
függvényt használjuk. A sorosítandó objektumot egyszerűen JSON-be sorosíthatjuk: JSON.stringify(object)
, illetve visszafejthetjük: JSON.parse(string)
.
Objektum sorosítás
Fontos, hogy a JSON.parse()
által visszafejtett objektumok egyszerű objektumok, ha a forrás objektumunkon pl. függvények is szerepeltek, azok a deszerializált objektumon nem lesznek elérhetők!
A részfeladatok tehát:
- Készíts egy függvényt, ami elmenti a teljes todos tömb tartalmát
localStorage
-ba, - Bármilyen változás hatására (elem állapotváltozása, létrejötte, törlése) mentsd el a függvény segítségével az elemeket,
- Alkalmazás indulásakor egyetlen alkalommal töltsd vissza az összes eltárolt todo elemet, és ez legyen a
todos
változó kiinduló tartalma! - Az elkészített alkalmazást az implementáció után teszted egy inkognító abalkban is, hogy ott is működik-e, vagy netán a
todos
tömbnull
vagyundefined
értéket kap, amin apush
nem értelmezett.
Storage debuggolás
A storage tartalmát böngészőtől függően különböző helyen tudjuk megvizsgálni, jellemzően az Application/Storage vagy Debugger fülön található.
Beadandó¶
BEADANDÓ (1.5 pont)
Illessz be egy képernyőképet a lokális tárolóban (localStorage) található perzisztált to-do elemekről! (f4.png
)