Overview

An essential part of Dodgeball is the ability to move as quickly as possible. The Purple Cobras Dodgeball team have decided that their Autocorrect website doesn't move quite enough (or at all). They demand that you change their website by learning JavaScript so it can move as quickly as they can. Dive! Duck! Dodge! Dip! and get on that JavaScript.

In Stars, you sent information that was entered by the user to your Java backend. Then, this backend rendered a new page that would show up to the user. This model can be very useful, but in many situations it's necessary to be able to send and receive information without leaving or completely reloading a page. This lab will give you some practice with this kind of programming; in this lab, we’ll update Boggle to validate words and update the score as the player enters them, without reloading the page.

Getting Started

Stencil code!

What is JavaScript?

JavaScript is the standard programming language used on the client side of web browsers; it’s used to make content dynamic on web pages. For the remainder of the course, you'll be using JavaScript to make your browser interfaces more interactive and friendlier to users.

In this brief tutorial, you will learn the basics of programming in JavaScript. Begin by opening your Developer console for this web page. Right-click on the web page, choose "Inspect Element", and navigate to the "Console" tab (or, use the shortcut on Windows: Command + Shift + J or Mac: Command + Option + J).

You will use this console to write JavaScript that directly modifies and interacts with this web page. Every time you see a code block in the handout below, type (or paste) the code into the console and press "Enter" to run it.

Try entering the following into the console:

console.log("Hello World!");

Your browser will execute this expression, and then try to output a return value for the code you entered. You should see a little gray undefined after "Hello World!" is printed. For the sake of this lab, think of undefined as the JavaScript equivalent of Java's null. console.log() is a function that’s similar to Java's System.out.print() method, which returns nothing (i.e. void). We'll go into some tricks for debugging JavaScript later, but it's important to remember that JavaScript will return undefined if nothing is explicitly returned.

Declaring Variables

JavaScript stores data in variables. The newest version of JavaScript, ES6, introduces two ways to declare variables: const and let.

const creates a read-only reference to a value. Primitive types, such as strings, booleans, and numbers, can’t be changed. const should always be your first choice when declaring a variable.

    const name = "East Side Pockets";
    const rating = 10;
    const isDelicious = true;

    name = "Chipotle"; // this will return an error!

Note that unlike Java, which makes you explicitly say what kind of data a variable is (e.g. String, boolean, int), JavaScript doesn't care. (Not to say that JavaScript doesn't know the difference between types; try running typeof name and then typeof age in your console.)

In addition to these data types you are familiar with, JavaScript also has objects, which are variables that contain information in the form of key value pairs. It's useful to think of JavaScript objects as convenient hash maps that are flexible enough to store different data types as values. (ES6 has also introduced a specific type, Map, which has some advantages, but we'll ignore it for now.)

    const restaurant = {
      name:"Kabob and Curry",
      rating: 9,
      isDelicious: true,
      neighbors:  ["sandwich.", "Chipotle"]
    }

    console.log(restaurant.name + " is rated " + restaurant.rating + "/10");

You can directly change the values in an object like so:

    restaurant.rating = 10;
    console.log(restaurant.rating);

Notice that we declared restaurant with the keyword const, but we were still able to change the restaurant name. This is because if an object is declared with const, the content (i.e. the fields) of the object can be altered; you just can’t assign a completely new object to the variable. In other words, objects declared with const are not guaranteed to be immutable because we can change their fields, just not what object is assigned to the variable. The situation is just like final in Java.

If you need to change the value of a primitive, you can use the keyword let. A common use case for let is in a for loop:

    for (let i = 1; i < 10; i++) {
      if (i === 1) {
        console.log(restaurant.name + " served " + i + " meal.");
      } else {
        console.log(restaurant.name + " served " + i + " meals.");
      }
    }

As you can see, JavaScript has very similar control flow and loops to Java. However, one unique aspect of JavaScript noticable in the previous example is the triple equals (===). The === will evaluate to false if the types of it's inputs do not match, while == will implicitly cast the inputs to the same type. You should probably never use == in this class because === is almost certainly the type of equality you're thinking of. For example, [] == ![];, false == [];, false == ![];, and !!"false" == !!"true"; all return true. In general, it's better practice to use === because it acts similiarly to a type-checker.

If you’ve ever used JavaScript before, you might have used or seen the keyword var to declare variables. Don’t do this! Compared to const and let, var has really confusing behavior and is bad practice. If you use var to declare variables in your projects, you will likely lose points.

