Web-Frontend:Systembolaget

From Juneday education
Jump to: navigation, search

Here, we'll give a short and very basic example of what a web front-end to a web API for accessing the product line from the Swedish Systembolaget (the alcohol monopoly retailer of Sweden).

In order to follow this small example, you need to have a web API running on your computer. We have an assignment in three parts for writing such a web API here. When you are finished with all three parts of that assignment, you should have a working web API, which we will use as back-end in this example.

The web page

Web front-end

First, we need a web page in HTML, to provide the structure of the view of the data. We are going to keep it simple and use a very small web page written in HTML with simply a table for displaying the products (which will include only the product name and price), and a text input for the user to enter the maximum price for the products to show, and a button to fire the fetching of the products.

The initial code for the web page will be shown below. Later in this example, we will add the JavaScripts needed for fetching and parsing JSON from your web API.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Fetch</title>
  </head>
  <body>
    <p>
      <label for="max_price">Max price:</label><input type="text" id="max_price" /><br />
      <input type="submit" onclick="goFetch();" value="Search" />
    </p>
    <table id="results_table">
    </table>

    <script>
    // Make the table headers.
    makeHeader();

    function makeHeader() {
        let tr = document.createElement('tr');
        let th1 = document.createElement('th');
        th1.appendChild(document.createTextNode('Product name'));
        let th2 = document.createElement('th');
        th2.appendChild(document.createTextNode('Price'));
        tr.appendChild(th1);
        tr.appendChild(th2);
        document.getElementById('results_table')
            .appendChild(tr);
    }
    // More scripts for the actual fetching will be added later!!!
</script>
  </body>
</html>

Security stuff

Accessing your web API (which will be running on localhost) from this web page, will not work, unless your web API serves this web page. In order to access it from your Servlet container, just save the file as front-end.html in your server's www (the webroot) directory (which you might have named "webroot" or anything). When you start Winstone, you pass along an argument pointing out this directory.

If you want to open the html file in your browser from the file system, your browser won't allow the call to the servlet. You can "fix"™ this by setting a header from your servlet:

response.addHeader("Access-Control-Allow-Origin", "*");

Put the above in your doGet() method. Putting the file as part of your servlet is easier and what we recommend.

The JSON response from the server

The screenshot above shows a query equivalent to http://localhost:8080/search/products/all?max_price=9. The JSON in the response should look like this:

[
    {
    "name": "Stödbenscider",
    "price": 8.00,
    "volume": 330,
    "alcohol": 4.50,
    "nr": 8849103,
    "product_group": "Cider"
  },
    {
    "name": "Bear Beer Premium",
    "price": 7.90,
    "volume": 330,
    "alcohol": 4.60,
    "nr": 134815,
    "product_group": "Öl"
  },
    {
    "name": "Åbro Export",
    "price": 8.40,
    "volume": 330,
    "alcohol": 5.30,
    "nr": 1143812,
    "product_group": "Öl"
  },
    {
    "name": "Tuborg Grön",
    "price": 8.90,
    "volume": 330,
    "alcohol": 4.20,
    "nr": 125512,
    "product_group": "Öl"
  },
    {
    "name": "Fagerhult Export",
    "price": 8.90,
    "volume": 330,
    "alcohol": 5.30,
    "nr": 148112,
    "product_group": "Öl"
  },
    {
    "name": "Dansk Fadøl",
    "price": 8.90,
    "volume": 330,
    "alcohol": 5.00,
    "nr": 146012,
    "product_group": "Öl"
  },
    {
    "name": "Guldkällan",
    "price": 8.50,
    "volume": 330,
    "alcohol": 4.80,
    "nr": 147212,
    "product_group": "Öl"
  },
    {
    "name": "Svensk Starköl",
    "price": 7.90,
    "volume": 330,
    "alcohol": 5.10,
    "nr": 169912,
    "product_group": "Öl"
  },
    {
    "name": "Småland",
    "price": 8.90,
    "volume": 330,
    "alcohol": 5.20,
    "nr": 147712,
    "product_group": "Öl"
  },
    {
    "name": "Zeunerts Original",
    "price": 8.90,
    "volume": 330,
    "alcohol": 5.30,
    "nr": 142615,
    "product_group": "Öl"
  },
    {
    "name": "Sofiero Original",
    "price": 8.90,
    "volume": 330,
    "alcohol": 5.20,
    "nr": 122215,
    "product_group": "Öl"
  },
    {
    "name": "Fem komma tvåan",
    "price": 7.90,
    "volume": 330,
    "alcohol": 5.20,
    "nr": 162515,
    "product_group": "Öl"
  }
]

See the assignment's pages for details.

The JavaScripts needed

We need a JavaScript in our small web page, which responds to clicks on the search button (the submit input), since we set this attribute on the input: onclick="goFetch();" . So the name of the JavaScript function will be goFetch().

What does goFetch need to do?

First, it needs to get the value of the text input where the user has entered e.g. 9 - meaning the user wants to see all products whose max price is 9.0 SEK.

That should be translated by the script to the following URL, from which to fetch the JSON: http://localhost:8080/search/products/all?max_price=9 .

A string can be composed in JavaScript using backticks and a variable. Let's see an example both getting the value entered by the user and logging a composed string to the console:

