Pouze tento týden sleva až 80 % na e-learning týkající se Pythonu
Aktuálně: Postihly zákazy tvou profesi? Poptávka po ajťácích prudce roste, využij podzimní akce 30% výuky zdarma!
Python týden

Lekce 8 - OOP diář v JavaScriptu - Ukládání, řazení, seskupování

V minulé lekci, Formát JSON, jsme si představili populární formát JSON.

Implementace localStorage v diáři

Konečně tedy můžeme diář upravit tak, aby se data ukládala a načítala z/do localStorage. Naše pole zaznamy třídy Diar budeme ukládat jednoduše pod klíčem "zaznamy".

Načítání

V konstruktoru diáře tedy místo vytvoření prázdného pole se záznamy záznamy načteme z localStorage:

constructor(jazyk = "cs-CZ") {
    const zaznamyZeStorage = localStorage.getItem("zaznamy");
    this.zaznamy = zaznamyZeStorage ? JSON.parse(zaznamyZeStorage) : [];
    this.jazyk = jazyk;

    this.nazevInput = document.getElementById("nazev");
    this.datumInput = document.getElementById("datum");
    this.potvrditButton = document.getElementById("potvrdit");
    this.vypisElement = document.getElementById("seznam-ukolu");

    this.nastavUdalosti();
}

Do proměnné zaznamyZeStorage vybereme naše záznamy z localStorage. Dále musíme zkontrolovat, pokud nějaké záznamy uložené vůbec jsou. Pokud ne, proměnná zaznamyZeStorage nabude hodnoty null. Proto zde máme ternární operátor, díky kterému vlastnosti zaznamy nastavíme buď naparsované pole z localStorage nebo prázdné pole.

Ukládání

Načítání záznamů z localStorage máme, nyní přidáme i jejich ukládání při přidání nového záznamu. Záznamy budeme ukládat jak do pole zaznamy, tak do storage. Pracovat v celé aplikaci jen se storage by vyžadovalo neustálé převádění záznamů z objektu na text a zpět. Proto jej využíváme jen pro načítání na začátku a průběžné ukládání.

Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

Do metody nastavUdalosti() přidáme jeden řádek ukládající záznam i do storage:

nastavUdalosti() {
    this.potvrditButton.onclick = () => {
        const zaznam = new Zaznam(this.nazevInput.value, this.datumInput.value);
        this.zaznamy.push(zaznam);
        localStorage.setItem("zaznamy", JSON.stringify(this.zaznamy)); // přidaný řádek
        this.vypisZaznamy();
    };
}

Můžeme projekt zkusit spustit, přidat úkoly a znovu načíst stránku. Úkoly v aplikaci již nyní zůstávají a to znamená, že máte svou první reálně použitelnou objektovou aplikaci. Gratuluji!

Výpis záznamů

Celkem snadno jsme si vyřešili náš problém s ukládáním. Pojďme ještě upravit výpis našich záznamů. Bylo by vhodné záznamy seřadit dle data a seskupit záznamy ve stejný den, které vypíšeme pod sebou v jednom bloku.

Řazení

Nejdříve si naimplementujeme metodu na řazení záznamů. K tomu použijeme metodu sort() na poli. Ta dokáže pole seřadit postupným porovnáváním dvojic prvků. Jako parametr přijímá porovnávací funkci, která definuje jakým způsobem se mají 2 prvky v poli porovnat. Naše metoda k seřazení záznamů v poli podle data bude ve třídě Diar vypadat následovně:

seradZaznamy() {
    this.zaznamy.sort(function (zaznam1, zaznam2) {
        return (new Date(zaznam1.datum) - new Date(zaznam2.datum));
    });
}

Funkce porovnává data dvou záznamů, která si naparsujeme na datum pomocí konstruktoru objektu Date. Ze záznamu samozřejmě vybereme vlastnost datum. Porovnání dvou datumů provedeme jednoduše pomocí operátoru -, čímž se vrátí jejich rozdíl v milisekundách. Pokud bude první datum až po druhém, vrátí se záporné číslo. Pokud budou stejné, vrátí se 0. Jinak se vrátí kladné číslo. Právě kladné, záporné nebo nulové číslo metoda sort() pro svou práci potřebuje a tím zjistí, zda je datum větší, menší nebo rovné.

