O projekcie

Analiza wymagań

Aplikacja ma funkcjonalność bez zmian (patrz Rozdział 9).

Aplikacja została zaimplementowana w oparciu o technologię AJAX z wykorzystaniem funkcji fetch()

Struktura plików

Struktura plików aplikacji podana jest na poniższym obrazku.

.
├── db
│   └── products.js
├── Gruntfile.js
├── package.json
├── public
│   ├── css
│   │   └── styl.css
│   ├── favicon.ico
│   ├── index.html
│   └── js
│       └── script.js
└── serwer.js

Główne pliki w tej implementacji — to: index.html,script.js, serwer.js.

index.html

Ten plik jest wysyłany do klienta bez zmian. Zawiera on podstawowe drzewo DOM strony. Drzewo to będzie uzupełniane i modyfikowane dynamiczne po stronie klienta.

script.js

Ten plik zawiera aplikację kliencką.

serwer.js

W tym pliku jest aplikacja serwerowa.

Plik serwer.js

Aplikacja serwerowa jest podobna do poprzedniej. Ponieważ całe drzewo DOM buduje się po stronie klienta, serwerze wysyła tylko dane: listę kategorii:

app.get('/categories', function (req, res) {
    let cats = baza().distinct("category");
    res.json(cats);
});

Oraz listę produktów określonej kategorii:

app.get('/category/:cat', function (req, res) {
    let products = baza({category: req.params.cat}).select("product", "price");
    res.json(products);
});

Dodatkowo aplikacja wykorzystuje moduł express-session:

let session = require('express-session');
app.use(session({
    secret: 'xxxyyyzzz',
    resave: false,
    saveUninitialized: true
}));

Ten moduł odpowiada za identyfikację klientów. W każdym zapytaniu teraz jest dostępna właściwość session, do której można dopisywać/odczytywać dane, na przykład,

app.post('/login', function (req, res) {
    req.session.login = req.body.login;
    …

Albo:

app.post('category/:cat', function (req, res) {
    if( req.session.login == 'admin'){
        …

Plik script.js

W momencie, kiedy drzewo DOM już jest zbudowano, wywołana jest funkcja setup:

document.addEventListener('DOMContentLoaded', setup);

Zadaniem tej funkcji jest uzupełnić elementy html,


function setup() {
   let form = document.querySelector('form');
   form.onsubmit = postProduct;
   fetch( 'categories')
   .then(
      response => {
         if(response.ok){
            return response.json();
         }
         else{
            throw new Error("Błąd fetch categories: " + response.status);
         }
      }
   )
   .then(
     categories => {
        let ul = document.querySelector('nav ul');
        for (let c of categories){
           let li = document.createElement('li');
           li.textContent = c;
           li.className = 'clickable';
           li.onclick = getShowCategory(c);
           ul.append(li);
        }
        
     }
   )
   .catch( error => alert(error) );
}

Mianowicie:

  • zarejestrować na zdarzeniu submit formularza funkcję postProduct
  • pobrać z serwera listę categorii, utworzyć dla każdej element li, zarejestrować na zdarzeniu click tego elementu funkcję oraz dodać ten element do listy ul.
Funkcja po kliknięciu na kategorię
function(){
     fetch('category/'+ cat)
     .then(
        response => {
            if(response.ok){
               return response.json();
            }
            else{
               throw new Error("Błąd fetch one category: " + response.status);
            }
        }
      )
      .then(
      products => {
         let h1= document.querySelector('h1');
         h1.className = 'clickable';
         h1.onclick = goRoot;
         let nav = document.querySelector('nav');
         nav.className='small';
         let section = nav.nextElementSibling;
         section.style.display = 'block';
         let h2 = section.firstElementChild;
         h2.textContent = cat;
         let ul = h2.nextElementSibling;
         while (ul.lastChild) ul.lastChild.remove();
         for (let pr of products){
            let product = document.createElement('span');
            product.className = 'product';
            product.textContent = pr[1];
            let price = document.createElement('span');
            price.className = 'price';
            price.textContent = pr[0];
            let li = document.createElement('li');
            li.append(product);
            li.append(document.createTextNode(': '));
            li.append(price);
            ul.append(li);
         }
      }
      )
      .catch( error => alert(error) );
}
  • pobrać listę produktów danej kategorii
  • ustawić dla nagłówka klasę clickable i zarejestrować na kliknięcie funkcję goRoot, która przełączy aplikację w tryb strony głównej
  • ustawić dla listy nawigacyjnej klasę small
  • pokazać section
  • zaktualizować h2
  • wyczyścić element ul i wypełnić go listą produktów
Funkcja goRoot
function goRoot(){
      let h1= document.querySelector('h1');
      h1.className = '';
      h1.onclick = null;
      let nav = document.querySelector('nav');
      nav.className='';
      let section = nav.nextElementSibling;
      section.style.display = 'none';
}
  • wyrejestrować callback na kliknięcie na nagłówku
  • usunąć liście nawigacyjnej klasę small
  • ukryć section

Szablon strony głównej (index.html)

W tym pliku są umieszczone tylko puste elementy HTML, które zostaną uzupełniane dynamicznie. Jest także formularz wysyłania nowych produktów, tylko ukryty.