Pouze tento týden sleva až 80 % na e-learning týkající se C# .NET. Zároveň využij akci až 30 % zdarma při nákupu e-learningu - Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 9 - OOP diář v JavaScriptu - Formátování a mazání záznamů

V minulé lekci, OOP diář v JavaScriptu - Ukládání, řazení, seskupování, jsme do našeho OOP diáře přidali ukládání, řazení a seskupování.

Formátování data a splněnosti

Jelikož formát data a splněnost úkolu není ideální, je třeba tyto výpisy upravit do "lidštější" podoby :) Pamatujete na vlastnost jazyk, kterou můžeme ovlivnit v konstruktoru? Nyní jí využijeme k lepšímu výpisu data a zároveň si vylepšíme výpis splněnosti úkolů:

vypisZaznamy() {
    this.seradZaznamy();
    this.vypisElement.innerHTML = "";
    let posledniDatum = null;
    for (const zaznam of this.zaznamy) {
        if (zaznam.datum !== posledniDatum) {
            const datum = new Date(zaznam.datum).toLocaleDateString(this.jazyk, {
                weekday: "long",
                day: "numeric",
                month: "short",
                year: "numeric"
            });
            this.vypisElement.insertAdjacentHTML("beforeend", `<h3>${datum}</h3>`);
        }
        posledniDatum = zaznam.datum;

        this.vypisElement.insertAdjacentHTML("beforeend", `<strong>${zaznam.nazev}</strong><br>úkol ${!zaznam.splneno ? "ne" : ""}splněn<hr>`);
    }
}

Vytvoříme instanci objektu Date z našeho data a použijeme její metodu toLocaleString(), kam předáme jako první parametr vlastnost jazyk naší třídy a jako druhý parametr formátovací objekt, jehož vlastnosti udávají jak přesně se má datum vypsat.

U splněnosti úkolu jsme použili jednoduchý ternární operátor, podle nesplněnosti přidáme "ne" nebo prázdný string.

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

Výsledek nyní vypadá takto:

Your page
localhost

Mazání záznamů

Budeme pokračovat základní interakcí s našimi úkoly, budeme je moci mazat, nebo je označit jako splněné. Začneme mazáním.

Uložení záznamů

Jelikož po smazání bude nutné záznamy znovu uložit, vyčleňme si uložení záznamů z metody nastavUdalosti() do samostatné metody ulozZaznamy():

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

V metodě nastavUdalosti() nyní metodu ulozZaznamy() zavoláme:

nastavUdalosti() {
    this.potvrditButton.onclick = () => { // this zůstane nyní stále this
        const zaznam = new Zaznam(this.nazevInput.value, this.datumInput.value);
        this.zaznamy.push(zaznam);
        this.ulozZaznamy();
        this.vypisZaznamy();
    };
}

Tlačítko

Ke každému záznamu vygenerujeme tlačítko na jeho odstranění. To vytvoříme jako nový element <button> pomocí metody document.createElement() a do <div> s výpisem záznamů jej vložíme pomocí appendChild(). Tlačítku rovněž přidáme událost reakce na kliknutí, kdy daný záznam odstraníme z pole, záznamy takto přeuložíme do localStorage a znovu vypíšeme. Metoda vypisZaznamy() bude po přidání mazacího tlačítka vypadat takto:

vypisZaznamy() {
    this.seradZaznamy();
    this.vypisElement.innerHTML = "";
    let posledniDatum = null;
    for (const zaznam of this.zaznamy) {
        if (zaznam.datum !== posledniDatum) {
            const datum = new Date(zaznam.datum).toLocaleDateString(this.jazyk, {
                weekday: "long",
                day: "numeric",
                month: "short",
                year: "numeric"
            });
            this.vypisElement.insertAdjacentHTML("beforeend", `<h3>${datum}</h3>`);
        }
        posledniDatum = zaznam.datum;

        this.vypisElement.insertAdjacentHTML("beforeend", `<strong>${zaznam.nazev}</strong>
        <br>úkol ${!zaznam.splneno ? "ne" : ""}splněn`);
        const smazatButton = document.createElement("button");
        smazatButton.onclick = () => {
            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();
            }
        };
        smazatButton.innerText = "Smazat záznam";
        this.vypisElement.appendChild(smazatButton);
        this.vypisElement.insertAdjacentHTML("beforeend", "<br>");
    }
}

Všimněte si použití potvrzujícího dialogu confirm(), odstranění záznamu je určitě akce, kterou nechceme udělat omylem :)

Možná by vás napadlo vložit tlačítko rovnou do HTML kódu jako text a přiřadit mu do data atributu index záznamu v poli, který má smazat. Taková tlačítka by se poté někde všechna vybrala a obsloužila tak, aby z pole smazala prvek pod daným indexem. Problém by ovšem nastal, kdybychom diář otevřeli ve více záložkách najednou. Když bychom v jedné záložce smazali nějakou položku a druhou záložku neobnovili, tato položka by zde stále byla, ale v localStorage by pod tímto indexem již byla položka jiná. Mohli bychom tak na neobnovené záložce nechtěně smazat jiný úkol. Proto budeme veškerou manipulaci s položkami vždy dělat přímo pomocí anonymních funkcí, kam tuto jednu konkrétní položku předáme.

Pokud kroutíte hlavou nad kódem odstraňujícím položku:

this.zaznamy = this.zaznamy.filter(z => z !== zaznam);

Je to v současné době bohužel nejjednodušší způsob, jak v JavaScriptu smazat prvek v poli, jehož index neznáme a nechceme jej zbytečně zjišťovat. Kód profiltruje dané pole tak, že v něm zůstanou jen záznamy, které se nerovnají záznamu, který chceme odstranit.

V další lekci, Dokončení objektového diáře v JavaScriptu, do našeho diáře přidáme tlačítko na splnění úkolu, validaci data a jednoduché CSS styly :)


 

Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.

Stáhnout

Stažením následujícího souboru souhlasíš s licenčními podmínkami

Staženo 100x (3.18 kB)
Aplikace je včetně zdrojových kódů v jazyce JavaScript

 

Předchozí článek
OOP diář v JavaScriptu - Ukládání, řazení, seskupování
Všechny články v sekci
Objektově orientované programování v JavaScriptu
Přeskočit článek
(nedoporučujeme)
Dokončení objektového diáře v JavaScriptu
Článek pro vás napsal Šimon Raichl
Avatar
Uživatelské hodnocení:
37 hlasů
Autor se věnuje hlavně tvorbě všemožných věcí v JS
Aktivity

 

 

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

Avatar
Jaroslav Drobek:22. června 5:49

Hodnocení:

  • "...použijeme její metodu toLocaleDateS­tring(),"
  • Kód odstraňující položku: "bohužel" jen pro nematematiky - až na drobnou odlišnost syntaxe používá metoda filter zápis množiny pomocí vlastnosti..je to docela sympatické 👍
 
Odpovědět
22. června 5:49
Avatar
Lubor Pešek
Člen
Avatar
Lubor Pešek:2. července 21:01

Viz komentář (popis funkcí ok, ale obecně se zde porušuje zásadní pravidlo OOP a naučíte nováčky velmi hnusný návyk!)

Odpovědět
2. července 21:01
Existují dva způsoby, jak vyřešit problém. Za prvé vyhoďte počítač z okna. Za druhé vyhoďte okna z počítače.
Avatar
Lubor Pešek
Člen
Avatar
Lubor Pešek:2. července 21:14

Zdravíčko,

s touto lekcí začínám mít zásadní problém a měli byste se nad tím velmi rychle zamyslet, než vychováte nevědomky programátory nemehla.
Bohužel se s tím setkávám i v praxi (v Javě), kdy se právě nováčci učí na takových tutoriálech.

Nemám nic proti funkcím, které se tu probírají. Tento seriál je i v pořádku, co se týče přehlednosti a vysvětlování.

Problém ovšem je, že pokud je to seriál o OOP, tak by se zde měly i základní myšlenky OOP dodržovat.
Nezlobte se na mě, ale OOP vychází především z faktu, že chce virtuální svět a programování přiblížit co nejvíce reálnému světu.
No a teď si vezměte tu metodu vypisZaznamy()

V rámci čitelnosti kódu. Co byste asi tak čekali, když byste někde viděli v seznamu možností .vypisiZaznamy()??
Pochybuji, že byste v takové metodě očekávali definici stylování datumu, vytváření nějakého tlačítka, atd.

Ano, funguje to a přece by bylo hodně zbytečné v triviálním programu vytvářet metodu pro 2 řádky a pak ji volat.

Jenže v praxi se pak stane, že učíte nováčky neoddělovat správně logiku programu.
Začíná to tím, že pak třeba vidím definici metody na 100 řádků. Metoda se jmenuje loadData a já v ní najdu kromě SQL dotazu definici parsování, formátu, mapování, if, který maže nějakou část databáze atd. atd. atd.

No a končí to tím, že pak v business logice vidím entity, části JPA kódu atd.

Prostě toto se mi z hlediska OOP vážně nelíbí. To, že něco funguje, tak je sice hezké, ale metody jsou od toho (a proto se kódy snad člení na jednotlivé metody), aby každá metoda vykonávala takovou funkci, pro kterou je určená.

Také byste v reálném životě nečekali, že funkčnost nastavování barvy ruky najdete v metodě ohniRuku(). Je to absurdita, ale toto se tady takhle ty lidi učí.
Prostě ok, ať ta metoda vypisZaznamy() třeba ty ostatní metody volá, ale ať v ní nejsou proboha ty definice!!!

Mimo jiné docílíte toho, že vám kód nebude odsunovat těla podmínek a cyklů mimo obrazovku.
I to už jsem viděl, že vnitřní logika metody byla tak rozvětvená a zanořená, že když jsem autoformátoval kód, tak mi skutečně kód odjel mimo screen a já viděl cca 20 otevíracích závorek.

Prosím, přepracujte tuhle lekci a klidně kód rozšiřte, ale rozložte definici logických částí do jednotlivých metod.
Je to triviální kód a lidi se to teprve učí, ano, ale právě je učíte nechutný zlozvyk a myslím si, že i když každou tu logiku uvidí samostatně, tak to budou i lépe chápat.

Jinak co se týče úkolu, funkcionalit a vysvětlování, tak za mě bezva práce.

Odpovědět
2. července 21:14
Existují dva způsoby, jak vyřešit problém. Za prvé vyhoďte počítač z okna. Za druhé vyhoďte okna z počítače.
Avatar
Odpovídá na Lubor Pešek
Blanka Svobodová:3. srpna 23:11