Metodu budeme volat před každým výpisem záznamů:

vypisZaznamy() {
    this.seradZaznamy();
    this.vypisElement.innerHTML = "";
    for (let i = 0; i < this.zaznamy.length; i++) {
        const zaznam = this.zaznamy[i];
        this.vypisElement.innerHTML += `<h3>${zaznam.nazev}</h3>kdy: ${zaznam.datum}<br>splněno: ${zaznam.splneno}`;
    }
}

Seřazení bychom mohli také volat po přidání záznamu a po jejich načtení, aby se nemuselo volat při každém výpisu. Nevýhodou tohoto řešení by ovšem bylo, že bychom na něj mohli zapomenou při jiné manipulaci se záznamy.

Pokud nyní diář spustíme a máme v localStorage již nějaká data, vypíší se nám již seřazená podle datumu.

Seskupování

Nyní výpis záznamů dokončíme. Budeme tedy vypisovat datum a k tomuto datumu vždy všechny záznamy v daný den. Náš cyklus lehce upravíme:

vypisZaznamy() {
    this.seradZaznamy();
    this.vypisElement.innerHTML = "";
    let posledniDatum = null;
    for (const zaznam of this.zaznamy) {
        if (zaznam.datum !== posledniDatum) {
            this.vypisElement.innerHTML += `<h3>${zaznam.datum}</h3>`
        }
        posledniDatum = zaznam.datum;

        this.vypisElement.innerHTML += `<strong>${zaznam.nazev}</strong><br>splněno: ${zaznam.splneno}<hr>`;
    }
}

Do proměnné posledniDatum přiřadíme vlastnost datum z předchozího záznamu. Protože při prvním průběhu cyklu poslední záznam ještě není, nastavíme proměnnou prvně na null. Datum současného záznamu poté vypíšeme jen pokud se liší od předchozího. Tak se záznamy ve stejný den budou seskupovat. Nyní máme vypsané hezky úkoly se stejným datem pod sebou a seřazené:

Your page
localhost

V příští lekci, Formát JSON, do našeho objektového diáře v JavaScriptu přidáme několik dalších funkcí.


 

Předchozí článek
Formát JSON
Všechny články v sekci
Objektově orientované programování v JavaScriptu
Článek pro vás napsal Šimon Raichl
Avatar
Jak se ti líbí článek?
5 hlasů
Autor se věnuje hlavně tvorbě všemožných věcí v JS
Aktivity (8)

 

 

Komentáře
Zobrazit starší komentáře (1)

Avatar
Patrik Pastor:13.4.2019 21:07

oprava, jede to ALE POUZE U POSLEDNIHO ZAZNAMU, proto jsem si myslel ze t nejede , kdyz jsem chtel mazat z prostredka... Jak to ale jde udelat, nebo jak to, ze zaznam lze mazat pouze posledni?

 
Odpovědět
13.4.2019 21:07
Avatar
Martin
Člen
Avatar
Martin :29.7.2019 15:35

Funguje vám řazení? Musel jsem zadávat datum ve formátu yyyy-mm-dd a pak následně upravit metodu serad zaznamy viz níže.....měl někdo podobný problém?

seradZaznamy() {
this.zaznamy.sor­t(function (zaznam1,zaznam2){
return (new Date(zaznam1.da­tum).getTime() - new Date(zaznam2.da­tum).getTime());
});
}

Editováno 29.7.2019 15:38
 
Odpovědět
29.7.2019 15:35
Avatar
Jakub Podskalský:27.10.2019 15:26

