Ciele

  1. Naučiť sa používať Fetch API na zmenu údajov na serveri a zakomponovať túto funkcionalitu do smerovačom na strane klienta riadenej jednostránkovej web aplikácie.
  2. Doplnenie jednostránkovej web aplikácie dynamického blogu o zobrazenie, pridávanie, odoberanie a editáciu článkov.
  3. Doplnenie jednostránkovej web aplikácie dynamického blogu o zobrazenie a pridávanie komentárov k článkom.

Úvod

    Na tomto cvičení doplníte vašu jednostránkovú web aplikáciu dynamického blogu o pridávanie, odoberanie a editáciu jednotlivých článkov a zobrazenie a pridávanie komentárov k nim.

    V "tutoriálovej" časti (krok 1 a 2 postupu) zobrazenie a editáciu článkov doplníte do vzorovej aplikácie, blogu o obľúbených stromoch. Potom to isté a ďalšiu funkcionalitu pridáte do vašej aplikácie, ktorá je výsledkom úlohy 5 (cvičenie 9).

    Poznámka: Všetko potrebné na splnenie dnešných úloh ste sa dozvedeli z prednášok 7 až 10 ([1], [2], [3], [4]) a z v nich uvedených príkladoch.
    Pri riešení úloh používajte Fetch API. Môžete použiť aj syntax s kľúčovými slovami async a await. V plnom rozsahu je tiež potrebné využiť smerovač na strane klienta (hash router).

