๐ฃ JS2
Moving from syntax basics to solving problems with functions
- JS2
- [ ] Access with variables
- โถ๏ธ Demo
- โ ๏ธ Side effects
- โ๏ธ Check your progress
- โ No parameters
- โ Single pair case
- โโโ Multiple parameters
- โ๐ชข Query strings
- โ Summation
- ๐ฒ Interacting with the page
- ๐ฌ Events
- ๐ค References
- ๐ป User interfaces
- ๐พ Related pieces of data
- ๐ Check progress
- ๐ Calculating the mean
- ๐ Calculating the median
- ๐ Ordered data
- ๐ Grouping data
- ๐ค Reacting to events
- ๐ Mutation
- ๐ Iterating
- ๐ Querying the DOM
- ๐ช Property access
- ๐ท๏ธ Updating the interface
- ๐๏ธ Mutating
- ๐๏ธ Key value pairs
- ๐ Character limit
- ๐งญ Breaking down the strategy
- ๐งฑ Assembling the parts
- ๐ช Discuss an app
- ๐ซฑ๐ฟโ๐ซฒ๐พ Pair up
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
:
|
|
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:
|
|
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 variables as keys.
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).toStrictEqual(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).toStrictEqual(targetOutput);
});
});
We’ve currently got the following test suite:
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
๐ก tip
- Keep your demonstration brief - under 10 mins!
- Have both people in the pair talk about the work
โ ๏ธ Side effects
Learning Objectives
Currently calculateMedian
mutates its input - the list
of numbers. This mutation is called a
In this case, the side effect has unintended consequences. We have introduced a
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
Learning Objectives
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
.
What we want in the case that the query string is an empty string, is something where any time we ask it for the value of a query parameter’s 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).toBe(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 {};
}
However, after re-running the tests, we get the following feedback:
activity
The error message contains the phrase “serializes to the same string”.
Research the meaning of this phrase and then interpret what this error message is telling you in your own words. Start a thread in Slack to discuss your interpretation of the error message.
Checking objects
We saw earlier that objects, like arrays, are reference types. That means that comparing two objects with ===
will only evaluate to true
if they are references to the same objects in memory. Two objects which happen to have the same properties, but are not in the same location in memory, will compare false
using ===
.
In our test, parseQueryString
returns a reference to an empty object. So currentOutput
is assigned this reference. But targetOutput
is assigned a reference to a different object.
In other words, toBe
checks whether the two objects are references to the same object in memory.
As currentOutput
and targetOutput
point to different objects - this can never be true. However, we can use a different matcher that compares the contents of the two objects. We can use toStrictEqual
to check that both objects have exactly the same contents:
expect(currentOutput).toStrictEqual(targetOutput);
โ Single pair case
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).toStrictEqual(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;
}
However, instead of accessing the array’s elements like this, we can use array destructuring to create new variables and assign them values, based on values in an array.
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.
โโโ Multiple parameters
Learning Objectives
Let’s consider the case when there are multiple query parameters in the query string.
๐ก tip
&
.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).toStrictEqual(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=yellow", "colour=yellow"]
. We can achieve this by calling split
with the "&"
separator.
|
|
๐ฏ 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).
|
|
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 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
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.
๐ฒ 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.
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
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.
๐ฌ 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:
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 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:
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
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.
- We make an array
[10, 20, 30]
and store it somewhere in memory. list
is assigned a reference to[10, 20, 30]
copy
is assigned a reference pointing at the same memory aslist
At this stage in the program, list
and copy
point to the same location in memory.
push
function mutates (changes) the array thatcopy
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.
|
|
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); // evaluates to 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 arrays themselves.
Value vs reference
In JavaScript, arrays and objects are reference types: everything else is a value type.๐
note
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
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.
๐พ Related pieces of 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
๐ Check progress
Learning Objectives
Let’s use the plan from earlier to check our progress.
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.
๐ 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:
- summing all the numbers in the array
- 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", function () {
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.
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;
}
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.
calculateMedian
, hopefully you see this implementation isn’t doing the right thing. Try implementing the functionality for this case.๐ 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:
index | value |
---|---|
0 | “Francesco” |
1 | “Leoni” |
2 | 33 |
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.
key | value |
---|---|
firstName | “Francesco” |
lastName | “Leoni” |
age | 33 |
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
We can declare an object like this.
const profileData = {
firstName: "Franceso",
lastName: "Leoni"
age: 33,
cityOfResidence: "Manchester",
};
๐ Grouping data
Learning Objectives
In JavaScript, we can store data inside an
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
๐ก
tip
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
index | 0 | 1 | 2 | 3 |
---|---|---|---|---|
element | 4.6 | 5.03 | 7.99 | 8.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
:
|
|
We need to tell the browser to call updateCharacterLimit
whenever a keyup event
addEventListener
:
|
|
Let’s break down the arguments that are passed to addEventListener
.
"keyup"
- this is the type of event we want to be notified aboutupdateCharacterLimit
- 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>
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
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 tocalculateMean
.
Play computer with the example above. Pay careful attention to what happens when salaries
is passed to calculateMedian
๐ Iterating
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
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()
๐ก tip
textarea
elementThe 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:
|
|
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
- On your local machine, set up a new directory with an
index.html
andscript.js
. - Make sure you start with the same static html as the example above.
- Double check your script file is linked to your html file.
- Try querying the DOM and accessing various elements like the
textarea
element.
๐ช Property access
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 theconsole
object”
Similarly we can use dot notation to access property values stored in object literals:
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.
|
|
const profileData = {
firstName: "Francesco",
lastName: "Leoni",
age: 33,
};
const twinData = profileData;
twinData.age++;
console.log(profileData === twinData);
console.log(profileData.age);
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.
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 the inner text. As before, we can use document.querySelector
to access an element in the DOM using an appropriate CSS selector:
|
|
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
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.
๐ 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.
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:
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.
|
|
activity
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
You’ll need to get into groups of no more than three
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.
Have one person in your group share their screen.
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
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.
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
Read the project brief carefully
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.