CSE 154

Section 6: Practice with JS and the DOM/Events

Agenda

  1. Summarize DOM selection with JS
  2. JS practice with DOM and events:
    • Option 1: Encrypt-It!
    • Option 2: Creative Project worktime

Reminder: Exploration Session today on Bootstrap!

Recall the DOM

Consider the DOM representation of yesterday's HTML page:

Groupizer DOM Tree

With JS, we can now really appreciate the DOM structure when thinking about selecting and modifying HTML elements.

Refreshing our Selector Skills

Groupizer DOM Tree

What CSS selector would you use to select the unordered list on this page?

What CSS selector would you use to select the inputs in that list?

Using Selectors in JS

Methods in document and other DOM objects:

Name Description
querySelector(selector) returns the first element that would be matched by the given CSS selector string
querySelectorAll(selector) returns an array of all elements that would be matched by the given CSS selector string

Back to groupizer HTML

What CSS selector would you use to select the unordered list on this page?

What CSS selector would you use to select the inputs in that list?

Now try using document.querySelector and querySelectorAll to select the DOM elements HTML page using the Chrome Dev Tool!

Summarize: How to get DOM elements in JS

  1. Ask for them by id: document.getElementyById(...)
  2. Query for them with CSS style selectors:
    • document.querySelector(...)
    • document.querySelectorAll(...)

Common querySelectorAll issues

Many students forget to write . or # in front of a class or id

// get all buttons with a class of "control"
let gameButtons = document.querySelectorAll("control");
let gameButtons = document.querySelectorAll(".control");

JS

querySelectorAll returns an array, not just a single element;
you must loop over the results

// set all buttons with a class of "control" to have red text
document.querySelectorAll(".gamebutton").classList.add("hidden");
let gameButtons = document.querySelectorAll(".gamebutton");
for (let i = 0; i < gameButtons.length; i++) {
  gameButtons[i].classList.add("hidden");
}

JS

Q: Can I still select a group of elements using querySelectorAll even if my CSS file doesn't have any style rule for that same group? (A: Yes!)

Getting the first element of a certain type

Use querySelector to highlight the first paragraph in the document:

<body>
  <p>This is the first paragraph</p>
  <p>This is the second paragraph</p>
  <p>You get the idea...</p>
</body>

HTML

// get all DOM objects that are <p>
let para = document.querySelector("p");
para.classList.add("highlighted");

JS

Handy Shortcut Functions

Two methods we will use a LOT are document.getElementById and document.querySelectorAll. It's handy to declare a shortcut to help us out. You may use the following in your JS programs (these are exceptions to the rule of having description function names):

function id(idName) {
  return document.getElementById(idName);
}

function qs(selector) { // less common, but you may find it helpful
  return document.querySelector(selector);
}

function qsa(selector) {
  return document.querySelectorAll(selector);
}

JS

Example:

<button id="encrypt-btn">Encrypt-It!<button>
          

HTML

let button  = document.getElementById("encrypt-btn");
let button2 = id("encrypt-btn");               // returns the same as above.

JS

We will start using these in examples!

Rest of Section

Worktime for Encrypt-It lab (following slides) or CP2!

Encrypt-It!

Encrypt-It solution output

Encrypt-It Overview

In this series of exercises, you will be practicing using different UI elements in HTML with a JavaScript program to perform some operations on user input. Specifically, you will finish a cryptogram generator, which takes any message as input and outputs a cipher message in a few different possible outputs.

We have provided encrypt-it.zip with starting HTML for you to use and build upon. We have also included some CSS, although you're free to add styling on your own!

Overview of HTML Page

output

The slides below describe some of the different HTML elements in the starter HTML, which you may find helpful for following the JS requirements (and learn a bit more about common types of UI elements)!

Overview of HTML Page: Grouping UI Elements

The starter HTML introduces two new elements that are useful for grouping UI controls:

  • fieldset: A container to group UI elements
  • legend: A legend for a fieldset which appears by default as the text within the top-left border

Note that we've also seen the form used to group UI elements, but since we're not submitting anything here, we choose to use a fieldset.

Overview of HTML Page: User input with textarea

In the first fieldset we have a textarea for users to enter large (multi-line) amounts of text. This will be used for the input to generate output ciphers.

Overview of HTML Page: Customization Controls

The second fieldset represents options for the cipher your program will generate from the input text. In this fieldset, we have:

  • An Encrypt-It! button and a Reset button
  • A select dropdown menu with the options for Shift Cipher and Randomized.
  • Two radio button elements with output font size options: 12pt and 24pt.
  • A checkbox element for customizing the upper-case option of the output message.