A ukázal bys prosím názorně, jak to elegantněji rozsekat? Já si to jako zelenáč neumím předělat zatím sama...respektive nefunguje mi to pak....díky předem.

Odpovědět
3. srpna 23:11
Kdy, když né teď. Kdo, když né já?
Avatar
Blanka Svobodová:3. srpna 23:15

Musela jsem si opravit dosavadní poktok kodu dle přiloženého souboru, v nastavUdalosti nekam zmizel puvodne pridany radek
localStorage.se­tItem("zaznamy", JSON.stringify(this­.zaznamy)); // přidaný řádek.
Proč?

Odpovědět
3. srpna 23:15
Kdy, když né teď. Kdo, když né já?
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!
Avatar
Lubor Pešek
Člen
Avatar
Odpovídá na Blanka Svobodová
Lubor Pešek:4. srpna 8:26

Věřím, že bys to zvládla i sama, ale nemám s tím problém.
Tak máš tu tuhle metodu:

vypisZaznamy() {
    this.seradZaznamy();
    this.vypisElement.innerHTML = "";
    let posledniDatum = null;
    for (const zaznam of this.zaznamy) {
        if (zaznam.datum !== posledniDatum) {
            const datum = new Date(zaznam.datum).toLocaleDateString(this.jazyk, {
                weekday: "long",
                day: "numeric",
                month: "short",
                year: "numeric"
            });
            this.vypisElement.insertAdjacentHTML("beforeend", `<h3>${datum}</h3>`);
        }
        posledniDatum = zaznam.datum;

        this.vypisElement.insertAdjacentHTML("beforeend", `<strong>${zaznam.nazev}</strong>
        <br>úkol ${!zaznam.splneno ? "ne" : ""}splněn`);
        const smazatButton = document.createElement("button");
        smazatButton.onclick = () => {
            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();
            }
        };
        smazatButton.innerText = "Smazat záznam";
        this.vypisElement.appendChild(smazatButton);
        this.vypisElement.insertAdjacentHTML("beforeend", "<br>");
    }
}

Základní myšlenka OOP je o tom, že by se měl virtuální svět co nejvíce podobat tomu skutečnému a každý objekt takhle programovat.
Takovým typickým příkladem je programování židle a podlahy. Ty musíš programovat židli tak, aby měla kromě svých atributů a funkcí i kontrolu (validaci), že když ji na nějakou plochu položíš, že se nepropadne "pod zem".
To samé i podlaha - ať na ní položíš cokoliv, ona to nesmí propustit.
V podstatě by stačilo takovou funkcionalitu naprogramovat jen jednomu předmětu (třeba podlaze) a tím bys docílila kýženého výsledku. Jenže ty se musíš na každou funkci dívat tak, aby byla v ideálním případě použitelná i v jiném projektu.
A tak to máš i v praxi. Ty když bys třeba vytvářela papír, tak jej nebudeš vytvářet pouze pro sešit (takže bys mu dala hned linky), ale vytvoříš prázdný papír a ten, kdo bude vytvářet sešit, tak jej potom následně a patřičně upraví podle toho, jak potřebuje.

No a teď si vem tuhle metodu - vypisZaznamy. Když ti řeknu, že existuje funkce vypsání záznamů, tak co bys od takové funkce očekávala? No nejspíš to, že odněkud vezme zdroj (ideálně z parametru, aby to bylo obecné) a někam to vypíše (ok, aby to bylo co nejvíc univerzálnější, tak bys mohla jako druhý parametr nastavit output, kam se to má vypisovat.

Rozhodně bys neočekávala, že tato metoda bude něco řadit, něco stylovat či že bude vytvářet tlačítka.
Teď si vem, že bys tuhle metodu zavolala třeba v aplikaci, která bude počítat goniometrické funkce.
No a najednou zjistíš, že budeš mít k dispozici posluchač pro tlačítko...(res­pektive ty zavoláš metodu vypisZaznamy() a dostaneš chybovou hlášku, že nemůže najít tlačítko smaž.. :) ) asi bys na to koukala jak půl prdele z křoví.

To, o čem mluvím, tak sice na první pohled "zbytečně" navýší kód, ale takhle by se mělo programovat (objektově).
Takže bych tuhle metodu osekal tak, aby skutečně POUZE vypisovala.

vypisZaznamy(output){
    this.vypisElement.innerHTML = "";
    this.vypisElement.insertAdjacentHTML("beforeend", output);
}

Samozřejmě tím najednou všechno chybí a je potřeba to postupně někde dopsat a provolat :)
Takže je tu nějaký průchod tou aplikací. Schválně pojďme si vypsat, co všechno ta metoda děá:

  • promaže output
  • v celém bloku bude třídit záznamy podle data
  • vypíše unikátní datum
  • vypíše názvy k příslušnému datu
  • zjišťuje, zda-li byl úkol splněn nebo ne a následně jej vypíše
  • vytváří nové tlačítko
  • tlaítku nastavuje posluchače
  • implementuje logiku tlačítka
  • nastavuje tlačítko
  • vypisuje tlačítko (ok, technicky to znamená, že přidá tlačítko do elementu)

