Bent and hammered nail with an emoji face illustration. Done by Andre Coetzer
Section 9

Data Structures, Modern Operators and Strings

Lecture 103

Destructuring Arrays

                    
                      const weekdays = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']

                      // Made up restaurant menu app
                      const openingHours = {
                        [weekdays[3]]: {
                          open: 12,
                          close: 22,
                        },
                        [weekdays[4]]: {
                          open: 11,
                          close: 23,
                        },
                        [weekdays[5]]: {
                          open: 0,
                          close: 24,
                        },
                      };

                      const restaurant = {
                        name: 'Just Delicious',
                        location: "74-5035 Queen Ka'ahumanu Hwy, Kailua-Kona, Hawaii",
                        categories: [
                          '- Seafood & Grill',
                          '- Hawaiian BBQ & Grill',
                          '- Hawaiian',
                          '- Organic',
                        ],
                        starterMenu: [
                          '- Garlic Bread',
                          '- Bread Sticks',
                          '- Hawaiian Salad',
                          '- Garlic Snails',
                          '- Hot Stone Muscles',
                        ],
                        mainMenu: [
                          '- Pizza',
                          '- Seafood Grill',
                          '- Hawaiian Fried Rice & Seafood',
                          '- Seafood Platter with Hawaiian Trio Sauce',
                          '- Ocean Fish, Catch Of The Day',
                        ],
                        sidesMenu: [
                          '- French Fries',
                          '- Hawaiian Coleslaw',
                          '- Hawaiian Macaroni Salad',
                          '- Pineapple Rice',
                          '- Hawaiian Rolls',
                        ],
                        order(starterIndex, mainIndex, sidesIndex) {
                          return [
                            this.starterMenu[starterIndex],
                            this.mainMenu[mainIndex],
                            this.sidesMenu[sidesIndex],
                          ];
                        },
                        openingHours,
                        orderDelivery({
                          starterIndex = 1,
                          mainIndex = 1,
                          sidesIndex = 'X',
                          time = '20:00',
                          address = 'No address was added',
                        }) {
                          console.log(
                            `Order Received! 
                            ${this.starterMenu[starterIndex]}, ${this.mainMenu[mainIndex]}, ${
                              sidesIndex === 'X' ? 'NO SIDES' : this.sidesMenu[sidesIndex]
                            } will be delivered to ${address} at ${time}`
                          );
                        },
                        orderPasta(ing1, ing2, ing3) {
                          console.log(
                            `Your pasta is ready and includes ${ing1}, ${ing2} and ${ing3}`
                          );
                        },
                        orderPizza(mainIngredient, ...otherIngredients) {
                          console.log(
                            `Your ${mainIngredient} pizza is ready, with the optional ingredients ${otherIngredients} added`
                          );
                        },
                      };

                      // Introduction to deconstructing arrays
                      const arr = [1, 2, 3];
                      const a = arr[0];
                      const b = arr[1];
                      const c = arr[2];
                      console.log(a, b, c);

                      // Using the deconstruct syntax
                      const [x, y, z] = arr;
                      console.log(x, y, z);
                    
                  

Deconstructing is basically a way of unpacking values from an array or object into separate variables. Deconstructing help to break down a larger data structure into a simpler one.

Above is the deconstructing assignment, brackets to the left of the assignment operator is known for being the deconstructing syntax even though it looks like an array it's not.

Note

The original array is not damaged or tampered with.

                    
                      // We don't need to select all values when deconstructing
                      const [first, second] = restaurant.categories;
                      console.log(first, second);

                      // We can even skip the second value
                      const [first, , third] = restaurant.categories;
                      console.log(first, third);
                    
                  

When deconstructing we don't need to select all the value, we can just select in this case the first and second value. Though, what if we didn't want the second value but the third? It's as easy as just placing an empty space as illustrated above.

                    
                      // Switching variables
                      let [main, , secondary] = restaurant.categories;
                      console.log(main, secondary);

                      // Old way of switch variables using temp
                      const temp = main;
                      main = secondary;
                      secondary = temp
                      console.log(main, secondary);

                      // using deconstruction as a method of switching
                      [main, secondary] = [secondary, main];
                      console.log(main, secondary);
                    
                  

Hypothetically, let's say that the owner of the restaurant wanted to switch the main category of his restaurant with the third. Above shows the old way of doing so by using a temp variable to hold the value of main and setting main as secondary and secondary as temp, which holds the original main value.

The deconstructing way of doing this, is, as you see, a lot simpler.

                    
                      // Unpacking a function into variable using deconstruction
                      const [starterMeal, mainMeal, sideMeal] = restaurant.order(0, 1, 0);
                      console.log(starterMeal, mainMeal, sideMeal);
                    
                  

Above shows how we can receive values from functions and immediately unpack them into variables.

                    
                      // deconstructing nested arrays
                      const nested = [1, 'skip me', 3, [4, 5]];
                      const [q, , w, [e, r]] = nested;
                      console.log(q, w, e, r);
                    
                  

Above illustrates how we can deconstruct nested arrays, by simply adding another deconstruction syntax within the already established deconstruction syntax.

                    
                      // Assigning a default value
                      const mysteryAPI = [8, 9];
                      const [xx = 1, yy = 1, zz = 1] = mysteryAPI;
                      console.log(xx, yy, zz);
                    
                  

Sometimes we won't know the length of an array, so we can assign default values to each variable just in case the array is not as long.

Practicing default values
                    
                      // Practice example, lets say we don't order a side dish
                      const [starterMeal = 'X', mainMeal = 'X', sideMeal = 'X'] = restaurant.order(0, 1);
                      console.log(starterMeal, mainMeal, sideMeal);
                    
                  

Let's say, when we order we do not order a side dish, so we add a default value to the variables just in case we leave one out.

Lecture 104

Destructuring Objects

                    
                      // Deconstructing an object
                      const { name, openingHours, categories } = restaurant;
                      console.log(name, openingHours, categories);
                    
                  

Similar to deconstructing an array, to deconstruct an object we use curly braces instead of square brackets. When deconstructing objects, we don't need to manually skip objects like we did with arrays, because with objects as we know, order doesn't matter.

In order to deconstruct we need to use the property names of the object, at the same time it creates variable based on those names.

                    
                      // Changing the variable name
                      const {
                        name: restaurantName,
                        openingHours: hours,
                        categories: tags,
                      } = restaurant;
                      console.log(restaurantName, hours, tags);
                    
                  

As mentioned above, we need to specify the property name in order to can deconstruct the object, however we can also change the variable to something other than the properties name as shown in the above example. This can be very useful when dealing with 3rd party data.

                    
                      // Setting default values as well as changing the names
                      const { menu = [], starterMenu: starters = [] } = restaurant;
                      console.log(menu, starters);
                    
                  