Zdravím. Kód nefunguje, nefunguje tlačítko "smazat záznam". Respektive se ani nezobrazí ten confirm. Snažil jsem se na to přijít sám, ale nenapadá mě příčina. Jshint v Sublime Textu mi hází jshint: warning W083 - Functions declared within loops referencing an outer scoped variable may lead to confusing semantics. (confirm). To se ale nezdá jako striktní problém. V další lekci jsou ohledně toho komentáře, nevidím tam ale žádné jasné řešení a nechce se mi pokračovat s kódem, když je nefunkční. Opravdu bych uvítal jakoukoliv pomoc. Díky. :)

 
Odpovědět
27.10.2019 15:26
Avatar
Šimon Raichl
Redaktor
Avatar
Odpovídá na Jakub Podskalský
Šimon Raichl:27.10.2019 16:21

Ahoj, vim o tom, tenkrat mi David dost zmenil kod pri korekture, ktery fungoval spravne, a pak hodne lidi zacalo psat, ze jim to nejde. Mozna bych se na to mel uz kouknout. :D

 
Odpovědět
27.10.2019 16:21
Avatar
Odpovídá na Šimon Raichl
Jakub Podskalský:27.10.2019 16:35

Aha. :D Díky za rychlou odpověď. Když opomenu tu nefunkčnost, tak se mi tenhle úvod do OOP dost líbí. :) A skvěle vybraný projekt, na kterém se to demonstruje.
Takže tedy prosím o tu opravu, je to škoda. :D

 
Odpovědět
27.10.2019 16:35
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!
Avatar
Šimon Raichl
Redaktor
Avatar
Odpovídá na Jakub Podskalský
Šimon Raichl:27.10.2019 16:43

Ted jsem na to koukal a je tam chyba v tom, ze se tam primo appenduje pomoci innerHTML nejaky HTMLko, jenze to ti samozrejme prekresli ten element a tim padem se ztrati eventy ze vsech elementu, to je taky duvod, proc to funguje jenom na poslednim zaznamu. Muzu ti sem poslat cely opraveny kod rovnou:

class Diar {

    constructor(jazyk = "cs-CZ") {
        const zaznamyZeStorage = localStorage.getItem("zaznamy");
        this.zaznamy = zaznamyZeStorage ? JSON.parse(zaznamyZeStorage) : [];
        this.jazyk = jazyk;

        this.nazevInput = document.getElementById("nazev");
        this.datumInput = document.getElementById("datum");
        this.potvrditButton = document.getElementById("potvrdit");
        this.vypisElement = document.getElementById("seznam-ukolu");

        this._nastavUdalosti();
    }

    _nastavUdalosti() {
        this.potvrditButton.onclick = () => { // this zůstane nyní stále this
                                if (this.datumInput.value !== "") {
                                        const zaznam = new Zaznam(this.nazevInput.value, this.datumInput.value);
                                        this.zaznamy.push(zaznam);
                                        this.ulozZaznamy();
                                        this.vypisZaznamy();
                                } else
                                        alert("Musíte vyplnit datum!");
                                };
    }

        seradZaznamy() {
                this.zaznamy.sort(function (zaznam1, zaznam2) {
                        return (new Date(zaznam1.datum) - new Date(zaznam2.datum));
                });
        }

        vypisZaznamy() {
                this.seradZaznamy();
                this.vypisElement.innerHTML = "";
                let posledniDatum = null;
                for (const zaznam of this.zaznamy) {
                        const kartaZaznamu = document.createElement("div");

                        if (zaznam.datum !== posledniDatum) {
                                const datum = new Date(zaznam.datum).toLocaleDateString(this.jazyk, {
                                        weekday: "long",
                                        day: "numeric",
                                        month: "short",
                                        year: "numeric"
                                });
                                kartaZaznamu.innerHTML += `<h3>${datum}</h3>`
                        }
                        posledniDatum = zaznam.datum;

                        kartaZaznamu.innerHTML += `<h4>${zaznam.nazev}</h4><br>úkol ${!zaznam.splneno ? "ne" : ""}splněn`;

                        this.vypisElement.appendChild(kartaZaznamu);

                        this._pridejTlacitko("Smazat", () => {
                                if (confirm("Opravdu si přejete odstranit úkol?")) {
                                        this.zaznamy = this.zaznamy.filter(z => z !== zaznam); // Ponechá vše co není rovné proměnné zaznam
                                        this.ulozZaznamy();
                                        this.vypisZaznamy();
                                }
                        });

                        this._pridejTlacitko("Označit jako " + (zaznam.splneno ? "nesplněný" : "splněný"), () => {
                                zaznam.splneno = !zaznam.splneno;
                                this.ulozZaznamy();
                                this.vypisZaznamy();
                        });
                }
        }