trošku dost drsné a tohle bych určitě jen tak od takového názvu metody nepředpokládal.
Určitě by nejideálnější řešením mohlo být, že by každý záznam byl skutečně objekt, který bys vytvářela a nastavovala jeho atributy. Klidně by ten objekt mohl obsahovat i to tlačítko (i když i to je věc zobrazování, ale furt by to bylo lepší, než tohle).
Dejme tomu tedy, že bychom měli objekt, Zaznam, který obsahuje atributy: datum, pole objektů úkol (úkol by měl dva atributy - název a jestli byl nebo nebyl splněn).
V rámci tohoto úkolu to tu tuším tak i je (teď si to úplně nevybavím dopodrobna, dělal jsem to před časem), ale o to víc je to smutné, že vše je připraveno a následně se s těmi objekty nepracuje.

V hlavním běhu bys evidovala někde tyto objekty (třeba v nějakém poli). Takže si můžeš vytvořit i funkci, která ti takové pole bude třídit (podle data jednotlivých objektů).
U každého objektu bys řešila tedy evidenci jeho úkolů (vždy, když by se přidával nový úkol, tak by se nejdřív prošlo pole objektů a zjistilo by se, jestli takové datum už neexistuje a pokud ano, tak se jednoduše tento úkol k takovému objektu přidá. Tohle je mimochodem práce s tzv. mapou, která tady ještě zmíněna nebyla, ale přesně tohle by krásně řešila mapa.

A nemusela bys pak v třízení řešit logiku, komu se co má přiřadit a už vůbec ne ve výpisu, kde bys takovou logiku neočekávala.

Takže když to všechno shrnu, tak bys prostě vyházela veškerou logiku z této funkce (vyházela, ne smazala) a použila ji před tím a trošku jinak a pro každou jednotlivou logiku vytvářela funkce, které bys potom postupně volala.

Výsledek se nesmí nijak změnit, kód ti sice naroste, ale důležitá myšlenka hlavně tkví v tom, že když bys za čas chtěla kód upravit, tak se budeš v něm lépe orientovat. Nehledě na to, že se lépe píšou unitové testy.
ANO, může dojít k tomu, že pak budeš v uvozovkách kvůli blbému výpisu vytvářet separátní metodu, takže místo 1 řádků napíšeš tři řádky.

Teď ti prozradím něco z praxe. Je jedno, pokud má soubor třeba 10 000 řádků, pokud máš kód rozdělen do logických celků (metod, funkcí a tříd). NIKDY si v praxi neprocházíš kód tak, že si otevřeš soubor a čteš si ho od shora dolů. To je strukturované programování. V objektově orientovaném programování si vždy čteš metodu, kterou chceš řešit. Pak je už důležité, aby taková metoda byla co nejstručnější.
Věř, že lépe budeš upravovat metodu, která bude řešit třeba jen formátování textu, než metoda, která bude mít v sobě formátování textu, výpis, přidání tlačítka, parsování data, načítání JSONu atd. atd. atd.
V praxi to doopravdy probíhá tak, že když debuguješ kód, tak jdeš po jednotlivých metodách. Pokud metodu jednoznačně nepochopíš do 2 sekund (i se čtením názvu), tak je špatně navržená.

To je i to, proč jsem ten komentář psal. Už je mi smutno, když třeba v práci otevřu metodu, která má ukládat data do databáze a první co v ní najdu, tak select, který mi nejdřív tahá data od jinud, abych část dat doplnil do textu, který chci uložit. Prostě když mám metodu ulož do databáze, tak ta metoda má skutečně jen použít již nastavenou sessionu a provolat metodu, která uloží text z parametru do databáze. Nic víc od toho nemůžu očekávat.

Po práci napíšu kód, jak bych si to konkrétně třeba já představoval. Není to úzus, určitě by se našlo i lepší řešení (vždycky existuje lepší řešení), ale bude to řešit problém, který já v tomhle vidím.

Odpovědět
4. srpna 8:26
Existují dva způsoby, jak vyřešit problém. Za prvé vyhoďte počítač z okna. Za druhé vyhoďte okna z počítače.
Avatar
Lubor Pešek
Člen
Avatar
Odpovídá na Blanka Svobodová
Lubor Pešek:4. srpna 17:46

Čau, tak ti to jdu sem hodit.
Dal jsem si s tím práci, kód samozřejmě narostl, ale můžeš sama zkusit, jaké to má výhody.

Předně - dobré je začínat z nějakého Managera. V podstatě na tom jsou postavené i frameworky - je někde nějaký manager nebo container, který vše spravuje.
Takto by i kódy měly vypadat (podotýkám, že to tak platí na backendu. Jestli to samé platí i pro FE, na to se musí když tak vyjádřit zkušenější frontendisti).
Takže začnu tou třídou manager (ta i obsahuje provolání window.onload, přijde mi zbytečné to volat v separátním scriptu)

window.onload = function () {
    new Manager();
}

class Manager {

    constructor() {
        this.init();
        this.vypisZpracovaneZaznamyDoVypisu();
    }

    init() {
        this.tool = new Tool();
        this.diar = new Diar(this.tool);
        this.elementy = new Elementy(this.tool, this);
    }

    refreshVypisu(pole) {
        this.elementy.vypisElement.innerHTML = "";
        for (let zaznam of pole) {
            let ukolInfo = document.createElement("p");
            ukolInfo.className = "ukol";
            let datum = document.createElement("h3");
            datum.innerHTML = this.tool.formatData(zaznam.datum);
            ukolInfo.appendChild(datum);
            this.pridejUkoly(zaznam, pole, ukolInfo);
        }
    }

    vypisZpracovaneZaznamyDoVypisu() {
        this.refreshVypisu(this.diar.zaznamy);
    }

    aktualizujASetridDiar() {
        this.diar.nactiZaznamyZLocalStorage();
        this.diar.setridDiar();
    }

    pridejUkoly(zaznam, pole, ukolInfo) {
        for (let ukol of zaznam.ukoly) {
            this.pridejPrvkyUkolu(ukol, zaznam, pole, ukolInfo);
        }
    }

    pridejPrvkyUkolu(ukol, zaznam, pole, ukolInfo) {
        ukolInfo.innerHTML += `<h4>${ukol.nazev}</h4><br>úkol ${ukol.splneno ? "<span class='zelene tucne'>" : "<span class='cervene tucne'>ne"}splněn</span><br>`;
        this.elementy.vypisElement.appendChild(ukolInfo)
        this.pridejTlacitka(ukol, zaznam, pole, ukolInfo);
    }

    pridejTlacitka(ukol, zaznam, pole, ukolInfo) {
        //tlačítko smazat
        this.elementy.vytvorAPridejTlacitko("Smazat", () => {
            if (confirm("Opravdu si přejete odstranit úkol?")) {
                zaznam.ukoly = zaznam.ukoly.filter(z => z !== ukol);
                if (zaznam.ukoly.length === 0) {
                    pole = pole.filter(z => z !== zaznam);
                }
                let jsonZaznamy = this.tool.prevedNaJSON(pole);
                this._ulozZaznamy(jsonZaznamy);
                this.vypisZpracovaneZaznamyDoVypisu();
            }
        }, ukolInfo);

        //tlačítko označit
        this.elementy.vytvorAPridejTlacitko("Označit jako " + (ukol.splneno ? "ne" : "") + "splněn", () => {
            ukol.splneno = !ukol.splneno;
            let jsonZaznamy = this.tool.prevedNaJSON(pole);
            this._ulozZaznamy(jsonZaznamy);
            this.vypisZpracovaneZaznamyDoVypisu();
        }, ukolInfo);
    }

    _ulozZaznamy(jsonZaznamy) {
        this.tool.ulozDoLokalnichZaznamu(jsonZaznamy);
        this.elementy.vypisElement.innerHTML = "";
        this.aktualizujASetridDiar();
    }
}

Když se na to podíváš, tak třída obsahuje 9 metod.
Neděs se toho, je tam i tolik metod už hned na začátku třeba proto, protože jsou některé preventivní, aby se mi v metodách nekupily závorky z cyklů a ifů (lepší než psát několik cyklů do sebe, tak je provolat metodu, která pak vyvolá vnořený cyklus. Trošku líp se to debuguje, ale kód je malinko lépe čitelnější).

Předně počítej s tím, že nebudeš nic nikde vyhledávat. V kódu se orientuješ pomocí odkazů. Stačí podržet CTRL a kliknout na název volané metody a IDE tě tam samo přepne.

Když se tedy podíváš do konstruktoru, tak tam jsou dvě metody. Jedna init (ta bude nejspíš něco inicializovat) a druhá vypisZpracova­neZaznamyDoVy­pisu.
Byl jsem hodný a zachoval jsem tu češtinu, ale věř, že to byla šílená oběť :D
Snad už z toho názvu jde také usoudit, že ta metoda zpracuje záznamy a pak je vypíše do nějakého výpisu.
Ano, dalo by se to řešit i parametricky. Popravdě jsem to tak měl, ale nechtěl jsem zase doopravdy vytvořit 20 metod o jednom řádku. Zase musíme být trošku i rozumní.

Když si přjdeš do metody init (podržíš CTRL a v konstruktoru klikneš na this.init() ), tak ona skutečně nebude dělat nic jiného, než že vytvoří nějaké tři tři třídy.

Tool třída vypadá takto:

class Tool {

    constructor(jazyk = "cs-CZ") {
        this.jazyk = jazyk;
    }

    prevedZJSON(object) {
        return JSON.parse(object);
    }

    prevedNaJSON(object) {
        return JSON.stringify(object);
    }

    nactiLokalniZaznamy() {
        return localStorage.getItem("zaznamy") !== null ? localStorage.getItem("zaznamy") : "[]";
    }

    ulozDoLokalnichZaznamu(zaznamy) {
        localStorage.setItem("zaznamy", zaznamy);
    }

    formatData(datum) {
        return new Date(datum).toLocaleDateString(this.jazyk, {
            weekday: "long",
            day: "numeric",
            month: "long",
            year: "numeric"
        });
    }

    setridPolePodleDatumu(pole) {
        pole.sort((a, b) => new Date(a.datum) - new Date(b.datum));
    }
}

V téhle třídě skutečně neděláš nic jiného, než že zpracováváš objekt do JSONu a zpátky + ukládáš záznamy (jakékoliv, nejen JSON) do localStorage, případně nastavuješ formát data nebo třídíš pole.
Prostě skutečně nástrojová třída.
Lepší by bylo, kdyby tato třída byla statickou knihovnou, ale přiznám se, nevím, jestli v JS statika vůbec je. Možná se k tomu dostaneme v seriálu :)