Note: Wrapping the radio buttons and checkbox elements with their labeled text in a label makes it so that a user can click on the text to check/uncheck.

Part I: Write the start of encrypt-it.js

Now you'll write a bit of JavaScript testing code that logs a message to the console. This is just a test to make sure that your browser is running your JavaScript file, before we move on to the main execises.

  • The starter zip file includes a starter JS file, which uses the module-pattern introduced this week.
  • Put the following line of code into the file: console.log("Window loaded!"); such that the message is logged to the console when the page is loaded
  • Link your HTML page to your JavaScript file using a script tag
  • Refresh your page in the browser. Do you see the console message? If so, move on. Otherwise, double-check your script tag syntax or ask a TA for help.

Part II: Testing a Button

Now let's set up a very basic JS event handler. Modify your JS code and so that the "Button clicked!" console message won't output until the user clicks the "Encrypt-It!" button.

Strategy:

  • First, make sure you understand why the event listener for window's load event is needed (remeber we don't have access to the DOM until the page is loaded).
  • Move your console statement inside a new function handleClick.
  • Add an event listener to the the "Encrypt-It!" button such that when clicked, your handleClick function is called.
  • Refresh your page in the browser. Click the button. Do you see the console.log message? If so, move on. Otherwise, double-check the syntax and for both of your event listeners (window load and button click), or ask a TA for help

Part III: Implementing a Basic Shift-Cipher

Modify your JS code so that when the user clicks "Encrypt-It!", the text in the input text area will be encrypted using a basic shift-cipher, and output into the page's paragraph element with the id of output.

Details:

  • To get text from the textarea, you'll need to make sure you can access it from JS. Remember you can use document.getElementById or document.querySelector to access a DOM element in JS.
  • Modify (and appropriately rename) your handleClick function so that when called, it now retrieves the textarea's text value and generates a shift cipher (algorithm discussed on the slide below; a solution for this cipher function is provided at the very bottom if you'd like to skip the algorithm part). This generated cipher will be output as text in the #result paragraph.

Part III: The Shift-Cipher Algorithm

The rules of a shift cipher are fairly straightforward. Let the English alphabet we all know and love(?) be called A. Let the shift-encrypted alphabet be called C. For simplicity, we will shift letters in our encryption function by 1 letter. Then C is defined as mapping each letters in A to the letter alphabetically next. For example, 'a' is mapped to 'b', 'b' is mapped to 'c', ... and 'z' is mapped to 'a' (creating a cycle of 26 letters). In this exercise, we will consider uppercase letters and lowercase letters equivalent to one another (ie, 'a' is considered equal to 'A').

Visually, the cipher can be represented as the following:

input letter     a b c d e f g h i j k l m n o p q r s t u v w x y z
                 | | | | | | | | | | | | | | | | | | | | | | | | | |
                 v v v v v v v v v v v v v v v v v v v v v v v v v v
output letter    b c d e f g h i j k l m n o p q r s t u v w x y z a
            

Your task in this part is to convert the text in the input text area from alphabet A to alphabet C. This is all you need to know to implement the cipher in this lab, but if you would like additional hints, there are some provided in the slide below.

Part III: Hints

Note that the value you get from the textarea is just a long string. So your goal is to build up a new string that is the result of applying the cipher to each letter in the input text, in order, and adding it to your result string.

There are a few ways to go about this, but note that one of the most intuitive approaches would be to use a for loop through the input string and add 1 to each letter (letters are actually represented by numerical values, so this is a natural operation). To handle the z -> a shift, you can add a special case for each letter, or use mod arithmetic to avoid this extra case.

You may find charCodeAt(index) and fromCharCode(asciiNum) helpful for this problem.

If you get stuck on this function, you may refer to a sample solution on the slide below. But it's strongly recommended you implement it on your own!

Part III: Algorithm Solution

One function solution is given below (you can also solve this with arrays):

/**
 * Returns an encrypted version of the given text, where
 * each letter is shifted alphabetically ahead by 1 letter,
 * and 'z' is shifted to 'a' (creating an alphabetical cycle).
 */
function shiftCipher(text) {
  text = text.toLowerCase();
  let result = "";
  for (let i = 0; i < text.length; i++) {
    if (text[i] < 'a' || text[i] > 'z') {
      result += text[i];
    } else if (text[i] == 'z') {
      result += 'a';
    } else { // letter is between 'a' and 'y'
      let letter = text.charCodeAt(i);
      let resultLetter = String.fromCharCode(letter + 1);
      result += resultLetter;
    }
  }
  return result;
}

JS

Part IV: Implement UI Functionality