Why var is Bad

    function f() {
      bla = 2;
      var bla;
      console.log(bla);
    }
    f(); // what is printed?
  
This prints 2! This example is from Mozilla. It shows that var bindings are actually lifted to the top of the scope they're declared in. Variables declared with var do not have lexical scope (the kind you are used to in Java)! For example,
    function g() {
      if (true) {
          var a = 1;
      } else {
          var b = 2;
      }
      console.log(a);
      console.log(b);
    }
    g(); // what is printed?
  
On the other hand, variables declared with let and const have lexical scope:
    function g() {
      if (true) {
        const a = 1;
        console.log("a in scope here and it ===", a);
      } else {
        const b = 2;
      }
      console.log("you should see exactly two errors printed next");
      try {
        console.log(a);
      } catch (err) {
        console.log(err);
      }
      try {
        console.log(b);
      } catch (err) {
        console.log(err);
      }
    }
    g();
  

Numbers

  0.1 + 0.2 === 0.3; // false
  1 === 1.0; // true

Every number in JavaScript is a 64-bit floating point arithmetic number, even if you don't write the decimal point in the number.

Functions

Functions are declared with the function keyword. If a function does not explicitly return a value, it will return undefined.

  function addThreeNumbers(a, b, c){
      return a + b + c;
  }

  addThreeNumbers(1, 2, 3);

  function sayName(person){
    console.log("My name is " + person.name + ".");
  }

  sayName(character);

What do you think will happen when you run the code below?

  sayName("Jessica");

In the previous statement, the string "Jessica" does not have a field called name, which is why "Jessica".name has a value of undefined. In Java, if you tried to access a nonexistent field of an object, this error would be caught at compile time. JavaScript is much more lenient than Java, and as you just saw, doesn't even throw an error while your program is running! Later in the course when you are debugging your programs that use many complex JavaScript objects, keep in mind that JavaScript doesn't complain when you try to access a field that isn't there.

ES6 introduced the arrow function, which is a cleaner, more concise way to write functions as expressions. Read about them here. In general, we recommend using arrow functions in the same types of cases where "lambdas" are used in Java. You'll also see examples of arrow functions in this week's stencil code.

Arrow Functions Are Different

Arrow functions are not a direct substitute for functions declared through function. For example, in function declared functions, this, arguments, super, and some other identifers are keywords (read the Mozilla documentation). Additionally, it can sometimes be harder use debuggers with arrow functions in cases where they are used as anonymous functions.

This

The keyword this does not behave the same as Java's this. In Java, this is a reference to the current object. However, in JavaScript, this can refer to many different things. You should definitely read about this here, here, or here. Be careful when using this in callback functions and in a method vs a standalone function.

IIFE (Immediatedly Invoked Function Expression)

You might have noticed that encapsulation is hard in JavaScript because there is no private, protected, etc. keywords like in Java. A common pattern in JavaScript is using an IIFE to hide variables in some intermediate computation. For example:

  const importantValue = (function (arg) {
    // does some important computation
    const stepOne = arg + arg;
    return stepOne + stepOne;
  })("input");
  // from here, importantValue is in scope but the intermediate stepOne is not
This pattern is also useful when you want to insert some values into a webpage after the page loads, but you don't want the variables to live longer than necessary or to pollute your scope.

Classes

ES6 also introduced classes, which provide a simple and clear syntax to create objects and deal with inheritance.

We can define a class by creating a class declaration.

  class Alien {
    constructor(name, numberOfArms) {
      this.name = name;
      this.numberOfArms = numberOfArms;
    }
  }

  const bob = new Alien("Bob", 8);
  console.log(bob.name + " has " + bob.numberOfArms + " arms.");

Classes are a nice way to write JavaScript in a more object-oriented way. As we begin adding more complex GUIs to our projects, classes will become essential to properly structuring and separating your JavaScript logic.

Modern JavaScript

There are many flavors of JavaScript, which was introduced originally in 1995 for web form validation. Today, there is TypeScript, strict mode, Node.js, React, React Native, Flow and more. These languages/frameworks/modes aim to fulfill the goal of "write once, run everywhere." You should also probably know that Babel and Webpack exist to bundle up UI code, styling, and assets (pictures, css files, and more) efficiently.

jQuery