Like arrays, we can give default values, for example, above there is no menu property in the restaurant object, so it will receive a default value of an empty array.

You can also combine a new name with a default value.

                    
                      // Mutating variables
                      let a = 111;
                      let b = 999;
                      console.log(a, b);

                      const obj = { a: 27, b: 34 };
                      ({ a, b } = obj);
                      console.log(a, b);
                    
                  

Just like switching variables with arrays, we can mutate variables with objects, but we need to wrap the syntax within parenthesis, otherwise it's expecting a code block, and you cannot assign anything to a code block.

                    
                      // Deconstructing nested objects
                      const {fri: {open: o, close: c}} = openingHours;
                      console.log(`Opening time is at ${o}, Closing time is at ${c}`);
                    
                  

With objects deconstructing we can also deconstruct nested objects, as seen in the example above, we can go as far as even changing the variable name for those nested object properties.

                    
                      // Using an object as an argument for a method, which will be deconstructed
                      restaurant.orderDelivery({
                        time: '22:00',
                        address: 'Texes State',
                        starterIndex: 3,
                        mainIndex: 2,
                      });
                    
                  

Many times in JavaScript, we have functions with a lot of parameters. It can become hard to know the order of these parameters for someone using this function. So instead of defining the parameters manually, we can just pass an object as an argument to the function and the function will then deconstruct the object. This is a standard practice in JavaScript, especially when using 3rd party libraries.

Lecture 105

The Spread Operator (...)

                    
                      const arr = [3, 4, 5];
                      const badNewArr = [1, 2, arr[0], arr[1], arr[2]];
                      console.log(badNewArr);

                      // Using the spread operator inside a array literal
                      const newArr = [1, 2, ...arr];
                      console.log(newArr);

                      // Using the spread operator in functions
                      console.log(...newArr);
                      console.log(...restaurant.mainMenu);
                    
                  

You can use the spread operator to basically unpack an array's elements. We use the spread operator whenever we would otherwise write multiple values that are separated by a comma.

That situation happens when we write array literals, for example, [1, 2, 3] or when we pass arguments into a function as the console.log() example above.

You might think that the spread operator is similar to that of deconstructing. The difference however is that it doesn't make new variables and takes all values from the array. The consequence of this is we can only use it where values are separated by commas are expected.

                    
                      // Creating a new array built off of another, while adding to it
                      const newMenu = [...restaurant.mainMenu, '- Grilled Cheese'];
                      console.log(...newMenu);

                      // Shallow copy of an array
                      const mainMenuCopy = [...restaurant.mainMenu]
                      console.log(mainMenuCopy);

                      // Merging arrays
                      const menuMerge = [...restaurant.starterMenu, ...restaurant.mainMenu, ...restaurant.sidesMenu];
                      console.log(menuMerge);
                    
                  

Two important use cases for the spread operator is to make shallow copies of arrays much like the object.assign({}, object) method, just this syntax is easier. The other being the ability to merge arrays together.

The spread operator is not only for arrays, it's for all so-called iterable elements. Iterable's are things like arrays, strings, maps and sets which we will look into later, but not objects.

Basically, most of the built-in data structures in JavaScript are iterable, just not objects. Though later we will see objects support the spread operator.

                    
                      // Using the spread operator on strings
                      const str =  'Andre';
                      const letters = [...str, , 'C.']
                      console.log(letters)
                    
                  

Since strings are iterable, we can unpack the string into an array, where each letter is its own value within the array. Remember, you can only use spread in an array or function argument, where they expect values separated by commas.

                    
                      // Spread operator using our restaurant example
                      const ingredients = [
                        prompt('Lets make your pasta!, Whats your first Ingredient?'),
                        prompt('Whats your second ingredient?'),
                        prompt('Whats your third ingredient?'),
                      ];
                      console.log(ingredients);

                      restaurant.orderPasta(...ingredients)
                    
                  

Above we wrote a function that when you order pasta you are prompt'ed with choosing 3 ingredients. We take this array of values and unpack them in the order pasta function using the spread operator.

                    
                      // Spread operator with objects
                      const newRestaurant = {foundedIn: 1989, ...restaurant, founder: 'Hala Lulu'};
                      console.log(restaurant);
                      console.log(newRestaurant);

                      // Coping is not pointing to same reference
                      const restaurantCopy = {...restaurant};
                      restaurantCopy.name = 'Hala Lulu';
                      console.log(restaurant.name, restaurantCopy.name);
                    
                  

Since 2018 the spread operator works with objects even though it's not an iterable.

Lecture 106

Rest Pattern and Parameters

                    
                      const anArray = [1, 2, ...[3, 4]];

                      // The rest operator in the deconstruction syntax
                      const [a, b, ...otherNumbers] = anArray;
                      console.log(a, b, otherNumbers);
                    
                  

The rest pattern has the same syntax as the spread operator, tough does the opposite of the spread operator. The spread operator is to unpack an array while the rest operator is to pack into an array.

How can we tell the difference between the spread and rest operators if they look exactly the same, we spread is on the right of the assignment operator, while rest is on the left.

In the example above, we see the variable called others, the rest operator is called rest as it takes the rest of the elements and packs them into an array.

                    
                      // Rest operator in objects
                      const {sat: weekendHours, ...weekdayHours} = restaurant.openingHours;
                      console.log(weekendHours, weekdayHours);
                    
                  

Above, we show that we can use both the spread operator and rest operator. A few important things to note is that the rest operator will only start collecting the rest of the elements from the last element in the deconstructing assignment, there can also only be one rest operator, and it has to be the last syntax written within the deconstructing assignment.

                    
                      // Rest operator in objects
                      const {sat: weekendHours, ...weekdayHours} = restaurant.openingHours;
                      console.log(weekendHours, weekdayHours);
                    
                  

Above, we see how the pattern places the rest of the objects into a new object.

                    
                      // rest in functions
                      function restAdd (...nums) {
                        let sum = 0;
                        for (let i = 0; i < nums.length; i++) sum += nums[i];
                        console.log(sum);
                      }

                      restAdd(1, 2);
                      restAdd(2, 3, 5);
                      restAdd(5, 5, 10, 30);
                      restAdd(5, 2, 3, 5, 15, 10, 5, 2, 3, 50);

                      // taking it one step further and using the spread operator in the function call
                      const x = [5, 5, 10, 30, 50];
                      restAdd(...x);
                    
                  

When the rest syntax is in a function's parameter, it is known as a rest parameter. The benefit of this is that the function can have an infinite amount of values, in this case numbers, without having to define each parameter.

                    
                      // Using the rest operator in our restaurant example
                      restaurant.orderPizza('pineapple', 'bacon', 'sour cream');
                                          
                      // using the spread in combination with the rest param
                      const pizzaIngredients = ['mushrooms', 'olives', 'spinach', 'pepperoni']
                      restaurant.orderPizza(...pizzaIngredients);
                    
                  