Postup

  1. V prvej "tutoriálovej" úlohe si zobrazenie článkov skúsite pridať do príkladu blogu o obľúbených stromoch.
    Úloha: Do jednostránkovej web aplikácie blogu o obľúbených stromoch, ktorá je výsledkom kroku 2 a 3 z cvičenia 9, doplňte zobrazenie jednotlivých článkov.
    Celý článok nech sa zobrazí po kliknutí na jeho názov v zozname. Zobrazenie implementujte pomocou smerovača, trasa nech má názov article. Parametre trasy využite na uloženie id článku a údajov pre návrat na príslušnú stránku zoznamu článkov (tzn. tú, kde ste na názov klikli). Pod článkom zobrazte tlačidlá
    • Back pre návrat na príslušnú stránku zoznamu článkov,
    • Edit pre editáciu článku a
    • Delete pre vymazanie článku.
    Úlohu môžete riešiť samostatne alebo môžete postupovať podľa nasledujúceho kompletného postupu riešenia:

    1. Stiahnite si a rozbaľte archív myDynamicBlogWArtEdit_starter.zip s aplikáciou. Ďalej budete pracovať s touto rozbalenou verziou aplikácie vo vhodnom IDE.

      Táto aplikácia je výsledkom splnenia úloh z krokov 2 a 3 cvičenia 9 s pridaným modulom articleFormsHandler.js a drobnými úpravami v css a ďalších súboroch. Modul articleFormsHandler.js obsahuje triedu na obsluhu formulárov pre pridávanie a editáciu článkov a vychádza zo skriptu v príklade [5].



    2. Najprv do šablóny so zoznamom článkov doplníme odkazy na jednotlivé články.

      V index.html v elemente script s id=template-articles zmeňte riadok
      
      <h2>{{title}}</h2>
                      
      na
      
      <h2><a href="{{detailLink}}">{{title}}</a></h2>                    
                      


    3. Teraz musíme do objektov, ktoré sa touto šablónou spracovávajú pridať do položky detailLink príslušné url fragmenty.

      Na koniec routes.js pridajte funkciu
       
      function addArtDetailLink2ResponseJson(responseJSON){
        responseJSON.articles = responseJSON.articles.map(
          article =>(
           {
             ...article,
             detailLink:`#article/${article.id}/${responseJSON.meta.offset}/${responseJSON.meta.totalCount}`
           }
          )
        );
      }                                      
                      
      Táto funkcia vyrobí potrebný url fragment. Jeho druhý a tretí parameter (offset a totalCount) v tomto príklade síce nepotrebujeme, ale boli by užitočné ak by sme mali implementované stránkovanie (čo vaša aplikácia má mať).

    4. Ďalej v routes.js zavedieme konštanty s dôležitými nastaveniami - základ url servera a počet článkov na stránku

      V routes.js pred funkciu createHtml4opinions pridajte riadky
        
      const urlBase = "https://wt.kpi.fei.tuke.sk/api";
      const articlesPerPage = 20;                                    
                      


    5. Funkciu fetchAndDisplayArticles vymeníme za novšiu verziu, ktorá pracuje s novými konštantami a tiež do objektov v zozname článkov pridá fragmenty, vytvorené funkciou addArtDetailLink2ResponseJson.

      V routes.js nahraďte funkciu
          
      function fetchAndDisplayArticles(targetElm){
      ...
      }                                                                           
                      
      funkciou
          
      function fetchAndDisplayArticles(targetElm, offsetFromHash, totalCountFromHash){
      
          const offset=Number(offsetFromHash);
          const totalCount=Number(totalCountFromHash);
      
          let urlQuery = "";
      
          if (offset && totalCount){
              urlQuery=`?offset=${offset}&max=${articlesPerPage}`;
          }else{
              urlQuery=`?max=${articlesPerPage}`;
          }
      
          const url = `${urlBase}/article${urlQuery}`;
      
          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 => {
                  addArtDetailLink2ResponseJson(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
                      );
              });
      }                                                                          
                      
      Názvy článkov sú teraz odkazmi so správnymi trasami, no pre tieto trasy ešte nie je spracovanie.

    6. Implementáciu spracovania trás začneme pridaním Mustache šablóny pre článok a tlačidlá pod ním.

      Do index.html za element
      
      <script id="template-addOpinion" type="text/template">
      ...
      </script>                    
                      
      pridajte element
      
      <script id="template-article" type="text/template">
          <article>
              <h3>{{title}}</h3>
              <p>
                  by {{author}}
              </p>
              {{#imageLink}}
              <figure>
                  <img src="{{imageLink}}" alt="article figure" />
              </figure>
              {{/imageLink}}
              <div>{{{content}}}</div>
              <p>
                  Keywords:
                  {{tags}}
              </p>
          </article>
      
          <footer>
              <a href="{{backLink}}" class="linkAsButton"><< Back</a>
              <a href="{{editLink}}" class="linkAsButton">Edit</a>
              <a href="{{deleteLink}}" class="linkAsButton">Delete</a>
              <a href="#menuTitle" class="linkAsButton">Up to the menu</a>
          </footer>
      
      </script>                    
                      


    7. Teraz pridáme objekt s trasou.

      Do poľa s trasami v routes.js pridajte objekt
      
          {
              hash:"article",
              target:"router-view",
              getTemplate: fetchAndDisplayArticleDetail
          }              
                      
      pre trasu article.

    8. Do routes.js ešte vložíme funkcie na spracovanie trasy.

      Na koniec routes.js pridajte kód
                         
      function fetchAndDisplayArticleDetail(targetElm,artIdFromHash,offsetFromHash,totalCountFromHash) {
          fetchAndProcessArticle(...arguments,false);
      }                   
      
      /**
       * Gets an article record from a server and processes it to html according to 
       * the value of the forEdit parameter. Assumes existence of the urlBase global variable
       * with the base of the server url (e.g. "https://wt.kpi.fei.tuke.sk/api"),
       * availability of the Mustache.render() function and Mustache templates )
       * with id="template-article" (if forEdit=false) and id="template-article-form" (if forEdit=true).
       * @param targetElm - id of the element to which the acquired article record 
       *                    will be rendered using the corresponding template
       * @param artIdFromHash - id of the article to be acquired
       * @param offsetFromHash - current offset of the article list display to which the user should return
       * @param totalCountFromHash - total number of articles on the server
       * @param forEdit - if false, the function renders the article to HTML using 
       *                            the template-article for display.
       *                  If true, it renders using template-article-form for editing.
       */
      function fetchAndProcessArticle(targetElm,artIdFromHash,offsetFromHash,totalCountFromHash,forEdit){
          const url = `${urlBase}/article/${artIdFromHash}`;
      
          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 => {
                  if(forEdit){
                  }else{
                      responseJSON.backLink=`#articles/${offsetFromHash}/${totalCountFromHash}`;
                      responseJSON.editLink=
                        `#artEdit/${responseJSON.id}/${offsetFromHash}/${totalCountFromHash}`;
                      responseJSON.deleteLink=
                        `#artDelete/${responseJSON.id}/${offsetFromHash}/${totalCountFromHash}`;
      
                      document.getElementById(targetElm).innerHTML =
                          Mustache.render(
                              document.getElementById("template-article").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 fetchAndDisplayArticleDetail iba volá funkciu fetchAndProcessArticle s parametrom forEdit=false. Funkciu fetchAndProcessArticle sme si už pripravili tak, aby nám mohla poslúžiť aj pri napĺňaní formulára údajmi z článku keď budeme chcieť článok editovať.
      Tým je úloha splnená. Tlačidlá Edit a Delete majú už správne odkazy (url fragmenty), ale zatiaľ bez spracovania.

  2. Teraz v druhej "tutoriálovej" úlohe do príkladu pridáme editáciu článku.
    Úloha: Do jednostránkovej web aplikácie blogu o obľúbených stromoch z predchádzajúceho kroku doplňte editáciu článkov.
    Editácia článku nech je dostupná cez v predchádzajúcom kroku pridané tlačidlo Edit a nech je implementovaná pomocou smerovača v trase s názvom artEdit.
    1. Začneme znova šablónou, tentokrát pre formulár článku.

      Do index.html za element
      
      <script id="template-article" type="text/template">
      ...
      </script>
                      
      pridajte element
      
      <script id="template-article-form" type="text/template">
          <article>
              <h2>{{formTitle}}</h2> <br><br>
      
              <form id="articleForm">
                  <label for="author">Author:</label>
                  <input type="text" name="author" id="author" value="{{author}}" size="50" 
                         title="Article author, max. length 100 characters." 
                         maxlength="100" placeholder="e.g. Ján Trieska" />
                  <br>
                  <label for="title">Title:</label>
                  <input type="text" name="title" id="title" value="{{title}}" size="50"  
                         maxlength="100" pattern="\S[\S\s]*" placeholder="e.g. My story." required 
                         title="Article title, mandatory item, max. length: 100 characters. 
      The first character must not be a space."  />
                  <br>
                  <label for="imageLink">Image (url):</label>
                  <input type="url" name="imageLink" id="imageLink" value="{{imageLink}}" size="50" 
                         title="Image URL, max. length 100 characters." maxlength="100"/>
                  <br>
                  <label></label>
                  <button type="button" id="btShowFileUpload">
                      Upload image
                  </button>
      
                  <fieldset class="added hiddenElm" id="fsetFileUpload">
                      <legend>Image Upload</legend>
                      <input type="file" id="flElm" name="file" accept="image/jpeg, image/png"/>
                      <br />
                      <button type="button" id="btFileUpload">
                          Send image to server </button>
                      <button type="button" id="btCancelFileUpload">
                          Cancel uploading </button>
                  </fieldset>
      
      
                  <br>
                  <label for="content">Article content:</label>
                  <textarea
                          name="content" id="content" spellcheck="true" lang="sk"
                          cols="50" rows="20" required
                          title="Article content, mandatory item, can be plain text or in HTML.">
                    {{content}}
                  </textarea>
                  <br>
                  <label for="tags">Keywords:</label>
                  <input  type="text" name="tags" id="tags" value="{{tags}}" size="50"
                          title="Keyword list, comma separated." placeholder="e.g. village, drama" />
                  <br> <br>
                  <button type="reset"> Reset Form </button>
                  <button type="submit"> {{submitBtTitle}} </button>
      
              </form>
          </article>
      
          <footer>
              <a href="{{backLink}}" class="linkAsButton"><< Back</a>
              <a href="#menuTitle" class="linkAsButton">Up to the menu</a>
          </footer>
      </script>                    
                      


    2. Ďalej pridáme objekt s trasou artEdit.

      Do poľa s trasami v routes.js pridajte objekt
      
          {
              hash:"artEdit",
              target:"router-view",
              getTemplate: editArticle
          }             
                      
      pre trasu artEdit.

    3. Teraz doplníme funkciu editArticle, ktorá volá funkciu fetchAndProcessArticle s parametrom forEdit=true.

      Do routes.js, na koniec alebo pred funkciu fetchAndProcessArticle, pridajte funkciu
          
      function editArticle(targetElm, artIdFromHash, offsetFromHash, totalCountFromHash) {
          fetchAndProcessArticle(...arguments,true);
      }                                   
                      


    4. Ďalej musíme do funkcie fetchAndProcessArticle doplniť vetvu pre prípad editácie.

      V routes.js vo funkcii fetchAndProcessArticle kód
                         
      if(forEdit){
      }else{
                      
      nahraďte kódom
          
      if(forEdit){
          responseJSON.formTitle="Article Edit";
          responseJSON.submitBtTitle="Save article";
          responseJSON.backLink=`#article/${artIdFromHash}/${offsetFromHash}/${totalCountFromHash}`;
      
          document.getElementById(targetElm).innerHTML =
              Mustache.render(
                  document.getElementById("template-article-form").innerHTML,
                  responseJSON
              );
          if(!window.artFrmHandler){
              window.artFrmHandler= new articleFormsHandler("https://wt.kpi.fei.tuke.sk/api");
          }
          window.artFrmHandler.assignFormAndArticle("articleForm","hiddenElm",artIdFromHash,offsetFromHash,totalCountFromHash);
      }else{                         
                      


    5. Nakoniec v index.html pridáme načítanie skriptu v ktorom máme kód pre obsluhu formulára.

      Do index.html za element
      
      <script src="js/addOpinion.js"></script>
                      
      pridajte element
      
      <script src="js/articleFormsHandler.js"></script>                    
                      
  3. Teraz manipuláciu s jednotlivými článkami zavediete aj do vašej aplikácie.
    Úloha: Rozšírte vašu jednostránkovú web aplikáciu o zobrazenie, editáciu, vymazanie a pridávanie jednotlivých článkov.
    • Celý článok nech sa zobrazí po kliknutí na jeho názov v zozname. Zobrazenie implementujte pomocou smerovača, trasa nech má názov article. Parametre trasy využite na uloženie id článku a údajov pre návrat na príslušnú stránku zoznamu článkov (tzn. tú, kde ste na názov klikli). Pod článkom zobrazte tlačidlá
      • Back pre návrat na príslušnú stránku zoznamu článkov,
      • Edit pre editáciu článku a
      • Delete pre vymazanie článku.
    • Editácia článku nech je dostupná cez vyššie spomenuté tlačidlo Edit a nech je implementovaná pomocou smerovača v trase s názvom artEdit.
    • Vymazanie článku nech je dostupné cez vyššie spomenuté tlačidlo Delete a nech je implementované pomocou smerovača v trase s názvom artDelete.
    • Pridanie nového článku nech je dostupné z hlavného menu aplikácie, v položke Add article a nech je implementované pomocou smerovača v trase s názvom artInsert.
    Na uloženie id článku, údajov pre návrat na príslušnú stránku zoznamu článkov a ďalšie potrebné údaje využite parametre trás. Obsluhu formulárov pre editáciu a pridávanie článkov implementujte v samostatnom module, ktorý je triedou (ako napr. articleFormsHandler.js v tutoriálových úlohách).

    Poznámka: Ako implementovať zobrazenie a editáciu článku ste sa naučili v predchádzajúcich "tutoriálových" úlohách.

    Poznámka: Teraz môžete na server zaviesť aj vaše články, ktoré ste tvorili v úlohe 1. Ako obsah článkov môžete vkladať aj HTML kód. Ak chcete aby sa vám zobrazovali iba vaše články, realizujte druhú doplnkovú úlohu.

    Poznámka: Nezabudnite, že vaša aplikácia má stále mať funkcionalitu, pridanú v cvičení 9. To znamená stránkovaný zoznam článkov a zobrazovanie obsahov článkov v zozname. Ak sa vám nepáči zobrazovať celé obsahy článkov, zobrazte namiesto nich náhľady obsahov. Ako na zobrazenie náhľadov vidíte v príklade 72_04artListDetailPrevComFetch.

  4. A nakoniec do vašej aplikácie pridáte manipuláciu s komentármi k článkom.
    Úloha: Doplňte vašu jednostránkovú web aplikáciu o zobrazenie a pridávanie komentárov k článkom.
    Komentáre nech sa vždy zobrazia pod článkom - je teda potrebné doplniť trasu article.
    Pod komentármi nech je tlačidlo Add Comment po stlačení ktorého nech sa zobrazí formulár pre nový komentár. Pri pridávaní nového komentára nech je stále viditeľný aspoň článok.

    Poznámka: Stačí keď zobrazíte prvých maximálne 100 komentárov. Pre editáciu nemusíte používať smerovač.

    Poznámka: Ak by sme zobrazenie komentárov pridali týmto spôsobom do blogu o obľúbených stromoch, mohlo by to vyzerať nasledovne:

    Po kliknutí na tlačidlo Add Comment by sa zobrazil formulár na pridanie nového komentára:

Zdroje

  1. Prednáška 7.
  2. Prednáška 8.
  3. Prednáška 9.
  4. Prednáška 10.
  5. 75_02articleAddFetchImgUpload.html - príklad nahratia údajov na server pomocou Fetch API a HTTP metódy POST.
  6. API dokumentácia poskytnutého servera.

Doplňujúce úlohy

    Úloha: Komentáre pod článkom zobrazte po maximálne 10 a aplikujte pri ich zobrazovaní smerovač.

    Poznámka: Trasu môžete ponechať tú istú ako v povinných úlohách (tzn. article). Bude však potrebné pridať ďalšie parametre trasy, pre stránkovanie komentárov.

    Poznámka: Pri získavaní komentárov zo servera viete v url použiť parametre max a offset, podobne ako pri zozname článkov. Bližšie informácie sú v dokumentácii servera [6].

    Poznámka: Pre stránkovanie komentárov môžete využiť aj samostatnú trasu, napr. s názvom artComment. Komentáre však stále majú byť zobrazené pod článkom. Preto by ste v tejto trase mali použiť iný cieľový element ako v ostatných.

    Úloha: Nepríjemným efektom využitia servera ako zdroja článkov je, že sa zobrazia aj články, ktoré vôbec nesúvisia s témou vášho blogu. Vymyslite si teda kľúčové slovo (tag), ktoré automaticky priradíte každému novému článku. Toto kľúčové slovo potom použite pri sťahovaní zoznamu článkov zo servera aby ste zobrazili len tie vaše. Toto kľúčové slovo by nemalo byť viditeľné pri zobrazovaní ani editácii článkov.

    Poznámka: Parametre vyhľadávania viete kombinovať aj s ďalšími ako napr.max a offset.
    Príklad: http://wt.kpi.fei.tuke.sk/api/article?tag=skrArt20&max=2&offset=1