JS2 block viewer

This block viewer lets you flick through all the existing blocks in the JS2 folder so you can choose what parts to add to your pages and what parts you might want to create, revise, or leave out.

It's literally just an alphabetical list of whatever is in this folder.

[ ] Access with variables

Learning Objectives

We can mutate an object using . dot notation. However, if we look at the return value in the previous implementation we get {key: "banana"}. Let’s take another look at our current implementation of parseQueryString:

1
2
3
4
5
6
7
8
function parseQueryString(queryString) {
  const queryParams = {};

  const [key, value] = queryString.split("="); // will hold ['fruit', 'banana']
  queryParams.key = value; // mutate the queryParams object

  return queryParams;
}

On line 4, we’re declaring an identifier called key. When parseQueryString is called with "fruit=banana" then key will be assigned the value of "fruit".

We want to add a property name to the object that is the value of the key variable and not the string "key". We can do this with square bracket notation:

1
2
3
4
5
6
7
8
function parseQueryString(queryString) {
  const queryParams = {};

  const [key, value] = queryString.split("="); // will hold ['fruit', 'banana']
  queryParams[key] = value; // will set the property name with the value of the key variable

  return queryParams;
}

We can’t use dot syntax if we don’t know what the name of the key is going to be. Square bracket notation is more powerful than dot notation, because it lets us use any expression as a key.

We’ve currently got the following test suite:

describe("parseQueryString()", () => {
  test("given a queryString with no query parameters, returns an empty object", function() {
    const input = "";
    const currentOutput = parseQueryString(input);
    const targetOutput = {};

    expect(currentOutput).toEqual(targetOutput);
  });
  test("given a queryString with one pair of query params, returns them in object form", function() {
    const input = "fruit=banana";
    const currentOutput = parseQueryString(input);
    const targetOutput = { fruit: "banana" };

    expect(currentOutput).toEqual(targetOutput);
  });
});

We’ve currently got the following test suite:

parse-query-test-feedback

We’ve got a situation where the first test case (for an empty string) is no longer working. Explain why this test case is no longer passing for the first test case. Playing computer will help you to explain why!

Sometimes when we’re solving a problem, it can be useful to work out different cases (like empty query strings, or non-empty query strings) and work out how to solve them separately, then come back when we think we understand the cases and work out how to put the solutions together into one function. This often is useful when there are really different cases to consider.

Most of the time, though, it’s useful to try to keep all of our existing tests passing as we cover more cases. If we wanted to do that here, we could make our function be something like:

function parseQueryString(queryString) {
  const queryParams = {};
  if (queryString.length === 0) {
    return queryParams;
  }

  const [key, value] = queryString.split("="); // will hold ['fruit', 'banana']
  queryParams[key] = value; // will set the property name with the value of the key variable

  return queryParams;
}

Here, we only add a key to the object if there was actually something to add - we return early if there’s no extra work to do.

โ–ถ๏ธ Demo

Learning Objectives

Take this time to demo the application you’ve been building this week to the rest of the group (your application doesn’t need to be finished!)

You can talk about the following:

  • Demo the app and talk through its functionality
  • Your problem solving strategy
  • Any challenges you faced in developing the app

๐Ÿ’ก Tips

  1. Keep your demonstration brief - under 10 mins!
  2. Have both people in the pair talk about the work

โš ๏ธ Side effects

Learning Objectives

Currently calculateMedian mutates its input - the array of numbers. This mutation is called a side effect ๐Ÿงถ ๐Ÿงถ side effect A function has a side effect if it does something which can be observed from outside of the function (aside from returning a value). Removing an element from an array is a side effect. Logging something to the console is also a side effect. .

In this case, the side effect has unintended consequences. We have introduced a bug ๐Ÿงถ ๐Ÿงถ bug Any unintended behaviour or effect from our software is called a bug. which makes calculateMean return the wrong value. Both calculateMean and calculateMedian need access to the original salaries array. Therefore, we should take make sure we don’t mutate the array unless we really mean to.

Testing no mutation

We can add an additional assertion to the tests for calculateMedian to check it isn’t modifying the original input:

test("doesn't modify the input", () => {
  const list = [1, 2, 3];
  calculateMedian(list);

  expect(list).toEqual([1, 2, 3]); // Note that the toEqual matcher checks the values inside arrays when comparing them - it doesn't use `===` on the arrays, we know that would always evaluate to false.
});

In this test, we don’t check the return value of calculateMedian. We assert that the input has the same contents as the original input. We can use the toEqual matcher to check the contents of the array referenced by the variable list.

Recall the current buggy implementation of calculateMedian:

function calculateMedian(list) {
  const middleIndex = Math.floor(list.length / 2);
  const median = list.splice(middleIndex, 1)[0];

  return median;
}

We’ve established that we shouldn’t use splice to retrieve the median from the input array. Fix the implementation of calculateMedian above so it no longer calls splice (which mutates the input), and instead gives the right answer without mutating the input.

โœ๏ธ Check your progress

Learning Objectives

This week you should have been building an app in pairs. Use this time to check your progress and identify areas/tasks you need to complete.

๐Ÿ”‘ Key questions

You can use the questions below to reflect on your progress this week.

  • Which acceptance criteria have you completed for your app?
  • Have you deployed your app? Do you have a link to the deployed version that you can share with others?
  • Is your code formatted properly?
  • Is your code readable? E.g. do you have clear function names, variable names etc.