Above, we just use the rest syntax with our restaurant example.

Lecture 107

Short Circuiting (&& and ||)

                    
                      // Short circuiting with OR
                      console.log('---- OR Short Circuit Evaluation ----');
                      console.log(3 || 'Andre');
                      console.log('' || 'Andre');
                      console.log(true || 0);
                      console.log(undefined || null);
                      console.log(undefined || 0 || '' || 'Hello' || 23 || null);
                    
                  

Up until now we used logical operators for boolean values, the truth is we can use them for a lot more. There are 3 properties for logical operators, and they are that they can use ANY data type, they can return ANY data type, and they can be used for short-circuiting or short circuit evaluation.

Short-circuiting in the case of the 'OR' logical operator is that it will return the first truthy value, though if both are true it will return the last operand or value.

                    
                      // Practical use case for OR
                      restaurant.numGuests = 0; // This can cause an issue
                      const guestOne = restaurant.numGuests ? restaurant.numGuests : 10;
                      const guestTwo = restaurant.numGuests || 10;

                      console.log(guestOne);
                      console.log(guestTwo);
                    
                  

The example above shows how we can use short-circuiting to set a default value, if something doesn't exist. There is a small problem with the example above, and that is if the number of guests is actually 0, it will default to 10, in the next lecture we look at how we can prevent this.

                    
                      console.log(0 && 'Andre');
                      console.log('Jonas' && 'Andre');
                      console.log('Hello' && 23 && null && '' && 'Andre');
                    
                  

When it comes to the short circuit evaluation of the 'AND' operator, it works the complete opposite of the 'OR' operator. The 'AND' operator short circuits when the value is falsy, if both are falsy just like the 'OR' it will return the last value or operand.

                    
                      // Practical use case for AND
                      if (restaurant.orderPizza) {
                        restaurant.orderPizza('Pepperoni', 'Basil', 'Feta');
                      };

                      restaurant.orderPizza && restaurant.orderPizza('Pepperoni', 'Basil', 'Feta');
                    
                  

We can use the 'AND' short circuit evaluation to check if something exists. As for a practical explanation, we can use the 'OR' short-circuiting to set default values if the value is falsy, and with 'AND' we can execute code if the value is truthy.

Lecture 108

The Nullish Coalescing Operator (??)

                    
                      // Practical use case for OR
                      restaurant.numGuests = 0; // This can cause an issue
                      const guestOne = restaurant.numGuests ? restaurant.numGuests : 10;
                      const guestTwo = restaurant.numGuests || 10;

                      console.log(guestOne);
                      console.log(guestTwo);

                      // Nullish coalescing operator (??)
                      const guestThree = restaurant.numGuests ?? 10;
                      console.log(guestThree);
                    
                  

Before we had this problem with the 'OR' short circuit evaluation, where it saw the 0 as a falsy value and then returned the next value which was 10 and truthy, even it were both falsy it will still return the last value or operand.

However, lucky for us, there is the nullish coalescing operator that works the same way as the 'OR' but will yield the correct result. This works because it doesn't work with falsy values, but nullish values. Nullish values are undefined and null, (NOT 0 or ''), these are seen as if they were truthy. It's important to know it works the exact same way as the 'OR' Short circuit evaluation, just not with falsy values.

Basically, if set to 0 it short-circuits and immediately the first non nullish value is returned as if it were truthy, but if there were a nullish value it would return the right side operand of 10.

Lecture 109

Logical Assignment Operators

                    
                      const rest1 = {
                        name: 'Pizzaria Maria',
                        numGuests: 0,
                      }

                      const rest2 = {
                        name: 'Taco Taco',
                        owner: 'El Sanchez',
                      }

                      // the logical OR assignment operator
                      rest1.numGuests = rest1.numGuests || 10;
                      rest2.numGuests = rest2.numGuests || 10;
                      console.log(rest1)
                      console.log(rest2)

                      rest1.numGuests ||= 15;
                      rest2.numGuests ||= 15;
                      console.log(rest1)
                      console.log(rest2)
                    
                  

Even more modern than the coalescing operator is the new logical assignment operators that were introduced in 2021. Basically, the 'OR' assignment operator assigns a value to the property if the property has a falsy value.

                    
                      // the nullish assignment operator
                        rest1.numGuests = rest1numGuests ?? 15;
                        rest2.numGuests = rest2numGuests ?? 15;

                        rest1.numGuests ??= 15;
                        rest2.numGuests ??= 15;
                        console.log(rest1)
                        console.log(rest2)
                    
                  

Above with the 'OR' logical operator, we ran into the same problem when setting the value to 0, but we do have a nullish assignment operator that works as we would expect it should.

                    
                      // the logical AND assignment operator
                      rest1.owner = rest1.owner && '<ANONYMOUS>'
                      rest2.owner = rest2.owner && '<ANONYMOUS>'
                      
                      rest1.owner &&= '<ANONYMOUS>'
                      rest2.owner &&= '<ANONYMOUS>'
                      console.log(rest1);
                      console.log(rest2);
                    
                  

We also have the 'AND' assignment operator, which works as expected. However, it's good to note that with rest1 not having an owner property, with the first method of using the short circuit evaluation, it assigns the property and gives it the value of undefined, while when using the logical 'AND' assignment operator it does not. Just something to keep in mind.

Lecture 110

Coding Challenge #1

We're building a football betting app (soccer for my American friends 😅)! Suppose we get data from a web service about a certain game ('game' variable on next page). In this challenge we're gonna work with that data.

Your Tasks:
  1. Create one player array for each team (variables 'players1' and 'players2').

  2. The first player in any player array is the goalkeeper and the others are field players. For Bayern Munich (team 1) create one variable ('gk') with the goalkeeper's name, and one array ('fieldPlayers') with all the remaining 10 field players.

  3. Create an array 'allPlayers' containing all players of both teams (22 players).

  4. During the game, Bayern Munich (team 1) used 3 substitute players. So create a new array ('players1Final') containing all the original team1 players plus 'Thiago', 'Coutinho' and 'Perisic'.

  5. Based on the game.odds object, create one variable for each odd (called 'team1', 'draw' and 'team2').

  6. Write a function ('printGoals') that receives an arbitrary number of player names (not an array) and prints each of them to the console, along with the number of goals that were scored in total (number of player names passed in).

  7. The team with the lower odd is more likely to win. Print to the console which team is more likely to win, without using an if/else statement or the ternary operator.