jQuery is a popular JavaScript library that simplifies interacting with the DOM (Document Object Model). It is especially useful for selecting and manipulating DOM elements, animation, and sending AJAX requests, which will be a focus of this lab.

The jQuery syntax is tailor-made for selecting HTML elements and performing some action on the element(s).

The basic syntax is $(selector).action():

  • A $ sign to define/access jQuery
  • A selector to find certain HTML elements
  • And a jQuery action to be performed on the element(s)

jQuery Selectors

jQuery selectors are used to select certain HTML elements based on various properties such as name, ID, classes, types, attributes, and more. All selectors in jQuery start with the dollar sign and are surrounded by parentheses.

Examples:

  • $("p") - selects all <p> elements.
  • $("p.intro") - selects all <p> elements with the class "intro".
  • $(".microwave") - selects all elements with the class "microwave".
  • $("#redMicrowave") - selects the element with an ID of "redMicrowave". jQuery still returns an object that represents a set of elements here, but it will only have one item in it.

Let’s apply some of these new concepts. In the box below, there is a sample DOM element — a blue square. The first thing you will do with jQuery is select it.


The code for this container in our HTML for this page is the following:

  <div id="square"></div>

To select this element, use the jQuery selector command below:

  const $square = $("#square");

We have selected the element in the DOM that has the ID "square" and stored it as an object called $square. The $ at the beginning of our variable name has no purpose other than to remind us that $square is a jQuery object. So, we may call various jQuery functions on this object to manipulate it through the reference.

  $square.hide();

  $square.show();

  $square.css("background-color", "red");

  $square.css("width", "200px");

Here, we can hide and show an element on the page with the hide and show functions, and change its CSS properties using the css function. In addition, jQuery also lets us automatically modify DOM elements in response to certain events. For example, let’s say you wanted to randomly change the color of the rectangle below when it is clicked.


jQuery lets us accomplish this by "binding" an event listener to this element.

$("#rectangle").on("click", event => {
  function random256() {
    return Math.floor(Math.random() * 256);
  }

  $("#rectangle").css("background-color",
    "rgb(" + random256() + "," + random256() + "," + random256() + ")");
});

jQuery's on function takes two arguments: A string containing the events to be tracking, and a function that will be run whenever those events happen. If you don't have functional programming experience, it may seem strange to see a function passed as an argument, but think of it as saying, "When this event happens, this code should be run". In this example, on a "click" event, the anonymous function will calculate three random RGB values, and change the background color of the rectangle. Notice that our function that gets run takes a parameter "event". When this function is run, jQuery will pass useful information into this event parameter (the coordinates of the click, etc.) that you can use for more descriptive and interesting bindings.

There are many other ways to interact with page elements using jQuery other than these basic commands, and we encourage you to look through the extensive jQuery documentation when designing more complex and interesting interfaces later in the class.

AJAX

In the previous lab, we transmitted information to and from the server using synchronous requests. This means that every user action involved filling out a form, pressing "Submit", and being directed to a new page with new information. This process can be inefficient because we have to resend all of the content, even if we only want to update part of a page. It can also be poor for user experience, because the user has to wait for the entire page disappear and reappear again!

Now, we’ll learn a new technique called AJAX (from Asynchronous JavaScript and XML). An AJAX call allows you to request and receive data from a server asynchronously (i.e. in the background) without interfering with the display and behavior of the original page. Using AJAX (along with HTML templates, CSS, and JavaScript), you will be able to start writing more sophisticated web interfaces.

GET, POST, and JSON

jQuery has convenient methods to make GET and POST requests (two types of web requests) without leaving your current page. GET is typically used for getting (receiving) some data from the server. POST is typically used to submit some data to the server that will update or create some resource. (Side note: GET and POST are called HTTP verbs.)

The code below makes a GET request to the same server that served the current web page, but to the url "/tweets". When that requests returns, it executes the function passed as the second (sometimes third) parameter. This a powerful concept of asynchronous programming and is often referred to as a "callback". The function is invoked with an argument (response) that contains the data returned by the server.

// Note: This won't actually run on our site because "cs.brown.edu/tweets" does not exist!
$.get("/tweets", response => {
  console.log("We made a get request!");
  console.log(response);
  // Do something with the response here
});

POST requests are very similar. In a POST request, we also provide a data object as the second argument. That object is sent as request parameters for the server to inspect and operate on.

  const postParameter = {text: "hello?"};

  $.post("/newTweet", postParameter, response => {
    console.log("We made a post request!");
    console.log(response);
    // Do something with the response here
  });