        _pridejTlacitko(titulek, callback) {
                const button = document.createElement("button");
                button.onclick = callback;
                button.innerText = titulek;
                this.vypisElement.appendChild(button);
        }

        ulozZaznamy() {
                localStorage.setItem("zaznamy", JSON.stringify(this.zaznamy));
        }

}
 
Odpovědět
27.10.2019 16:43
Avatar
Jakub Podskalský:27.10.2019 19:06

Moc Ti děkuji, žes mi to hned takhle poslal. Ale stejně se musím možná hloupě zeptat... Jakým způsobem mi to překreslí ten element (a tím se myslí vypisElement, že?) a tím pádem se ztratí i všechny ostatní eventy? Když se to innerHTML přidává pomocí +=, tak jak se může něco překreslit, když se to pouze přidává k aktuálnímu HTML? Nechápu to. :( Třeba by stačilo jen trochu navést... Fakt mi to vrtá hlavou.

 
Odpovědět
27.10.2019 19:06
Avatar
Šimon Raichl
Redaktor
Avatar
Odpovídá na Jakub Podskalský
Šimon Raichl:27.10.2019 20:47

Rozumim tomu, ze si myslis, ze jen "pridavas HTML", ale takhle to bohuzel nefunguje. Ty vlastne prirazujes puvodni HTML + to tvoje HTML, takze to funguje takto:

element.innerHTML = element.innerHTML + someHtml;

Tim samozrejme prekreslis cely element a znicis puvodni elementy s jejich eventy a nahradis za tvoje HTML, kde tvoje elementy zadne eventy nastaveny nemaji.

Na dynamicky checkovani zmen v DOMu muzes pouzit MutationObserver a do callbacku si hodit to, ze nastavis event listenery nejakym elementum. Neco u MutationObserveru si muzes precist zde: https://developer.mozilla.org/…tionObserver

 
Odpovědět
27.10.2019 20:47
Avatar
Šimon Raichl
Redaktor
Avatar
Odpovídá na Jakub Podskalský
Šimon Raichl:27.10.2019 20:54

Jeste kratky dodatek: zkus si treba ve webovy konzoli tady na itnetworku spustit:

document.body.innerHTML += "";

Ted si zkus otevrit chat. Nepujde ti to. :D Asi by sis rekl, ze prece jen pridavas prazdny string, ale jak jsem psal nahore, tak to neni.

Jinou veci by bylo pouzit funkci insertAdjacentHTML, jako prvni parametr das pozici kam v elementu chces HTML vlozit a jako druhy parametr tvoje HTML.

document.body.insertAdjacentHTML("beforeend", "");

Tohle uz ti nic nerozbije. :D

 
Odpovědět
27.10.2019 20:54
Avatar
Odpovídá na Šimon Raichl
Jakub Podskalský:27.10.2019 22:53

Už to chápu! :D Já jsem se ještě nikdy s takovým chováním totiž nesetkal. Bral jsem to tak, že se to HTML zkopíruje doslova celé včetně eventů jeho elementů. Vyzkoušel jsem si to ještě vedle na jednodušším příkladu a opravdu to funguje tak, jak říkáš.
Ještě jsem se kouknul na ten tvůj kód a pokud jsem dobře pochopil, tak je to řešeno tím, že se dají jednotlivé záznamy ještě do kontejnerů. To asi udělám taky... než používat tu Tvojí ukázkovou, méně známou metodu. :D Především pro budoucí lepší manipulaci s jednotlivými záznamy. Ovšem taky díky za zajímavost.
Ještě jednou díky za odpovědi a pomoc. Teď už to jdu přepsat na funkční verzi. :)

 
Odpovědět
27.10.2019 22:53
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 11. Zobrazit vše