Test Data for 6:
  • First, use players 'Davies', 'Muller', 'Lewandowski' and 'Kimmich'. Then, call the function again with players from game.scored

                    
                      const game = {
                        team1: 'Bayern Munich',
                        team2: 'Borrussia Dortmund',
                        players: [
                          [
                            'Neuer',
                            'Pavard',
                            'Martinez',
                            'Alaba',
                            'Davies',
                            'Kimmich',
                            'Goretzka',
                            'Coman',
                            'Muller',
                            'Gnarby',
                            'Lewandowski',
                          ],
                          [
                            'Burki',
                            'Schulz',
                            'Hummels',
                            'Akanji',
                            'Hakimi',
                            'Weigl',
                            'Witsel',
                            'Hazard',
                            'Brandt',
                            'Sancho',
                            'Gotze',
                          ],
                        ],
                        score: '4:0',
                        scored: ['Lewandowski', 'Gnarby', 'Lewandowski', 'Hummels'],
                        date: 'Nov 9th, 2037',
                        odds: {
                          team1: 1.33,
                          x: 3.25,
                          team2: 6.5,
                        },
                      };

                      // Task 1
                      const [playersOne, playersTwo] = game.players;
                      console.log(`Players One: ${playersOne}`);
                      console.log(`Players Two: ${playersTwo}`);

                      // Task 2
                      const [gk, ...fieldPlayers] = playersOne;
                      console.log(`GoalKeeper is ${gk} and field players are`, fieldPlayers);

                      // Task 3
                      const allPlayers = [...playersOne, ...playersTwo];
                      console.log('All players are ', allPlayers);

                      // Task 4
                      const playersFinal = ['Thiago', 'Coutinho', ...playersOne, 'Perisic'];
                      console.log('The final team for Team One ', playersFinal);

                      // Task 5
                      const { odds: {team1: teamOne, x: draw, team2: teamTwo} } = game;
                      console.log(`Team 1: ${teamOne}, Draw: ${draw}, Team 2: ${teamTwo}`);

                      // Task 6
                      game.printGoals('Lewandowski', 'Gnarby', 'Lewandowski', 'Hummels');
                      game.printGoals('Lewandowski', 'Gnarby');
                      game.printGoals(...game.scored);

                      // Task 7
                      teamOne < teamTwo && console.log('Team one is more likely to win');
                    
                  
Lecture 111

Looping Arrays: The for-of Loop

                    
                      const menu = [...restaurant.starterMenu, ...restaurant.mainMenu];

                      // The for-of loop
                      for ( const item of menu) console.log(item);
                    
                  

When looping over an array we already know how it's done using the traditional for loop and setting up the counter, a condition and the incrementing the counter, however, with the 'for-of' loop we skip all that.

This will automatically loop over the entire array. It's important to note that 'for-of' loop can still utilize the break and continue keywords.

                    
                      // To get the index value
                      for (const item of menu.entries()) {
                        console.log(item)
                      }

                      for (const [i, item] of menu.entries()) {
                        console.log(`${i + 1} ${item}`)
                      }
                    
                  

The 'for-of' loop was not really designed to provide an index value, however it is still possible using the 'entries()' method, which will place each iteration in its own array with an index number.

Since item is now an array, we can use array deconstruction to set each index to its own variable.

Lecture 112

Enhanced Object Literals

                    
                      const spices = {
                        driedFruit: ['Apples', 'Pears', 'Strawberry'],
                        spiceMix: ['cinnamon sticks', 'allspice berries', 'lemongrass'],
                      };

                      const tea = {
                        name: 'Lemon Tang',
                        spices,
                        isWaterBoiled: true,
                        addWater() {
                          if (this.isWaterBoiled) console.log(`${this.isWaterBoiled &&= `Water is boiled, lets make some ${this.name} tea betch`}`);
                        },
                      }
                      tea.addWater()
                    
                  

In modern JavaScript, they introduced 3 ways to make it easier to write object literals. The first is you can place an outside object inside an object just a providing the name.

The second is you no longer need to write function expressions (see example above). Third is you can compute an object name if it was already declared in an array for example.

Lecture 113

Optional Chaining (?.)

                    
                      if (restaurant.openingHours && restaurant.openingHours.mon) {
                        console.log(restaurant.openingHours.mon.open);
                      } else {
                        console.log('Does not exist');
                      }
                      // The optional chaining operator
                      console.log(restaurant.openingHours.mon?.open);
                      console.log(restaurant.openingHours?.mon?.open);
                    
                  

With optional chaining, if a certain property does not exist, it will return undefined immediately. Above, with the optional chaining example, we are recreating the first example or if statement.

What it says basically if .mon exists then return .open if it does not exist it will return undefined immediately. A property exists if it's not null or undefined, so the nullish coalescing concept is adopted here, meaning if it were 0 or an empty string it will exist.

                    
                      // practical Example
                      const days = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'];

                      for (const day of days) {
                        const open = restaurant.openingHours[days]?.open ?? 'closed';
                        console.log(`On ${day} we are open at ${open}`);
                      }
                    
                  

Above is a practical example if we had used an API and wanted to know if the restaurant was open and on which day it was open, using the optional chaining and nullish coalescing. 

                    
                      // Optional chaining with methods
                      console.log(restaurant.order?.(0, 1) ?? 'Method does not exist');
                      console.log(restaurant.orderRisotto?.(0, 1) ?? 'Method does not exist');
                    
                  

Optional chaining can also check if methods exists, and if it does exist to then execute the function. Remember to always use the nullish coalescing operator with optional chaining.

                    
                      // Optional chaining with arrays
                      const usersOne = [{ founder: 'Andre', email: 'learning@somewhere.com' }];
                      console.log(usersOne[0]?.founder ?? 'User does not exist');
                                          
                      const usersTwo = [{ name: 'Andre', email: 'learning@somewhere.com' }];
                      console.log(usersTwo[0]?.founder ?? 'User does not exist');
                                          
                      // This show we would have to write it without optional chaining
                      if (usersOne.length > 0) console.log(usersOne[0].founder);
                      else console.log('User does not exist');
                    
                  

Again, optional chaining, can also be used with arrays for example if an array is empty or not. The if statement above is an example of how we would have had to do this with the if statement.

Lecture 114

Looping Objects: Object Keys, Values, and Entries

                    
                      // Iterating over objects
                      for (const days of Object.keys(openingHours)) {
                        console.log(days);
                      }
                    
                  

As we know we can iterate over arrays, strings etc... however we can also iterate over objects in an indirect way. There are different ways we can go about it, we can decide to iterate over 3 things, keys (property names), values or entries.