At this point you might be wondering, "But CS32 TAs! I don't know if the server is written in Java, Ruby on Rails, Python, GoLang, or something else! How will I know what to do with the response object I get from the server?". That's an excellent question, and the answer is to choose a common text output format, regardless of what the server is written in. We'll use JSON.

JSON (JavaScript Object Notation) is a standard data interchange format, based on the syntax we described above for writing out JavaScript objects. If you have a JavaScript Object that you want to send in a POST request, it can be converted to a string of JSON like so:

  const myJson = JSON.stringify(object);

If you receive a JSON response from a GET or a POST request, you can parse it into a JavaScript object like this:

  const object = JSON.parse(response);

You can read more about JSON here. However, the commands above should be enough for you to convert between JSON and JavaScript objects.

Error Handling

You can attach an error handler in as a callback function like so:

  $.get("/tweets", data => {
    console.log("in the success callback");
    console.log(data);
  })
   .error(err => {
     console.log("in the .error callback");
     console.log(err);
   });

There are many ways to handle errors in Ajax, which can be found in the documentation.

Organization

You should strive to keep your JavaScript code free from bugs, maintainable, and readable (just like the Java code you write). Especially since JavaScript doesn't have static type checking, you should try even harder to choose good names, comment your code, breakout code into reusable abstractions, etc.

You can separate JavaScript code into different files. You should separate JavaScript code into different files when it becomes good to do so. Use your judgemnet. To use a function or class into another file, you need to include it in the HTML/Freemarker page. Here is the documentation. The order of the imports matter here.

  <script src="file-a.js"></script>
  <script src="file-b.js"></script>
  // resources in file-a.js are available in file-b.js, but file-b.js is not available to file-a.js

The Assignment

In this lab you will be creating a drop down and select menu for your autocorrect

  • When you type in the text box, autocorrect suggestions should automatically appear below the box
  • When a suggestion is clicked, it should replace the word currently in the text box

We’ve left TODOs in the main.js and Main.java files that point out where and how to make these changes.

Preparing to Receive Post Requests

We need to do a few things in order to respond to our front end's autocorrect needs

  1. Fill in the ResultsHandler class located in Main.java to query the value of the input and to output the suggestions from the Autocorrecter instance (follow the TODOs)
  2. Create a call to Spark.post() in order to map a url to our ResultsHandler

Your ResultsHandler will be implementing the Route interface. You can read the documentation on this interface here. Essentially, your handler will be invoked when a request is sent to its corresponding url, which is set in your Spark.post call and it will take in information about the post request made in the front end and will return a response object for which will be returned to the front end.

Interacting with the Server

To get the results from our autocorrecter in the backend, we can make a POST request to the url you set in your call to Spark.post. Your POST request will require the player’s guess to be sent along with it in the form of an object. This object contains the following field:

  • text: string that represents the word the user inputted and wants to be autocorrected

The response received from the POST request will also be an object, which by convention is usually named responseObject. This object contains the suggestions from the autocorrector. You should use the response object to update the current page with a drop down menu of suggestions which when clicked will replace the text in the input text box. The response object contains the following field:

  • results: an array of the top five suggestions generated by the autocorrecter

Follow the TODOs in main.js for more detailed information

Accessibility Testing

Turn on the default screen reader for your computer. For instructions on how to do this, see the HTML lab. Use the screen reader to navigate your autocorrect website. Think about what might be difficult or easy. What functionality disappears, if any, when using your screen reader? If you didn't already know what the site were for, would you know how to interact with it without looking at it?

AFAQ (Anticipated Frequency Asked Questions)

Q: My JavaScript and ResultsHandler logic seem to be fine but nothings happening in the frontend? A: Check that your --data flag is set correctly (--data=data/dictionary.txt is an example) and the word you typed in has a valid result in that file (try using dictionary.txt as it contains all words). Additionally make sure you turned on prefix, whitespace, and/or led or else you may just be getting no autocorrect results. Try --data=data/dictionary.txt --led=2 --prefix and type 'hello' into the input box, this should generate results.

Q: JavaScript not working / "classes not supported by current JS version"? A: Choose the ES6 the config of the linter you're using.

Q: I really don't understand how to write JavaScript? A: Come to lab hours!

Finishing Up and Check Off

When you finish this lab your autocorrect will be interactive! When you're ready to get checked off, call the TA look over your lab.