Web-Frontend:SubstituteTeacherScheduler

From Juneday education
Jump to: navigation, search

Here, we'll give a short and very basic example on how one could write a presentation layer of the Substitute Teacher schedule from the Substitute teacher scheduler assignment, in the form of HTML and JavaScript.

Note: The authors of this Wiki are not web developers. Any web stuff taught here may only be taken seriously on your own risk.

Thanks to Jon Kristensen for code examples and pointers

We are going to make a small example of how you could display data from the JSON given from the Substitute Teacher Scheduler web API, in a web page, using HTML and JavaScript.

The web page

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 bullet list with the schedules.

The page will consist of a <ul> element (that's the HTML element for an unordered list, a bullet list). Each bullet will represent one assignment on the form:

At YYYY-mm-dd HH:MM:ss, TeacherName is assigned to SchoolName

For instance: At 2018-01-15 08:00:00, Rikard is assigned to Yrgo

Inside a <ul> element, we have <li> elements, which are the list items (the actual bullets). This means we will have to populate the list with <li> items with the data from the JSON from the servlet. We'll use JavaScript and the fetch() method for this.

We'll start by showing you the HTML of the page, and return to the JavaScript part in a moment.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Schedule</title>
  </head>
  <body>
    <ul></ul>
    <script>
      /* We'll put the script here in a moment! */
    </script>
  </body>
</html>

Adding some input stuff

Wouldn't it be nice if the web page also had some user interaction, e.g. the user could choose a teacher from a drop-down list?

So, let's add a drop-down list (<select>) with some teacher names and their corresponding substitute_ids.

When the user selects an option (the value of the list changes) from the list, another JavaScript function should be called, which fetches the JSON for the assignments for this teacher alone.

    <p>
      <select name="substitute_id" onchange="getForTeacher()" id="teachers">
      <option value="0">Choose teacher</option>
      <option value="1">Rikard</option>
      <option value="2">Henrik</option>
      <option value="3">Anders</option>
      <option value="4">Nahid</option>
      </select>
    </p>

Security restrictions

The fetch() method (and other methods using the network) for fetching e.g. JSON used in this example is restricted to JSON from the same source as the HTML file with the JavaScript. So for this example to work, simply put the HTML page in the webroot directory of your server serving the JSON, and access it via http://localhost:8080/ (the file is called index.html so accessing winstone (or whatever servlet container you are running) that way will request the index.html file).

You can read more about the restrictions here: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

One way to by-pass this, is to have your servlet set the following HTTP header:

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

That would, actually, allow you to run e.g. PHP on the command line as the web server for index.html in this example.

Just download (or write yourself) the example index.html to some directory, and start PHP as a web-server there:

$ php -S localhost:8888

Or use Python:

$ python -m SimpleHTTPServer 8888

Make sure you choose a number outside of the ports your servlet container is using.

Now you can test the example. So for this setup (you need to install php), you do the following:

  • change your servlet so that it sets the header as per above
  • recompile the servlet and start your servlet container so that you have your JSON schedule system up and running on e.g. localhost:8080
  • download the index.html file to some directory
  • in that same directory, start php as a server on localhost:8888
  • open http://localhost:8888 in your browser and play with this small example

Summary: For the security of your browser to allow you to run this example, do one of the following:

  1. put the example html file in your servlet's webroot and access it on localhost:8080 OR:
  2. see above for how to add a header allowing cross site stuff in your servlet

The scripts

We'll start with the first JavaScript, which fetches all assignments for all teachers. The JSON the script will get we'll be e.g.:

[
  {
    "date": "2018-01-15 08:00:00",
    "school": {
      "address": "Lärdomsgatan 3 402 72 GÖTEBORG",
      "school_name": "Yrgo"
    },
    "substitute": {"name": "Rikard"}
  },
  {
    "date": "2018-01-16 08:00:00",
    "school": {
      "address": "Lärdomsgatan 3 402 72 GÖTEBORG",
      "school_name": "Yrgo"
    },
    "substitute": {"name": "Rikard"}
  },
  {
    "date": "2018-01-17 08:00:00",
    "school": {
      "address": "Lärdomsgatan 3 402 72 GÖTEBORG",
      "school_name": "Yrgo"
    },
    "substitute": {"name": "Rikard"}
  },
  {
    "date": "2018-01-18 08:00:00",
    "school": {
      "address": "Ebbe Lieberathsgatan 18 c 412 65 Göteborg",
      "school_name": "ITHS"
    },
    "substitute": {"name": "Rikard"}
  },
  {
    "date": "2018-01-16 08:00:00",
    "school": {
      "address": "Lärdomsgatan 3 402 72 GÖTEBORG",
      "school_name": "Yrgo"
    },
    "substitute": {"name": "Henrik"}
  },
  {
    "date": "2018-01-17 08:00:00",
    "school": {
      "address": "Lärdomsgatan 3 402 72 GÖTEBORG",
      "school_name": "Yrgo"
    },
    "substitute": {"name": "Henrik"}
  },
  {
    "date": "2018-01-18 08:00:00",
    "school": {
      "address": "Kruthusgatan 17 411 04 Göteborg",
      "school_name": "Jensen"
    },
    "substitute": {"name": "Henrik"}
  },
  {
    "date": "2018-01-16 08:00:00",
    "school": {
      "address": "Ebbe Lieberathsgatan 18 c 412 65 Göteborg",
      "school_name": "ITHS"
    },
    "substitute": {"name": "Anders"}
  },
  {
    "date": "2018-01-17 08:00:00",
    "school": {
      "address": "Grepgatan 9 424 65 Angered",
      "school_name": "Angeredsgymnasiet"
    },
    "substitute": {"name": "Anders"}
  },
  {
    "date": "2018-01-18 08:00:00",
    "school": {
      "address": "Kyrkogatan 46 411 15 Göteborg",
      "school_name": "Aniaragymnasiet"
    },
    "substitute": {"name": "Anders"}
  },
  {
    "date": "2018-01-16 08:00:00",
    "school": {
      "address": "Skånegatan 20 402 29 Göteborg",
      "school_name": "Burgårdens utbildningscentrum"
    },
    "substitute": {"name": "Nahid"}
  }
]

So, the script needs to fetch this JSON from http://localhost:8080/v1?format=json (your web API servlet URL).

Then, the script needs to read the JSON into some variable. Then, the script needs to create an <li> element for each assignment object in the JSON.

Let's look at the JavaScript for this:

    <script>
      fetch('http://localhost:8080/v1?format=json')
        .then(response => response.json())
        .then(result => {
          result.forEach(assignment => {
            let li = document.createElement('li');
            li.appendChild(document.createTextNode
               (`At ${assignment.date}, ${assignment.substitute.name} is assigned to ${assignment.school.school_name}`));
            document.querySelector('ul').appendChild(li);
        });
      });
    </script>

The fetch method returns a "promise". We can call then() on this "promise" and provide a function expression which will return the json of the response from the fetch. You can read more about the json() method on mozilla.org. This is also a "promise", so we can add a second "then" which takes a function expression which will loop over each element in the JSON array and call the element assignment. For each such assignment, we create a new <li> element, and append text from the JSON object to it, before we add this <li> element to the document's <ul> list element. In this way, we are adding LI-elements to the UL of the page, using data from the fetched JSON. Read about document.querySelector() here.

The JSON data is accessed just like any object in JavaScript. We want to get the date of the object, the substitute's name and the school's name from for instance:

  {
    "date": "2018-01-16 08:00:00",
    "school": {
      "address": "Lärdomsgatan 3 402 72 GÖTEBORG",
      "school_name": "Yrgo"
    },
    "substitute": {"name": "Henrik"}
  }

To access data from the object above, we simply dereference the parts. In the forEach loop, we call each object assignment. So we get the following syntax:

  • assignment.date - for the assignment's date
  • assignment.substitute.name - for the assignment's substitute's name
  • assignment.school.school_name - for the assignment's school's school_name

Next, we'll look at the other function, called when something from the SELECT drop-down list is selected.

      function getForTeacher() {
        document.querySelector('ul').innerHTML = '';
        let subst = document.getElementById('teachers').value;
        fetch(`http://localhost:8080/v1?format=json&substitute_id=${subst}`)
        .then(response => response.json())
        .then(result => {
          result.forEach(assignment => {
            let li = document.createElement('li');
            li.appendChild(document
              .createTextNode(`At ${assignment.date}, ${assignment.substitute.name} is assigned to ${assignment.school.school_name}`));
            document.querySelector('ul').appendChild(li);
          });
        });
      }

This script does a similar thing as the previous script. Only, it uses the new value from the SELECT drop-down as part of the URL for the fetch() call.

It starts by clearing the UL list of its LI items. Next, it declares a local variable and initializes it to the new value of the SELECT drop-down. After that, it fetches the JSON using the substitute_id GET parameter with the value of the variable. Then, it saves the JSON and then it reads some data for each assignment object in the JSON array, and creates an LI-element which is added to the UL-bullet list element (pretty much the same as the other function did).

The complete web page

Here's the complete HTML and JavaScript code for the web client:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Fetch</title>
  </head>
  <body>
    <p>
      <select name="substitute_id" onchange="getForTeacher()" id="teachers">
      <option value="0">Choose teacher</option>
      <option value="1">Rikard</option>
      <option value="2">Henrik</option>
      <option value="3">Anders</option>
      <option value="4">Nahid</option>
      </select>
    </p>
    <ul></ul>
    <script>
      function getForTeacher() {
        document.querySelector('ul').innerHTML = '';
        let subst = document.getElementById('teachers').value;
        fetch(`http://localhost:8080/v1?format=json&substitute_id=${subst}`)
        .then(response => response.json())
        .then(result => {
          result.forEach(assignment => {
            let li = document.createElement('li');
            li.appendChild(document
              .createTextNode(`At ${assignment.date}, ${assignment.substitute.name} is assigned to ${assignment.school.school_name}`));
            document.querySelector('ul').appendChild(li);
          });
        });
      }
    </script>
    <script>
      fetch('http://localhost:8080/v1?format=json')
      .then(response => response.json())
      .then(result => {
        result.forEach(assignment => {
          let li = document.createElement('li');
          li.appendChild(document
                       .createTextNode(`At ${assignment.date}, ${assignment.substitute.name} is assigned to ${assignment.school.school_name}`));
          document.querySelector('ul').appendChild(li);
        });
      });
    </script>
  </body>
</html>

Getting rid of code duplication

We see that a lot of code is duplicated in the two functions. Let's start with writing a function for creating the LI element from three strings:

      function getLi(date, name, school) {
        let li = document.createElement('li');
        li.appendChild(document.createTextNode(`At ${date},` +
          ` ${name}` +
          ` is assigned to ${school}`));
        return li;
      }

This function can be called from the two functions used for fetching the JSON. Now the functions will look like this:

      function getLi(date, name, school) {
        let li = document.createElement('li');
        li.appendChild(document.createTextNode(`At ${date},` +
          ` ${name}` +
          ` is assigned to ${school}`));
        return li;
      }

      function getForTeacher() {
        document.querySelector('ul').innerHTML = '';
        let subst = document.getElementById('teachers').value;
        fetch(`http://localhost:8080/v1?format=json&substitute_id=${subst}`)
        .then(response => response.json())
        .then(result => {
          result.forEach(assignment => {
            document.querySelector('ul').appendChild(getLi(assignment.date, assignment.substitute.name, assignment.school.school_name));
          });
        });
      }

      fetch('http://localhost:8080/v1?format=json')
        .then(response => response.json())
        .then(result => {
          result.forEach(assignment => {
            document.querySelector('ul').appendChild(getLi(assignment.date, assignment.substitute.name, assignment.school.school_name));
        });
      });

There is still some duplication of code. Let's make a parameterized goFetch() function which performs the fetch() operation with optional parameters to the URL used for fetching:

      function goFetch(params) {
        fetch(`http://localhost:8080/v1?format=json${params}`)
        .then(response => response.json())
        .then(result => {
          result.forEach(assignment => {
            document.querySelector('ul').appendChild(getLi(assignment.date, assignment.substitute.name, assignment.school.school_name));
          });
        });        
      }

Now we can shorten the functions to this:

    <script>
      function getLi(date, name, school) {
        let li = document.createElement('li');
        li.appendChild(document.createTextNode(`At ${date},` +
          ` ${name}` +
          ` is assigned to ${school}`));
        return li;
      }

      function goFetch(params) {
        fetch(`http://localhost:8080/v1?format=json${params}`)
        .then(response => response.json())
        .then(result => {
          result.forEach(assignment => {
            document.querySelector('ul').appendChild(getLi(assignment.date, assignment.substitute.name, assignment.school.school_name));
          });
        });        
      }

      function getForTeacher() {
        document.querySelector('ul').innerHTML = '';
        let subst = document.getElementById('teachers').value;
        goFetch(`&substitute_id=${subst}`);
      }

      goFetch('');
    </script>

Fixing getForTeacher()

Let's fix getForTeacher() so that it fetches every teacher if the user selects the "Choose teacher" option (the default).

For this, we need to make sure that if the value of the SELECT is 0 (the default option value), we don't include the substitute_id in the URL to fetch:

      function getForTeacher() {
        let subst = document.getElementById('teachers').value;
        let query = '';
        if (subst !== '0') {
          query = `&substitute_id=${subst}`;
        }
        document.querySelector('ul').innerHTML = '';
        goFetch(`${query}`);
      }

Alternatively, we could put the document.querySelector('ul').innerHTML = ''; (for emptying the list) inside goFetch() too. See below.

Complete page

The complete web page is now:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Fetch</title>
  </head>
  <body>
    <p>
      <select name="substitute_id" onchange="getForTeacher()" id="teachers">
      <option value="0">Choose teacher</option>
      <option value="1">Rikard</option>
      <option value="2">Henrik</option>
      <option value="3">Anders</option>
      <option value="4">Nahid</option>
      </select>
    </p>
    <ul></ul>
    <script>
      function getLi(date, name, school) {
        let li = document.createElement('li');
        li.appendChild(document.createTextNode(`At ${date},` +
          ` ${name}` +
          ` is assigned to ${school}`));
        return li;
      }

      function goFetch(params) {
        fetch(`http://localhost:8080/v1?format=json${params}`)
        .then(response => response.json())
        .then(result => {
          document.querySelector('ul').innerHTML = ''; // always clear the UL list
          result.forEach(assignment => {
            document.querySelector('ul').appendChild(getLi(assignment.date, assignment.substitute.name, assignment.school.school_name));
          });
        });        
      }

      function getForTeacher() {
        let subst = document.getElementById('teachers').value;
        let query = '';
        if (subst !== '0') {
          query = `&substitute_id=${subst}`;
        }
        goFetch(`${query}`);
      }
      
      goFetch('');

    </script>
  </body>
</html>

Further reading

Swedish web course @ jonkri.se

Tested

This example is tested in Chrome™ and Firefox™. It seems like the json() method doesn't work on InternetExploder. We see no reason for using that browser anyway. Should work in Safari 10.1 and later versions.