CS50W Lecture 5 – Client vs Server, why JS?

Why Javascript

Currently, we have learned how to use Python for the back-end, using Flask microframework to get the app up and running. (We can also use Node.js for the back-end code, but that’s something we’ll learn later.)

It is useful to differentiate code that is run by the client (front-end) and the server (back-end). These are some considerations of how you might partition the code:

  1. Client-side processes reduce load on server, and are often faster.
  2. Server-side processes hide the architectural layers you do not want exposed to the client.

JavaScript is a programming language that was developed to be run inside a web-browser, and modern browsers have JS engines (Chrome’s V8 and Firefox’s SpiderMonkey) that can execute the code very quickly. There are many versions of JS, and we will be using ES6 in CS50W.

As with CSS stylesheets, JS can be saved as a separate file and referenced in the HTML page:

<script src="main.js"></script>

In the JS file we can write the functions that will be manipulating the DOM (Document Object Model) when certain events happen.

JavaScript events include:

  • onmouseover : triggers when an element is hovered over
  • onclick : triggers when an element is clicked
  • onkeydown : triggers when a key is pressed
  • onkeyup : triggers when a key is released
  • onload : triggers when a page is loaded
  • onblur : triggers when an object loses focus (when moving away from an input box, for example)

Let’s look at a simple DOM manipulation function:

document.addEventListener('DOMContentLoaded', function() {
document.querySelector('button').onclick = count;
});

let counter = 0;