let max_price = document.getElementById('max_price').value;
console.log(`http://localhost:8080/search/products/all?max_price=${max_price}`);

Inside backticks, the text ${max_price} will be expanded to the value of the variable max_price.

In JavaScript, we can fetch stuff from a web resource using the function call fetch(), which takes a URL as the argument and returns a "promise".

The function will have the following logic:

  1. clear the result table's current content
  2. get the value from the text input and store in max_price
  3. fetch the URL composed with the value of the variable max_price
  4. then get the response's JSON
  5. then, for each element in the JSON array (which we will call product, create a table row with the product's name and price

The complete function is here:

    function goFetch() {
        clearResults();
        let result;
        let max_price = document.getElementById('max_price').value;
        fetch(`http://localhost:8080/search/products/all?max_price=${max_price}`)
              .then(response => response.json())
              .then(result => {
                  result.forEach(product => {
                      console.log(product.name); // you can use this for debugging!
                      makeRow(product.name, product.price);
                  })
              });
    }

Where does the table rows with name and price come from? A second function!

Such a function could have (and in our case has) the following logic:

  1. take two arguments, name and price
  2. create a TR (table row) element
  3. create a TD (table data) element, and append a text node to it with the value of name
  4. create a TD element, and append a text node to it with the value of price (formatted as Swedish currency SEK)
  5. append the first TD (with the name) as a child to TR
  6. append the second TF (with the price) as a child to TR
  7. find the table element in the document, and append TR to it as a child

The function is shown below:

    function makeRow(name, price) {
        let tr = document.createElement('tr');
        let nameTD = document.createElement('td');
        nameTD.appendChild(document.createTextNode(name));
        let priceTD = document.createElement('td');
        priceTD.setAttribute('align', 'right');
        let formattedPrice = new Intl.NumberFormat('sv-SE', { style: 'currency', currency: 'SEK' }).format(price);
        priceTD.appendChild(document.createTextNode(formattedPrice));
        tr.appendChild(nameTD);
        tr.appendChild(priceTD);
        document.getElementById('results_table')
            .appendChild(tr);
    }

However, we shouldn't keep appending results to the table. We need to clear the current content (if any) first, and add back the header (yes, we couldn't figure out a smarter way to do this, please help us :-D.

This is done in the following functions:

    function clearResults() {
        console.log('apa');
        document.getElementById('results_table').innerHTML = '';
        makeHeader();
    }
    
    function makeHeader() {
        let tr = document.createElement('tr');
        let th1 = document.createElement('th');
        th1.appendChild(document.createTextNode('Product name'));
        let th2 = document.createElement('th');
        th2.appendChild(document.createTextNode('Price'));
        tr.appendChild(th1);
        tr.appendChild(th2);
        document.getElementById('results_table')
            .appendChild(tr);
    }

The complete web page with JavaScript

Here's the complete web page:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Fetch</title>
  </head>
  <body>
    <p>
      <label for="max_price">Max price:</label><input type="text" id="max_price" /><br />
      <input type="submit" onclick="goFetch();" value="Search" />
    </p>
    <table id="results_table">
    </table>

    <script>
    makeHeader();
    function clearResults() {
        console.log('apa');
        document.getElementById('results_table').innerHTML = '';
        makeHeader();
    }

    function makeHeader() {
        let tr = document.createElement('tr');
        let th1 = document.createElement('th');
        th1.appendChild(document.createTextNode('Product name'));
        let th2 = document.createElement('th');
        th2.appendChild(document.createTextNode('Price'));
        tr.appendChild(th1);
        tr.appendChild(th2);
        document.getElementById('results_table')
            .appendChild(tr);
    }
        
    function makeRow(name, price) {
        let tr = document.createElement('tr');
        let nameTD = document.createElement('td');
        nameTD.appendChild(document.createTextNode(name));
        let priceTD = document.createElement('td');
        priceTD.setAttribute('align', 'right');
        let formattedPrice = new Intl.NumberFormat('sv-SE', { style: 'currency', currency: 'SEK' }).format(price);
        priceTD.appendChild(document.createTextNode(formattedPrice));
        tr.appendChild(nameTD);
        tr.appendChild(priceTD);
        document.getElementById('results_table')
            .appendChild(tr);
    }
    function goFetch() {
        clearResults();
        let result;
        let max_price = document.getElementById('max_price').value;
        fetch(`http://localhost:8080/search/products/all?max_price=${max_price}`)
              .then(response => response.json())
              .then(result => {
                  result.forEach(product => {
                      console.log(product.name);
                      makeRow(product.name, product.price);
                  })
              });
    }
</script>
  </body>
</html>

The empty call at the top of the script element, makeHeader(); is run when the page is loaded, and will add the headers to the table (so that the user can expect where the result will occur.

That's it! Feel free to correct our mistakes (there are probably plenty!), since we are not web developers. Our knowledge of HTML and JavaScript is limited to say the least. We still wanted to offer you a way to create a small web front-end to the Exposing data... assignments!

You should also feel free to add capabilities to the front-end, like more text inputs with minimum price, minimum alcohol and maximum alcohol. We are confident that you can, working from our minimal example code, add capabilities for creating a more complex URL from those inputs, and also to modify the table so that it has an alcohol column (and many more columns from the JSON if you'd like).

Enjoy!

Links

JavaScript