๐Ÿ“ User feedback

If you have time, get some user feedback on your deployed application. Share your deploy link with volunteers or trainees.

โ“ No parameters

Let’s look at the case where the query string is an empty string.

In this case, we need to think of an output that makes sense.

We saw before that we can try to look up a property on an object which the object doesn’t actually have - this will evaluate to undefined.

When we parse the empty query string, we want to return something where any time we ask it for the value of a key, we get back undefined.

An empty object behaves this way, so it makes sense to return an empty object.

Let’s create a test to explore this idea. In your prep dir, touch parse-query-string.js && touch parse-query-string.test.js. Write the following test in the parse-query-string.test.js file.

test("given a query string with no query parameters, returns an empty object", function() {
  const input = "";
  const currentOutput = parseQueryString(input);
  const targetOutput = {};

  expect(currentOutput).toEqual(targetOutput);
});

We can pass this test just by returning an empty object for now. We can define a function parseQueryString as follows:

function parseQueryString() {
  return {};
}

We run our tests and see them pass. Great news.

โ“ Parsing a single key-value pair

Learning Objectives

Let’s consider another test case: when the query string contains a single key-value pair.

Write a test in the parse-query-string.test.js file:

test("given a query string with one pair of query params, returns them in object form", function() {
  const input = "fruit=banana";
  const currentOutput = parseQueryString(input);
  const targetOutput = { fruit: "banana" };

  expect(currentOutput).toEqual(targetOutput);
});

๐Ÿงญ Strategy

We first need to separate out the "fruit=banana" string so we can access "fruit" and "banana" separately. We can do this by splitting up the string by the = character. We can split the string into an array consisting of ['fruit', 'banana']. Then we can grab the array’s contents and assign the elements meaningful names:

function parseQueryString(queryString) {
  const queryParams = {};

  const keyValuePair = queryString.split("=");
  const key = keyValuePair[0]; // key will hold 'fruit'
  const value = keyValuePair[1]; // value will hold 'banana'
  queryParams.key = value;

  return queryParams;
}

An equivalent, but more concise way to do this uses array destructuring to create new variables and assign them values, based on values in an array.

This code does the same thing as the previous code:

function parseQueryString(queryString) {
  const queryParams = {};

  const [key, value] = queryString.split("="); // key will hold 'fruit', value will hold 'banana'
  queryParams.key = value;

  return queryParams;
}

๐ŸŽฎ Play computer with the implementation of parseQueryString above to see why it isn’t working properly.

โ“โ“โ“ Parsing multiple parameters

Learning Objectives

Let’s consider the case when there are multiple query parameters in the query string.

๐Ÿ’ก Recall

In the case when the query string has multiple query parameters, then each key-value pair is separated by an ampersand character &.

Write this test in the parse-query-string.test.js file.

test("given a query string with multiple key-value pairs, returns them in object form", function() {
  const input = "sort=lowest&colour=yellow";
  const currentOutput = parseQueryString(input);
  const targetOutput = { sort: "lowest", colour: "yellow" };

  expect(currentOutput).toEqual(targetOutput);
});

๐Ÿงญ Strategy

We’ve already worked out how to update the query params object given a single key-value pair in the query string.

To work out our strategy, let’s consider what we already know how to do. We already know how to take a key-value pair as a string, and add it to our object.

๐Ÿ’ก Key insight: If we can do it for one pair, we can try doing it for a list of pairs too.

So we’re missing a step - breaking up the string of multiple key-value pairs into an array where each element is a single key-value pair. If we do this, then we can iterate over the array, and do what we already know how to do on each key-value pair.

Our strategy will be to break the query string apart into an array of key-value pairs. Once we’ve got an array we can try iterating through it and storing each key value pair inside the queryParams object.

Let’s start with the first sub-goal.

๐ŸŽฏ Sub-goal 1: split the query string into an array of key-value pairs

Query strings with multiple key-value pairs use & as a separator e.g. sort=lowest&colour=yellow. We want to split sort=lowest&colour=yellow into ["sort=lowest", "colour=yellow"]. We can achieve this by calling split with the "&" separator.

1
2
3
4
5
function parseQueryString(queryString) {
  // suppose queryString has a value of "sort=lowest&colour=yellow"
  const queryParams = {};
  const keyValuePairs = queryString.split("&"); // keyValuePairs will point to ["sort=lowest", "colour=yellow"]
}

๐ŸŽฏ Sub-goal 2: add each key-value pair in the array to the query params object

Once we’ve got an array we can iterate through the key-value pairs and update the queryParams object each time (like we did when we just had one key-value pair).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
function parseQueryString(queryString) {
  // assume queryString has a value of "sort=lowest&colour=yellow"
  const queryParams = {};
  const keyValuePairs = queryString.split("&"); // keyValuePairs will point to ["sort=lowest", "colour=yellow"]

  for (const pair of keyValuePairs) {
    const [key, value] = pair.split("=");
    queryParams[key] = value;
  }

  return queryParams;
}

Play computer with the implementation of parseQueryString above and pay attention to how the queryParams object is updated.

Now that we’ve worked out how to solve this problem in the case of multiple query parameters, let’s integrate that solution into our previous implementation, to make sure it works for all cases.

