Building Web Applications

In this code lab we will build a single page application with a 3rd party integration. Single Page Applications are websites which aim to offer an ‘app like' behavior. This is mainly achieved by providing experiences which do not cause page loads(reloads or redirecting), hence the name "Single Page". Instead we rely on javascript to show different parts of our application and perform AJAX to retrieve and send data to an application server.

This lab presents a client side rendered alternative implementation of the app built in lab 4 which was server side rendered. For your project you may choose either architecture.

Task 1

Follow the link below to get started.

Open Lab 6

When creating large web applications it is desirable to split your javascript into files to improve the manageability of the code base. util.js contains two functions which would be reused at different parts of the application.

util.js contains three things:

server : a variable which stores the url of our application server.
Note: you will need to replace this with your fork of the completed lab 3 repl.

Lab 3 Completed

toast() : a function which makes use of materialize to flash messages to the user

sendRequest()

sendRequest() does the following:

  1. accepts a url, method and data (optional) parameters
  2. checks localstorage for an entry called ‘access_token'
  3. If data was supplied adds it to an http request
  4. adds the ‘access_token' to the header of the http request
  5. Sends the http request at the specified method, url and data
  6. Returns the data from the response

Form Submission

The default behavior of an HTML5 form submission is to have the browser make an HTTP request to the url provided in the action attribute of the form. However, this would cause a redirect and thus is not allowed in a SPA.

Instead we let javascript interrupt the submission, get the data from the form and send the http request in the background.

Task 3

Update signup.js with the following.

async function signup(event){
  event.preventDefault();//prevent page redirect

  let form = event.target;
  let fields = event.target.elements;
  
  let data = {
    username: fields['username'].value,
    email: fields['email'].value,
    password: fields['password'].value,
  }

  //reset form
  form.reset();

  //send data to application server
  let result = await sendRequest(`${server}/signup`, 'POST', data);
  
  if('error' in result){
    toast("Login Failed: "+result['error']);//show error message
  }else{
    toast("Logged Successful");
    window.location.href= 'index.html';//redirect the page
  }
}
//attach signup to submit event of form
document.forms['signUpForm'].addEventListener('submit', signup);

signup() retrieves the data from the form and uses sendRequest() to send the data to the application server. The function also uses toast() to flash an appropriate message based on the response of the server. Then redirects the browser by changing window.location.href

The form in signup.html should now be functional and redirect to the login page when a user is successfully created.

login.js already has code similar to signup.js which submits the form and submits to the application server. You can now log in with the user credentials given in the sign up page. You open the application tab in chrome dev tools (F12) to inspect the JWT in localStorage.

Software applications are usually comprised of multiple views/screens, for many websites this is achieved by hosting multiple web pages linked to each other. Websites of this kind are sometimes referred to as multi-page applications.

However, in single page applications the markup for all of our views are either

  1. Written in a single file page.
  2. Injected into the page using javascript at runtime.

INFO 1601 Lab 9 is an example of a SPA using approach 2 where ajax was used to navigate to different sections of the application.

In this lab we use materialize css tabs to implement the 1st approach. The markup for both of our tabs exist in index.html but materialize would only show the contents of one tab at a time.

index.html

By using tabs we can add multiple views to index.html. Note that materialize can only do this if the tab elements have been properly initialized in javascript.

app.js

Task 4

Update the contents of the todo tab (div with id todo) with the following:

index.html

<section id="todo" class="col s12 container">

  <div class="card" >
    <div class="card-content">
      <span class="card-title">Create Todo</span>
      <form name="addForm">
        <div class="input-field">
          <input type="text" name="addText" placeholder="enter to-do" class="materialize-textarea"></textarea>
          <label for="addText">Enter todo Text</label>
        </div>
        <input class="btn white-text" type="submit" value="SAVE" />
      </form>
    </div>
  </div>

  <ul class="collection with-header" id="result"></ul>

</section>

The markup should add a form to the page.

Dynamic Applications

Applications are described as dynamic when a scripting language is used to dynamically write html to the page. Often this is done to present data which is stored in a database.

Application Servers

As web applications run on the browser they typically do not talk to databases directly for security reasons. Instead an application server would sit on the internet, query the database and expose the desired data via a url for our web applications.

Web Service API

The set of urls supported by an application server is often referred to as a web service API (Application Programming Interface) as they provide an interface for our web applications to the database.

