Ciele

  1. Naučiť sa tvoriť jednostránkové aplikácie (single page applications) použitím smerovača na strane klienta (client side router) [1].
  2. Naučiť sa získavať údaje zo servera pomocou HTTP metódy GET a Fetch API [3], [4], [5].
  3. Aplikovať smerovač na strane klienta a získavanie údajov zo servera pomocou Fetch API vo vlastnej webovej aplikácii dynamického blogu(Úloha 5).

Úvod

    Na tomto a ďalších cvičeniach budete pracovať na druhom zadaní - webovej aplikácii dynamického blogu. Váš blog bude jednostránkovou aplikáciou (single page application, SPA) s jediným HTML súborom (index.html). Navigáciu bude zabezpečovať smerovač na strane klienta (client side router), ktorý bude využívať časť url nazývanú fragment alebo hash (za znakom #).

    Všetko si najprv vyskúšate formou tutoriálu na aplikácii, ktorá je podobná výsledku kroku 2 z cvičenia 7. Tutoriál je obsiahnutý v krokoch 1 až 3 posupu.

    Vznikne webová aplikácia, ktorú potom prerobíte do podoby podobnej vášmu prvému zadaniu - statickému blogu. Z prvého zadania prevezmete tému, vzhľad a názory návštevníkov. Výslednú aplikáciu rozšírite o získavanie ďalších údajov zo servera a ďalej ju budete rozširovať v nasledujúcich týždňoch.

    Poznámka:
    Všetko potrebné na splnenie dnešných úloh ste sa dozvedeli z prednášok 7 až 9 ([1], [2], [3]) a z v nich uvedených príkladoch.
    S problematikou, ktorú máte dnes zvládnuť vás podrobne oboznámia aj prvé tri úlohy (kroky) tohto cvičenia.
    Pri riešení úloh používajte Fetch API. Môžete použiť aj syntax s kľúčovými slovami async a await.

    Poznámka:
    Toto cvičenie sa od predchádzajúcich aj nasledujúcich lýši v jednej podstatnej veci. Kým v ostatných sú "tutoriálové" úlohy doplňujúce a môžete ich preskočiť, tu sú dôležitou súčasťou tvorby vášho zadania. To, čo v nich vytvoríte bude tvoriť základ finálnej podoby vášho zadania.

Postup

  1. Najprv sa pozriete na rozšísenú verziu hash router-a, ktorý ste videli na prednáške [1]. Tento router budete používať v ďalších krokoch.
    Úloha: Oboznámte sa s parametrickým hash router-om 06hashRouterModularPaged.

    Stiahnuteľná verzia router-a je dostupná v archíve 06hashRouterModularPaged.zip.

    Všimnite si, že oproti hash router-u z prednášky [1] je tento načítavaný ako modul a jednotlivé trasy (v routes.js) majú v položke getTemplate funkciu. Funkciu preto, aby sme mohli HTML tvoriť na základe parametrov. Funkcia sa navyše stará aj o vloženie HTML do cieľového elementu (položka target). To je preto, aby sme zvládli aj spracovanie údajov získaných pomocou asynchrónnych požiadaviek zo servera.

    Poznámka: Dôsledkom načítania router-a ako modulu je, že príklad bude funkčný iba pri spustení na serveri. Preto na experimentovanie použite vhodné IDE, napríklad WebStorm alebo VS Code s LiveServer. To platí aj pre aplikáciu, ktorú budete vytvárať v ďalších krokoch.

  2. Teraz vytvoríte na klientovi smerovanú verziu blogu o obľúbených stromoch.
    Úloha: Vytvorte takú verziu jednostránkovej aplikácie z kroku 2 v cvičení 7, kde bude riadenie stavu aplikácie zabezpečené hash router-om. Všetky trasy budú dostupné z hlavného menu aplikácie. Trasy budú 4:
    • welcome (odkaz Welcome!) pre uvítací článok
    • articles (odkaz Articles) pre články o stromoch
    • opinions (odkaz Visitor opinions) pre názory návštevníkov a
    • addOpinion (odkaz Add your opinion) pre formulár na zadanie názoru.
    Pri generovaní HTML využite šablónovací systém Mustache. Po úspešnom zadaní nového názoru nech sa prejde na názory návštevníkov.
    Úlohu môžete riešiť samostatne alebo môžete postupovať podľa nasledujúceho kompletného postupu riešenia. Prvý krok postupu však musíte vykonať, keďže v ňom vytvoríte základ druhého projektu

    1. Použite odkaz https://kurzy.kpi.fei.tuke.sk/gitlab-classroom/wt2 na vytvorenie druhého projektu v katedrovom GitLab-e. Postupujte rovnakým spôsobom ako pri prvom projekte v cvičení 1. Ďalej budete pracovať s týmto projektom vo vhodnom IDE.

      V prípade problémov s vytvorením projektu v katedrovom GitLab-e použite archív myDynamicBlog_starter.zip.

      Vytvorili ste základ druhého projektu, ktorý už obsahuje js súbory s routerom (paramHashRouter.js), trasami (routes.js), kódom pre hlavné menu (mainMenu.js) a spracovanie údajov z formulára (addOpinion.js). Skript routerInit.js inicializuje router. Funkčná je len prvá položka v menu (Welcome!).



    2. Do index.html za element
      
      <script id="template-welcome" type="text/template">
         ...
      </script>     
                      
      pridajte element
      
      <script id="template-articles" type="text/template">
          <article  id="artPine">
              <h2>Pine</h2>
              <figure>
                  <img src="fig/pineBw.png" height="150"
                       title="fig.pine" alt="pine" />
              </figure>
      
              <p>Pine is a softwood coniferous tree.</p>
      
              <dl> <!-- zoznam moze byt iba mimo odsekov -->
                  <dt>Latin name: <!-- uzatvaracia znacka nemusi byt uvedena -->
                  <dd>Pinus
                  <dt>Division:</dt>
                  <dd>Pinophyta</dd>
                  <dt>Class:</dt>
                  <dd>Pinopsida</dd>
              </dl>
          </article>
      
          <article  id="artOak">
              <h2>Oak</h2>
              <figure>
                  <img src="fig/oak.png" height="150"
                       title="fig.oak" alt="oak" />
              </figure>
              <p>
                  Oak is a deciduous tree with hardwood. It lives long and grows slowly. 
                  Its leaves are simple, lobate and fall off before the winter.
              </p>
              <footer  class="menuLink"><a href="#menuTitle">Back to the menu</a></footer>
          </article>
      </script>    
                      
      so šablónou pre články.
      To, čo sme pridali síce nazývame šablóna, ale v tomto prípade je to len HTML kód, ktorý sa pri kliknutí na odkaz Articles vloží do príslušného HTML elementu.

    3. Do poľa v routes.js pridajte objekt
      
          {
              hash:"articles",
              target:"router-view",
              getTemplate:(targetElm) =>
                  document.getElementById(targetElm).innerHTML = 
                    document.getElementById("template-articles").innerHTML
          }                    
                      
      Obsah routes.js teraz bude
      
      //an array, defining the routes
      export default[
      
          {
              //the part after '#' in the url (so-called fragment):
              hash:"welcome",
              ///id of the target html element:
              target:"router-view",
              //the function that returns content to be rendered to the target html element:
              getTemplate:(targetElm) =>
                  document.getElementById(targetElm).innerHTML = 
                    document.getElementById("template-welcome").innerHTML
          },
          {
              hash:"articles",
              target:"router-view",
              getTemplate:(targetElm) =>
                  document.getElementById(targetElm).innerHTML = 
                    document.getElementById("template-articles").innerHTML
          }
      
      ];                  
                      
      Teraz je funkčná aj druhá položka v menu (Articles).
      Funkcia, ktorá je v položke getTemplate nového objektu iba načíta html obsah script elementu s id=template-welcome do cieľového elementu. Cieľový element je ten s id daným argumentom targetElm, tu je to router-view. Ak chcete vedieť ako sa router-view dostane do targetElm, preštudujte si kód router-a, konkrétne paramHashRouter.js.

    4. Do index.html za element
      
      <script id="template-articles" type="text/template">
      ...
      </script>                  
                      
      pridajte element
      
      <script id="template-opinions" type="text/template">
          <article  id="artOpinions">
              <h2>Visitor Opinions</h2>
              {{#.}}
              <section>
                  <h3>{{name}} <i>{{createdDate}}</i></h3>
                  <p>{{comment}}</p>
                  <p>{{willReturn}}</p>
              </section>
              {{/.}}
              {{^.}}
              <section>
                  Sorry, no opinions found.
              </section>
              {{/.}}
              <footer  class="menuLink"><a href="#menuTitle">Back to the menu</a></footer>
          </article>
      </script>                    
                      
      s Mustache šablónou pre názory návštevníkov.

    5. Do poľa v routes.js pridajte objekt
      
          {
              hash:"opinions",
              target:"router-view",
              getTemplate: createHtml4opinions
          }                    
                      
      Na spracovanie názorov návštevníkov do HTML potrebujeme viac ako jeden riadok kódu. Aby to bolo prehľadné, dáme tento kód do samostatnej funkcie createHtml4opinions. Túto funkciu pridáme v ďalšom kroku. Teraz sme len v položke getTemplate objektu s príslušnou trasou (opinions) uviedli jej názov.

    6. Na koniec routes.js pridajte funkciu
      
      function createHtml4opinions(targetElm){
          const opinionsFromStorage=localStorage.myTreesComments;
          let opinions=[];
      
          if(opinionsFromStorage){
              opinions=JSON.parse(opinionsFromStorage);
              opinions.forEach(opinion => {
                  opinion.created = (new Date(opinion.created)).toDateString();
                  opinion.willReturn = 
                    opinion.willReturn?"I will return to this page.":"Sorry, one visit was enough.";
              });
          }
      
          document.getElementById(targetElm).innerHTML = Mustache.render(
              document.getElementById("template-opinions").innerHTML,
              opinions
              );
      }                    
                      
      Funkčná je už aj tretia položka v menu (Visitor opinions).

    7. Do index.html za element
      
      <script id="template-opinions" type="text/template">
      </script>
                      
      pridajte element
      
      <script id="template-addOpinion" type="text/template">
          <article  id="artOpnFrm">
              <h2>Your Opinion</h2>
              <p>
                  Please, use the form below to state your opinion about this page.
              </p>
              <form id="opnFrm">
                  <label for="nameElm">Your name:</label>
                  <input type="text" name="login" id="nameElm" size="20" maxlength="50" 
                         placeholder="Enter your name here" required />
                  <br><br>
                  <label for="opnElm">Your opinion:</label>
                  <textarea name="comment" id="opnElm" cols="50" rows="3" 
                            placeholder="Express your opinion here" required></textarea>
                  <br><br>
                  <input type="checkbox" id="willReturnElm" />
                  <label for="willReturnElm">I will definitely return to this page.</label>
                  <br><br>
                  <button type="submit">Send</button>
              </form>
              <footer  class="menuLink"><a href="#menuTitle">Back to the menu</a></footer>
          </article>
      </script>                    
                      
      čo je HTML kód pre formulár pre nový názor.
      Podobne, ako v prípade template-articles to ani nie je skutočná šablóna, keďže neobsahuje žiadne značky, ktoré by sa nahrádzali údajmi z JavaScript objektov.

    8. Do poľa v routes.js pridajte objekt
      
          {
              hash:"addOpinion",
              target:"router-view",
              getTemplate: (targetElm) =>{
                  document.getElementById(targetElm).innerHTML = document.getElementById("template-addOpinion").innerHTML;
                  document.getElementById("opnFrm").onsubmit=processOpnFrmData;
              }
          }
                      
      Tým ste ukončili sfunkčnenie poslednej položky v menu. Kód na spracovanie údajov z formulára už totiž je v súbore addOpinion.js.

  3. Ďalej vymeníte statické články za dynamické zo servera https://wt.kpi.fei.tuke.sk/. Teda, za ich názvy a autorov.
    Úloha: V trase articles nahraďte statické články za zoznam prvých maximálne 10 článkov zo servera https://wt.kpi.fei.tuke.sk/. Ku každému zobrazte názov (title) a autora (author). Na získanie článkov použite Fetch API.

    Poznámka: Všimnite si, že tu používame https verziu servera. Ten je dostupný v oboch verziách, http://wt.kpi.fei.tuke.sk/ aj https://wt.kpi.fei.tuke.sk/. Odporúčame použiť https verziu, aby ste nemali problém s funkcionalitou riešenia pri umiestnení online cez codesandbox, glitch alebo iné podobné služby.

    Postup riešenia je nasledovný:

    1. V index.html vymažte element
      
      <script id="template-articles" type="text/template">
      ...
      </script>  
                      
      a namiesto neho pridajte elementy
      
      <script id="template-articles" type="text/template">
          {{#articles}}
          <article>
              <h2>{{title}}</h2>
              <p>
              by {{author}}
              </p>
          </article>
          {{/articles}}
          <footer  class="menuLink"><a href="#menuTitle">Back to the menu</a></footer>
      
      </script>
      
      <script id="template-articles-error" type="text/template">
      
          <article>
              <h2>Articles acquisition failed</h2>
              <p>
                  {{errMessage}}
              </p>
          </article>
          <footer  class="menuLink"><a href="#menuTitle">Back to the menu</a></footer>
      
      </script>                    
                      
      Nová šablóna template-articles slúži na zobrazenie zoznamu článkov, získaných zo servera. Šablóna template-articles-error sa použije keď sa články získať nepodarí a to na výpis chybového hlásenia.

    2. V poli v routes.js zmeňte riadky
      
              getTemplate:(targetElm) =>
                  document.getElementById(targetElm).innerHTML = 
                    document.getElementById("template-articles").innerHTML
                       
      na
      
              getTemplate: fetchAndDisplayArticles
                       
      Podobne ako pri trase opinions, aj tu spracovanie dáme do samostatnej funkcie, konkrétne fetchAndDisplayArticles. Túto funkciu pridáme v ďalšom kroku.

    3. Na koniec routes.js pridajte funkciu
      
      function fetchAndDisplayArticles(targetElm){
          const url = "https://wt.kpi.fei.tuke.sk/api/article";
      
          fetch(url)
          .then(response =>{
              if(response.ok){
                  return response.json();
              }else{ //if we get server error
                  return Promise.reject(
                    new Error(`Server answered with ${response.status}: ${response.statusText}.`));
              }
          })
          .then(responseJSON => {
              document.getElementById(targetElm).innerHTML =
                  Mustache.render(
                      document.getElementById("template-articles").innerHTML,
                      responseJSON
                  );
      
          })
          .catch (error => { ////here we process all the failed promises
              const errMsgObj = {errMessage:error};
              document.getElementById(targetElm).innerHTML =
                  Mustache.render(
                      document.getElementById("template-articles-error").innerHTML,
                      errMsgObj
                  );
          });
      
      }
                       
      Funkcia fetchAndDisplayArticles získa údaje o článkoch zo servera pomocou Fetch API, vygeneruje z nich HTML kód použitím Mustache šablóny a vloží ho do elementu s id vo vstupnej premennej targetElm.

  4. Úloha: Aplikáciu z predchádzajúceho kroku upravte a rozšírte použitím vášho prvého projektu. Zachovajte pri tom použitie routera v plnom rozsahu.
    V tejto úlohe máte s použitím toho, čo ste vytvorili v cvičeniach 1 až 7 urobiť s aplikáciou z predchádzajúceho kroku nasledujúce:
    • Názov stránky a uvítaciu časť (obsah šablóny s id=template-welcome) zmeňte tak, aby zodpovedali vašej téme.
    • Formulár v trase addOpinion vymeňte za váš z úlohy 3 (cvičenie 6). Z vášho riešenia úloh 3 a 4 tiež prevezmite spracovanie obsahu formulára do localStorage.
    • Zobrazenie názorov v trase opinions zmeňte na to vaše, z úlohy 4 (cvičenie 7).
      HTML kód však definujte pomocou Mustache šablóny a to aj v prípade ak ste v úlohe 4 použili šablónové literály. Použite šablónu s id=template-opinions.
    • Na celú stránku (aplikáciu) aplikujte vaše vlastné CSS štýly. Môžu ale nemusia to byť tie, čo ste vytvorili pre váš prvý projekt (cvičenia 2 až 6).

    Poznámka: Jediné, čo nebude v súlade s vašou témou sú články načítané so servera. To ale budete môcť zmeniť v doplňujúcej úlohe v cvičení 11.

  5. Úloha: V aplikácii chýbajú telá článkov (položka content). Doplňte ich vykonaním príslušných požiadaviek na server a zobrazte v HTML. Kód doplňte do funkcie fetchAndDisplayArticles a za účelom zobrazenia rozšírte šablónu s id=template-articles.
    Zachovajte pri tom použitie routera v plnom rozsahu a využite Fetch API.

    Poznámka: Ako vnárať požiadavky s fetch a promises a následne zobraziť výsledky vidíte v príklade 72_03artListDetailComFetch.html.
    POZOR: Príklad 72_03artListDetailComFetch.html získava a zobrazuje aj komentáre. Tu ale komentáre zobrazovať nechceme, preto ak využijete kód z tohto príkladu, musíte ho upraviť. (Zobrazenie komentárov pridáme v cvičení 11).

  6. Úloha: V aplikácii sa teraz zobrazí iba prvých 10 článkov. Zmeňte to tak aby bolo možné zobraziť všetky články. Články nech sa zobrazujú po stránkach, kde na každej stránke (okrem poslednej) bude 20 článkov. Pomocou odkazov Previous a Next nech sa dá prejsť na stránku s predchádzajúcimi resp. nasledujúcimi (maximálne) 20 článkami. Ak sa prejsť na nasledujúce alebo predchádzajúce už nedá, príslušný odkaz nech sa nezobrazí. Prechádzanie riešte pomocou router-a a Mustache šablón. Pre komunikáciu so serverom použite Fetch API.

    Poznámka: Stránkovanie s router-om vidíte v príklade 06hashRouterModularPaged (06hashRouterModularPaged.zip) v trase main (odkaz Main Content).
    Ako získať požadovaný počet článkov od požadovaného indexu (offset) zistíte z dokumentácie servera [7] (časť Parametre pre rozsah článkov).

Zdroje

  1. Prednáška 7.
  2. Prednáška 8.
  3. Prednáška 9.
  4. Fetch API at MDN web docs (tutorials and reference), https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API.
  5. Promises, fetch and async/await at The Modern JavaScript Tutorial, https://javascript.info/async.
  6. Virtuálny stroj s nainštalovaným serverom (pre tvorbu zadania bez pripojenia na internet). Po stiahnutí je ho možné spustiť v prostredí VirtualBox.
  7. API dokumentácia poskytnutého servera.

Doplňujúce úlohy

    Úloha: Ak ste stránkovanie v úlohe v kroku 6 urobili presne podľa príkladu 06hashRouterModularPaged tak sa po kliknutí na odkaz Articles vždy články zobrazia od prvého. Prerobte aplikáciu tak, aby sa zobrazili od toho článku ako po poslednom kliknutí na Previous alebo Next.