Abych nemusel pořád tuto třídu tedy vytvářet, tak jsem vytvořil v init metodě pouze jednu instanci této třídy a předávám ji jako parametr třídám Diar a Elementy.
Elementům ještě předávám i instanci managera, protože tam jsou posluchače (teda jeden posluchač), který potřebuje dát managerovy podnět, aby překreslil výsledky.

Opět ve frameworku by bylo lepší použít autowired notaci, ale teď ani žádný framework nepoužíváme, takže si holt musím poradit takto.

Třída Diar vypadá takto:

class Diar {

    constructor(tool) {
        this.tool = tool;
        this.zaznamy = [];

        this.nactiZaznamyZLocalStorage();
    }

    /**
     * Kdyby tato metoda byla pojmenována pouze nactiZaznamy, tak by pak nebylo moc logické, proč tu parsuje nějaký JSON
     * Ono když se takto přemýšlí i nad tou logikou, tak pak to pomáhá trošku i k pojmenování metod
     */
    nactiZaznamyZLocalStorage() {
        let JSONzaznamy = this.tool.nactiLokalniZaznamy();
        this.zaznamy = this.tool.prevedZJSON(JSONzaznamy);
    }

    setridDiar() {
        this.tool.setridPolePodleDatumu(this.zaznamy);
    }
}

