2. gyakorlat - Órai feladatok
Today has two parts. In the first part (tasks a to g) we continue with arrays: this time the arrays contain objects, and we meet a few new array methods. In the second part (tasks h to l) we finally leave the Console and start changing the page itself with JavaScript.
You work on your own today. Each task starts with a short explanation whenever it needs something new, so read the task text before you start typing. The tasks get harder as you go. Whatever you do not finish in class, finish at home.
How to run it
Download the starter files, open the folder in VS Code, then right-click index.html and choose Open with Live Server. Press F12 and open the Console tab.
The starter has three files:
index.html: the page. You do not need to change it.arrays.js: tasksatog. The results go to the Console.dom.js: taskshtol. The results appear on the page.
Look at the bottom of index.html:
<script src="arrays.js"></script>
<script src="dom.js"></script>
Two things matter here. First, the scripts are at the end of the body, so by the time they run, every element of the page already exists. Second, the order matters: arrays.js runs first, so dom.js can use products and every function you write in arrays.js.
How to check your work
Same as last week: write the function, remove the // from the console.log line under it, save, and compare the Console with the expected output in the task. If you see undefined, you probably forgot a return.
Objects
Last week our arrays held numbers. An object stores several values together, each under a name. These named values are called properties.
const product = { name: "Keyboard", price: 25, stock: 12 };
console.log(product.name); // "Keyboard"
console.log(product.price); // 25
product.price = 20; // change a property
product.color = "black"; // add a new property
A property can hold anything, even an array:
const product = { name: "Mouse", ratings: [3, 4] };
console.log(product.ratings[0]); // 3
Arrays of objects
In arrays.js you will find a products array. Every element is an object with the same shape:
{ name: "Keyboard", price: 25, category: "electronics", stock: 12, ratings: [5, 4, 4] }
The array methods you already know work exactly the same way. The only difference is that the callback receives a whole object, so you reach for its properties inside:
// numbers: numbers.filter((n) => n > 10)
// objects: products.filter((product) => product.price > 10)
Array methods at a glance
You do not need to memorize this table. Come back to it whenever a task needs something.
| Method | The callback returns | The method returns |
|---|---|---|
map | a new value | a new array, same length |
filter | true or false | a new array with the elements that got true |
find | true or false | the first element that got true, or undefined |
some | true or false | true if at least one element got true |
every | true or false | true if all elements got true |
reduce | the new accumulated value | one single value |
sort | a number (negative, zero, positive) | the same array, sorted in place |
A few more that need no callback: includes(value) (is it in the array?), join(separator) (glue elements into a string), flat() (unpack nested arrays), slice(start, end) (copy a part).
Kezdőcsomag
A feladatleírás és a kezdőfájlok helyi munkához előkészítve.
Tartalmazott fájlok: arrays.jsdom.jsindex.html
Feladatok
Part 1: arrays of objects. Tasks a to g go into arrays.js.
Collects the names of the products that can be bought right now. Chain filter and map, just like cubeEvens last week, but reach into the objects.
- Parameter:
products: an array of product objects. - Returns: a new array with the
nameof every product whosestockis greater than0, in the original order.
console.log(inStockNames(products));
console.log(inStockNames([]));
Should print:
["Keyboard", "Monitor", "Notebook", "Backpack"]
[]Looks up one product by its name.
New: find. filter always returns an array, even when you only want one element. find returns the first matching element itself, not an array. If nothing matches, it returns undefined.
const numbers = [4, 9, 12, 20];
console.log(numbers.find((n) => n > 10)); // 12
console.log(numbers.find((n) => n > 100)); // undefined
- Parameters:
products: an array of product objects;name: a string. - Returns: the product object whose
nameis exactlyname. If there is no such product, returnnull(notundefined).
console.log(findProduct(products, "Monitor"));
console.log(findProduct(products, "Laptop"));
Should print:
{ name: "Monitor", price: 180, category: "electronics", stock: 4, ratings: [5, 5, 4, 5] }
nullTwo short functions that answer a yes or no question about the whole array.
New: some and every. Both take a callback that returns true or false, and both return a single boolean.
someasks: is there at least one element for which the callback istrue?everyasks: is the callbacktruefor all elements?
const numbers = [4, 9, 12];
console.log(numbers.some((n) => n > 10)); // true, 12 is bigger
console.log(numbers.every((n) => n > 10)); // false, 4 and 9 are not
hasCheaperThan(products, limit)
- Parameters:
products: an array of product objects;limit: a number. - Returns:
trueif at least one product has apricestrictly less thanlimit, otherwisefalse.
allInStock(products)
- Parameter:
products: an array of product objects. - Returns:
trueif every product has astockgreater than0, otherwisefalse.
console.log(hasCheaperThan(products, 5));
console.log(hasCheaperThan(products, 1));
console.log(allInStock(products));
Should print:
true
false
falseFinds the cheapest and the most expensive price.
New: Math.min, Math.max and spreading into a call. Math.min and Math.max take separate numbers, not an array:
console.log(Math.max(3, 8, 2)); // 8
console.log(Math.max([3, 8, 2])); // NaN, an array is not a number
Last week you used ... to unpack an array inside [ ]. It works inside the parentheses of a function call too, turning the array into separate arguments:
const values = [3, 8, 2];
console.log(Math.max(...values)); // 8, same as Math.max(3, 8, 2)
So first collect the prices into an array of numbers (which method turns objects into numbers?), then spread them.
- Parameter:
products: a non-empty array of product objects. - Returns: an object with two properties:
min, the lowestprice, andmax, the highestprice.
console.log(priceRange(products));
Should print:
{ min: 1, max: 180 }
Try Math.max() with no arguments at all in the Console. The result (-Infinity) is the reason this task promises a non-empty array.
Calculates how much all the products in the warehouse are worth together.
New: reduce. reduce walks the array and boils it down into one value. You already wrote this by hand in sumAll last week:
let sum = 0; // start value
for (const n of numbers) {
sum = sum + n; // new sum = old sum + current element
}
With reduce, the same thing is:
const sum = numbers.reduce((sum, n) => sum + n, 0);
The callback gets two parameters: the value accumulated so far and the current element. It returns the new accumulated value. The second argument of reduce (here 0) is the start value. Always give one: without it, reduce crashes on an empty array.
- Parameter:
products: an array of product objects. - Returns: the sum of
price * stockover all products. Returns0for an empty array.
console.log(totalStockValue(products));
console.log(totalStockValue([]));
Should print:
1450
0Orders the products from cheapest to most expensive.
New: sort with a compare function. sort calls your function with two elements, a and b, and looks at the number you return:
- negative:
acomes first - positive:
bcomes first - zero: keep their order
For numbers, a - b gives exactly that, and sorts in ascending order:
const numbers = [30, 4, 100];
numbers.sort((a, b) => a - b);
console.log(numbers); // [4, 30, 100]
Two warnings:
- Without a compare function,
sortcompares elements as strings:[30, 4, 100].sort()gives[100, 30, 4]. Always pass one. sortdoes not create a new array. It rearranges the original one. To keep the original untouched, copy it first with spread:[...numbers].sort(...).
- Parameter:
products: an array of product objects. - Returns: a new array with the same product objects, ordered by
pricefrom lowest to highest. The input array must stay in its original order.
console.log(sortedByPrice(products).map((product) => product.name));
console.log(products[0].name);
Should print:
["Pen", "Notebook", "Mouse", "Keyboard", "Backpack", "Monitor"]
Keyboard
If the second line prints Pen, you sorted the original array.
Two functions that deal with arrays inside arrays.
New: flat. A matrix in JavaScript is simply an array of arrays (the rows):
const grid = [
[3, 8, 1],
[9, 2, 7],
[4, 6, 5],
];
console.log(grid[1][0]); // 9, row 1, column 0
flat() removes one level of nesting, so the rows are unpacked into one long array. After that, every method you know works on the matrix:
console.log([[1, 2], [3], [4, 5]].flat()); // [1, 2, 3, 4, 5]
matrixMax(matrix)
- Parameter:
matrix: a non-empty array of non-empty arrays of numbers. - Returns: the largest number anywhere in the matrix.
allRatings(products)
- Parameter:
products: an array of product objects. - Returns: a new array with every rating of every product, in order. Hint:
mapeach product to itsratingsarray first. What do you get? What doesflatdo with it?
console.log(matrixMax(grid));
console.log(matrixMax([[-4, -2], [-9]]));
console.log(allRatings(products));
Should print:
9
-2
[5, 4, 4, 3, 4, 5, 5, 4, 5, 4, 2, 3, 5]Part 2: the DOM. Tasks h to l go into dom.js, and you check them by looking at the page.
The browser turns the HTML into a tree of objects called the DOM (Document Object Model). JavaScript can find these objects and change them, and the page updates immediately.
New: selecting an element. document.querySelector takes a CSS selector (the same kind you write in a stylesheet) and returns the first matching element. If nothing matches, it returns null.
const heading = document.querySelector("h1"); // by tag name
const box = document.querySelector("#subtitle"); // by id, with #
New: changing text, HTML and style.
heading.innerText = "Hello <i>world</i>"; // shows the tags literally: Hello <i>world</i>
heading.innerHTML = "Hello <i>world</i>"; // renders them: Hello world, in italics
heading.style.color = "red";
heading.style.backgroundColor = "yellow"; // CSS background-color becomes backgroundColor
In JavaScript, CSS property names with a hyphen are written in camelCase: background-color becomes backgroundColor, font-size becomes fontSize.
Your job:
- Select the element with the id
title. Change its text toPractice 2 - DOM, its text color towhiteand its background color todarkslateblue. - Select the element with the id
subtitle. Set its content toThis page is now <strong>controlled by JavaScript</strong>.so that the middle part really appears in bold.
Expected result: a white heading on a dark purple background, with a partly bold line under it.
If the Console says Cannot set properties of null, querySelector found nothing: check the spelling of the selector, and do not forget the #.
Fills the product section of the page using the array function from task a.
- Select
#product-countand set its text to4 of 6 products are in stock. Do not type the numbers in: calculate them withinStockNames(products).lengthandproducts.length, and build the sentence with a template literal. - Select
#product-list(an empty<ul>). Put one<li>into it for every name returned byinStockNames(products).
New: building HTML from an array. innerHTML expects one string. map turns each name into a string like "<li>Keyboard</li>", then join("") glues the pieces together with nothing between them:
const colors = ["red", "blue"];
console.log(colors.map((c) => `<li>${c}</li>`).join(""));
// "<li>red</li><li>blue</li>"
- Make every
<li>inside#product-listgreen.
New: selecting many elements. document.querySelectorAll returns all matching elements in a list (a NodeList). If nothing matches, the list is empty. You can walk it with for...of:
const paragraphs = document.querySelectorAll("p");
for (const p of paragraphs) {
p.style.fontWeight = "bold";
}
Expected result: the text 4 of 6 products are in stock, and a green list with Keyboard, Monitor, Notebook and Backpack.
When the Increase button is clicked, the number next to it goes up by one.
New: event handling. Until now, all our code ran once, when the page loaded. To react to the user, you tell the browser: when this happens to this element, run this function. Every event handling task follows the same three steps:
// Step 1: select the elements you need
const button = document.querySelector("#some-button");
// Step 2: write the function that does the work (the event handler)
function handleButtonClick() {
console.log("clicked");
}
// Step 3: connect them (the event listener)
button.addEventListener("click", handleButtonClick);
Note that in step 3 you write handleButtonClick without parentheses. You are handing over the function for the browser to call later. With handleButtonClick() you would call it immediately, once, and hand over its result.
Your job: the counter is the #counter span, the button is #increase. In the handler, read the current number from the span, add one, and write it back.
innerText is always a string. "0" + 1 gives "01", so convert it first with Number(...), like in Practice 1.
Expected result: clicking the button shows 1, 2, 3, and so on.
The user types a maximum price and clicks Filter. The list below shows every product whose price is at most that amount, in the form Keyboard (25 EUR).
- Input field:
#max-price, button:#filter-button, list:#filtered-list. - If no product matches, the list should contain a single item:
No products found. - Every click replaces the previous result, it does not add to it.
New: reading an input field. The text inside an <input> is in its value property, not in innerText. Like innerText, it is always a string:
const input = document.querySelector("#some-input");
const typed = input.value; // "25", a string
const amount = Number(input.value); // 25, a number
Read the value inside the event handler. If you read it at the top of the file, you get whatever was in the field when the page loaded: an empty string.
This task combines everything from today: an event, input.value, filter, map, join and innerHTML.
Expected results:
- max price
20:Mouse (15 EUR),Notebook (3 EUR),Pen (1 EUR)(in the order of theproductsarray) - max price
0:No products found
Challenge task. A small game: when the page loads, the computer picks a secret whole number between 1 and 100. The user types a guess into #guess and clicks #guess-button. #guess-result then shows:
Higherif the secret number is bigger than the guessLowerif the secret number is smaller than the guessCorrectif the guess is right
New: random numbers. Math.random() returns a random decimal number from 0 (included) to 1 (not included), for example 0.4271. To get a whole number from 1 to 100:
Math.random() * 100 // 0 to 99.999...
Math.floor(Math.random() * 100) // 0 to 99, Math.floor cuts off the decimals
Math.floor(Math.random() * 100) + 1 // 1 to 100
Think about where the secret number is created. If you create it inside the event handler, the computer picks a new number on every click, and the game can never be won.
While testing, it helps to console.log the secret number once so you know what to type.
Extra: count the guesses and show them too, for example Correct, 6 guesses.
Követelmények
- Write tasks
atogas function declarations, in the formfunction name(...) { ... }. - Declare variables with
constorlet. Do not usevar. - A function that returns an array must return a new array and leave its input unchanged. This includes
sortedByPrice. - Use strict comparison (
===and!==), not==and!=. - Do not change
index.html. Select elements by the ids given in the tasks. - Attach event handlers with
addEventListener. Do not useonclickattributes in the HTML.