We still loop over the object using a 'for-of' loop, but in an indirect way, as we will still be looping over an array.

                    
                      // Iterating over object key/names
                      const properties = Object.keys(openingHours);
                      let openStr = `We are open on ${properties.length} days`;

                      for (const day of properties) {
                        openStr += ` ${day}`;
                      };
                      console.log(openStr)
                    
                  

Above we can see that indeed the thing we are iterating over is an array. We can also use this to compute how many properties are in the object.

                    
                      // Iterating over object values
                      const values = Object.values(openingHours);
                                          
                      for (const {open, close} of values) console.log(open, close);
                    
                  

Above is how we iterate over the values.

                    
                      // iterating over the object entries
                      const entries = Object.entries(openingHours);

                      for (const [keys, {open, close}] of entries) {
                        console.log(keys, open, close)
                      }
                    
                  

Again, here we are iterating over both the keys and values, just take note that deep objects or objects on a second layer will remain objects.

Lecture 115

Coding Challenge #2

Let's continue with our football betting app! Keep using the 'game' variable from before.

Your Tasks:
  1. Loop over the game.scored array and print each player name to the console, along with the goal number (Example: "Goal 1: Lewandowski").

  2. Use a loop to calculate the average odd and log it to the console (We already studied how to calculate averages, you can go check if you don't remember).

  3. Print the 3 odds to the console, but in a nice formatted way, exactly like this: Odd of victory Bayern Munich: 1.33 Odd of draw: 3.25 Odd of victory Borrussia Dortmund: 6.5. Get the team names directly from the game object, don't hardcode them (except for "draw"). Hint: Note how the odds and the game objects have the same property names 😉.

  4. Bonus: Create an object called 'scorers' which contains the names of the players who scored as properties, and the number of goals as the value. In this game, it will look like this:

                            
                              {
                               Gnarby: 1,
                               Hummels: 1,
                               Lewandowski: 2
                              }
                            
                          
                    
                      // Task 1
                      for (const [i, player] of game.scored.entries()) {
                        console.log(`Goal ${i + 1}: ${player}`);
                      }

                      // Task 2
                      const gameAverages = function () {
                        const odds = Object.values(game.odds);
                        let sum = 0;
                      
                        for (const odd of odds) {
                          sum += odd;
                        }
                        sum /= odds.length;
                      
                        return sum.toFixed(2)
                      };
                      gameAverages();

                      console.log(gameAverages());

                      // Task 3
                      const printOdds = function () {
                        const odds = Object.entries(game.odds);
                      
                        for (const [team, odd] of odds) {
                          const newStr = team === 'x' ? 'draw' : game[team];
                          console.log(`Odd of ${newStr}: ${odd} `)
                        }
                      }
                      printOdds()

                      // Task 4
                      const scorers = {};

                      for (const player of game.scored) {
                        scorers[player] ? scorers[player]++ : scorers[player] = 1;
                      }

                      console.log(scorers);
                    
                  
Lecture 116

Sets

                    
                      // creating a set
                      const orderSet = new Set(['Pizza', 'Pasta', 'Risotto', 'Pizza', 'Pasta']);
                      console.log(orderSet);
                      console.log(new Set('Andre'));

                      // Checking if an item exist, and the size
                      console.log(orderSet.has('Pizza'));
                      console.log(orderSet.size);
                    
                  

In the past, JavaScript had only has two built in data types, that being objects and arrays. However, with ES6, sets and maps were introduced.

A set is just a collection of unique values, whether it be primitive or reference types, therefor a set can never have any duplicates. Note, sets can hold mixed data types as well. With the example above, all duplicates in 'orderSet' are gone, as will in the string.

We can also check if a set have a particular value, as well as the size of a set.

                    
                      // Adding to a set
                      orderSet.add('Garlic Bread');
                      console.log(orderSet);
                    
                  

In the example above, is how we can add to a set using the 'add()' method. This brings us to the question of if we can add to a set, how can we retrieve from a set? The answer is, we cannot, apart from using the '.has()'method.

If you want to retrieve data, then it's better to use something like an array.

                    
                      // Clearing a set
                      orderSet.clear()
                      console.log(orderSet);
                    
                  

There is one more method that be used on sets, and that is the 'clear()' method. Clear, will remove all data from the set.

                    
                      // iterating over sets
                      for (const order of orderSet) console.log(order)
                    
                  

Sets are iterable and therefor we can loop over it.

                    
                      // Practical example
                      const staff = ['Waiter', 'Chef', 'Waiter', 'Manager', 'Waiter', 'Chef'];
                      // we wrap this in brackets and spread the set to turn it from set to array.
                      const uniqueStaff = [...new Set(staff)] 
                      console.log(uniqueStaff)

                      // If we just want the size of the set
                      console.log(new Set(['Waiter', 'Chef', 'Waiter', 'Manager', 'Waiter', 'Chef']).size);
                    
                  

In our practical example, we create an array that holds duplicate data, we then create a new variable, whereby we input the first array into a set data structure and then use the spread operator to turn the 'Set' back into an array, this creating a new array holding only the unique values.

However, if we just wanted to know the unique size of an array, then we can simply just log the 'Set' with the '.size' property.

In Summary

Sets are not intended to replace arrays, set are used to determine unique values or remove repeated values from an array.

Lecture 117

Maps: Fundamentals

                    
                      // creating a map
                      const rest = new Map();

                      // adding data to the map using set
                      rest.set('name', 'Classico Pizza');
                      rest.set(1, 'Italy');
                      rest.set(2, 'France');
                    
                  

We learn't that maps is the other new built-in data structure in JavaScript. They are more useful than sets.

A map is a data structure to map values to keys. We know within object, keys are always strings, however with maps the keys can be any data type, including arrays, objects or even other maps.

The '.set(key, value)' method is how we add to a map, the first value is always the key and the second the value. It's important to know that the '.set()' method not only adds data to the map, but also returns it.

                    
                      // Chaining sets
                      rest.set('categories', ['Seafood', 'BBQ', 'Hawaiian', 'Organic',]).set('open', 11).set('close', 23).set(true, 'Yes, we are open').set(false, 'No, We are closed');
                      console.log(rest)
                    
                  

The fact that the '.set()' method returns the map, means that we can chain the set method when adding data, as seen in the above example.

                    
                      // To get values from maps using get
                      console.log(rest.get('name'));
                      console.log(rest.get(false));
                    
                  

To get values from maps, we use the '.get(key)' method. One thing to note about the '.get()' method is the key data type entered is important, meaning it needs to match the 'typeof' basically.

                    
                      // Working with boolean values
                      const time = 21;
                      console.log(rest.get(time > rest.get('open') && time < rest.get('close')));
                    
                  

The example above is not really practical, but it shows the power of using booleans as keys.

                    
                      // Checking for keys, the size and deleting elements
                      // checking for a key using has
                      console.log(rest.has('name'));

                      // deleting from a map
                      console.log(rest) // before
                      rest.delete(2)
                      console.log(rest) // after

                      // Checking the size
                      console.log(rest.size)
                    
                  

Above are a few examples of further properties and methods that the map data structure has. Starting with the '.has(key)' method, this checks if a key is present within the map structure. Map also has the '.delete(key)' method, which, as you guessed, deletes a key value pair.

Furthermore, map has the '.size' property to check the size of the map, as well as the'.clear()' method to clear all values or data from the map.

                    
                      // using an array as a key
                      const arr = [1, 2];
                      // rest.set([1, 2], 'Hello There') This wont work
                      rest.set(arr, 'Hello There')

                      console.log(rest.get(arr));
                    
                  

Above we show that we can set an array as a key, but if we directly use an array as a key, if we had to call on it later, that would point to different reference types in memory.

The workaround for that is to assign an array to a variable and use that.

Lecture 118

Maps: Iteration

                    
                      const question = new Map([
                        ['question', 'What is the best programming language in the world?'],
                        [1, 'C'],
                        [2, 'Java'],
                        [3, 'javaScript'],
                        ['correct', 3],
                        [true, 'Correct'],
                        [false, 'Try again'],
                      ]);

                      // Object.entries() method
                      console.log(Object.entries(openingHours));
                      const hoursMap = new Map(Object.entries(openingHours));
                      console.log(hoursMap);
                    
                  

In the last lecture, we added values to the map using the '.set(key, value)' method, but there is a better way of doing that and the is writing the map as an array of arrays. However, when we're adding additional elements to the map, then using something like the '.set()' method is a good option.

                    
                      // Object.entries() method
                      console.log(Object.entries(openingHours));
                      const hoursMap = new Map(Object.entries(openingHours));
                      console.log(hoursMap);
                    
                  

The first example is an array of arrays, is a similar structure to that of the Object.entries() method we saw. This means there is an easy way of converting from object to maps.

                    
                      // Iterating over the map
                      console.log(question.get('question'));
                      for (const [key, value] of question) {
                        if (typeof key === 'number') console.log(`Your answer: ${key} : ${value}`);
                      };

                      // let answer = Number(prompt("What's your answer?"));
                      let answer = 3;

                      // Challenge
                      console.log(question.get(answer === question.get('correct')));
                    
                  

Above is a challenge for a 'quiz app' that if the user had to input the answer ranging from 1 to 3 that it should output the boolean value of either true or false.

                    
                      // Convert map back to an array
                      console.log(question);
                      console.log([...question]);
                    
                  

Above is how we can go back from a map to an array if need be.

Lecture 119

Summary: Which Data Structure to Use?

To help us understand which data structure to use, we first need to understand where we can receive data.

There are 3 main sources of data collection, that being, from the program itself, so data that is written directly into the source code, an example of that being a status message.

Data from UI, so data input from the user or data written in the DOM, example being a to-do list app and data from outside sources such as API's, an example of that being a recipe object.

Once we have this data, also known as the data collection or collection of data, we need a place to put it. We store this data in data structures, but there are four built-in data structures, and we need to decide which to use.

Do we just need a simple list of values?, then we will use arrays or sets, but if we need key values pairs, then we will need objects and maps. The big difference between the two is that keys allows us to describe the values, whereas a list or sets simply have values without description.

Going back to web API's, which is usually the most common source of data. Data from web API's usually come in the form of JSON formatting. JSON is essentially just text, so a long string, but can easily be converted into JavaScript objects, because it uses the same formatting as JavaScript objects and arrays.

There are other built-in data structures we didn't talk about yet, which are 'weakSets' and 'weakMaps'. However, there are non-built-in data structures that exist, especially in other programming languages, and to name a few, stacks, queues, linked lists, Trees and hash tables.

These don't matter as for now, but it's good to know they exist.

                    
                      // Arrays vs Sets
                      // array
                      const tasksArr = ['Code', 'Eat', 'Code', 'Sleep'];
                      console.log(tasksArr);
                      // ['Code', 'Eat', 'Code', 'Sleep']

                      // set
                      const tasksSets = new Set(['Code', 'Eat', 'Code', 'Sleep']);
                      console.log(tasksSets);
                      // {'Code', 'Eat', 'Sleep'}
                    
                  

Arrays should be used when you need an ordered list, which might contain duplicates, and when you need to manipulate data. Sets on the other hand, are used when you need to work with unique values, or when higher performance is really important and of course when you want to remove duplicates from an array.

                    
                      // Object vs Map
                      // Object
                      const tasksObj = {
                        task: 'code',
                        date: 'today',
                        repeat: true,
                      };
                      console.log(tasksObj);
                      // {task: 'code', date: 'today', repeat: true,}

                      // Map
                      const tasksMap = new Map([
                        ['task', 'code'], 
                        ['date', 'today'], 
                        [false, 'Start coding!'], 
                      ]);
                      console.log(tasksMap);
                      // {task => 'code', date => 'today', repeat => true,}
                    
                  

Objects were just the traditional way of writing objects, it's easier than maps to write, and we can use '.' and [] to write and read data. Whereas, map give better performance, key can have any data type, easy to iterate and easy to compute size.

However, the fact objects are just easier to write and access data make them the preferred way of writing key, values pairs. However, the main reasons are when to use which, so, you use objects when you need to include functions (methods), and when working with JSON or the 'this' keyword.

Maps are to be used when you simply need to map keys to values, or when you need keys that are not strings

Lecture 120

Coding Challenge #3

Let's continue with our football betting app! This time, we have a map called 'gameEvents' (see below) with a log of the events that happened during the game. The values are the events themselves, and the keys are the minutes in which each event happened (a football game has 90 minutes plus some extra time).

Your Tasks:
  1. Create an array 'events' of the different game events that happened (no duplicates).

  2. After the game has finished, is was found that the yellow card from minute 64 was unfair. So remove this event from the game events log.

  3. Compute and log the following string to the console: "An event happened, on average, every 9 minutes" (keep in mind that a game has 90 minutes).

  4. Loop over 'gameEvents' and log each element to the console, marking whether it's in the first half or second half (after 45 min) of the game, like this:[FIRST HALF] 17: âš½ GOAL

                    
                      // Task 1
                      const events = [...new Set(gameEvents.values())];
                      console.log(events);

                      // Task 2
                      gameEvents.delete(64);
                      console.log(gameEvents);

                      // Task 3
                      console.log(`An event happened, on average, every ${90 / gameEvents.size} minutes`);

                      const time = [...gameEvents.keys()].pop();
                      console.log(time);

                      console.log(`An event happened, on average, every ${time / gameEvents.size} minutes`
                      );

                      // Task 4
                      for (const [min, value] of gameEvents) {
                        const half = min <= 45 ? 'FIRST' : 'SECOND';
                        console.log(`[${half} HALF] ${min}: ${value}`);
                      };
                    
                  
Lecture 121

Working With Strings - Part 1

                    
                      const airline = 'Bulgaria Air';
                      const plane = 'A320';

                      // indexing strings
                      console.log(plane[0]);
                      console.log(plane[1]);
                      console.log(plane[2]);
                      console.log('B737'[0]);

                      // checking the length of a string
                      console.log(airline.length);
                      console.log('Andre'.length);
                    
                  

Above we see that we can index strings, both when stored in a variable and directly. We can also check the length, using the '.length' property, again both on the variable and string directly.

                    
                      // using the indexOf() & lastIndexOf() methods
                      console.log(airline.indexOf('r'));
                      console.log(airline.lastIndexOf('r'));
                      console.log(airline.indexOf('Air'));
                    
                  

Strings have similar methods to that of arrays, for example the '.indexOf()' method above. '.indexOf()' returns the first occurrence of the specified value, we also have '.lastIndexOf()' method which will return the last occurrence of the specified value.

                    
                      // using the slice(begin, end) method
                      console.log(airline.slice(9));
                      console.log(airline.slice(9, 11));
                    
                  

The '.slice(start, end)' method, slices strings based on a given numerical value, of course we do not have to hard code the value, we can also use the '.indexOf()' method to identify the index values for us. We also do not need to provide a end value, in this case, slice will return everything but where it started.

The '.slice()' method can also receive negative numbers, keep in mind that the result of slice will always be 'end' minus 'start'.

                    
                      // Not harding coding index values
                      console.log(airline.slice(0, airline.indexOf(' ')));
                      console.log(airline.slice(airline.indexOf(' ') + 1)); // + 1 to remove empty space
                    
                  

If we don't know the index number of something, instead of hard coding it, we can use '.indexof()' to provide the values, as mentioned above.

                    
                      // Slice with negative numbers
                      console.log(airline.slice(-2));
                      console.log(airline.slice(1, -1));
                    
                  

We can also provide negative values for slice.

                    
                      // function incorporating the use slice
                      const checkMiddleSeat = function (seat) {
                        // B and E are middle seats
                        const s = seat.slice(-1);
                        if (s === 'B' || s === 'E') console.log('You got the middle seat');
                        else console.log('You got lucky');
                      };

                      checkMiddleSeat('11B');
                      checkMiddleSeat('23C');
                      checkMiddleSeat('1E');
                    
                  

Above, we wrote a function for checking if your seat number is a middle seat or not, utilizing the use of the '.slice()' method.

This brings us to the question of how is it possible for strings to have methods?

This is because when you use a method on a string, JavaScript automatically converts the string to a box, this process is known as boxing. It basically takes the primitive and turns it into an object.

                    
                      // Showing javascript boxing
                      console.log(new String('Andre'));
                      console.log(typeof new String('Andre'));
                      console.log(new String('Andre').slice(2)); // the methods return back a string
                    
                  

Above is what JavaScript uses to convert our strings to objects, when we call a method on a string. It's important to know when this operation is done, it converts the object back into a string.

Lecture 122

Working With Strings - Part 2

                    
                      // changing the case of a string
                      console.log(airline.toLowerCase());
                      console.log(airline.toUpperCase());
                    
                  

Above is just a method that takes in no arguments, but transforms the string to an either lower case or upper case. 

                    
                      // Practical example
                      const passengerName = 'AndRE';
                      const passengerLower = passengerName.toLowerCase();
                      console.log(passengerName, passengerLower);

                      const passengerCorrect =
                        passengerLower[0].toUpperCase() + passengerLower.slice(1);
                      console.log(passengerCorrect);

                      // Challenge recreate the above in a function
                      const checkName = function (firstName) {
                        return firstName[0].toUpperCase() + firstName.slice(1).toLowerCase();
                      };

                      console.log(checkName('ANdrE'));
                      console.log(checkName('SAM'));
                      console.log(checkName('LUCus'));
                    
                  

The example above is how we could hypothetically fix a misspelling of a name, in this case, the passenger's name.

                    
                      // checking/comparing email
                      const email = 'hello@mail.io';
                      const loginEmail = '   HeLlO@mAIl.iO  \n'; // \n means enter syntax
                      // const lowerEmail = loginEmail.toLowerCase();
                      // const trimmedEmail = lowerEmail.trim();

                      const correctEmail = loginEmail.toLowerCase().trim();
                      console.log(correctEmail);
                    
                  

Above, the '.trim()' method removes all white space from the start and end of the string. With ES 2019, we now have a '.trimStart()' and '.trimEnd()'.

                    
                      // replacing part of strings
                      const priceEu = '499,95€';
                      const priceUs = priceEu.replace('€', '$').replace(',', '.');
                      console.log(priceEu, priceUs);

                      // can also replace entire words
                      const announcement =
                        "All passengers for flight Hawaii, please go to door 2b, that's door 2b!";

                      console.log(announcement.replace('door', 'gate'));
                      console.log(announcement.replaceAll('door', 'gate'));

                      // regular expression
                      console.log(announcement.replace(/door/g, 'gate'));
                    
                  

The above method, '.replace(x, y)', replaces the specified value of 'x' with 'y'. Note, that this method is case-sensitive and can work for single letters and entire words.

'.replace(x, y)' only replaces the first occurrence of the value 'x', though as of recent, there is a '.replaceAll(x, y)' method that replaces all occurrences of 'x'. Though, before this was an option, people used something called regular expressions to select all occurrences of 'x'. Note, the 'g' in '/door/g' stands for global.

                    
                      // string methods that return booleans
                      const newPlane = 'Airbus A319neo';
                      console.log(newPlane.includes('A319'));

                      console.log(newPlane.startsWith('Air'));

                      if (newPlane.startsWith('Airbus') && newPlane.endsWith('neo'))
                        console.log('Part of new planes');

                      // how we can use booleans methods in a practical sense

                      const checkBaggage = function (items) {
                        const baggage = items.toLowerCase();
                        if (baggage.includes('knife') || baggage.includes('gun'))
                          console.log('Sorry, but you not welcome on the plane!');
                        else console.log('Welcome Aboard');
                      };

                      checkBaggage('I have a bottle of water, a KNIfe and a camera');
                      checkBaggage('I have some snacks, and a laptop for work');
                      checkBaggage('I have a gUN for protection, and some magazines about kniFES');
                    
                  

The above example is to do with methods that return boolean values. The '.includes()' methods checks if a string has a specified value in it. We then have '.startsWith()' and '.endsWidth()' that checks if a string starts with and or end with a specified value.

The function is just a demonstration of how we can use boolean methods.

Lecture 123

Working With Strings - Part 3

                    
                      // the .split('') method
                      console.log('this+is+a+string'.split('+'));
                      console.log('Harry Potter'.split(' '));

                      const [firstName, lastName] = 'Harry Potter'.split(' ');

                      const newName = ['Mr.', firstName, lastName];
                      console.log(newName.join(' '));
                    
                  

The '.split()' method splits a strings based on a specified value, it also places the results in an array, which means we can even use deconstruction on it. The '.join()' method is completely opposite of split, and can join array values, or values separated by a comma, into a single string.

                    
                      // a function to capitalize names given
                      const capitalizeNames = fullname => {
                        const names = fullname.toLowerCase().split(' ');
                        const capitalizedName = [];
                        for (const n of names) {
                          // capitalizedName.push(n[0].toUpperCase() + n.slice(1));
                          capitalizedName.push(n.replace(n[0], n[0].toUpperCase()));
                        }
                        console.log(capitalizedName.join(' '));
                      };

                      capitalizeNames('HARry POTter');
                      capitalizeNames('RON WEASLEY');
                      capitalizeNames('hermione granger');
                      capitalizeNames('This is just a sentence about nothing');
                    
                  

Above is an exercise to capitalize names passed into the function.

                    
                      // padding a string
                      const message = ' pad me';

                      console.log(message.padStart(25, '*'));
                      console.log(message.padEnd(25, '*'));
                      console.log(message.padStart(25, '*').padEnd(30, '*'));
                    
                  

Padding strings means to add a certain amount of characters to a string until it reaches the desired length. This is done by using two methods, '.padStart(amount, value)' and '.padEnd(amount, value)'.

                    
                      // practical example masking a credit card
                      const maskCreditCard = function (cardNumber) {
                        // const str = cardNumber + '';
                        const str = String(cardNumber)
                        const mask = str.slice(-4)
                        return mask.padStart(str.length, '*')
                      }

                      console.log(maskCreditCard(1234567812345678));
                      console.log(maskCreditCard(1235541564565451));
                      console.log(maskCreditCard(9456812354895236));
                    
                  

Above is an example of how we can use padding with other methods to mask credit card numbers.

                    
                      // the repeat method
                      const warningMessage = 'Bad weather ... All Departures Delayed ... '
                      console.log(warningMessage.repeat(5))

                      // practice repeat method, making a tree like structure
                      const christmasTree = function (sym) {
                        for (let i = 0; i <= 5; i++ ) {
                          console.log(sym.repeat(i));
                        }
                      }

                      christmasTree('🎄');
                    
                  

The '.repeat()' method repeats a string by a given value, and personal challenge to make a weird Christmas tree.

Lecture 124

Coding Challenge #4

Write a program that receives a list of variable names written in underscore_case and convert them to camelCase. The input will come from a textarea inserted into the DOM (see code below to insert the elements), and conversion will happen when the button is pressed.

Test data (pasted to textarea, including spaces):
                    
                      underscore_case
                        first_name
                      Some_Variable 
                        calculate_AGE
                      delayed_departure
                    
                  
Should produce this output (5 separate console.log outputs):
                    
                      underscoreCase      ✅
                      firstName           ✅✅
                      someVariable        ✅✅✅
                      calculateAge        ✅✅✅✅
                      delayedDeparture    ✅✅✅✅✅
                    
                  
Hints:
  1. Remember which character defines a new line in the textarea 😉

  2. The solution only needs to work for a variable made out of 2 words, like a_b

  3. Start without worrying about the ✅. Tackle that only after you have the variable name conversion working 😉

  4. This challenge is difficult on purpose, so start watching the solution in case you're stuck. Then pause and continue!

Example Of How To Insert Elements In DOM:
                    
                      document.body.append(document.createElement('textarea'));
                      document.body.append(document.createElement('button'));
                    
                  

                    
                    
                      // Selection & Creation of elements
                      const conversionContainerEl = document.querySelector('.conversion-container');
                      conversionContainerEl.style.display = 'flex';
                      conversionContainerEl.style.gap = 'var(--space-16)';

                      const outputContainerEl = document.querySelector('.output-container');

                      const textArea = document.createElement('textArea');
                      const convertBtn = document.createElement('button');

                      conversionContainerEl.append(textArea);
                      conversionContainerEl.append(convertBtn);

                      // Styling of elements
                      textArea.style.minHeight = 'calc(var(--space-128) + var(--space-64))';
                      textArea.style.padding = 'var(--space-16)';
                      textArea.style.border = '1px solid var(--clr-shade)';
                      textArea.style.borderRadius = 'var(--radius-5)';
                      textArea.style.display = 'flex';
                      textArea.style.flex = 1;
                      textArea.style.font = 'inherit';
                      textArea.style.fontSize = 'var(--font-16)';
                      textArea.style.letterSpacing = 'var(--letter-space-n05)';
                      textArea.style.lineHeight = 'var(--line-height-17)';
                      textArea.style.color = 'var(--clr-shade)';
                      textArea.style.backgroundColor = 'var(--clr-tint)';

                      convertBtn.textContent = 'Convert';
                      convertBtn.classList.add('btn', 'project-btn');
                      convertBtn.style.alignSelf = 'flex-end';

                      // Functionality of elements
                      convertBtn.addEventListener('click', function () {
                        outputContainerEl.innerHTML = ''
                        const text = textArea.value;
                        const textInput = text.split('\n');
                      
                        for (const [i, name] of textInput.entries()) {
                          const [first, second] = name.toLowerCase().trim().split('_');
                          const conversion = first + second.replace(second[0], second[0].toUpperCase());
                          const output = conversion.padEnd(20, ' ') + '✅'.repeat(i + 1);
                          console.log(output);
                          const newLine = document.createElement('p');
                          newLine.style.color = 'var(--clr-grey-read)';
                          newLine.style.letterSpacing = 'var(--letter-space-n05)';
                          newLine.style.lineHeight = 'var(--line-height-17)';
                          newLine.textContent = output;
                          outputContainerEl.append(newLine)
                        };
                      });
                    
                  
Lecture 125

String Methods Practice

Format the below string the the example output

                    
                      // Example output
                      // 🔴 Delayed Departure from FAO to TXL (11h25)
                      //              Arrival from BRU to FAO (11h45)
                      //   🔴 Delayed Arrival from HEL to FAO (12h05)
                      //            Departure from FAO to LIS (12h30)

                      const flights =
                        '_Delayed_Departure;fao93766109;txl2133758440;11:25+_Arrival;bru0943384722;fao93766109;11:45+_Delayed_Arrival;hel7439299980;fao93766109;12:05+_Departure;fao93766109;lis2323639855;12:30';