We can keep our if (queryString.length === 0) { check from before. We still need it because split on an empty string still returns an empty string. If we don’t have this special case, we’ll try to parse the empty string, probably incorrectly.

We don’t need to do anything special for the one-value case, as an array containing one element gets iterated the same as an array of multiple elements:

function parseQueryString(queryString) {
  const queryParams = {};
  if (queryString.length === 0) {
    return queryParams;
  }
  const keyValuePairs = queryString.split("&");

  for (const pair of keyValuePairs) {
    const [key, value] = pair.split("=");
    queryParams[key] = value;
  }

  return queryParams;
}

When we’re solving problems involving several values, often we need slightly differently handling for the cases when there are 0, 1, or more than 1 values. In our example here, we need to treat 0 values specially (if the query string is empty, we return early), but we can handle 1 and more than 1 the same way.

When you’re breaking down problems, think to yourself: What are special cases we may need to handle differently?

โ“๐Ÿชข Query strings

Learning Objectives

Letโ€™s define a problem.

Websites have addresses called URLs like this: “https://example.com/widgets". URLs often have query strings ๐Ÿงถ ๐Ÿงถ query strings Query strings go at the end of a URL and are used to specify more information about the content you get back from a request to a server too. Here is an example of a URL with a query string on the end:

https://example.com/widgets?colour=blue&sort=newest

For this URL, the query string is "colour=blue&sort=newest". Query strings consist of query parameters, separated by an ampersand character &. colour=blue is a query parameter: we say that colour is the key and blue is the value.

URLs must always be strings. However, a string isn’t a useful data type for accessing query parameters. Given a key like colour, accessing the value from a query string stored as a string is not straightforward. However, objects are ideal for looking up values with keys.

We’re going to implement a function parseQueryString to extract the query parameters from a query string and store them in an object:

Given a query string and a function parseQueryString,
When we call parseQueryString with a query string,
Then it should return an object with the key-value pairs.

E.g.

parseQueryString("colour=blue&sort=newest");
// should return { colour: "blue", sort: "newest" }`

โž• Summation

Learning Objectives

๐ŸŽฏ Sub-goal: compute the sum of an array of numbers.

To sum a list we can start by creating a variable total with an initial value of 0.

We then need to repeatedly add each value in the list to our total.

function sumValues(list) {
  let total = 0;
  total += list[0]; // access a list element and add to total
  total += list[1];
  total += list[2];
  total += list[3];
  total += list[4];
  return total;
}

sumValues([1, 2, 3, 4, 5]);

However, this approach is flawed.

Explain why the approach above is flawed when it comes to summing the numbers for an array of any length.

๐ŸŒฒ Interacting with the page

Learning Objectives

Let’s consider the starting html. We need a way of interacting with the elements of this page once it is rendered.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <section>
      <h3>Character limit</h3>
      <label for="comment-input"
        >Please enter your comment in the text area below
      </label>
      <textarea id="comment-input" name="comment-input" rows="5"></textarea>
      <p id="character-limit-info">You have 200 characters remaining</p>
    </section>
  </body>
</html>

๐ŸŒณ HTML tree

HTML documents form a tree-like structure. We start at the top html element and from there other html elements are nested inside.

html tree

When we use a web browser, it takes this HTML document, and provides us with an interface - a visual representation of the document, which we can read, and interact with (e.g. with a keyboard and mouse).

Document Object Model

When the browser first renders a web page it also creates the DOM - short for Document Object Model ๐Ÿงถ ๐Ÿงถ Document Object Model The Document Object Model is a data representation of the content in a web page. All html elements are represented as objects that can be accessed, modified and deleted. .

Just like a web browser provides us a visual interface, the DOM is an interface. But it is not an interface for humans to see and interact with, it is an interface for JavaScript to interact with. We can write JavaScript programs to interact with the Document Object Model so we can make the page interactive.

We can use Dev Tools to inspect the DOM and look at the elements on the page. Use Dev Tools to inspect the character limit component from earlier.

๐ŸŽฌ Events

Learning Objectives

In the case of the textarea element, we want to update the p element text every time the user types inside the textarea. In other words, we want our application to react to the user typing on the keyboard. Currently our plan looks like this:

flowchart TD A[Step 1: Define the character limit] --> B[Step 2: Access the textarea element] --> C[Step 3: Calculate the number of characters left] --> D[Step 4: Update the interface with the number of characters left]

However, we’re missing a step in our plan. We need to find a way of running some code in response to an event.

Definition: events

An event is something that occurs in a programming environment.

Events are things that happen in the browser, which your code can ask to be told about, so that your code can react to them. In a browser context, an event could be a user clicking on a button, a user typing something into a textarea box, a user submitting a form etc. Not all events are in response to user actions, for instance there is an event for the browser finished initially rendering the page. You can find a complete reference all the different event types on MDN.

When a user presses a key in our textarea, the browser will create an event. If we asked the browser to tell us about it, we can respond to it. So we can update our plan as follows:

flowchart TD A[Step 1: Define the character limit] --> B[Step 2: Access the textarea element] --> C["`**Step 3: Ask to be notified when a user presses a key**`"] D["`**Step 4: When the browser tells us a user has pressed a key**`"] --> E[Step 5: Calculate the number of characters left] --> F[Step 6: Update the interface with the number of characters left]

Notice a few things here:

  • There’s no arrow between Step 3 and Step 4. The trigger for Step 4 is a user doing something - if the user doesn’t type anything in the textarea, Step 4 will never run (and neither will Step 5 and Step 6).
  • We don’t run Step 4. The browser runs Step 4. In Step 3 we asked the browser to do something for us in the future. This is something new - up until now, we have always been the ones telling JavaScript what to do next.

๐Ÿค References

Learning Objectives

Arrays are stored by reference ๐Ÿงถ ๐Ÿงถ reference A reference points to a location in memory.

Consider the following example,

const list = [10, 20, 30];
const copy = list;
copy.push(60, 70);

console.log(list);
console.log(copy);

Let’s break down what is happening in this program.

Play computer with the code above to step through the code and find out what happens when the code is executed.

point-to-array

  • We make an array [10, 20, 30] and store it somewhere in memory.
  • list is assigned a reference to the location in memory containing [10, 20, 30]
  • copy is assigned a reference pointing at the same location in memory as list

At this stage in the program, list and copy point to the same location in memory.

  • push function mutates (changes) the array that copy points to.
  • prints out list: [10, 20, 30, 60, 70]
  • prints out copy: [10, 20, 30, 60, 70]

So as copy and list point to the same array. If we mutate list then we’re mutating the same list that copy points to.

So the console output is the same.

1
2
3
4
5
6
7
8
const salaries = [10, 20, 30, 40, 60, 80, 80];
const median = calculateMedian(salaries);

console.log(salaries, "<--- salaries input before we call calculateMean");
const mean = calculateMean(salaries);

console.log(`The median salary is ${median}`);
console.log(`The mean salary is ${mean}`);

In the example above, salaries is assigned a reference on the first line. Explain why calculateMedian and calculateMean both get access to the same array.

Shared reference

We can also check these variables share the same reference.

const list = [10, 20, 30];
const copy = list;

console.log(list === copy); // logs true

If we’re comparing 2 array variables with ===, then it will evaluate to true only if they have the same reference. === is comparing the references to the arrays, not the contents of arrays.

If we made two different arrays with the same contents, they would not be === equal:

const list = [10, 20, 30];
const copy = [10, 20, 30];

console.log(list === copy); // logs false

Value vs reference

In JavaScript, arrays and objects are reference types: everything else is a value type.

๐Ÿ“ Passing by value

As strings are value types, they are passed by value. Passed by value means that every time you assign a value to a variable then a copy of that value is made.

Use the tabs below to compare the effects of passing by reference and passing by value. There are two different but similar implementations of pluralise - a function that appends an s to the end of its input.

Here pluralise is passed an array by reference.

lettersInAnArray is passed by reference. pluralise’s modification is visible here, because the same underlying storage was modified.

Step through the code to observe this behaviour:

Here pluralise is passed a string by value.

This means a copy of string’s value is passed to pluralise in the second tab. pluralise’s reassignment is not visible here, because a copy was made just for the function before the value was modified.

Step through the code to observe this behaviour:

๐Ÿ’ป User interfaces

Learning Objectives

User interfaces provide the gateway between a user and a complex application. When navigating the internet, we continually interact with web pages to access data and interact with complex web applications.

A web browser ๐Ÿงถ ๐Ÿงถ web browser provides a user interface to interact with web pages. is capable of fetching HTML documents from a server, and then rendering the document to create a user interface. If a user visits a website and gets a plain HTML document back, we say this content is static.

By static, we mean that the server’s job was just to hand over the HTML document, and then the browser takes over. A user may interact with the browser’s interface, e.g. to scroll, type in a text field, or drag-and-drop an image around, but this is done purely by interacting with the browser - the browser won’t talk to the server about this.

๐Ÿ’พ Grouping data

Learning Objectives

In programming, we often have related pieces of data.

Let’s consider a list of prices in a bill:

4.6, 5.03, 7.99, 8.01

limitations of many variables

We can store this list of prices in a JavaScript program by declaring multiple variables:

const price0 = 4.6;
const price1 = 5.03;
const price2 = 7.99;
const price3 = 8.01;

Each identifier is the word price with a numerical suffix to indicate its position in the list. However, this is undoubtedly the wrong approach.

  • If the number of items in the bill is huge, we must keep declaring new variables.
  • If the number of items changes, we must reassign the values of variables so they’re in the correct order, and change any place we’re using the variables to know about the new one.
  • If we do mutliple things to all of the values (say we have one loop adding them, and one loop printing them), we will need to change all of the places any time we add new values.

Instead we have to group the data together using a data structure ๐Ÿงถ ๐Ÿงถ data structure A data structure is a collection of data. It may have functions that can be applied to access or manipulate the data.

๐Ÿ“ˆ Check progress

Learning Objectives

Let’s use the plan from earlier to check our progress.

flowchart TD A[Step 1: Define the character limit] --> B[Step 2: Access the textarea element] --> C["`**Step 3: Ask to be notified when a user presses a key**`"] D["`**Step 4: When the browser tells us a user has pressed a key**`"] --> E[Step 5: Calculate the number of characters left] --> F[Step 6: Update the interface with the number of characters left]

Let’s consider our code at the moment:

const characterLimit = 200;
const textarea = document.querySelector("textarea");

function updateCharacterLimit() {
  console.log(
    "keyup event has fired... The browser called updateCharacterLimit..."
  );
}

textarea.addEventListener("keyup", updateCharacterLimit);

We’ve done the following:

  • Step 1: Defined a characterLimit
  • Step 2: Accessed the textarea element
  • Step 3: Registered an event handler updateCharacterLimit

The browser will do the following for us:

  • Step 4: The browser will tell us when a user has pressed a key

We must still complete the following steps:

  • Step 5: Calculate the number of characters left
  • Step 6: Update the user interface with the number of characters left

To obtain the characters left, we can calculate the difference between characterLimit and the number of characters in the textarea element:

const characterLimit = 200;
const textarea = document.querySelector("textarea");

function updateCharacterLimit() {
  const charactersLeft = characterLimit - textarea.value.length;
  console.log(`You have ${charactersLeft} characters remaining`);
}

textarea.addEventListener("keyup", updateCharacterLimit);
<section>
  <h1>Character limit</h1>
  <textarea id="comment-input" rows="5" maxlength="200"></textarea>
  <label for="comment-input"
    >Please enter a comment in fewer than 200 characters
  </label>
  <p id="character-limit-info">You have 200 characters remaining</p>
</section>

Typing in to the textarea element, we should see a string like "You have 118 characters left" printed to the console each time a key is released. However, we have one final step: we must now update the DOM label with the information about how many characters are left.

๐Ÿ“ˆ Implementing all the cases

Learning Objectives

Try writing a test case to check calculateMedian works in the case when it is passed an array of even length.

Use documentation to check how the median is computed in this case.

Once you’ve written your test case for calculateMedian, hopefully you see this implementation isn’t doing the right thing. Try implementing the functionality for this case.

๐Ÿ“Š Calculating the mean

Learning Objectives

Let’s consider a problem where we calculate the mean of a list of numbers.

Given an array of numbers
When we call calculateMean with the array of numbers
Then we get the mean.

Let’s create a test to check its functionality. In your prep dir, touch mean.js && touch mean.test.js. Write the following test in the mean.test.js file.

test("calculates the mean of a list of numbers", () => {
  const list = [3, 50, 7];
  const currentOutput = calculateMean(list);
  const targetOutput = 20;

  expect(currentOutput).toBe(targetOutput); // 20 is (3 + 50 + 7) / 3
});

In this test, we’re checking we get a value of 20 by adding together 3 + 50 + 7 and then dividing by the number of items (3). We calculate the mean of a list of numbers by:

  1. summing all the numbers in the array
  2. dividing the sum by the length of the array

We can define a ๐ŸŽฏ sub-goal of calculating the sum of all numbers in the list.

๐Ÿ“Š Calculating the median

Learning Objectives

Let’s define another problem.

We want to calculate the median value from an array of numbers.

Given an array of numbers in ascending order
When we call calculateMedian with this array
Then we get the median value.

We calculate the median of a list of numbers by finding the middle value in the list.

Let’s start with a test to check the return value of calculateMedian given an ordered list of numbers. In your prep dir, touch median.js && touch median.test.js. Write the following test in the median.test.js file.

test("calculates the median of a list of odd length", () => {
  const list = [10, 20, 30, 50, 60];
  const currentOutput = calculateMedian(list);
  const targetOutput = 30;

  expect(currentOutput).toBe(targetOutput);
});

๐Ÿ”จ Implementing calculateMedian

So we can implement calculateMedian.

We can summarise our approach as follows.

flowchart LR A[Step 1: Find the middle index of the array] --> B[Step 2: Get the middle item] B --> C[Step 3: Return the middle item]

In code we can we can use splice to to get the middle item.

function calculateMedian(list) {
  const middleIndex = Math.floor(list.length / 2);
  const median = list.splice(middleIndex, 1)[0];

  return median;
}

๐Ÿ“ Ordered data

Learning Objectives

Let’s imagine we’re writing a program that involves information about a user’s profile. We could store some user’s profile details in an array:

const profileData = ["Franceso", "Leoni", 33, "Manchester"];

At the moment, we could visualise profileData in a table like this:

indexvalue
0“Francesco”
1“Leoni”
233
3“Manchester”

Inside profileData we access items using an index. However, with an ordered list of items we can’t tell what each item in the list represents. We only know the position of data in the array. We could access the item at index 3 to get "Manchester": however, we don’t know what "Manchester" tells us about the user. "Manchester" could be the city they currently live in, it could be their city of birth, a place where they studied in the past etc. We need to know the values but also what these values represent about the user.

We might think we can just remember (and maybe write in a comment) “index 0 is the person’s first name”, but this has problems. What if we need to introduce a new piece of data? We may need to change every piece of code that uses the array. What if some of the data is optional (e.g. a middle name)? It’s also really hard for someone new to come read our code.

Keys not indexes

However, instead of ordering data with indexes, we can label data with keys.

keyvalue
firstName“Francesco”
lastName“Leoni”
age33
cityOfResidence“Manchester”

We can look up values in this table by the key. With data stored like this, we can see what values like "Manchester" actually mean - in this case, it refers to a city of residence for the user.

In JavaScript, we can use an object ๐Ÿงถ ๐Ÿงถ object An object is a collection of properties. Each property is a key-value pair to store data in a table-like way, where we can look up data using a key.

We can declare an object like this.

const profileData = {
  firstName: "Franceso",
  lastName: "Leoni"
  age: 33,
  cityOfResidence: "Manchester",
};

๐Ÿ“œ Arrays

Learning Objectives

In JavaScript, we can store data inside an array ๐Ÿงถ ๐Ÿงถ array An array is an ordered list of data

Instead of writing:

const item0 = 4.6;
const item1 = 5.03;
const item2 = 7.99;
const item3 = 8.01;

We can declare an array literal as follows:

const items = [4.6, 5.03, 7.99, 8.01];

Notice the identifier for the array is items. We chose to use the plural word items instead of the singular item, because arrays can store multiple pieces of information.

ordered data

๐Ÿ’ก Recall

Zero-indexing means we start counting from 0
We’ve already encountered ordered data before. A string is an ordered collection of characters. Let’s recall an example of a string:

const volunteer = "Moussab";

The character "M" is at index 0, "o" is at index 1, and so on.

As with strings, arrays are also zero-indexed in a similar way:

const items = [4.6, 5.03, 7.99, 8.01];

So we can refer to the elements ๐Ÿงถ ๐Ÿงถ elements An element is another name for an item inside an array. of the array by an index.

index0123
element4.65.037.998.01

In JavaScript, we can use square bracket notation to access specific elements in the array using an index.

items[0]; // evaluates to 4.6
items[1]; // evaluates to 5.03
items[2]; // evaluates to 7.99
// etc

๐Ÿ“ค Reacting to events

Learning Objectives

As a user, we interact with the elements on a web page. We click on buttons, input text, submit forms etc.

To react to an event, we can declare a function that we want to run whenever a certain event occurs. We call this function an event handler. In the example below, we name this function updateCharacterLimit:

1
2
3
const textarea = document.querySelector("textarea");

function updateCharacterLimit() {}

We need to tell the browser to call updateCharacterLimit whenever a keyup event fires ๐Ÿงถ ๐Ÿงถ fires “fires” means “an event is triggered” . We do this using addEventListener:

1
2
3
4
5
const textarea = document.querySelector("textarea");

function updateCharacterLimit() {}

textarea.addEventListener("keyup", updateCharacterLimit);

Let’s break down the arguments that are passed to addEventListener.

  • "keyup" - this is the type of event we want to be notified about
  • updateCharacterLimit - the second argument is a function. It is a function that is called when an event occurs.

In JavaScript, we can pass functions as arguments to other functions. In this case, we’re passing a function updateCharacterLimit to addEventListener as an input. We can think of this as saying: whenever a key is released on the textarea element, then the browser will call the function updateCharacterLimit. Any code we want to run in response to the keyup event will need to be inside the updateCharacterLimit function.

We can add a log to updateCharacterLimit to check it is called every time the "keyup" event fires.

const characterLimit = 200;
const textarea = document.querySelector("textarea");

function updateCharacterLimit() {
  console.log(
    "keyup event has fired... The browser called updateCharacterLimit..."
  );
}

textarea.addEventListener("keyup", updateCharacterLimit);
<section>
  <h3>Character limit</h3>
  <label for="comment-input"
    >Please enter your comment in the text area below
  </label>
  <textarea
    id="comment-input"
    name="comment-input"
    rows="5"
    maxlength="200"
  ></textarea>
  <p id="character-limit-info">You have 200 characters remaining</p>
</section>
In your local project, define your own event handler and then use addEventListener to register that event handler for a keyup event. Add a console.log to the event handler and check the event handler is being called when the event fires. Check the console tab in dev tools to see the log appear in the console.

๐Ÿ”€ Mutation

Learning Objectives

Let’s take another look at our earlier implementation of calculateMedian:

function calculateMedian(list) {
  const middleIndex = Math.floor(list.length / 2);
  const median = list.splice(middleIndex, 1)[0];

  return median;
}
const salaries = [10, 20, 30, 40, 60, 80, 80];

const median = calculateMedian(salaries);
// At this point, the array referenced by salaries has been mutated after calculateMedian(salaries), and a reference to the same array is given to calculateMean
const mean = calculateMean(salaries);

console.log(`The median salary is ${median}`);
console.log(`The mean salary is ${mean}`);

calculateMedian gets the middle value by calling splice. However, splice is a mutating ๐Ÿงถ ๐Ÿงถ mutating For arrays, mutation means changing the contents of an array. This could mean changing a value at some index or removing an item altogether. array method.

When we call splice it does 2 things:

  • removes the specified item from the list
  • returns the removed item

splice modifies the array: however, calculateMean is also passed a reference to the same array too.

In other words, calculateMedian modifies the same array that is passed to calculateMean.

Play computer with the example above. Pay careful attention to what happens when salaries is passed to calculateMedian

๐Ÿ” Iteration

Learning Objectives

To solve the sub-goal, we have to repeatedly add each number in the array to the total, one at a time. In programming, the process of repeating something is called iteration.

In programming, we can iterate by using a loop ๐Ÿงถ ๐Ÿงถ loop A loop is a sequence of instructions that is continually repeated until some condition is reached. .

In particular, we can use a for...of statement to sum the elements of the array.

function calculateMean(list) {
  let total = 0;
  for (const item of list) {
    total += item;
  }
}

๐Ÿ”Ž Querying the DOM

Learning Objectives

Inside the body of the html document, we start with the following html:

<section>
  <h3>Character limit</h3>
  <label for="comment-input"
    >Please enter your comment in the text area below
  </label>
  <textarea
    id="comment-input"
    name="comment-input"
    rows="5"
    maxlength="200"
  ></textarea>
  <p id="character-limit-info">You have 200 characters remaining</p>
</section>

querySelector()

๐Ÿ’ก recall

In the plan defined earlier, we had the following step: Step 2: Access the textarea element

The DOM is an interface. It represents html elements as objects and provides functions to access these objects. Letโ€™s start by accessing the textarea element and its value. To access DOM elements, we can use a method on the DOM API - document.querySelector

We can create a javascript file, script.js, and link it to the html document using a script element:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script defer src="script.js"></script>
  </head>
  <body>
    <section>
      <h3>Character limit</h3>
      <label for="comment-input"
        >Please enter your comment in the text area below
      </label>
      <textarea id="comment-input" name="comment-input" rows="5"></textarea>
      <p id="character-limit-info">You have 200 characters remaining</p>
    </section>
  </body>
</html>

Inside script.js, we can call document.querySelector:

const textarea = document.querySelector("textarea");

document.querySelector takes a single argument a string containing a CSS selector (just like we use when defining what elements a CSS rule should apply to).

document.querySelector returns an element object representing the first element in the page which matches that CSS selector. This element object lets us inspect and modify the element in the page.

Here we have given it the string "textarea", which is the CSS selector used to look up the elements with tag name textarea. The function returns an element object, which represents the first textarea in the web page. Once we can access the textarea object, we can access its properties. In particular we want to access the value a user types into the textarea box. We can do this by accessing the value property:

const textarea = document.querySelector("textarea");
console.log(textarea.value); // evaluates to the value typed by the user
  1. On your local machine, set up a new directory with an index.html and script.js.
  2. Make sure you start with the same static html as the example above.
  3. Double check your script file is linked to your html file.
  4. Try querying the DOM and accessing various elements like the textarea element.

๐Ÿšช Accessing properties

Learning Objectives

We’ve already accessed object property values. console is an object:

Welcome to Node.js v16.19.1.
Type ".help" for more information.
> console
Object [console] {
  log: [Function: log],
  warn: [Function: warn],
  dir: [Function: dir],
  time: [Function: time],
  timeEnd: [Function: timeEnd],
  .
  .
  .
}

We use dot notation to access the property value associated with a key. When we write console.log - think of this as saying:

“access the value associated with key of "log", inside the console object”

Similarly we can use dot notation to access property values stored in objects we have defined:

const profileData = {
  firstName: "Francesco",
  lastName: "Leoni",
  age: 33,
};
console.log(profileData.firstName); // logs "Francesco"

Objects also allow looking up property values using square brackets, similar to arrays. Instead of an index, we use a string of the key inside the square brackets:

const profileData = {
  firstName: "Francesco",
  lastName: "Leoni",
  age: 33,
};
console.log(profileData["firstName"]); // logs "Francesco"

Using dot notation or square brackets both work the same way.

Mutation

Objects are mutable data structures. We can use the assignment operator = to update the value associated with a particular key.

1
2
3
4
5
6
7
const profileData = {
  firstName: "Francesco",
  lastName: "Leoni",
  age: 33,
};
profileData.firstName = "Fraz";
console.log(profileData.firstName); // firstName is now "Fraz"
const profileData = {
  firstName: "Francesco",
  lastName: "Leoni",
  age: 33,
};
const twinData = profileData;
twinData.firstName = "Emilia";
console.log(profileData === twinData);
console.log(profileData.firstName);

Predict and explain what the console output be when we run the code above runs.

Properties are optional

It’s possible to add properties to an object that already exists. Objects don’t always have the same properties.

exercise

const profileData = {
  firstName: "Francesco",
  lastName: "Leoni",
  age: 33,
};
console.log(profileData.cityOfResidence);

profileData.cityOfResidence = "Manchester";

console.log(profileData.cityOfResidence);

Predict and explain what the console output will be when the code above runs.

Object literals vs objects

What’s the difference between an object, and an object literal?

An object is the thing we’re making, which maps keys to values.

An object literal is how we can write one out specifying all of its key-value pairs in one statement.

These two blocks of code construct equivalent objects:

const object1 = {
  firstName: "Francesco",
  lastName: "Leoni",
};

const object2 = {};
object2.firstName = "Francesco";
object2.lastName = "Leoni";

object1 is all constructed in one object literal. object2 starts off with an empty object literal, and then adds some properties to it.

Note: This same terminology is used for other types:

"abc" is a string literal, "a" + "b" + "c" makes the same string, but by concatenating three string literals together.

๐Ÿท๏ธ Updating the interface

Learning Objectives

We can calculate the remaining characters available every time a user’s key is released from the keyboard in the textarea. Finally, we must update the p element in the user interface with the number of characters remaining.

Step 5: Update the interface with the number of characters left

To achieve this goal, we’ll need to access the p element with id "character-limit-info" and then update its text content. As before, we can use document.querySelector to access an element in the DOM using an appropriate CSS selector:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const characterLimit = 200;
const textarea = document.querySelector("textarea");

function updateCharacterLimit() {
  const charactersLeft = characterLimit - textarea.value.length;
  console.log(`${charactersLeft} characters remaining`);

  const charactersLeftP = document.querySelector("#character-limit-info");
  charactersLeftP.textContent = `You have ${charactersLeft} characters remaining`;
}

textarea.addEventListener("keyup", updateCharacterLimit);
Explain why the code to access the p element is written inside the scope of updateCharacterLimit.

๐Ÿ–Š๏ธ Mutating

Learning Objectives

Arrays are a type of object in JavaScript, they are still mutable data structures.

profileData.firstName = "Sam";

console.log(profileData);

We can use the assignment operator to reassign the value that for a particular key.

๐Ÿ—๏ธ Key-value pairs

Learning Objectives

The profileData object is made up of properties. Each property is an association between a key and a value.

{
  firstName: "Francesco",
  lastName: "Leoni",
  age: 33,
  cityOfResidence: "Manchester"
};

In the object literal ๐Ÿงถ ๐Ÿงถ object literal An object literal is an object defined by writing a comma-separated list of key-value pairs inside of curly braces. above, there are 3 properties. The first property consists of firstName and "Francesco". firstName is the key, "Francesco" is the value associated with the key firstName.

In object literals, each key-value pair is separated by a comma.

๐Ÿ“ Note

Defining properties in JavaScript object literals looks a lot like defining properties in a CSS rule:

p {
  color: red;
  background-color: blue;
}

๐Ÿ›‘ Character limit

Learning Objectives

Letโ€™s define a problem.

Suppose we’re working on a website where users will need to comment on articles. In the user interface, they’ll be provided with a textarea element where they can type their comment. However, there is a character limit of 200 characters on their comment. As users type in to the textarea they should get feedback on how many characters they’ve got left for their comment.

Try typing in the character limit box above and observing the behaviour as you type.

We can define acceptance criteria for this component:

Given an textarea and a character limit of 200
When a user types characters into the textarea
Then the interface should update with how many characters they’ve got left

๐Ÿ Starting point

In the user interface, we will start off with some static html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <section>
      <h3>Example character limit comment component</h3>
      <label for="comment-input"
        >Please enter your comment in the text area below
      </label>
      <textarea
        id="comment-input"
        name="comment-input"
        rows="5"
        maxlength="200"
      ></textarea>
      <p id="character-limit-info">You have 200 characters remaining</p>
    </section>
  </body>
</html>

To implement the acceptance criterion, we’ll need to interact with the elements on the page. We’ll need a way to access and update elements based off user interactions.

๐Ÿงญ Breaking down the strategy

Learning Objectives

To implement the character limit component, we need to update the interface as the user types in the text area. We can outline a strategy as follows:

flowchart TD A[Step 1: Define the character limit of 200] --> B[Step 2: Access the textarea element] --> C[Step 3: Calculate the number of characters left] --> D[Step 4: Update the interface with the number of characters left]

This strategy gives us a rough guide for the road ahead. However, as we learn more about this problem, we may need to update our strategy.

๐Ÿงฑ Assembling the parts

Learning Objectives

Now suppose we have a program where we use the functions we implemented earlier:

const salaries = [10, 20, 30, 40, 60, 80, 80];
const median = calculateMedian(salaries);
const mean = calculateMean(salaries);

console.log(`The median salary is ${median}`);
console.log(`The mean salary is ${mean}`);

Predict and explain what will get printed to the console when the code above runs.

Then run the code above on your local machine to check your prediction. Does your initial explanation now make sense?

(Note: you’ll have to declare the functions somewhere too)

๐Ÿ› Finding the bug

In the code above, the median value is correct: however, the mean is incorrect.

We can add a log to the program to identify the origin of the bug.

1
2
3
4
5
6
7
8
const salaries = [10, 20, 30, 40, 60, 80, 80];
const median = calculateMedian(salaries);

console.log(salaries, "<--- salaries input before we call calculateMean");
const mean = calculateMean(salaries);

console.log(`The median salary is ${median}`);
console.log(`The mean salary is ${mean}`);

Run it

Try re-running the code above with the additional log. What does this tell you?

To understand why this bug occurs, we need to explore more concepts.

๐Ÿช€ Discuss an app

Learning Objectives

This week you’re building small UI components/apps using DOM manipulation. The aim of this session is to get together in groups and discuss strategy and implementation for the week’s backlog issues.

๐Ÿงฐ Setup

  1. You’ll need to get into groups of no more than three

  2. Choose one of the “Build a …” issues e.g. “Build a slideshow app” from your JS2 Week 3 backlog. It can be an app that one of you have already started or one that you’d all like to tackle together during this session.

  3. Have one person in your group share their screen.

  4. As a group, you’ll have to discuss your strategy and implementation for your chosen issue. Remember to use the acceptance criteria to check your progress.

๐Ÿซฑ๐Ÿฟโ€๐Ÿซฒ๐Ÿพ Pair up

Learning Objectives

For the final week of this module, you will need to work in pairs to build an application from scratch.

Use this time to setup and plan your work together for next week.

๐Ÿงฐ Setup

  1. You’ll need to split up into pairs with someone you can work with over the final week of this module. Double check you will have availability to work together during the week.

  2. Together in your pairs, you’ll have 2 options:

Option 1

Choose an app from the backlog that you can work on together from scratch. E.g. alarmclock app.

Option 2

Choose a project brief from the from the dom-app projects section. and

  1. Read the project brief carefully

  2. Create a user story for the chosen project brief and sketch out one acceptance criterion based on the description in the project brief. This workshop will guide you on coming up with user stories and acceptance criteria.

โฒ๏ธ Availability

In your pairs, discuss your availability and then agree on times to meet up during next week to work on your app together.