Ta opravdu jako správné DTO (Data Transfer Object) zpracovává pouze a jenom záznamy + metoda k jejich třízení.
No a přišlo mi rozumné, že si diář dokáže načíst sám do sebe (do svých záznamů) položky z localStorage. Diáři jako takovému nebude nikdo nic zapisovat. On si to vždycky zapíše sám (pomocí metody nactiZaznamyZ­LocalStorage() ).
Takže je to dynamické a pokud budeš chtít něco uložit do diáře, tak to uložíš do localStorage a pak provoláš jen tuto metodu a Diář si to sám načte.
V případě, že bys chtěl použít jiný načítací zdroj, tak to změníš pouze tady na jednom místě (+ všude tam, odkud se bude do diáře ukládat).

Nejlépe by se to celkově řešilo přes nějakou další třídu, která by zdroj ukládání měla přímo na starost. A diář, který by z aktuálního zdroje četl + jiní posluchači, kteří by do toho zdroje ukládali, tak by se vždy odkazovali na nějakou konfigurační třídu)

No a na práci s tím ukládáním DIář používá třídu Tool. Je mu jedno, jak si to ta třída zařídí - on si to prostě chce uložit a o víc se nestará. O to, ať se stará třída Tool.

Vrátíme se zpátky k třídě Manager do metody init. Je tu poslední třída - Elementy, které předám zase tool + toho managera.
Ve třídě elementy nebudeme řešit nikdy nic jiného, než práci s komponentami, které se budou zobrazovat. Jejich vytváření, načítání a používání. Klidně by tam mohlo být i jejich modifikování (úprava textu atd.). Takže pro tebe jako pro programátorku je to velice jednoduché. Když bys měla někdy task, že budeš muset přidat/odebrat nebo vytvořit nové chování nějakého elementu, tak to budeš hledat pouze v téhle třídě.

Třída Elementy:

class Elementy {

    constructor(tool, manager) {
        this.nazevInput = document.getElementById("nazev");
        this.datumInput = document.getElementById("datum");
        this.potvrditButton = document.getElementById("potvrdit");
        this.vypisElement = document.getElementById("seznam-ukolu");

        this.tool = tool;
        this.manager = manager;
        this.nastavUdalosti();
    }

    nastavUdalosti() {
        this.potvrditButton.onclick = () => {
            let zaznam = this.vytvorNovyZaznamZInputu();
            this.pridejZaznamDoLokalnichZaznamu(zaznam);
            this.manager.aktualizujASetridDiar();
            this.manager.vypisZpracovaneZaznamyDoVypisu();
        }
    }

    vytvorNovyZaznamZInputu() {
        let datum = this.datumInput.value;
        let nazev = this.nazevInput.value;
        return new Zaznam(datum, [new Ukol(nazev, false)]);
    }

    /**
     * Toto je čistě logická metoda. Takováto logika (co se týče ukládání) by za normálních okolností určitě byla na BE
     *
     * U této metody musíme usuzovat, že přidat záznam do lokálních záznamů znamená:
     * 1. načíst lokální záznamy
     * 2. zpracovat je do použitelného pole (vyparsovat z JSON objektu)
     * 3. PŘIDAT ZÁZNAM (s mapou by to bylo lehčí)
     * 4. převést pole zpátky do JSON
     * 5. uložit záznam
     *
     * @param zaznam
     */
    pridejZaznamDoLokalnichZaznamu(zaznam) {
        let jsonZaznamy = this.tool.nactiLokalniZaznamy();
        let zaznamy = this.tool.prevedZJSON(jsonZaznamy);
        this._vlozNovyZaznamPokudNeexistuje(zaznamy, zaznam); //tohle by nahradila mapa, která v Javě má metodu putIfAbsent. V JS nevím, jestli něco takového je
        jsonZaznamy = this.tool.prevedNaJSON(zaznamy);
        this.tool.ulozDoLokalnichZaznamu(jsonZaznamy);
    }