Performing AJAX

Web applications rely on AJAX (Asynchronous Javascript & XML) which is a model whereby javascript makes an HTTP request to an application server then dynamically writes html with the data in the response.

https://lab5-completed.herokuapp.com/todos is an endpoint of the flask application from lab5 deployed on heroku. Below is the response when an http request is made to the endpoint however the data is restricted to authenticated users

Task 5

Update app.js with the following functions which perform ajax to render the todo data onto the page.

apps.js

async function displayTodos(data){

  let result = document.querySelector('#result');//access the DOM

  result.innerHTML = '';//clear result area
  
  let html = '';//make an empty html string 

  if("error" in data){//user not logged in 
    html+= `
      <li class="card collection-item col s12 m4">
                <div class="card-content">
                  <span class="card-title">
                    Error : Not Logged In
                  </span>
                </div>
        </li>
    `;
  }else{
    for(let todo of data){
      html+= `
        <li class="card collection-item col s12 m4">
                  <div class="card-content">
                    <span class="card-title">${todo.text}
                      <label class="right">
                        <input type="checkbox" data-id="${todo.id}" onclick="toggleDone(event)" ${todo.done ? 'checked': ''} />
                        <span>Done</span>
                      </label>
                    </span>
                  </div>
                  <div class="card-action">
                    <a href="#" onclick="deleteTodo('${todo.id}')">DELETE</a>
                  </div>
          </li>
      `;//create html for each todo data by interpolating the values in the todo
    }
  }
  
  //add the dynamic html to the DOM
  result.innerHTML = html;
}

async function loadView(){
  let todos = await sendRequest(`${server}/todo`, 'GET');
  displayTodos(todos);
}

loadView();

We haven't created any user data so the code added wouldn't have any visual effect just yet.

Next we add the functionality to create user data.

Task 6

Add the following functions to app.js.

async function createTodo(event){
  event.preventDefault();//stop the form from reloading the page
  let form = event.target.elements;//get the form from the event object

  let data = {
    text: form['addText'].value,//get data from form
    done: false,// newly created todos aren't done by default
  }

  event.target.reset();//reset form

  let result = await sendRequest(`${server}/todo`, 'POST', data);

  if('error' in result){
    toast('Error: Not Logged In');
  }else{
    toast('Todo Created!');
  }
    
  loadView();
}

//attach createTodo() to the submit event of the form
document.forms['addForm'].addEventListener('submit', createTodo);

The form should now be functional and newly created todos should render on the page with a message.

Applications can also support updating and deleting data, this is done by having javascript make the appropriate http request in the background then reloading the data from the database so the user can see the result.

Task 7

Add the following to scripts.js

async function toggleDone(event){
  let checkbox = event.target;

  let id = checkbox.dataset['id'];//get id from data attribute

  let done = checkbox.checked;//returns true if the checkbox is checked
  let result = await sendRequest(`${server}/todo/${id}`, 'PUT', {done: done});

  let message = done ? 'Done!' : 'Not Done!';
  toast(message);
}

async function deleteTodo(id){
  let result = await sendRequest(`${server}/todo/${id}`, 'DELETE');

  toast('Deleted!');

  loadView();
}

When previewing the app you can observe functioning updates and deletion of the todos.

Lastly we implement a simple logout function by deleting the JWT from localStorage then redirecting the page. The logout click is already set to call the login function on click.

Task 8

Update app.js with the following

function logout(){
  window.localStorage.removeItem('access_token');
  window.location.href ="index.html";
}

Clicking the link should now take you back to the login page. If you navigate to app.html without logging in you should see the following error message.

Attempting to create a todo while not logged in should also flash an error message.

We rely on data attributes to send data to our elements. Then using the event object we can get the element in our event handlers. From there, we access the data by using the element's dataset property.

Example

Understanding this pattern can go a long way in the implementation of the view logic of our web applications.

References & Additional Reading

Single Page vs Multi Page Apps

Imgur API Documentation

https://codeburst.io/jwt-authorization-in-flask-c63c1acf4eeb

https://pythonbasics.org/flask-cookies/

https://flask.palletsprojects.com/en/1.1.x/security/

https://realpython.com/token-based-authentication-with-flask/

https://security.openstack.org/guidelines/dg_cross-site-scripting-xss.html