function count() {
count++;
document.querySelector('#counter').innerHTML = counter;

if (counter % 5 === 0) {
alert(`Counter is at ${counter}!`);
}
}

  • document refers to the webpage currently displayed.
  • addEventListener listens to events (in the first one, ‘listening’ for webpage to load before calling the nested function
  • JS uses higher order functions, so functions can be passed like values (eg: count). The function being passed is called a ‘callback function’. In the above case, the callback sets the onclick property of the button element to the count function.
  • querySelector(”) is a function that searches through your DOM for a CSS selector and returns the first result.
  • querySelectorAll(”) returns all results.
  • innerHTML is the content contained within the element’s tags.

 

Variables

  • const – constant variable, can’t be redefined
  • let – scoped to the innermost pair of curly braces
  • var – scoped to the function it is in

 

Arrow functions

You can use arrow/ anonymous functions in ES6:

() => { alert('Hello World')}
x => x *2;

 

Tasklist Demo

document.addEventListener('DOMContentLoaded', () => {


                  document.querySelector('#new-task').onsubmit = () => {

                      // Create new item for list
                      const li = document.createElement('li');
                      li.innerHTML = document.querySelector('#task').value;

                      // Add new item to task list
                      document.querySelector('#tasks').append(li);

                      // Clear input field
                      document.querySelector('#task').value = '';

                      // Stop form from submitting
                      return false;
                  };

              });

This creates a tasklist that adds a task on a list when submitted.

Integrating JavaScript with Python and Flask

  • Ajax, is used to get more information from server without needing to reload an entirely new page. As an example, Ajax can be used with the currency conversion example from last week to display a conversion without needing to load a new page. This is not done by pre-loading all possible exchange rates, but by making an Ajax request to the Flask server, which will get a particular exchange rate whenever it is asked for. JavaScript can then be used to update the DOM to render the new content.
  • Here’s the interesting part of application.py. There’s not much different here from last week, but note that what’s being returned is not a new webpage, but rather just a JSON object.
      @app.route("/convert", methods=["POST"])
      def convert():
    
          # Query for currency exchange rate
          currency = request.form.get("currency")
          res = requests.get("https://api.fixer.io/latest", params={
              "base": "USD", "symbols": currency})
    
          # Make sure request succeeded
          if res.status_code != 200:
              return jsonify({"success": False})
    
          # Make sure currency is in response
          data = res.json()
          if currency not in data["rates"]:
              return jsonify({"success": False})
    
          return jsonify({"success": True, "rate": data["rates"][currency]})
    
  • The HTML is simply a basic form. The JavaScript code is in a different file, but linked in the head.
      <html>
          <head>
              src="">
              <title>Currency Converter</title>
          </head>
          <body>
              <form id="form">
                  <input id="currency" autocomplete="off" autofocus placeholder="Currency" type="text">
                  <input type="submit" value="Get Exchange Rate">
              </form>
              <br>
              
    id=“result”>

     

    
          </body>
      </html>
    
    • url_for('static', filename='index.js') is Flask’s way of incorporating .js files. static is a separate folder.
    • The result div will contain the conversion, but is currently blank.
  • The interesting code is inside of index.js.
      document.addEventListener('DOMContentLoaded', () => {
    
          document.querySelector('#form').onsubmit = () => {
    
              // Initialize new request
              const request = new XMLHttpRequest();
              const currency = document.querySelector('#currency').value;
              request.open('POST', '/convert');
    
              // Callback function for when request completes
              request.onload = () => {
    
                  // Extract JSON data from request
                  const data = JSON.parse(request.responseText);
    
                  // Update the result div
                  if (data.success) {
                      const contents = `1 USD is equal to ${data.rate} ${currency}.`
                      document.querySelector('#result').innerHTML = contents;
                  }
                  else {
                      document.querySelector('#result').innerHTML = 'There was an error.';
                  }
              }
    
              // Add data to send with request
              const data = new FormData();
              data.append('currency', currency);
    
              // Send request
              request.send(data);
              return false;
          };
    
      });
    
    • An XMLHttpRequest is just an object that will allow an Ajax request to be made.
    • request.open is where the new request is actually initialized, with the HTTP method and route being specified.
    • JSON.parse converts the raw response (request.responseText) into an object that can be indexed by keys and values.
    • The rest of the callback simply updates the HTML using template literals to reflect the result of the conversion.
    • FormData is just an object that holds whatever the user input is.

 

todo: websockets

 

Websockets

  • The request-response model, which has been the basis for how HTTP requests and client-server interaction has been discussed so far, is useful as long as data is only being passed when a request is made. But, with ‘full-duplex communication’, more simply described as real-time communication, there is (or shouldn’t be) a need for reloading a webpage and making a new request just to check, for example, if someone sent a message in a chat room. Websockets are a protocol that allow for this type of communication, and Socket.IO is a particular JavaScript library that supports this protocol.
  • This example will be based around a voting application that will count and display votes in real-time. Here’s the full application.py, with all the setup and import statements.
      import os
      import requests
    
      from flask import Flask, jsonify, render_template, request
      from flask_socketio import SocketIO, emit
    
      app = Flask(__name__)
      app.config["SECRET_KEY"] = os.getenv("SECRET_KEY")
      socketio = SocketIO(app)
    
      @app.route("/")
      def index():
          return render_template("index.html")
    
      @socketio.on("submit vote")
      def vote(data):
          selection = data["selection"]
          emit("announce vote", {"selection": selection}, broadcast=True)
    
    • flask_socketio is a library that allows for websockets inside a Flask application. This library allows for the web server and client to be emitting events to all other users, while also listening for and receiving events being broadcasted by others.
    • submit vote is an event that will be broadcasted whenever a vote is submitted. The code for this will be in JavaScript.
    • Once a vote is received, the vote is announced to all users (broadcast=True) with the emit function.
  • index.html:
      <html>
          <head>
              type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.6/socket.io.min.js">
              src="">
              <title>Vote</title>
          </head>
          <body>
              <ul id="votes">
              </ul>
              <hr>
              <button data-vote="yes">Yes</button>
              <button data-vote="no">No</button>
              <button data-vote="maybe">Maybe</button>
          </body>
      </html>
    
  • index.js:
      document.addEventListener('DOMContentLoaded', () => {
    
          // Connect to websocket
          var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port);
    
          // When connected, configure buttons
          socket.on('connect', () => {
    
              // Each button should emit a "submit vote" event
              document.querySelectorAll('button').forEach(button => {
                  button.onclick = () => {
                      const selection = button.dataset.vote;
                      socket.emit('submit vote', {'selection': selection});
                  };
              });
          });
    
          // When a new vote is announced, add to the unordered list
          socket.on('announce vote', data => {
              const li = document.createElement('li');
              li.innerHTML = `Vote recorded: ${data.selection}`;
              document.querySelector('#votes').append(li);
          });
      });
    
    • First, the websocket connection is established using a standard line to connect to wherever the application is currently running at.
    • submit vote is the name of the event that’s being submitted on a button click. That event just sends whatever the vote was.
    • announce vote is an event received from the Python sever, which triggers the updating of the vote list.
  • An improvement to this application would be to display a total vote count, instead of just listing every individual vote, and making sure that new users can see past votes.
  • Changes to application.py:
      votes = {"yes": 0, "no": 0, "maybe": 0}
    
      @app.route("/")
      def index():
          return render_template("index.html", votes=votes)
    
      @socketio.on("submit vote")
      def vote(data):
          selection = data["selection"]
          votes[selection] += 1
          emit("vote totals", votes, broadcast=True)
    
    • Now, any vote submissions are first used to update the votes dictionary to keep a record of vote totals. Then, that entire dictionary is broadcasted.
  • Changes to index.html:
      <body>
          
    Yes Votes:  id="yes">

    No Votes: id=“no”>

    Maybe Votes: id=“maybe”>

     

    <hr> <button data-vote=“yes”>Yes</button> <button data-vote=“no”>No</button> <button data-vote=“maybe”>Maybe</button> </body>

    • The span elements allocate a space for vote tallies to be filled in later.
  • Changes to index.js:
      socket.on('vote totals', data => {
          document.querySelector('#yes').innerHTML = data.yes;
          document.querySelector('#no').innerHTML = data.no;
          document.querySelector('#maybe').innerHTML = data.maybe;
      });

Leave a comment

Design a site like this with WordPress.com
Get started