    /**
     * Tato metoda projde záznamy. Pokud záznam již existuje (existuje datum v seznamu), tak přidá k tomuto datu úkol.
     * Pokud neexistuje, tak celý záznam přidá do pole
     *
     * PS: a současně tu máme i připravenou v podstatě logiku, kdybychom najednou někdy v budoucnu rozšířili aplikaci
     * a chtěli i přidávat víc úkolů pro jeden den :)
     *
     * @param zaznamy
     * @param novyZaznam
     * @private
     */
    _vlozNovyZaznamPokudNeexistuje(zaznamy, novyZaznam) {
        for (let zaznam of zaznamy) {
            if (zaznam.datum === novyZaznam.datum) {
                zaznam.ukoly.push(novyZaznam.ukoly[0]);
                return;
            }
        }
        zaznamy.push(novyZaznam);
    }

    vytvorAPridejTlacitko(titulek, callback, element) {
        const button = document.createElement("button");
        button.onclick = callback;
        button.innerText = titulek;
        element.appendChild(button);
    }
}

Pak jsou tu ještě dvě poslední třídy, které souvisí s tím Diářem. Obě jsou opět jen DTO (DTO, neboli Data Transfer Object je obyčejná třída, která pouze a jenom představuje třídu, kde jsou jeji atributy, které do sebe ukládá a které vrací. Žádnou logiku by moc v sobě neměla obsahovat. Maximálně nějaou úpravu těch atributů (jako třeba v tom diáři třízení), ale ani to není úplně čisté.
No a přiznám se, že by neměla obsahovat ani to načítání, ale z lenosti jsem to tam teď hodil :D
Pořád je to menší prohřešek, než to cpát všechno do jedné metody.

Takže takto vypadají:
Třída Záznam:

class Zaznam {

    constructor(datum, ukoly) {
        this.datum = datum;
        this.ukoly = ukoly;
    }
}

a třída Úkol:

class Ukol {

    constructor(nazev, splneno) {
        this.nazev = nazev;
        this.splneno = splneno;
    }
}

Jsou skutečně jednoduché.

No a hlavní logika tedy celkově spočívá ve třídě Manager.
Další část logiky je ještě ve třídě Elementy, protože ta obsahuje posluchače pro komponenty.
Klidně bych mohl funkci těchto posluchačů hodit také do třídy manager, ale posluchač by měl být u té dané komponenty. Toto je diskutabilní a záleží fakt na architektuře programátora. Já to zvolil takto.

No a tím je celé roztřízení hotovo.
Teď se můžeš jen podívat a projít si postupně ty metody.
Není tam nic ale fakt vůbec nic jiného, než co je v těhle tutoriálech.
CSSko je stejné, jako tady na konci ukázky a to samé i index html:

<!DOCTYPE html>
<html lang="cs-cz">
<head>
    <meta charset="UTF-8">
    <title>Diář</title>
    <script src="js/diar.js"></script>
    <script src="js/tool.js"></script>
    <script src="js/ukol.js"></script>
    <script src="js/elementy.js"></script>
    <script src="js/zaznam.js"></script>
    <script src="js/manager.js"></script>
    <link rel="stylesheet" href="css/styly.css" type="text/css">
</head>
<body>
<h1>Diář</h1>
<div>
    <input type="text" id="nazev" placeholder="Vyplňte název úkolu"><br>
    <input type="date" id="datum" placeholder="Vyplňte datum"><br>
    <button id="potvrdit">Uložit úkol</button>
</div>
<div id="seznam-ukolu">

</div>
</body>
</html>

Akorát jsem si to cssko hodil separátně do jiného filu:

* {
    font-family: Segoe UI Light, "Calibri Light", sans-serif;
}

button {
    padding: 10px 16px;
    font-weight: 600;
    background: #2792e0;
    color: #fff;
    border: none;
    cursor: pointer;
    transition: 1s;
}

button:hover {
    background: #2954c2;
}

button:active {
    background: #5a2099;
}

input {
    min-width: 250px;
    padding: 8px;
    border: 1px solid #909090;
    height: 20px;
}

button, input {
    margin-top: 1rem;
    outline: none;
}

.ukol {
    width: 75%;
    padding: 2rem;
    margin: 1rem auto;
    border: 1px solid #c5c5c5;
    box-shadow: 2px 2px 4px #b1b1b1;
}

.zelene {
    color: green;
}

.cervene {
    color: red;
}

.tucne {
    font-weight: bold;
}

Jo a změnil jsem si barvičku u aktivace tlačítka + transition jsem zvýšil na sekundu :D to je celá velká změna v css :D:D

Každopádně když si projdeme ty dvě třídy (Elementy a Manager), tak jaké tam jsou metody:
Elementy:

  • V konstruktoru je to jasné - nastaví se základní elementy (vytáhnou se z HTMLka ty elementy, které budeme používat)
  • metoda nastav udalosti je stejná, jako v ukázce. Ta prostě jen přidá všem (v tomto konkrétním případě jednomu) elementům posluchače. Konkrétně tlačítku.

Ale jak vidíš, tak tam také není žádná implementace, ale provolávání metod.
Takže když se klikne na tlačítko tak se:

  1. vytvoří nový záznam z inputů (metoda je tak pojmenovaná a nic jiného, než že to vytáhne data z jiných inputů a vytvoří to jeden objekt, neudělá). Takže opět pro tebe je to info, že kdybys tam přidala tlačítko, které nastaví každému úkolu třeba barvičku, tak to určitě budeš potom přidávat na tomto místě
  2. přidej záznam do Lokálních záznamů (tam je i JavaDoc, neboli komentář, ve kterém to popisuju. Nepojmenoval jsem to záměrně ulozData, ale přidej k záznamům. Pak si můžu dovolit, že nejdřív v takové metodě musím získat zdroj, klidně si ho upravím, pak to do něj přidám a až pak to uložím
  3. aktualizuj a setřid diař. Tak od této metody bych očekával, že si natáhne aktuální data a následně je setřídí. No a když si do té metody přejdeš tak zjistíš, že to tak je (vykonává to manager).

Určitě bys v takové metodě asi nečekala, že ti vytvoří kočárek a pochová dítě. Nebo že ti bude formátovat čas

  1. no a když to mám všechno nachystané, tak jen vypíšu zpracované záznamy. Uznávám, že toto bych tady mohl klidně přejmenovat (ale už se mi nechtělo :D) tak, jak to pak volám v té metodě - že pro mě výpis znamená, že přepíšu ty stávající záznamy (a to znamená, že smažu výpis a nahraju to tam znova).

No a to také není úplně jednoznačně vypovídající. Mohl jsem toto pojmenovat líp. Od výpisu by málo kdo čekal refresh.

  • Pak je tu ještě metoda _vlozNovyZaznam­PokudNeexistu­je. Tu jsem tam popsal, tak nemusím už opakovat, co je jednou napsáno
  • no a metoda vytvorAPridej­Tlacitko(?,?,?) asi bude vytvářet a přidávat k nějakému elementu tlačítka. No a kde jinde by měla taková metoda být, než ve třídě, která má elementy na starost?:)

No a ve třídě Manager máme ještě metody:
refreshVypisu
Tato metoda ke konci používá metodu pridejUkoly
která proiteruje všechny úkoly, které záznam obsahuje a pak s nimi pracuje pomocí metody pridejPrvkyUkolu a v této metodě ještě na konci provolávám metodu pridejTlacitka

No a každá metoda tedy vykonává to, podle čeho je pojmenována.

Určitě by se i tento můj výtvor dal po této stránce vylepšit.
On nic neurychluje ani nijak jinak neimplementuje. Vše, co bylo v tomto seriálu, tak je akorát jinak zapsáno.
Ovšem když ti následně potom třeba v konzole přijde tato hláška:

**Uncaught TypeError: pole is not iterable
    at Manager.refreshVypisu (manager.js:20:28)
    at Manager.vypisZpracovaneZaznamyDoVypisu (manager.js:31:14)
    at new Manager (manager.js:9:14)
    at window.onload (manager.js:2:5)**

Tak uznej sama, že se to daleko lépe debuguje.
Podíváš se do třídy Manager na řádek 20.
Zjistíš, že pole nemůže proiterovat. Řekneš si a proč?
Tak si tam na ten řádek hodíš brakepoint a zjistíš, že pole je undefined.
Tak se podíváš, kde jej bereš - aha z parametru.
Tak kdo tuhle metodu volá? řádek 31 ve stejné třídě.
Tak se na to podíváš a zjistíš, že jako parametr předáváš ne this.diar.zaznam, ale this.diar.zazna (žes třeba omylem smazala to M na konci).

Samozřejmě takhle jde i poznat chyba v ukázkovém příkladě. Také to jde takhle debugovat.
Ale čím víc logických prvků (tříd a metod) máš, tak se ti lépe pak v tom kódu orientuje.

Nehledě na to, že kdybych ti třeba teď zadal úkol, abys přidala input a každý úkol bude mít třeba index závažnosti, tak už se v tom budeš mnohem rychleji orientovat.

Když máš dva vnořené cykly a metodu na 100 řádků a musíš v jedné metodě scrollovat, tak pak se v tom i tvá orientace velmi rychle ztrácí.
Nevím teď přesně kolik (dá se to vygooglit), ale metoda by měla mít tuším že max 50 řádků. Pokud se nejedná o nějaký složitý výpočet umělé inteligence :D

Tak, myslím, že je to hodně vyčerpávající a pokud něco pochytíš, bude jen dobře.
Kdybys měla jakékoliv otázky, neboj se zeptat. Kdo se ptá - naučí se, kdo se neptá a brečí, tak je ztracený.

Odpovědět
4. srpna 17:46
Existují dva způsoby, jak vyřešit problém. Za prvé vyhoďte počítač z okna. Za druhé vyhoďte okna z počítače.
Avatar
Lubor Pešek
Člen
Avatar
Lubor Pešek:4. srpna 17:46

kua, zase komentář delší jak článek -.-

Odpovědět
4. srpna 17:46
Existují dva způsoby, jak vyřešit problém. Za prvé vyhoďte počítač z okna. Za druhé vyhoďte okna z počítače.
Avatar
Odpovídá na Lubor Pešek
Blanka Svobodová:5. srpna 22:01

tak tomu říkám hustá odpověď!!!!! To si snad zarámuju! Megadík

Odpovědět
5. srpna 22:01
Kdy, když né teď. Kdo, když né já?
Avatar
Michal D.
Supertvůrce
Avatar
Michal D.:16. srpna 17:24

ve vysvětlivkách použito nesprávně toLocaleString(), správně toLocaleDateString()

 
Odpovědět
16. srpna 17:24
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 14. Zobrazit vše