Now that you have a cool encryption feature implemented, let's add some features for the user to decorate their encrypted message.

We have broken this part into are a few smaller exercises - move down the slides to work through them!

Part IV: Dropdown UI

First, we want to restrict the shift cipher functionality to only work when "Shift Cipher" is selected in the drop down menu. Only when this is the currently-selected option and then the "Encrypt-It" button is clicked should the input text be encrypted.

For dropdown menus, you can get the value of the current option using el.value, where el is the DOM element for the select dropdown.

Part IV: Reset Functionality

When the Reset button is clicked, the textarea and the output paragraph should both be cleared (using innerText).

Part IV: Checkbox Elements

Next, implement a feature such that when the all-caps checkbox is checked, all of the text in the output paragraph appear only in uppercase.

To do so, add a CSS file with a class ".uppercase" that uses text-transform: "uppercase"; to make an element all-uppercase. When the "All Caps" option is checked, the output paragraph should have this class. When unchecked, it should not have this class so that it uses whatever casing was used in the original input text.

Hint 1: The event corresponding to changing the state of a checkbox input as checked/unchecked is change.

Hint 2: To update the class list of a DOM element, you can use el.classList.add("classname") and el.classList.remove("classname"). We'll see this more in lecture!

Part V: Radio Button Elements

Implement a feature to such that the text in the output paragraph should have the font size of whatever is currently selected in the radio button group.

To minimize changing styles in JavaScript, you can add CSS classes to accomplish font size changes (similar to the previous step).

Part V: Implementing the Randomized Cipher

Note: This part is a more challenging feature we have provided if you'd like to explore a more advanced cipher algorithm and and add a few more UI features. We have also suggested other features to try on the next slide!

In this part, you will implement the "Randomized" cipher option. This is similar to the Shift Cipher, only "result alphabet" C is randomly-generated. In other words, each letter in the English alphabet is mapped to one of the 25 other letters, but no two letters may have the same mapping (e.g., "a" and "c" can not both map to "d").

Part V: Details

First, you should modify your button event handler to determine whether the "Shift Cipher" or "Randomized Cipher" option is selected in the dropdown menu. If the "Randomized Cipher" option is selected, it should call a new function which outputs a randomized encrypted version of the input text area to the output div.

If you would like some hints for writing this algorithm, there are some in the slides below.

Part V: Hints

Because we aren't simply adding 1 to each letter (or mapping 'a' to 'z'), you might find an array helpful when creating a random cipher alphabet. One way to randomly-generate the cipher is to keep track of unchosen letters in the English alphabet using a string or array. Then, randomly choose one of the unchosen letters (you may find Math.floor() and Math.random() useful here).

Next, push this randomly-chosen letter to the end of the cipher array. Continue this process until you have removed all letters from your English alphabet and have 26 letters in your cipher array (where the first index is the letter randomly-chosen first, and the last index is the letter that was chosen last).

If you get stuck on this function, you may refer to a sample solution on the slide below. But it's strongly recommended you implement it on your own.

Part V: Algorithm Solution

One possible solution is given below:

function generateCipher(text) {
  let alphabet = "abcdefghijklmnopqrstuvwxyz".split("");
  let cipher = [];
  // it's poor style to hardcode a magic number like 26
  let alphabetLength = alphabet.length;
  for (let i = 0; i < alphabetLength; i++) {
    let randomIndex = Math.floor(Math.random() * alphabet.length);
    cipher.push(alphabet.splice(
      [Math.floor(Math.random() * alphabet.length)], 1));
  }
  document.getElementById("output").innerText = cipher;
  let result = "";
  for (let i = 0; i < outputText.length; i++) {
    if (text[i] >= 'a' && text[i] <= 'z') {
      let letterCode = text.charCodeAt(i) - 'a'.charCodeAt(0);
      result += cipher[letterCode];
    } else {
      result += text[i];
    }
  }
  document.getElementById("output").innerText = result.replace(",", "");
}

JS

Challenge Problems

Did you enjoy making your own encrypted messages? Here are a few other ideas to add to your file:

  • Add a dropdown menu to select a value to shift by in the Shift Cipher
  • Adjust your ciphers so that digits 0-9 can be encrypted as well as letters
  • Explore other cipher types
  • Change your cipher to output something other than letters): For example, you could output using emojis, gifs, colored boxes, etc.
  • Write a "Decryption" tool! This goes beyond the scope of this exercise, but with a .txt file of dictionary words, you can try to write a tool that takes input (probably from a Shift or Randomized cipher) and tries to decode it, matching sequences of characters to words in the dictionary.