๐Ÿง‘๐Ÿพโ€๐Ÿ’ป prep

๐Ÿ“ 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",
};

๐Ÿ—๏ธ 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 4 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;
}

๐Ÿšช 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.

โ“๐Ÿชข 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" }`

โ“ 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.

[ ] 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.

โ“โ“โ“ 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?

๐Ÿ“ผ Objects Workshop

Learning Objectives

To get the most out of this workshop - don’t just watch, code along ๐Ÿ’ป You can use the code samples below as a starting point.

Exercise 1

// Write a function that returns true if I can eat the ice cream
//  The function has 1 parameter representing an ice cream object
//  I can eat the ice cream if it is lactose-free and contains less than 10 grams of sugar

function canEat(iceCream) {}

const iceCream1 = {
	flavour: "Vanilla",
	lactoseFree: false,
	gramsOfSugarPerScoop: 12,
}

const iceCream2 = {
	flavour: "Mango Sorbet",
	lactoseFree: true,
	gramsOfSugarPerScoop: 10,
}

const iceCream3 = {
	flavour: "Coconut",
	lactoseFree: true,
	gramsOfSugarPerScoop: 8,
}

const iceCream4 = {
	flavour: "Strawberry",
	lactoseFree: false,
	gramsOfSugarPerScoop: 8,
}

const iceCream5 = {
	flavour: "Lemon Sorbet",
	lactoseFree: true,
	gramsOfSugarPerScoop: 7,
}

console.log(canEat(iceCream1)) // what should this output?
console.log(canEat(iceCream2)) // what should this output?
console.log(canEat(iceCream3)) // what should this output?
console.log(canEat(iceCream4)) // what should this output?
console.log(canEat(iceCream5)) // what should this output?

Exercise 2

// Write a function called `getCheapest` that will take 2 book objects as parameters
//  and return the name of the cheaper book

const fictionBook = {
	title: "1984",
	author: "George Orwell",
	category: "Dystopian Fiction",
	subcategory: "Political",
	cost: 9.99,
}

const productivityBook = {
	title: "Atomic Habits",
	author: "James Clear",
	category: "Self-Help",
	subcategory: "Productivity",
	cost: 16.2,
}

// this should output 1984
console.log(getCheapest(fictionBook, productivityBook))

Exercise 3

// Write a function that takes an array of ice cream objects as a parameter
//	and returns an array of the names of ice creams I can eat
//  I can eat the ice cream if it is lactose-free and contains less than 10 grams of sugar
//  Use the solution from Exercise 1 to help you
function whichIceCreamsCanIEat(iceCreams) {
}

const iceCream1 = {
	flavour: "Vanilla",
	lactoseFree: false,
	gramsOfSugarPerScoop: 12,
}

const iceCream2 = {
	flavour: "Mango Sorbet",
	lactoseFree: true,
	gramsOfSugarPerScoop: 10,
}

const iceCream3 = {
	flavour: "Coconut",
	lactoseFree: true,
	gramsOfSugarPerScoop: 8,
}

const iceCream4 = {
	flavour: "Strawberry",
	lactoseFree: false,
	gramsOfSugarPerScoop: 8,
}

const iceCream5 = {
	flavour: "Lemon Sorbet",
	lactoseFree: true,
	gramsOfSugarPerScoop: 7,
}

const allIceCreams = [
	iceCream1,
	iceCream2,
	iceCream3,
	iceCream4,
	iceCream5,	
]

const iceCreamsICanEat = whichIceCreamsCanIEat(allIceCreams)
console.log(iceCreamsICanEat) // what should this output?

Prep Conflict Resolution

Learning Objectives

Introduction

People at work will inevitably disagree with each other at some time. Most people want to do whatโ€™s best for them and those around them. Often, conflicts arise from misunderstandings or different attitudes to general problem-solving.

These exercises will ensure you understand the theory and have reflected on this theme, so you can do the in-class exercises more effectively.

How to deal with workplace conflicts

๐ŸŽฏ Goal: Understand tips that will help you to handle conflicts on day-to-day basis. (10 minutes)

Watch the video โ€œHow to deal with workplace conflicts - Develop your personality and business skillsโ€.

Conflict Resolution Curve

๐ŸŽฏ Goal: Understand what is meant by Conflict Resolution (30 minutes)

Read about the Conflict Resolution Curve on Wikipedia

Protective Factors

๐ŸŽฏ Goal: Understand and reflect on your protective factors (20 minutes)

  1. Read this text: โ€œProtective Factorsโ€
  2. Answer the reflective questions.

Reflect on your own conflict experience

๐ŸŽฏ Goal: Reflect on your conflict experience (30 minutes)

Think of a time you have had a conflict with a work colleague.

Answer these questions in a Google Doc:

  1. Was there a misunderstanding of the facts?
  2. Have you learned different lessons from your experience which affected your views?
  3. Did either of you have a hidden agenda directly opposed to the other person?
  4. How did either of you try to resolve the conflict?
  5. What was the outcome?