Transcripts
1. Introduction: [MUSIC] Hey, welcome
to this class. As we all know, learning
any new skill can be challenging and JavaScript
is no exception. JavaScript has such
a wide range of features and things
it can be used for, and this can often
leave beginners in a difficult situation
wondering where to even begin, so this is why I've
created this class. This is Part 2 of the
class building on the foundation you've
learned in Part 1. If you've not taken Part 1, be sure to go through this
class first unless you do have some JavaScript
experience already and want to cover the
topics in this section. If you've not taken one
of my classes before, my name is Chris and
I've been building websites for over 20 years. I've also been teaching these skills for
over seven years, both video classes alongside leading web development
boot camps too. This class has many
JavaScript subjects, including loops and
conditional statements, a deep look of
objects, math, date, and timers along with it
drawing to the canvas. All these topics I've covered in a clear and structured way all while building it practical
projects as we go, including real-world
example use cases and also some mini challenges too along with building a pizza chef game to combine many parts of what
you've learned. This project will
be reasonably big, but we will break it down, so you can see how all
the parts fit together. Later on we will
discover some of the trickier parts such
as async JavaScript, scope, hoisting, and closures. Before finishing off
with two more final project,s we will a
small application called Leaving so soon, which is an exit intent pop-up, which is used to
entice visitors with an offer when they try
to leave your site. If all completing
the class faithfully functioning JavaScript
image carousel. It includes a
project folder which we'll add to as we
progress throughout this class and it
will also develop as a useful reference
in future too. Thank you for your interest in this class and I'll see
you in the first lesson.
2. Project Folder Download: If you've already completed
part one of this class, you can continue on with the exact same starter files
which we used in part 1. This part 2 of the
class we'll begin at Section number 6, all
the way to the end. I'll begin in the first
lesson which is for loops. If you have this,
you're completely good to go for the next lesson. If you're jumping into part
2 without taking part 1, which is completely fine too, but you will need to go
over to github.com and download the starter
files which we are going to be using
throughout this class. Follow the link which you
can see on the screen here. Once you land on this page, click on the green coal button, and then download
this as a zip file. Open up this project folder inside your favorite
text editor. I'm going to be using
Visual Studio Code, but you can use any
which you prefer. This is all you
need to get started , and I'll see you next. We'll begin by taking
a look at for-loops.
3. Share Your Work On Skillshare!: When taking any course, it's really important to
not get in the habit of following along just for the sake of ticking
off another lecture. Take the time to
process each lesson, review the code which you write, and think about how you might approach these
solutions yourself. With this in mind, this
class is project-based, and this gives you
the opportunity to really make something
personal and unique. You don't need to get
too lost and divert away from the class
and you can even take a step back after you've
finished the class and come back and make some
project changes afterwards. This will really give you
a good chance to practice what you've learned
away from the class. Also, remember to share
your project too here on Skillshare and only
will I check it out, but it will also inspire
fellow students too. For more information
on the class project, head into the project
and resources tab, where you can not only
upload your project, but you can also see
other class projects too. With this in mind, I look
forward to seeing what you create and upload
here on Skillshare.
4. For Loops: Welcome back. This section is going to be all about loops. We're going to kick off
things with a for-loop. When we started to work
with arrays earlier, we looked at some
different ways to repeat certain tasks
using these loops. The types of loop we
looked at was map, and forEach, both of
these were array methods. Loops mean we can repeat
tasks a lot easier. Even though there are
different types of loops they generally keep repeating
a task a number of times. Often, the main
difference is how we tell the loop to then stop these map, and forEach loops
were array methods, but loops are not
exclusively for arrays. There's also different
types of loops we can use to be able to loop over pretty much
anything we need to. This video is going to be
focusing on a for-loop. This is how a basic
for-loop looks. It sets out a bit like a
function or an if statement. Notice inside of the brackets
that is two semi-colons. These are used to pass in the free expressions which set
up how our loop will work. Really simple example here is
just to use it to increase the number and we will look at some better examples
in just a moment. But for the first expression, it contains the initial or
starting value for our loop. If we just want to start at a number zero or
increase each time, we set up a variable to
be zero, just like this. Second of all, we have
a condition and here we'll see we have the
number is less than 10. This condition determines if
the loop is to keep going. If this is true, the
loop carries on. If false, the loop
will then end. Lastly, if we don't do
something on each loop, the second condition
will always be true. Here we increase the value of the number by one on each loop. Therefore we can
check the condition after each one of these changes. To run through this example
after each loop runs, the number will be increased by one until it reaches number 10, causing the second statement to be then resulting in false, causing the loop to then end. Meaning this particular example, we'll log 10 values, which will be zero
through to number nine. If you have the variable already declared outside
of the for-loop, you can leave out
and just include the second and third statements, and things will still
run the same as before. Also, it's worth
noting you will see lots of examples with
the variable name, the letter i, which is short for
initializer or initial value. This generally is if we're only using this variable
for the purposes of the loop and not needing a descriptive variable
names elsewhere. Note, we must still keep the semicolons in
place so we can clearly separate these three
values onto some examples. Let's jump into
our starter files. For this section jump
into Section number 6, which is loops and conditionals. The first lesson,
which is for-loops, jump into the index page, which is pretty
much empty for now. Then we'll copy the path to this and then paste this
inside of the browser. We have no content
at the moment, just the for-loops title, and then the empty script down at the bottom for us to work in. Let's jump inside of
the script section and we can start to create
our first for-loop. As we looked up before, we use the for keyword, bracket start our
free statements, and then the curly braces. For this example, we're
going to simulate somebody eating a pizza
with 10 different slices. Then we'll deduct a pizza slice after each one of these loops. First of all, our
initializer or our variable, which we'll call slices. We'll set this to be
the initial value of 10 and the next we'll keep
the loop running as long as these slices is
equal to or greater than the value of
one add a semicolon. Then we'll deduct the value of slices by the value
of one each time. Now just to recap, we're going to start off with 10 slices or the value of 10. Then after the loop is finished, this will deduct a slice
to then be equal to nine, nine is greater or equal to one so the loop will
therefore keep running. Then it will run a second time. Then this will deduct
the pizza slices to be eight and then seven, then six. It will keep running until the value is greater
or equal to one. Inside of the curly braces, we add the code which
you want to run. Grab a simple example, we can do a console log with
the value of slices. This is going to
be deducted from 10 all the way down to one. After this, a second
console log with the text of each slice. We can also add things like conditional statements
inside of here too. This conditional statement
is going to check if the slices is equal
to the value of one. If it is, we'll in place in a console log with the
text of no slices left. Let's save this and jump
into the Developer Tools. right-click and "Inspect"
into the console. We see the value of 10 on the first loop is then deducted by the value
of one each time. We get all the way
down to the value of one and then it takes
a note of slices left. Something you may see,
but probably not as much is a second value missing, which is this condition. We do need to leave
in the semicolon in place though and
before we test this, you may be already
spotting a problem. If you have no condition. We have no way of
stopping a loop. This will just keep
running infinitely. This is something we
need to be careful of when creating loops. We must make sure that
the loops condition eventually becomes false. Otherwise, we would end
up in an infinite loop, which would therefore cause
the browser to crash. Don't do this and don't follow
along with this example. But I'm going to show you what happens inside the browser. If we save this, refresh. We see the loop runs
thousands of times, which takes up all of
the browser's resources, causing it to eventually crash. To avoid this crushing
or this infinite loop, we need to manually
use the break keyword inside of the loop when
we want it to then stop. For example, if we go
to this bottom section, we know inside the if statement
this is the last loop. What we can do is we can
add the break keyword. This then break out
of the loop and then stops the
code from running. We can test this by opening
up the browser once more. Open up the Index Page, and then jump into the Developer Tools and
the console and our loop, it now works as expected. Another useful thing
we can do with for-loops is to create
multiple elements, such as list items. Using a loop, we can
create, let's say, 10 elements with a lot less code than we would do if we
wrote this out 10 times. Let's do this just
inside the script, will create a
constant called ul, where we'll create a
new unordered list. We do this with
document.createElement. Passing in the ul elements
which you want to create. Just before we jump into
creating the list items, we'll append our unordered
list to the body. Grab the body with
document.body.appendChild, passing in our ul variable. Just going back to what
we looked at before, we're going to modify
our pizza example and replace this with
the variable name of i. Remember i is a common
naming convention for a variable inside of a loop. We'll initially set this
to be a value of one. We're going to do this in
the reverse way from before. We'll start with the
initial value of one, and then we'll keep
the loop running while i is less than or equal
to the value of 10. Therefore, we need to increase this by the value of
one on each loop, which will cause the
loop to run 10 times. We can move all of the
contents from inside here and then create
our list items. Let's create our list
item we'll select the document.createElement
pass in the list item. Store this inside of
a constant called li. This will then give us
an empty list item. We need to then
place the content inside of the opening
and closing tag. We can do this with
documents.createTextNode, and we can place
in any sample text we want to inside of here. Rather than a regular string, I'm going to insert a
variable using the backticks. We'll say list item placed in our variable with the $ symbol and
the curly braces. We know from above we
have a variable called i, which is equal to
the value of one. Then if you keep increasing by the value of one for each loop. We can check this out by
placing this inside of here. This should start with the
value of list item one, list item two, and so on. All this inside of a
constant, say text. Then we can merge these two together by accessing
the parent, which is the list
item use appendChild. Then place our text inside
of the list element. Finally, when we create each
one of these list items, we then want to push us
to our unordered list. In the same way as we
did with our list item. We select our unordered
list appendChild, where we pass in our list item. Let's check this out,
refresh the browser. This just needs to be i to
match the variable refresh. Now we see the
value of list item one all the way through
to list item 10. As you can imagine, if we
created this 10 separate times, this would result
in a lot more code. Using this loop makes it
a much shorter example.
5. For...in & For...of: We're now going to cover
two more types of loops, which is called for-in
and also for-of. They do look pretty similar and they both loop over something
as you would expect, but the key difference is
what they can loop over. Let's open up the starter
files and see what we have. Into the sidebar, jumping to lesson Number 2
in this section, which is for-in and for-of, and then open this up
inside the browser. First of all, we see a
for-in section which has a product object with various
properties nested inside. As you may expect, this is
how a for-in loop works. We use this type of
loop to loop over the properties
inside of an object, such as the name and the size. Now, this will have
the for-of section, and this is used to loop
over iterable objects. An iterable object is basically some of them
we the loop over, such as an array or a node list. Let's begin up at the top
with our for-in loop, and and we're going
to use this to loop over our product objects. We can do this just below. This looks a bit
like our for-loop from the previous video, we set up the for keyword, the brackets and then
the curly braces. Jumping to the first
set of brackets, and this is how a
for-in loop works. We set up a variable, and we'll call this property
since we're looping over all of the properties
inside this object, which is in our product object. This is where the term
for-in comes from, we have the for
keyword and then the in keyword inside the brackets. We're looping over this full product object and then we're storing the value
of the property inside of this constant. Let's see what it
looks like with a console log of the
value of our variable, which is property, refresh, and we see the value of SKU, the name, the size,
and the color. This gives us the
value of the key, but what about the
actual property value such as cool shirt, medium, and also blue? Well, we can also do
this by accessing our product and then using
the square brackets. Let's take a look at how this looks inside
of a console log. We can use the back ticks
to insert a variable, and we'll start by inserting
the value of property. This is just the
keys which we'll see already inside the console, but afterwards add
a colon to separate this and then insert
our second variable. The second variable is going to be each one of these products. We'll grab the product, and then we can use
the square brackets, to narrow this down to
a particular property. We're passing in
the value of SKU, name, color, or size. If we save this and refresh, we've now created a new string for each one of
these properties, which includes both the key and also the value
of the property. This is how a for-in loop
works to loop over an object. Let's now take a look at for-of. I want to comment out
this section and then go down to the bottom
below our permissions. Again, this begins with the
for keyword, the brackets, and the curly braces, and it also takes in a variable. Let's say const value, which is going to be set to each one of the values
inside of the array. This time rather than using
the in keyword we use of, and then pass in exactly
what we want to loop over. In our case, it's the permissions
array from just above. This is in permissions, and then inside of
the curly braces, we can access our value. On the first loop,
the value is user, the second loop is editor, and then admin,
save and refresh, and there's our three
values inside the console. As you can see inside of
his comments at the top, things like a node list can
also be looped over too. Remember, when we
use something like query selector all to
get multiple elements, a node list is what we get back. To try this out, we also need
some elements to loop over. I'll jump outside
of these scripts, create an unordered list with some sample list
items just inside. Let's duplicate this two more times instead of
spin Number 2 and 3, and then we can loop over
our three list items, which will then return
back a node list, therefore, we can
use the for-of loop. Step 1 is to access all
three of these list items. I'll just above our for loop, create a constant call links, grab these with
document.querySelector, in fact, we need querySelectorAll
since we're accessing multiple elements and once
you grab our list items. Now, rather than accessing
our permissions, what we're going to do
is to access all links, which is our three list items and then change the
variable name to the link. The value of link on our first loop is
the first list item, and the second list item,
and then the third. We can do anything we
want to with these links. For this example,
I'm going to access our links and then
use addEventListener. Listen out for a click
on any of these. This will then run a function, we're going to pass in
the event information , create an alert. To access the contents
inside of each one of these list items
such as 1, 2, and 3, we can do this by accessing
the events information, select e. target, which is the information about that particular list item
which was clicked on, and then display the
innerHTML of that element. Let's try this out.
Now, if we refresh, have our free links at the top, let's click on Number 2,
there's the innerHTML. Number 3, and also Number
1 works correctly too. This is a couple of
really useful variance on the for-loop, which we can use to loop
over things like arrays, and node list, and
also objects too.
6. While & do...while: In my opinion, while
loops look a little simpler than the four loops
that we looked up before. The basic setup looks again similar to many other
things in JavaScript, such as a basic function and if statements and even
the for loop too. A while loop will
keep running as long as a condition
is set to true. For this example, we have
a simple number variable and then the loop
will keep running while the number
is less than 30. This check is performed before the code in
the loop is run, so if it's false the
loop will then stop. As with the for loop, we also need a way of actually stopping things so it doesn't turn
into an infinite loop. We do this inside of the
loop by incrementing the value of the number
by one on each loop. Meaning it will run 30
times before it then stops. Let's head over to our starter
files and give this a try. The next lesson which
we need is number 3. It is the while loop section. Open this up, and also
inside of the browser too. As you can see, we have
an empty starter file with the script at the bottom. What we'll do inside of here is set up a couple of variables. First of all, a constant
called stadiumCapacity, and set this equal
to a value of 100. This is the stadium
capacity and next we'll set up a second variable, which is going to
be for the number of fans which we've
currently entered. Since this can be increased, we need to use the keyword and set this equal to a
value of currentlyEntered, which will begin at
the value of zero. What we want to do is to keep running a loop while the value of currentlyEntered is less
than the stadiumCapacity. A while loop is ideal for this. We set up our while keyword, the brackets, and then the
curly braces just afterwards. What we're going to do
is to keep this loop running while the value
of currentlyEntered, which is zero, is less
than the stadiumCapacity. Basically, if zero
is less than 100, the code inside of
here will then run. Placing a console log
with a text of enter, and we should be
able to go over to the console and check this out. But just before we do this, let's take a step back and
take a look at this loop. If we think about
this, we want to take a look at our condition where they currentlyEntered is less than the stadiumCapacity. At the moment, we have no way of changing these two
variable values. Basically this will always
be the value of true. This will then result
in an infinite loop and again cause a crash
inside the browser. To only run this while loop
a certain amount of times, we need a way of
increasing the value of currentlyEntered on each
loop. This is pretty simple. We just access our value
and then use plus plus, which will increase this
by the value of one on each loop all the way
up to the value of 100. Once this gets to the value of 100 this condition will
no longer be true, meaning this will then
cause our loop to stop. Let's check this out
inside the browser, refresh and jump
into the console. Good and we see
our text of enter has been repeated 100 times. The variance of this
while loop is do-while. This is a switched around
version of this while loop. Before inside the slides, I did mention that the
code inside of this loop will always run after this
condition has been checked. Basically, if this
result in false, the code inside of this loop
will never run even once. We can flip this and make sure that the code runs at least once and then perform
the check afterwards. This is useful if we want to always make sure
that the code runs at least once before
the loop has stopped. To do this, just above our while section we'll
create a do section. We'll say do, open
up the curly braces, and since we always
want this to run once, we just jump straight
into the curly braces, we don't need to
add any condition. Then we can grab our code from the while loop. Cut
this out of place. Add this into the do section. I'm going to remove the curly
braces from the while loop. As you can see, the code now sits inside of the do section, meaning this will
always run at least once before we even
perform our check. Our check is still true, this will keep running
the second time, in our case, all
the way up to 100. Let's check this. We can save this and refresh the browser. We still got the exact
same results as before. We can test this by
setting the value of currentlyEntered to be 100, which then sets
this to be false. If we now save this and refresh, we see our single
console log value of enter because our code runs first and then it will perform the check inside
of the while loop. This is just an
alternative depending on if we always want
the code to run first or to first test the condition before running
any code inside of our loop.
7. If / Else Statements & Nesting: For this video jump into the if-else statements section and also open this up
inside the browser. You can see at the top we
have a user object already setup and also if
statement just below. So we've used if
statements a few times already during
previous examples. There's not a lot more to add to them than what we've
already covered. They simply run some code
between these curly braces. If the condition inside of
here is set to be true. Using this example, we have
a user object at the top. This would result in true since we have an object present, meaning our console
log will work. If the user was logged out, it would maybe set our variable to be null, just like this, so that user to
be equal to null, which would result
in a false value and the code inside the if
statement would not run. So here we are handling
if the condition is true. But what about if
we also want to handle if the
condition is false? Well, this is where else
statements come into play. We place these immediately after the if statement and
this block of code will run in all of the cases which are not covered
by the if section. We have this similar
examples setup inside of the starter files and here we only handling the condition if the
user is logged in. You may be thinking,
why not just handle the logout
condition below this. Or we could do
something like this. We could do a console log
or any code you want to, where we can say the
user is logged out. Well, let's test this out
and see what happens if we refresh and jump
into the console. Here we can see the
console log has run twice. We have the user
is logged in and also the user is logged out. So obviously for something
security sensitive, like a user being logged
in or logged out, we only want one of
these conditions to run. This, we can set up
our if statements, which we have above, followed
by the else statement. Let's remove the console log. I'm placing the else section and add our code inside of here. Let's refresh and we see
user is logged in since we have a user object
present at the top. If we also then change this
to be the opposite and set the user to be equal
to a false value of null. This should then be
user is logged out. If we wanted to, we could also add in multiple if statements to and let`s just push our else statement
down to the bottom. Then in-between we can add a second if statement,
just like we did above. We can check a second condition
such as a user or role, which is currently set to admin. We can check if this is a particular value and if it is inside the curly
braces we`ll do a console log, say, Hey admin,
test this, refresh. Now we see the user
is logged in and they also the admin role. This works, but this also
presents a problem too, we need to be really careful
with this kind of setup. Because this else statement, which we'll have
down at the bottom immediately follows the
second if statement. This means now the
else section will only run if the user's role
is not set to admin, which makes no sense. So we had a user such as null, so with the we're not logged in. Refresh. This will now
throw an error since we are trying to access
the user dot role, which is no longer
available because we no longer have our user objects. For this kind of use
that example to make it a little bit more sense
and to be more workable, we only want to check
if the user's role is equal to admin, they actually logged in. To do this, we can
nest our if statements by cutting out the
second if statement. We can paste this inside
of the first one. This brings us back
to the first example. We have two top-level checks. We have the if statement, the user is logged in followed by the else section just below. Then the if statement
which is nested inside, will only ever ruined if the
user actually logged in. If the user is logged in, this console log will run. Then if they happen to be also logged in as
the admin role, the second console log will also run too. Let's try this out. Refresh, and the
error is now cleared. We see the user is logged out because the user is set to null. Let's reinstate this,
the name to be equal to anything on the
role back to admin. With this now in place,
we now have a user. So the first console
log should run. We also have a user role
which is equal to admin. We should now see two
console logs inside of here. This is how we can use
top-level if else statements and also how we can nest an
if statement inside too. If you wanted to,
you could also go even further end just after
the nested if section, we could also place
in an else section too so we could
do a console log. For this section,
we know the user is actually logged in, but we'll want to also check
if they are not the admin. So here this will run
if they are the admin. Otherwise, we'll just
say, Hey user, refresh. We still see hey admin since
the role is set to this. But if we change this
to be subscriber, the nested if statement
is now false, meaning the hey user
section will now run. If you want to. You
could also go as deep as we wanted to by nesting multiple if else sections
inside of the nested ones too. But we must be careful not to go overboard and make the code hard to read and also to understand
for other developers too. As a general rule, we can
usually cover most use cases without needing
to go to deep. So if you find
that you are maybe three or four levels deep
with if statements or checks, there's usually a better
way to do things.
8. Else if: Inside of this lesson's folder, which is the else if section, you'll find just below
inside the script we have the same example from
the previous video. Here we are handling two top-level conditions
we check in. First of all, if the user
is logged in and then else section capture what happens if the user is not logged in. Inside the logged in section, we also have a nested
if else section too. Add a different greeting if
the user is equal to admin. We have two options
at the top level and also two nested options too. But what about if we
wanted a third option too? For this, we also have else if. Let's take a look at this,
we're going to simplify this example by removing
the nested statements. Inside of here, we'll
remove the nested if else leaving us with a simple
logged in or logged out. If you remember from
the previous video, we took a look at how
to briefly handle a third condition by adding a second if statement,
just like this. But the problem this gives
us is the else section is now connected to our
second if statement. Essentially, meaning
our first if statement is now disconnected. If we still want to
keep all three of these connected unrelated
to each other, we can turn this second one into else if, some place else. Just before the if section, then we can add a second
condition inside of here. First of all, we want to check
if the user is logged in, and then using the
double ampersand, we can also provide
a second check. We can check if the user.role
is equal to subscriber, and if it is, we'll then run
the code inside of here. Now we'll just place in a
simple console log saying the else if section has run, save this, and then over to the browser and we'll see
what happens inside of here. We see the text of
user logged in. You may be wondering why we
see the text of only user logged in and not also
the else if section has run because after
all the user is present and also the user.role
is equal to subscriber. Well, the reason is because only one of these
sections will run. If condition has been met, the code inside will
run and it won't go any further down the rest
of these sections. Effectively, only the
first match will be run. If we want it to be more
specific with the first section, we could also copy
this user.role drop this into the first section and we can also check if
this is equal to admin. Since our user role is
equal to subscriber, they should now be false. Because nowhere else
if section to now run. Let's save this and test
this out in the browser. The else if section
will now run. Since this is our first section which results in through, so now we can do
whatever we want in each one of these sections, we can show and hide content depending on the user's role. We can redirect them
to an account area. We can disable features which the user may
not be allowed to access or anything else
you can possibly think of. We can also use multiple
else if sections too. If we wanted to check more
than these three conditions, we could also do the same just after the else if section, placed in a second one. It's probably conditioned
from just above. This time, we'll check
if the user.role is equal to author. If it is placed
in a console log, give this a save and
as you would expect, we should see the
same result as before since we have the
subscriber set, which is a match
for this section. But if we now change
this to be the offer, the if section is
now false and then the first else if section is also false and our new console log with
the text of author, would now run
inside the console. Finally, if none of these, if or else if sections are true, so we change to be
something like user, we should now fall back to use the else
section down at the bottom.
9. The Switch Statement: Switch statement is
a way to provide as many outcomes as we need
based on a single condition. We can also provide
multiple outcomes with if statements and if else statements like we
looked at previously. But as we discovered, we need to add something to
check against for each one of these conditions such
as when we check things like the user's role for each
one of these conditions. With a switch
statement, however, we just test against the
same condition for each one. Just here with our
favorite pizza variable. The switch statement
just below, again, looks similar in its
setup to things like empty if statements
and also for-loops. We then pass in what
we want to check, such as our favorite
pizza variable. Then inside, we then set up the different cases to handle
what the favorite pizza is. Here we'll have three
separate cases to handle what to do
for each value. We can have as many of
these cases as we want to. Let's look at the first one. We check if the favorite
pizza is equal to pepperoni. If it is, we then run
some code inside, such as this console log. We then provide the break
keyword to break out of the switch statement
once we have a match and this will stop all
the rest of the code and all of the rest of the
checks below from running. This is the same break
keyword we used earlier in this section to avoid the browser creating
an infinite loop. This will keep running
until a match is found, but if one cannot be found, we can also add a
default clause too. This will always run if
there are no other matches. Over to the project and
let's give this a go. This time we're in the
switch statements section and inside the starter file, we'll just have a
simple user object with the name property
and also the role. We can set up a switch
statement to check the user's role and decide if the user can edit
something on our site. This, we can start it in a
variable called canEdit. So canEdit is going to be initially set to
a value of false, so the user is not going
to be assigned any editing permissions by default. Then we can use a
switch statement to check the particular
role of the user. If the particular role
of the user is equal to a higher level such as the
admin or even an editor, we can then set canEdit
to be equal to true. Just like we've
seen in the slides, set up our switch
statements and then we'll pass in the condition
which is user.role. Open up the curly braces, then we can add our
cases inside of here. The first case is
going to be the user. This is just a regular
logged in user. Add the colon. Then
afterwards we add what we want to do for
this particular case. Well, in our case we want to
keep canEdit to be equal to false since they don't have the admin or the
editor permissions. If this particular case
happens to be true, I don't want to use
the break keyword to break out of the rest of
this switch statements. That's the first section. Then we go down to
the second case and this one is going
to be a subscriber. This would just mirror the
same condition as above. We can set canEdit be equal to false. Add the break keywords. That's our second
case now complete. The next one, we'll add
a case for the editor. And as it sounds, the
editor is a case where we want to allow the user
to be able to edit. For this, we'll set our canEdit variable
to be equal to true, then break out of the
switch statement. Finally, the last case is
going to be for the admin. In the Admin we do
want to provide all the permissions which they need to then edit our site. Just like above, we'll
set canEdit to be also equal to true and then break
out of the switch statement. As we'd have seen in the slides, we also need to add
a default case, which is going to be run if none of the cases
above is a match. Very simple example,
just place in a console log and we'll say the user role cannot be found. We'll also set canEdit
to be equal to false. Now we just need to
give this a test. When we first started
off here we have the user role to
be equal to user. This should set canEdit
to be equal to false. Let's test this out by
going down to the bottom, we'll placed in a console log, place in the value of canEdit. We'll see if this changes
for each one of our cases. As we know no canEdit will
be equal to false since we currently have the user
role to be equal to user. Let's jump into the developer
tools into the console. It's false as expected. Let's check our
second condition by switching the role
over to subscriber, refresh and as you would
expect, this is false. The third one is the editor, this should now be true. Finally, we can
test out the admin. This is true also. Just to catch the default condition
down at the bottom, we should see the
console log and they canEdit be equal to false if we don't have a match for any of these four conditions. Let's turn this to be
something completely random. We'll just type in any
random role inside of here. Refresh. This is false and the console log is
now run as expected. Just one thing to
watch out for if we have multiple matching cases. If I have two or more
cases that both match, the first matching
case will be used. Also as a shortcut if
we have multiple cases, just like we have here, which
run the same code inside. The user will set,
canEdit to be equal to false and also the subscriber. This is a duplicated section, the same for the last two cases. Both of these set canEdit
to be equal to true. We can shorten
this code a little bit to make it a
little bit simpler. For the first two, which
are exactly the same, what we can do is remove the
code from the first one. Both of these cases
immediately follow each other. Then this code just
afterwards will run for each one of these cases. The exact same for these
two down at the bottom. We can move the canEdit
and also the break. These two are essentially
now grouped together. We can just test this
out and if we refresh, we still see the default
clause run at the bottom. Let's try the editor.
It should be true. The admin true also. The user should be false. Finally, the subscriber. This is it for the
switch statements. Just one quick thing
before we move on. If we forget to put a break keyword in any
one of these cases, all of the following
cases would also run too until a break
is finally found. For example, if we had missed a break clause inside of this section, we
can remove this. If we have a match
for the user or the subscriber, canEdit will still be equal to false but it would then run the
code it just below, and then override the
canEdit to be equal to true. Let's try this. We have the
role equal to subscriber. Refresh. Now the
subscriber canEdit has taken effect but the
program has kept going down to the editor and also
the admin section and cause an override so
canEdit is now equal to true.
10. Conditional Operator (ternary): As an alternative to the if-else statements
which we've looked at, Javascript also has a
conditional operator, often referred to as
the ternary operator, since it's made up
of three parts. Since it works,
just like if-else, the main reasons why
it's used is to offer a simple single
line alternative. Looking at this user object, we may want to check
the user's age before allowing them in. The conditional operator
can help with this. It's set out like a question. Here we're asking if the
user's age is greater or equal to 18 using
the question mark. Before, I said that the ternary is made up of three parts. The question is just
this first part. The second and third
parts are what we want to do if the question
is true or false. If true, we can do anything but this simple example just
has the text of enter. If it's false, which is
correct for this example, the code after the
colon will run instead. Let's now head over to the
starter files and we can take a look at the
example inside of there. Here we have an example of how we can use an
if statement to check if a user is admin who can edit
something on our site. The user object has
the role of admin, and currently we set the
canEdit functionality to be equal to false. The if statement is checking
if the user's role, which is just here,
is equal to admin. In our case, we can set the
canEdit variable to true, allowing the user
to go ahead and publish something
like a blog post. If not, canEdit will
be equal to false, removing any permissions
which they may have. What we're going to
do is to convert this example to make use
of the ternary operator, which is going to simplify this example onto
one single line. Just like we've seen
in these slides, the first part is to
actually ask the question. The question we
want to ask is if the user.role is equal
to the string of admin. Use a question mark, followed by our true and false outcomes. If the user is equal to admin, we want to set this canEdit
to be equal to true, just like we do in
the true section in the if-else statement. Set canEdit to be equal to true, then separated by a colon, we add the false
statement where we say canEdit to be equal to false. Sometimes depending on your text editor settings or plugins, you may also see
these two statements wrapped inside of the brackets. This is completely fine. What I want to do
now is to comment out or remove the original
if-else statements, and then we can save this
and go over to the browser, refresh, and we see this
example is set to true. This is true because the
user.role is equal to admin. But if we change this
to be something else, let's try subscriber, you should update this to
be the value of false. We can also shorten this
example even further too by directly assigning
the outcome of this ternary operator
to this variable. What we mean by
this is rather than assigning canEdit to be
equal to true or false, when cut the full conditional
operator out of here, we can then remove the
initial assignment of false, paste this in. Now rather than
setting our variable, what we do is simply set the
true value to be equal to true and the false value
to be equal to false. Now the true or false
result to this will be directly assigned to
our canEdit variable. Let's test this
out one more time. Currently we have
this set to be false, but if we change this
back to be the admin, this is now equal to be true. Both the conditional
operator and the if-else version are both
perfectly valid to use. The conditional operator,
as we've just seen, is usually shorter and
placed onto a single line. Some argue that an
if-else statement is more clear and more readable, but in the end, it's your
choice which you want to use, and which one you prefer.
11. Type Coersion & Conversion: In the upcoming few videos
we're going to cover some type and also some true
or false related things, which can give you some
strange or unexpected results. Being aware of these
could help you understand things like why and if statement is not
running correctly, and also save you lots
of time debugging. The first one is type coercion. We already know
about types such as objects, strings, and Booleans. Type coercion relates
to how the values of datatypes are converted
to other data types. Looking inside the
lessons folder which is type coercion and comparison, jump into the index.html where
we have a simple example. We have two variables at the top with numbers and
then we're going to add both of these
together and store them inside of the total. One thing to notice between these two numbers
is the first one is wrapped inside these quotations so technically this is a string. The second one is a number,
as you would expect. But what do you think
happens when we add both of these together? If we were adding together strings we may know
what to expect, because this may be
combined to make a sentence or a word. If we're also adding
together two numbers, these two numbers would be added together to make a new total. But here, since we are adding
a string and a number, what do you think will happen? Well, rather than
cause an error, JavaScript is designed to choose one datatype to convert
to automatically. This is called coercion. But this example it can
choose to convert so they are both strings or they
are both numbers. Well, let's not guess. Let's save this and
go into the console and see exactly what's
going to happen. Jump into the console tab and refresh and we see
the value of 72. The value of 72 means that this is being
converted to a string because we have the value of seven followed by
the value of two, rather than the value of nine, which would happen if these were converted for both
to be numbers. This conversion has happened automatically for us and
this can be referred to as implicitly and this implicit conversion
is what coercion is. Not to be confused,
but something similar is type conversion. This works similar to
coercion by converting a value from one data
type to another. But we can also do this
ourselves manually. If we wanted this to be
converted to a number manually, we have a number
function available. The way we can use this is to cut our number one,
which is a string. We can access a number
function and then place inside what we want to
convert to a number. Here we convert a
string to be a number. Now if we save
this, we should see the value of nine
in the console, meaning that both of
these attract as numbers. To recap, coercion happens
implicitly or automatically. Type conversion can also
happen manually too, just like we're seeing
with this number function. Sometimes these words are
used interchangeably, but this is the official
line on both of these. If you wanted to, we could
also do the opposite. Rather than converting
this one to be a number, we could convert the
second one to be a string. We do this with the
string function. Pass in the number which
you want to convert to a string and this
would then return back to the string value of 72. For this reason, it may
be good to always convert incoming data to be exactly what data type
we want it to be. If, for example, we're getting
data from a third party, such as a server or an API, we can eliminate bugs and errors by making sure our
data type is correct. For this reason, you
may have heard of tools such as TypeScript, which enforces us to
declare the type of data which you want
to use upfront, which can therefore eliminate
any errors in our code. This is how things work
with the plus operator, but the other operators
can also give us an expected results too
after coercion has applied. We can also see this
coercion in action with the greater than or the
less than operator. See this will change
the console log up with the value or the results of number one is greater
than number two. Let's see what happens.
We get the value of true. This may seem a little
strange because here inside the console log, we're still comparing a
string with a number. But in this case,
JavaScript is comparing them as though they
were both numbers. This is crazy behavior
for people who are used to other
programming languages, which is strongly typed. Meaning the data
is constrained to a certain type and
not converted. Although this coercion
happens behind the scenes, sometimes the examples
which we'll see have some pretty sensible or
predictable behavior. But often we do see some
strange results just like this. We can also see some
strange results when it comes to
incrementing too. See this, instead of having
a constant for our string, what we're going to do is
change this to be let. Therefore, we can update
this value of number one. Let's do this just below. We'll access our number
one variable then use plus plus to increment
this by the value of one. Console log the value of our variable and let's see what
happens inside the console. Refresh and we see
the value of eight. This may seem strange
because we know that number one is initially
set to be a string. Technically we
shouldn't be able to increase this by
the value of one, like we would do with a number. What happens behind the
scenes with coercion is number one is converted
to a number therefore, we can use the incrementor. I think you'd see the value
of eight in the console. As another example, imagine
this was also an array. If we surrounded this inside the square brackets
containing our string inside, what do you think would
happen now in the console? We still incrementing
this value. Lets say this and see what
happens inside of here. Refresh. Again, this
makes no sense. We still get the value of eight, but this is the
world of JavaScript. What about if we
commented out this plus, plus and directly added
a value to this array. Let's say plus 2, save, refresh, and we see
the value of 72. Again, this may seem
like crazy behavior, but what's happened
behind the scenes, as you may have guessed, is this has been converted to a string. If you wanted to prove
this was a string, or if we wanted to double-check, we could also use typeof
inside of the console log. I'll type of juice
before our number one, save this and this will then output the datatype
which is a string. Also here is something
else really strange too. This is a string as we've
just seen but what do you think would happen if we
re-introduce the increments? Well, let's uncomment this out, save and refresh, and we're
now back to a number. What about if we also remove the typeof and we can see
what happens when we add together the seven and
the two and then also use the increment.
Let's save this. This now gives us the result of the numerical value of 73. Meaning that what happens originally for number one is our was 7 and 2 get added together
to create a string of 72, just like we've seen earlier. This is still a string, but when it gets down to the next line, as we also seen earlier
when we use the plus plus, it then adds the
value of 1 giving us the value of 73
inside the console. In addition to all
of this craziness, the equality operator
also introduces a whole new world of things
to watch out for two. For example, in our console log, we can check if 1 is equal
to true. This is true. But what about if we
convert this to be a different number such as 11? This now gives us
the value of false. Just to recap, the value
of one was equal to true, but the value of 11
was equal to false. Let's try one more
number such as two, refresh and this is also
the value of false. But this is another
quirk of JavaScript, but just before we explain, I just want to add a few
comments at the bottom. So true is number 1 and
0 is equal to false. To understand this better, we need to understand
that when using the double equals,
just like this, this is the loose
equality meaning we're not checking for any datatypes
unlikely triple equals, which we've seen before. All we're doing is
checking if two values are equal regardless
of the datatype. Since we are comparing a number here with a Boolean
on the right, we're not checking it
through equal types. JavaScript will convert
using coercion and in this case it converts the
Boolean to be a number. Just as we wrote before, the number one is
equal to true and JavaScript will use the number
zero to be equal to false. We can test this in
the console log. We can check if the value of one is equal to false,
which is not true. It should give us the value
a false inside the console. However, if we change it
to be the value of zero, zero is equal to false, just like we've
seen below and it should then result in true. That's a lot of
quirkiness to take in and it doesn't
end there either. JavaScript has so many of these things which you
have to watch out for. I don't expect you to remember
them all at this stage. But knowing that this behavior exists and give you
a good head start knowing where to look if you're encountering bugs
inside of your code.
12. Truthy & Falsey: In a lot of these examples in this section and JavaScript
coding in general, we check if a value
is true or false. If it's true, we
then run some code, and if it's false, we
do something else. But it's not just
a Boolean value of true and false,
which we can use, as we've seen in the
last video other values can also evaluate to
be true and false too. Such as zero
evaluates into false, and the number wants
to be equal to true. There are also many
more examples too, such as a string
of text is true, and an empty string is
the opposite of false. This is referred to
as truthy or falsey. Truthy is when a value
is evaluated to be true, such as our string of
texts being present, and falsey is when a
value evaluates to false, such as our empty string
or our number zero. Jump into our editor, and
we can take a look at some examples down inside
of our empty script. If statements will run for
not only a Boolean value of true but also any other
truthy value too. Let's say this by
first setting up an empty if-else statement
then inside of here, we're just going to place in
it too simple console logs. For the first section, we're going to evaluate
something to be true, we'll pass in the text of true V. In the L section,
will do the opposite. Adding the text of falsey. Of course, this won't run at
the moment because we don't have any condition
inside the if statement. But we'll start with
what we already know. Previously we looked at the
values of zero and one, where the value of zero
is equal to false. This would be
evaluated to falsey, however, the opposite is
true with the value of one. Also, we can test the string which you mentioned just before. We have a string with
some contents inside, such as the text of hey. This will also
evaluate to be truthy too since we have some
value inside the string. But if we were to remove this
and have an empty string, this would evaluate in JavaScript
to the value of falsey. The coercion we've
talked about is happening behind the scenes, and values such as our
numbers zero and one and also this string value here is being converted to have a
Boolean equivalent. We can determine if
things like these if statements are to run on not. Falsely values, as
you would expect, are generally empty
values such as zero, which we just looked at, and also these empty strings too. Let's take a look at some
more examples such as no. This is also falsey. Undefined this should
also be falsey too. Generally, anything which
doesn't have a present value, so the word false. As you'd expect, this would
also evaluate to falsey. Finally, note the
number, which is NaN. This is also falsey too. We can consider pretty much
everything else to be truthy, such as the strings which
we just looked at, arrays, numbers other than
zero, and in fact, any object type
such as a function, array, or an actual object
will result to be truthy. This is also true
regardless of if we use the constructor or a literal
type, just like this. We could use the constructor. We've looked at things
like new array, and new object in the
past. This will be truthy. Let's try a function
constructor which will also be truthy too, and finally the object. In the exact same
way if we want to use a constructor
like this and we use this literally like we have done more commonly
in these videos, such as creating an array
with different values inside. This would be truthy just
like the constructor, along with placing in an
object in a literal font too. This coercion, combined with
understanding how values are converted to be
truthy or falsey, is a good thing to
understand inside of JavaScript since
it can really help you out to both
avoid problems in the future and also help
with debugging issues too.
13. Optional Chaining: This video is going to focus on something called
optional chaining, which was introduced in
JavaScript in ES2020. As we already know, we
need to have access to certain some inside
of an object, such as our name and
also our role of admin. Also as we know, we can go
even deeper by accessing objects nested inside
of an object too. What happens when properties inside of this chain
are not there? Look at some examples of
chaining in the events section, where we access
the event object. We then went deeper into
properties such as the target, the parent node, the children, and then into the
inner text value. If at any point in this chain
a property does not exist, we would then get an error. This is not a bad thing though, as we should get errors
if we do something wrong. But what about if
the property is only sometimes there
and sometimes not? An example of this
is what we've been using already inside
of the script. It will have the current user
which is set to a logged in user object and we have all the information
which we need, such as the name and the role. Therefore, when we go down
to the if statements and check if the user's
role is equal to admin. We know this should work
without any errors, but with users though we don't always have them
to be logged in. The current user is not always set to be an
object like this. For example, if the
user is not logged in, in place of this user object we may see the value of null. Now, if we try to run
the if statement, we don't have access
to this.role property. Therefore, let's go into the console and
see what happens. Inside the console we see
this red error message, which tells us there's a
problem with reading our role. This happens because the
current user is no longer an object which contains
the role property, therefore causing an error. But we still need a way to keep this role active inside
the if statement. But after the user
has logged in. This is where optional
chaining comes into play. What we can do is we can
insert a question mark to declare the property may
not always be there. We add this just before.role. Now if we save this and refresh instead of the
error inside the console, since it's now
evaluates to false, the else section will now
run inside the console. What happens inside of here is we get to the if statements. JavaScript will then
read the current user, which is the value of null. Therefore it knows not to go any further with the
rest of our chain. It now skips the
error and instead it returns back a
value of undefined, which we can check inside
of the console log. Currently we know
the else section is running because we've
seen the value of false. But instead what we
can do is we can access the current user, a question mark since this
is not always going to be there at the
optional chaining. Then we can add on the
value of the role. If we save this and refresh, we can now confirm we get
back the value of undefined. This is really useful
if we know upfront a certain object property may
or may not always be there. It's common for
objects like this to grow over time and we don't always know how our app
may develop in the future. We may need to add
extra features or collect more information. This optional chaining
can avoid any errors. For example, in the
future we may need to add more information about the
user's sign up source. We could add a nested
object inside, just like this, we will return
back to the user object. Let's reinstate the
name and the role. Then a second nested object inside which I'm going to
call the sign up data, this is going to be a nested object inside. We
will place in the date. But now an empty string is fine. It doesn't really matter and confirmed to be equal to true. Then even further down the line as our app developed even more, we may need to go even
deeper into this object. For example, we may want to
collect further details about the user's sign up source
inside of this object. We may also add the source, which is also an object where we saw the URL of
the sign up source. Again, this is just
an example so we can add anything inside of the string on the browser
which the user came from. Now we need to longer chains to access this nested information. If we just remove these
if else statements, and we'll simplify this
with a console log. We need to first access
the user objects, which was current user. The object inside which
was sign up data, so.signupdata, For example, if we wanted to
access the browser, we'd also need to jump into
the source just before this. The source object and
then the browser, refresh and this should now
be the value of Chrome. But remember in this example up, all of this was added to the
object at a later stage. Some of the earlier sign
up users won't have the sign up data associated
with their objects. What we can do, we can simulate this by commenting
this section out, which will then cause an error, just like we've seen before. Again, optional chaining
can help us out with this by allowing our code to fail silently rather than displaying an error
inside the console. Inside the sign up
data would no longer have access to the
source or the browser. We can add the optional
chaining inside here, refresh and now we see
the results of undefined. Overall errors are good to let us know when we've
done something wrong. But in cases like this one, we know we have a property which may or may not be there, optional chaining can really
help us out by failing silently and therefore allowing us to handle the
errors ourselves.
14. Creating & Modifying New Objects: Pretty much anything
we can think of has certain properties can be a good use case for an
object in JavaScript. For example, a person can be an object and the properties
could be the name, age, height, and
any other piece of information which we want
to give to a person. A computer could
be an object too with properties
such as the brand, the model, the CPU, the year of manufacture,
and also the memory. When we think of
objects like this, the use cases become huge. As with arrays, there
are different ways to construct an object, and the style we've looked at so far is called the
object literal. This is used to
create and define a new object with
the curly braces, which includes some properties, just like we have here. Over to the start files. Jump into this lesson's file, which is the object constructor, and open this up
inside the browser. As previously mentioned
with the slides, we have two different ways
we can create an object. We have the literal approach, just like we've used previously, and we also have an approach
called the constructor. The constructor is nothing new. We've looked at this
previously with arrays. Typically, we've created arrays using a literal approach, such as a pizza array, and set up our properties inside of these square brackets. As well as this
literal approach, we can also create an array
using the constructor. For example, if we wanted
an array of ingredients, we can set this up using the new keyword
followed by array. This is a literal approach
and this is how we can create an empty array with
the constructor. We can then access
our empty array and add new values with methods such as push to add
any new value to this array. In the exact same way as
our arrays just here, we can also do the same
for other objects. What I'm going to do
is to comment out this literal approach and recreate this using the
object constructor. First of all, we'll
create a new variable called user to mimic the above objects and set
this equal to a new object. This new object is going to be an object wrapper
which is empty, and then we can
go ahead and push new properties to this object. We do this by accessing
our variable. Then we can set the name of the property directly
to our object. First, It's going to be the
key, just like we have here. Then we assign this
to a certain value. This is equal to a
string of Chris. Then the second one is for
the last name, also a string. The third one was
the occupation. Next, we have the
logged-in Boolean of true for user.logged.in. This is a Boolean so we
don't need the quotations. Then finally, we can also assign an array to all foods property. I'm just going to copy this
array from just above. We can test this is
now working by doing a console log for
the value of user, and then jump into the developer
tools into the console. There's our object with all of our properties nested inside. We can also access individual properties too
using a dot notation. This works exactly like we did
with the literal approach. We can use user.first. This will print out our first
name inside the console. As well as this dot notation, just like we've used previously, we can also access
our properties with the bracket notation too. What we need to do is to surround our property name in these square brackets,
remove the dot. This also needs to be a string. Both of these give
exactly the same result. If we wanted to, we could
also directly update one of our properties
too, so user.first. We can then reassign this or update this to
be a new value. Then we can print this
out to the console. This will give us
our updated value. As a side note, if
we accidentally have more than one property
which is a duplicate, just like here where
we've got the first name twice in our code, since ES 2015, the last occurrence will be used rather than
throwing an error. As well as this, we can
also delete any one of these properties using the
JavaScript delete operator. What we need to do
is to add delete, which points our first
name, save this, and refresh, and
we'll just print out the full user object. Open this up, and
this will now remove the first name from
our use object. These is two different
ways to create an object in JavaScript. Personally, I prefer the literal approach
which we had at the very top since to me it seems more
clean and simple to use. But this is completely down
to your personal preference. In upcoming videos, we'll
cover how to set up a template-like function for
creating multiple objects.
15. Object Constructor Function: Great. We're now aware of two different ways of creating objects
inside of JavaScript. If you jump into
this lesson's file, which is object
constructor functions, we see we just have an empty
script inside of here. The two different ways
which we've created objects is to create a new
variable just like this, and then we can
add our properties directly inside of
the curly braces. Alternatively, we can
also use new object, which is the constructor
approach and this will give us an empty object at
our properties too. Both of these
approaches work fine, but they're also limited to only creating a single object. It would be more helpful
if we could have a object template so we can create multiple objects
based off this, each showing the same
properties and methods. We can also do this with
the constructor function. What we'll do is
we'll remove this and create a new
constructor function. It's also good practice to name these constructor functions
with a capital letter. We use in our user example, it's an uppercase U, and then create the rest
of our function. The purpose of this
constructor function for our user is to create multiple users based
off this template. Inside of our function,
we're also going to pass in some values which we're going
to need inside of here. The first one, we'll
keep this pretty simple, we just say first, last
name, and occupation. We'll come back to this
function in just a moment. But before we created
a new object, just like this, so we used the new keyword and then
the object function. But now instead of creating
a new empty object, we want to create a new
instance of our user. Rather than new object, we'll
say new User capital U, where we're going to pass
in the actual values which you want to use
for our function. We need to pass in the
first name, the last, and the occupation and
these are going to be the actual three values
we need for our user. We'll say Chris, the last name, and finally the occupation. We can also duplicate this
as many times as I want to, because as we mentioned, the whole purpose of creating
this constructor function is to act as a
template to create as many objects as we want to. We create our second one,
has a different name, and also eight different
occupation too. With both of these new users, we can also store this
inside of constants or variables so we can
access them later on. So say const chris
equal to our one, and then the second
one is equal to homer. Great. Now what we're
going to do is to log the value of any
one of these new users. Placing a console log, the value of homer, and
then jump into the console. As you can see, we've
got a user object. But if we open this
up, we don't have any properties inside of here. All we have is an
empty user object. This can be expected because our constructor
function which we created earlier is empty. We don't have any code
inside of the curly braces. We receive in the values
inside the function but we're not actually doing anything with these just yet. To do this, we need
to set something to be equal to these three values. We need to set something
to be equal to our first, some things to be
equal to our last, and something to be
equal to our occupation. But what do we do to turn these into our object properties? Well, to set a property, we need to make use
of the this keyword. To understand the this
keyword a little bit better, I want to quickly
take you back to an earlier file in
this project folder. If we jump back into number
three, which is functions, and then number five, which
is a function or a method, jump into this index
page just here. Inside of here, you may
remember we created an object called check recipes. Check recipes had
some properties such as the current
number of recipes, the maximum recipes, and also a method
called recipes left, which deducted the
current number of recipes from the maximum. Remember a method is just a function which is
located on this object. Inside of this method,
we made use of the, this keyword to access other
properties on this object. We access the max recipes, which is on this object and
also the current recipes too. The this keyword can be a complex thing to
understand in JavaScript, but in these circumstances, it will simply point to the
object which owns here. Meaning we can access any of the other properties
inside of this object. With this in mind, back
to our current file, we can now add all properties in the exact same way by using the, this keyword followed
by the property name. For the first will say this.firstName and also the
same for the others too, this.lastName, and
finally, this.occupation. This will now construct
our two user objects with the properties
which we pass inside. Save this and with
our console log still in place, refresh. Our Homer user is now
constructed correctly and we can also duplicate
this for our first user to. This now gives us
two unique objects, which are both based
off the same template. If we need to add extra
properties and methods, we have some options too. We can add them directly to
one of our objects like this. Just like we looked on
the previous video, we can access our
variable such as homer. We can say lives, and set
this equal to our value. We can also add methods
to our objects too. We can access our object, which is homer and
then we can give this a property name
which is going to be full name and set this
equal to a function. The responsibility
of this function is going to be to
return a new string, which is the first name
added to the second name, so return our string, which is homer.firstName, add an empty space in-between and then add onto the
end homer.lastName. Let's give this a
try. First of all, we'll try the lives property. Add this to the console, refresh and we get the
value of Springfield. Then next we can try our
method which was full name. Refresh the browser. Whoops, we've got a value of undefined so let's
check this out. We've got homer.firstName,
that's fine. Just a spelling mistake up at the very top of the air
so we'll change this. Now we see this is
Homer Simpson this so this is working
completely fine. But these are only added
to our single objects. These are only added
tall Homer variable. If we did try to
access in our Chris variable and try
this in the console, we see an error inside of here. This approach is
only useful if you want to change one
specific object. If we want to make these apply to all of the created objects, we do need to place these on the constructor
function at the top. So first of all,
we need to pass in the property of lives to our function and then we can add these to
our two objects below. Chris, value of UK and Homer, the value of Springfield. We also need to modify
our function body. We'll duplicate this and
we'll say this.lives is equal to the value of
lives which you pass in. We also need to pass
in the method too. The method will
always be the same, it's just using the existing
first and the last name. I'm going to just cut and
paste this into our function. Cut this, paste this
into our function. This time we're going
to remove homer, replace homer with
the, this keyword. Also the same inside of
our function body too. Remember, just like
the previous example in the earlier video, this keyword will point to any other property on
this user function. Finally, we can also
remove homer.lives. We no longer need
this, so now we can save this and refresh. Now the full name method works
for both of our objects.
16. Object Prototypes: If you did not quite
understand the role of the prototype in
the early videos, hopefully now seeing
it in action with our own objects may help
to make some things click. In the starter project, we begin with the object
prototype section with the same example
from the previous video. We have a constructor
function to create templates or blueprints. This means we can
create multiple objects with the same properties
and methods available. We then looked at how to
add properties and methods, both for a single
objects and also to the function so they apply
to all new objects created. Along with adding to the
constructor function, we can also add directly
to the prototype too. But before we do
this, let's first refresh what we know
about the prototype. Down to the bottom, we're doing a console log with
the value of homer, which has created a new user
object from our constructor. If we open this up inside
the browser and jump into the Developer Tools
into the Console, and let's take a look at
what we see inside of here. Refresh, and we see our user. Let's open this up. It
contains our properties. We've got the first
name, the last name, and all of the values which
you see down at the bottom. Then down at the bottom
we have this prototype, which is a link to the
prototype chain. Open this up. Inside, we can see our own constructor,
which is our user. Also alongside this, a
second prototype objects. The first protocol, which is
the one up at the top here, this points to our own
prototype additions, which we'll look at soon. Then the second one
which we just opened up, this has now lots of
things inside of here, which we didn't add ourselves. We have things like
valueOf, toString. These are not things which
we created ourselves. Remember, from earlier, JavaScript objects
inherit properties and methods from parent objects. At the very top of this
inheritance chain is the object. Let's now log this to the
console for reference. Just with Homer I'll do
a second console log with the value of object. Now let's save this and
refresh the browser, leaving us with the
main object type alongside our own home object. This first object value
is an empty object, which you can see if
you open this up. It's empty in terms of having no properties of values
inside like we have here. But the purpose of
this top level object, even though it's empty, is to contain the bare minimum
properties and methods that pass down to other objects through
the prototype chain. We can see this if
we open this up. This contains all of these properties and
methods which we can now have access to through
our own created objects. If we now open up
our own home object, go down to the prototype. Then the second
prototype inside here, you can see this has inherited the same properties and methods from our
top level objects. This now means these
are now available to use in our own custom objects. As we looked at earlier, this is why things
like custom arrays have methods available, such as for each push and pop. We didn't actually create
these methods ourselves. But instead, they are inherited
from the prototype chain. All the rays can
make use of them. We shouldn't add our own
properties and methods to objects which we've
not created ourselves, but we can add them to our
own custom constructor, such as this user. We can do this by accessing the prototype property
on this user. Just below this. Let's
make some space. Access our user constructor. Then the prototype. We can set a new property
on here, so any value. This can be other data
types such as strings, numbers, or even methods. We can also remove
the first object, leaving our second
homer object in place. Refresh, open this up, jump into the prototype. Now, I'm going to open up
this prototype object. We also see the custom property,
which we have just set. We see cool is not placed on the original object alongside the first name, for example. But if we try to access it, cool, then be looked up
on the prototype chain. If it was not on our prototype
chain, which you see here, it would look even further up the inheritance chain.
We can test this out. Jump into the console log. Now, homer.cool. Remember,.cool is not available on our
original constructor. It's then going to look down the prototype chain, refresh. We get the value of true. The same goes for methods too. We can also add different
methods to the prototype. Rather than having this method available on our constructor, what I'm going to do is to copy this and comment this out. Paste this outside of our user. This time, rather than accessing
the object through this, we're going to access
our user.prototype , just like we did before. This time, full name is going to be equal to our function. Let's test this out down
in our console log. The name this time was fullName. With this being the
method, we also need to access this with the brackets. This should now
return our firstName, added it to our lastName. Good. If you also
want to check this is on the prototype alongside cool. We can remove fullName. Log to the console,
the value of Homer. Open this up, jump
into the prototype, and our two new editions
are inside of here. To begin the prototype
may seem complex, but it's just a way to inherit properties and methods
from a parents object, even from a parent which
were created like our user, or from the top level object
which JavaScript provides. To recap, the prototype is
a property on an object. It will make available to be
inherited by other objects. An example of this
was our fullName and our cool additions. Both of these were
added to our prototype, meaning these have now
been made available for other objects to inherit, such as our Homer user
down at the bottom.
17. Inheriting Object Properties: We've talked a bit so
far about how we have this main parent
JavaScript object, which has a prototype, and how one would create
new ones they automatically inherit certain things
through the prototype chain. But what if we
wanted to also allow our own objects to
be inherited too? In a start files we have
a similar user example from the previous video. We have a user object, and we may begin to realize
that this user object has some limitations
for our particular use. Here we are creating
real people such as me and also just below
a fictional character. But if we think about this, real and fictional people may
need different properties. We may, for example,
want to add the name of the show which the fictional
character was from. But this would not
apply to a real person. This now makes it difficult to expand this object much further. What we can do though, is use
this user object as a base, including only the bare
minimum properties which apply to both the real
and the fictional user. Then we create two more objects, which will both inherit
from this original user. We still have access
to all the things like the names and
the occupations. But then it will also have specific properties which
we need for each use case. To do this, we have an
object method called create. Just before we add this
to our current example, let's take a look at
how we can do this with a regular standalone object. First, let's add any regular
object down at the bottom. Remove our console log for now, create a new object
called product1. Open up the curly braces and
we'll add our properties. The title, again, this
is pretty generic, so an amazing blue shirt. Also the description. Simply all blue shirt. As a standalone object is a generic object which could apply to
multiple blue shirts. The title and also
the description will be fitting for
other blue shirts. We could also change
some details such as the brand or size
for other products. We could then use this
object.create method we mentioned before to create a second product based on this, install this inside
of a variable called product2 and set this
equal to object.create. Inside of object.create, we pass in our product1, meaning it will create
a new product called product2 based off our
original product1. Let's log this to this console, product2, and see what
happens inside of here. Refresh. Let's just update
this link inside the browser. Initially, we see
an empty object. We don't have any properties or methods associated with this. But if we go ahead and open up the prototype which is attached, we have access to our title and also our description which is inherited from our product1. This is how the
create method works. It creates a new object and uses the existing object we
pass in as the prototype. Again, notice how we also
have a second prototype too. We have our own first, which is the description
and then title. Then further up the
chain we have all of the properties and methods
which we've seen earlier, which is inherited
from the top of the chain from the object. As we've already learned, even though our own
object is empty, we can still access these inherited properties
from product1 by their name. product2, remember this is completely empty as we've
just seen in the console. We can access the inherited
values such as title. We can also add our
own properties and methods too, as
you would expect. Just underneath our product2, we can access this by
the name and then set new properties such as
the size, such as small. Log this to the console, which then updates our object. Now we can remove all
this example and we can use this knowledge
which we've just learned. We can now try to apply the
same example to our user. But the key difference
here is we have a constructor function rather
than an actual object. If we were to do
the same as before, let's create a
variable called copy, which is set to object.create. Then if we try to make
a copy of our user, let's see what happens
inside the console. Save and refresh. The copy is a copy
of the function, which is not what we want. What we're looking for
is a way to create more constructive
functions which all inherit the
properties of this user. Let's start with the character. Just below our user, create our new object constructor
called character. Remember the purpose of
this function is to inherit all of the base properties
from our user objects. We're also going to add some additional properties which only apply to our character. This extra information
which we're going to pass in is the show which
the character is from. Then just like above,
we'll access this.show, setting this equal to
the value which is passed into this constructor. Now, what we do is to create
a new character object with the show property and also inherit all of these
properties too. But this, we need to go into
our character and first access our user
constructor function, which we can then access
a call function on. This call method is available on a function's prototype and it allows for a function
belonging to an object called inside
of a different object. Meaning, we can now call a user function from inside
our character function. This call method is not
exclusive to this use case. It can be used anytime
I want to access a function or a method located
on a different object. This user function also takes
in these four parameters. We can copy these and also
add this to our character. Now, remember Homer
is a character, so rather than basing
this off the new user, we're going to base this
off the new character. As the fourth
arguments I'll show, just after Springfield, we
can pass in our last value, which is the Simpsons. These five values now match the five character
values passed to our constructor.
Let's try this out. We can remove our
copy from before, and instead we'll log
the value of Homer. This now shows that Homer
is a character object. It has the show property with
the value of the Simpsons. We know homer.show
should now also work. But what about the other
inherited properties? We could see before the
Homer object it would only had access to the show
property on the top level. We didn't have
anything else such as the firstName and lastName, the occupation or lives. Well, let's try this out
and see what happens. First, try the occupation. Refresh, and we get
back the value of undefined. Let's try one more. homer.lives and this
is also undefined too. The inherited properties
don't seem to work here. This is because we're
missing some details when calling our user function
inside of our character. First of all, we need
to copy over all of these parameters
which are passed in and also add this
to our call method. But in addition to this, we also need to pass
in this keyword. We mentioned earlier
that this keyword can be complex thing
to understand. We'll have more on this later. But this is basically
saying that when running our function, we can access the
values using this. Just as we were above. Scrolling down, we're still
trying to log homer.lives. Let's refresh this and we now get the value
of Springfield. We can try one more,
the firstName. This is all working correctly. Just as a recap, because this
can be pretty confusing, we're creating a
new Homer object based off our character. This character
constructor function only has one property of its own, which is the show which
the character belongs to. But it's also inheriting all of the properties and methods
from our user objects, which we are passing
in just here. In fact, we also don't need
to add this shows since it belongs on this
particular function. With all of this complexity, if we want to check which one of our constructors created
a particular object, such as Homer, we can use
something called instanceof. We can check this
inside the console log. We can check if the homer object is an
instanceof character. Should be the value
of true since we're basis of our new character. But if we were to
pass in our user, this one will be false. This is how JavaScript
does things. It has objects right from the very beginning that
we can inherit it from. As a newcomer, we
don't even know this is happening
behind the scenes. But if we were to dig a
little bit deeper like this, we can begin to
understand that this is so each new object, objects also referring
to other types such as arrays and functions
and have a set of helpful properties
and methods which we can use each time
we create our own objects. As we just discussed, we
can also take advantage of this prototype inheritance by
our own objects if needed.
18. Copying Object Properties: In the past videos, we've
gone pretty deep into objects with things like
object constructors, and copy and prototypes. But sometimes we simply
just want to copy the properties from one
object over to another. There are a couple of
different ways to do this. First of all, we have
the base character with information which could apply to all new Simpsons
characters we create. Pretty generic information, and then below we also
have a homer object with some properties which only
apply to this one character. For homer and also any other
characters which we create, we also need to merge in all of the properties from the
base character too. A straightforward way to do
this is just to reference this base character inside
of the homer object. We got add this as a property, has it in the base character, and test this over
in the console. We already have a console
log for the value of homer. If we open this up, we have the four original properties and we also see our
base character. This works completely fine, but it does create a
base character object which we need to open
up and dive into. Our base character is nested one level deeper than all of
our other properties. But it would be better
if we could grab all of these base character
properties and effectively put them alongside
what we already have. One way of doing this is
by using objects spread, which we looked at earlier, which was the three dots. Passing the three dots before
our base character object. Now we don't see the
base character object which we need to dive into. Instead, we see all of the base character properties
alongside our homer ones. We could also use this
technique to spreading as many different
objects as we wanted to. For example, if we had a homer Simpson character with superpowers for one
particular episode, we could do something like this. We could create a new
object called a superHomer. SuperHomer would
also need to inherit the base character and also all of the existing
information from homer. We can also remove this too. We can use spreads pass
in both of these objects. The base character and also the existing
homer information. In addition to this,
we can also pass in our own properties, which is specific to
this single object. For example, we could set
powers to be equal to true, and this should now display one big object
inside the browser. Let's just change over our
console log to be superHomer. Refresh and now we have nine different properties
on this object. We have the fall from
the base character, the forefront of Homer object, and also the powers set to true. We could remove or
comment out this object, and we'll now take a look
at another way to copy over the object properties
to a new object. This is done with an object
method called assign. Access our main object,
the assign method. This is going to take
in it two things. First, the target object
which you want to copy to. We'll copy it to
our homer object, and the second one is the
object which you want to copy. Using our original
example to move all the properties from our
base character into homer. Pass this in as
the second value. In the console log to be homer. Jump into the console. This now merges all
the properties from base character into
our homer object. Effectively works just like the spread operator which
we looked up before. Also if we needed to, we can
store this new object into a variable such as mergedHomer. This should also work
exactly the same. This assigned technique and
also the previous ones, are really useful and
something which you may often use in JavaScript. It has lots of use cases such as if we had a user and an order, we may want to attach
a user object to our order before saving
to the database, so we know who actually
placed the order. You will also probably find many use cases of your own too.
19. CSS Style Objects: In the DOM section, we created
our own HTML elements. Then we looked at how to add CSS styling to these
elements using JavaScript. In the Style's file we have an example of this
down at the bottom, a simple header section with
a title no meat burgers. Then below this inside
of our script we have multiple style
properties applied. This works fine as we can
see inside the browser, but it can be a pretty
long way of doing things. It's not re-usable on
other elements either. A solution to this is to
create a style object containing all of
the CSS properties and values which we need. Let's go down to the bottom
and we'll comment out all of these existing
styles and we'll recreate these inside
of a style object. Store this inside of a
variable called styles. Say this equal to an object, and we'll effectively replicate
these inside of here. First of all, the display is
equal to the value of flex. Since this is an object, we need to separate
this with a comma and the next one is justifyContent. Since this is JavaScript, we also need to make
this camel case, justifyContent and
every word after the first one needs to begin
with a capital letter. The value, this can be the same, this can be space between. Next, we have a padding value. Again as a string,
zero top and bottom, and 10 pixels on
the left and right. Background. What
do we have here? We have an RGB value. Let's copy this over.
Finally, the color. Let's grab the value
which we already have , paste this in. Let's go over to the
browser and refresh. We don't have any
styling applied. Now we need a way
to actually apply the style object to
our header element. Just like the last video
we can use object.assign. This will copy all of our
style object properties over to this header element. Down at the bottom of the
script object.assign. We want to apply
these to the header. The object properties
which you want to apply is our styles. But just as it is like this, it doesn't work if we
refresh the browser. The reason why is just
like we did originally, instead of applying these
to the header element, we need to apply these
to header.style, so at.style and refresh and it should now all take
effect in the browser. At the moment you may be a
little bit confused as to why an object method such as assign is working on an
element such as header. Well this is because
our elements, like many things in JavaScript are also classed as objects. We can see this
with a console log. We can use the typeof operator,
and the value of header. Then let's see what happens
inside the console. Refresh and you can
see that the header, even though this is an element, is a type of an object. When we create new
elements, we're actually creating element objects and this is how we can
have access to properties, methods, and events. We've already used
properties such as accessing the attributes, class list, and inner
HTML to name a few. Methods which we have used include queryselector
and addEventListener. These are all available
on this element object. But why create a style object
instead of regular CSS? Well, CSS is probably
easy for many use cases, but this way has
its own uses too. The main one for me
is the fact that it is controlled with JavaScript. We can change
values dynamically. If we had a game, for example, we could change the
background to be red if the game was over. We can make a count down flush faster as it's getting
closer to the end and so many more great use cases by updating any one of these
values dynamically.
20. Looping With Objects: Objects can have
many values as we know and just like with arrays, looping is a convenient way to repeat something
for each property. There is a couple of
different ways we can use to loop over objects, and first, we take a
look at the for-in loop. Inside of this lesson's file, we have a constructor
function up at the top, which we've seen before
to create a new user. Notice the prototype
additions are commented out and
we'll see why soon. For-in loop should
look pretty familiar. We've already looked at this
in the loop in section, but I want to show you
something else related to it. As a refresher, this is how
it looks inside of our code. It begins with the for keyword, and for this example,
we're going to loop over our homer object. We need to create a
variable for each one of the properties
inside of here, so the first name,
the last name, the occupation, and lives. This inside of a variable or a constant called property
inside of our homer object. Now we'll just open
up the curly braces and then inside here, we can do anything we want
to with this property. But now we'll do a console log, open up the back ticks so we can insert our property variable, which is our property name, such as first name, add a colon, and
afterwards we could also access the value
of the property. Again, we'll insert
this as a variable so we need to access
our full homer object, and inside the square brackets, we'll pass in the property we
want to grab the value of. Save this, refresh, and we see each one of
these four properties inside the console. We see the actual property
names such as first name, and then we use this to select the property value from the
homer object, such as homer. We can then use these values to construct elements to
now add it to the DOM. First, an unordered
list is required, which we can use as a wrapper
for all of these values, so jump up to our body
section outside the script, has an unordered list, and the first step is to grab a reference to our elements. Const ul is equal to
document.querySelector, pass in our unordered list. This now gives us two things. We have a loop, so
we're looping over all of the properties
inside of our object, and now this gives us an
element to attach this to. With this being an
unordered list, we need to create
a new list item for each one of these values. Const li for our list item, we'll create this with
document.createElement(). An element is in li. Remember when creating
new elements, this is generally a
three-stage process. We create the elements, we create the contents, such as the text node, and then we merge
these together. The second part of this is for the text which goes inside
of our list element. We do this with
document.createTextNode(), replicate what we've
seen inside the console. All we need to do is to copy the contents from
inside of here, and paste this inside
of our method. Remove the console log,
merge these together by first accessing our elements and use the method
called appendChild. We will pass in our text. This has created a new
standalone elements or a standalone list item, and then for each
loop we need to merge this or insert this into
our unordered list. Just like we did
above, we'll grab our unordered list and
use the appendChild. Passing it the list item we just created. Save and refresh. There we go and this is
a really good way to structure our objects to
show inside the browser. But back to this prototype, which you mentioned before, which is currently
commented out. If we uncomment this out, let's save this and now
again, refresh the browser. We also now see these two prototype values inside of here. This is something we need
to watch out for and if we don't want to loop
over these prototypes, which is often the case, we can eliminate them using an object method called
hasOwnProperty(), which we can use
inside of the loop. Let's go down to
our for-in loop, and at the very top,
add an if statement. We will pass in
homer.hasOwnProperty(), passing in our
property variable. What we're doing here
is we're accessing our full homer object and then checking each
property one-by-one. We are checking if the
particular property belongs to this homer object, or if it is inherited
via the prototype. If this is true, we want
to run our code below. If not, it will be a prototype value and
therefore ignored. We can now cut this out of place and add this inside
of our if statements. Refresh, and this is a way to ignore our inherited
prototype values. Another way of looping
over objects is to first convert them to be arrays. Object has some methods available which you
can use to do this, and depending on if you want to access the object's property, the value of both
of them as a pair. Let's begin with a
method first called entries at the very bottom, place in a console log
for objects.entries(), and our object value of homer. Test this out, jump
into the console, and it still returns
back in array. Not only is this
returning back in array, each one of the properties
is also an array too. It contains the property name and also the value
as you can see here. This method does not include the prototype values like
it does with for in. If we were using
this, we could ignore the if statements that
we have just here. To give this a try, and
keep the same example, up the for in example, and then comment this out. Paste this in just below. I must mention, we don't need this if statement
since we don't need to eliminate any one of the
inherited prototypes. Remove the wrapper, leaving
these four lines inside. This time rather than it
looping over our homer object, we're going to now make
use of object.entries. We're going to move this,
and just like we did inside the console log
replacing objects, data entries with
the value of homer. Save and refresh.
Inside the browser, we see the values of undefined. This may not be obvious
at first to why this is, but this is because of the
type of loop we're using. We currently using
a for in loop, which is used on objects. But now we're converting
homer to be an array. This now means we need to make a small change and
instead use a for loop, so change in to be of, and remember now this property, instead of returning a single
property as it was before, is now going to point
to an array which contains two values such that
our first name and homer. What we can do instead of this is to D structure
using the array, which will give us the
key and also the value. The key in this case is
equal to first name, and the value is going to
be equal to homer for each one of our items. Pass this in. First of all, the key,
and then also pass in our value. Save this. Reload. This again
works as expected. This is a really
good way of looping, which doesn't involve
the prototype. If we want to, we can
also access the keys, all the values individually
too. The way to do this. As an example in
our console log, if we just want to
access the keys such as first name
and last name, we can use an object
method called keys, where we pass in
our homer object, and there's our four
keys without the values. On the other hand,
if we just wanted to access the values
and not the keys, we can use object.values, and this will return this
back inside the console. This is also commonly used in if statements just like this. We could say if object.keys(), pass in our object, and access the length property
is greater than zero. We can then do something
inside of here. This checks if an object is not empty before running some code, and it just depends if you want to access either the key, or the value, or both.
21. Dynamic Objects: This video is going to convert creating more dynamic objects. By that, I mean both the key and value can
be changed using JavaScript. When we create new objects, just like we did with
this home object, we're using simple primitive
value such as strings, but our programs are not
always going to be this rigid. We may also need to insert
data which may change, such as a variable. Also, even with the
key names such as firstName and lastName
and occupation. This could all be dynamic too. I'm going to now show you
some ways to do this. First, the more
traditional ways, and then a way which was
introduced in ES2015. We begin by inserting
variables as values, and this is pretty simple to do. Let's first create a variable called firstName and set
this equal to homer. We can then reference
this variable inside of our property value when we
create our new homer user. Remember, since we're
now referring to a variable rather than a string, we need to remove the
surrounding quotes. Save this and refresh. This works just like before, but this time we are now
pointing to a variable. We already know we can add new
properties also like this. We can access our object, access the likes property, and set this equal to a string. Save and refresh, and this is also
added to our object. Currently, value of likes
is set to be a string. We may also want this
to be dynamic too. Let's try and set this as a variable rather than a string. Remove the quotations. I'm going to create
this as a variable just above, const likes, and insert our string inside the back ticks so we can insert our variable all
passing our firstName. In this case, it
will say homerlikes, followed by a particular value. I send firstName,
a text of likes, and now our likes inside of here will point to our variable, which is now shown
in the browser. It also takes into account
our firstName variable too, and places it in as
part of the string. If we had multiple likes, we may also want to number them. We can begin by creating
an initial value. We'll say let number be equal
to an initial value of one. Then append this number
value to our variable. As we would expect, we only
have the single number 1. If we were to duplicate this and change this to be a second
value such as Donuts, we could increment this number
by one using plus plus. This will be increased for
each one of our values. Remember that all of this
is constructed using our object constructor
function up on the top. We can also do all of this using the object literal approach. At the bottom, let's
create an object literal, and we'll call it homer2, and set this equal to a
literal object where we set our properties such as firstName or we can also make use of our
firstName variable too. Log this to the console. As you can see, our
variable can also be inserted using
this approach too. But this may start
to look a little bit confusing because here, we point to all
firstName variable, but also the property name is still pointing to this
firstName variable too. So why do we see this output as a string rather than
the value of homer? Well, for the key, we don't need to wrap this name in quotes. This will always be
displayed as a string. So how would we go about
making this property named dynamic if we can't use a
variable just like this? Well, one option would
just be to create an empty object without
any properties inside. Then we could add
properties just like we do above using the
square brackets or alternatively using
an ES2015 feature called computed property names. We could use the square brackets directly inside of our objects. So surround our firstName
property with the brackets, which now says that
the code inside of these brackets is not to
be treated as a string. Instead, it's evaluated as
a JavaScript expression, meaning if we now display
this inside the browser, instead, we now see
the value of homer, which is our variable. Even this full control
over the properties key, and also the properties
value using JavaScript. We could also pull
in our properties just like we did from above. We could access this
without the homer prefix , paste this in. The only difference is
rather than the equals, we need to change
this via colon. We can also add our
second one to of Donuts. Add a comma, place a colon. Let's try this in the
browser. Open this up. So this is now
inserted, but instead of the values of one and two, we see the values of
two and three since we already incremented
this just above, we can comment this one out. It's taking us back to
our original example. This is how we can use
computed property names with our objects to make both the key and also the value more dynamic
using JavaScript.
22. Primitive & Reference Types: We use variables a
lot in coding and JavaScript to store our data. But if we go a little deeper, there are some important
concepts to understand and that is the we values we
store fall into two groups. To better understand this, I want to first refresh what we already know about data types. We have primitive values, which are simple values, such as a string or a number
which has no methods. We also have object types too. These two groups directly relate to what we're
going to look at now and this is storing
primitive and reference types. A string variable
would be a primitive, and an object type, such as an array variable would be classed as
a reference type. Both of these types
impact the behavior of values when we pass them
around and compare them. Let's first jump into
our starter project and take a look at primitives. In the script for
this lesson's file, we'll create a new
variable called Dog and set this
equal to a string. Next, create a second
variable called newDog, and we'll set this equal to
our original dog variable. This one is effectively a
copy of our first variable. Then we're going to reassign
newDog to be something else. Remember, the way
to do this is to select our variable name, and set this equal
to any new value. The next thing to do is to do a quick test with a console log, beginning with our
first dog variable, and then a second console
log for our new dog. Let's see. Well, this froze
up inside the console. We see the value of poodle, which you would expect from
our first console log, and the second console
log of newDog. Even though this was originally assigned the variable of poodle, this has been updated to
be the text of Labrador. Nothing unexpected
here, even though we've copied a
variable originally, the new one is still
a separate value, completely independent
of the original. Basically, we can modify
the original dog, or the new dog and they
will not affect each other. This is the behavior
of primitives. The variable points to
the actual saved value. Storing object types
behave differently. Let's do an example
using an object which we can copy just
like we have done here. First of all, we'll
create our variable called laptop 1 and set
this equal to an object, which of course is
an object type. The properties or brands, and the data inside
of it doesn't matter. The model set any
value as a string, and then just below, we'll make a copy of this, just like we did
with our newDog. This one will be
called laptop 2, which is equal to laptop 1. In the same way we did at the very top with our primitives, we'll update or re-assign our laptop 2 then we'll
add a new property, such as the size. This time for the console logs, the first one is laptop 1. I will also see what
the value of laptop 2 is. Test this out. Refresh. We'll see both of these values are
exactly the same. This may seem really
strange because we have the size property on both laptop 1 and also laptop 2 even though we've only added this
to our second variable. Meaning that seems
to be a link between laptop one and laptop 2. When we created our
variables stored in a primitive just above
with a string, the variable points to the actual unique value
stored in memory, and these are all unique values. However, though, when
we create a variable containing an object type, the object is still
stored in memory. But these variables,
such as laptop 2, holds a reference to
that object's location rather than the actual value, hence the name reference type. Here we're creating our
original laptop one, and then each time we create
a copy such as laptop 2, it all points to the
same original object. Since all copies are
pointing to the same value, this is why when
modify any of them, such as here, the original
will also be updated too. Some other languages allow
us to change this behavior. But with JavaScript it's fixed. Just as a quick recap and maybe clarify if you are unsure. When we create a
variable to store in a primitive, such as a string, the variable points to an actual unique value stored in memory, meaning our Dog and
also our newDog, are completely unique
and independent values. However, though, when we
create a new object type based off an original object, the copied version will
hold a reference to the original object's
location in memory, hence the name reference type. This is something which
you probably don't care too much about
it until we maybe run into an issue where you change one object and they all
change unexpectedly, and you don't know what
is causing the issue. With this in mind, next, we'll cover how we can
compare two objects.
23. Comparing Objects: What we learned in
the previous video is that a primitive value is unique and stored by a value and an object type is
stored as a reference. This will now help us better understand the behavior we're about to see when
comparing two objects, it's that inside of our
starter files we have two similar objects with
the same properties. They both have the
brand and also the model and also
the same values too. We're going to use these
to compare quality. From a sensible guess, you would think that if we
compared both of these, that they will be
considered a true match. This would be a fair assumption
since both of these have the same brand and
also model too. Jumping into the console, if we do a comparison
with primitives, this is pretty simple. If 1 is equal to 1, this would return back
the value of true. This is simple because
primitives have their own unique value
stored in memory. Comparing values works
as you may expect. Objects though are
little less obvious. As you may expect, comparing the same object like
this results in true. Laptop 1, if we compare
this to itself, which is laptop 1,
this is also true. But if we change laptop 1
to be equal to laptop 2, even though these both have
the same brand and model, we get back a value of false. Even though these
two objects have the exact same contents, they are not considered equal. The reason is because
these are reference types. Remember, a reference type variable points to a
location in memory, not the actual value. Here we're not
actually comparing the contents of both
of these objects. Instead, we're comparing two
different memory locations. This is why the
comparison is false. I know this can seem
a little confusing, but this is just how
JavaScript works. Going back to what we looked
at in the previous video, what do you think will happen
if we copied an object? For example, if we
set the constant with laptop 3 and set this
equal to laptop 1. Well, let's test this out. We can check if laptop number
1 is equal to laptop 3. Remember, here we're making a copy and this results
in a value of true. Now, this one comes
back true because when we created laptop 3, we didn't copy the
contents of laptop 1. Instead, laptop 3
will now point to our original laptop 1
location in memory. Regardless of how all of these variables are
stored behind the scenes, we may still need
a way to compare the properties of two objects. We need a way to compare
laptop 1 with laptop 2, which is going to
result in true. One way to do this is to use a JSON method called stringify. We'll look more into JSON data. Stringify is a way
to convert an object into a string and
it looks like this. Parsing JSON, a method
called stringify, we're going to parse in
the value of laptop 1. If we test this
inside the console, we can see our object is
being converted to a string. Comparing a string
version of laptop 2 with a string
version of laptop 2 will make our comparison much easier. We'll do the same. We'll check if this is
equal to json.stringify. This time we'll parse in
the value of laptop 2. Jump into the console. This is equal to
true since both of our string values are
exactly the same. One of the issues though
with using this method is the properties must be in the exact same order
for this to work. If laptop 1 and the brand
is our second property, this would result in false. To fix this, we have a couple of different
ways to check this. The first one, which
is only really good for comparing
simple objects like this is we could create a
function called check equality, which is going to manually
check our two objects. When we call this, we're
parsing object 1 and object 2. Then we'll return back
the value of if object 1. The brand is equal to object 2. That is our first comparison. Afterwards also we want to check if the model
is also the same. The double ampersand, check if object1.model is
equal to object2.model. This is a long way round. It's a manual way of doing
things, but it would work. We can check this out by calling our check
equality function. Our two objects is
going to be laptop 1 and also laptop 2, these are the two which
you want to compare. To see what the return value is, we can parse this into
our console log, refresh. Object 1 one not defined, I would just need to make
sure these have a j, refresh, and this is
the value of true. Now it doesn't matter
which way around our properties are in
each one of the objects. We can still do a simple
or manual comparison of each one of these. Another option is making use of object methods to access
the keys and the values. We've already looked
at these in the looping with object video. Remember that we access
the object, a capital O. We can then go up the
keys such as model and brand or laptop 1, like this. There's
model and brand. Also, we can do the same
for object.values too. Using this approach,
we can modify our check equality function for all of the keys and values
and check if we have a match. This way also means that the order of the
properties is not important as it was with
the first stringify method. As you can see, object types
can be a complex thing. If you don't understand how
they work in JavaScript, it just takes a bit of
experience to get used to. But I don't expect you to remember all of this first-time. But just being aware of things like this will really
help you out in the future if you run into
any object related issues.
24. Section Introduction: Coming up, we'll
have a great project for you to work
through and this is going to give you
lots of practice with what we have learned so far. This is going to be our project which is named Speedy Chef. The whole idea is we're going to cook pizzas that come through via orders and try and get
as many completed as we can, so with the chef and
we can start the game, which is going to start the
service for the kitchen and then we can see our orders
coming in over on the right. When we want to start
working on an order, we simply click on one and it then pulls it over to
the working on section, which tells us what
pizzas we need to make. For this example,
we need one ham and pineapple, and two pepperonis. Currently, we'll start with
the ham and pineapple. Go through the steps on
the method just here and the first one is to roll
the dough. Click on this. We then need to add our sauce, pepper, cheese, add
12 pieces of ham. Finally, 12 pieces of pineapple and this is all updated on our
section just here. Once we are done,
we can add this to the oven and if you
were to make a mistake, we can also add this to waste. Click on this and then
behind the scenes, it also does a check
to make sure we have all the correct ingredients
which we need for our pizza. There's a ham and pineapple, which goes into the oven. We can move on to the pepperoni, and then once we're
done with this, we can complete our order
and move on to the next one. Also we need to keep on top of things because these orders will keep coming in as
the time progresses. This is a reasonably big project compared to what
we've built so far. But most of which you
will already know. It's just a case of
building one small feature at a time and you'll
be perfectly fine. Along the way, I will
give you a series of tasks to try things
out by yourself, but don't worry, you
are not on your own. I'll also run through
each stage too so we can compare and remember, as always, there is multiple
ways we can approach things. If we both come up with different solutions which
both work, this is fine. Inside the starter files, we have a basic starter
with our index page, which adds all of the initial
layout which we need. We also have some styling in our style sheets and
this just means we can jump straight into our
JavaScript and focus on this. Even though it looks the
same as the final project, nothing actually
works yet and we will be coding all of
this during the class. As a starter, I've also
included the pizza.svg and also a JavaScript file which is linked in
our index page, which contains three arrays. These three rays are going
to save us a lot of typing. First is a pizza's
array and inside here, we have various pizza
objects with the pizza name, a method to make
this pizza and also the required steps which we
use in the project to check if the chef has made
the correct pizza and also placed the ingredients
on in the correct order. Then just below this, some sample orders to
get the game going and we will also be generating
these dynamically too. Finally, a list of ingredients we need inside the kitchen. Great, so this is
all now ready to go. Open up this project
folder inside the browser and then jump into the next lesson where
we'll get to work on listing these orders
inside the browser.
25. Listing Orders: This video will only have
one main objective and this is to loop
through all orders and display them in the browser. Remember that just above, provided with this folder is the oldest array which
we're going to loop through and display
inside the browser. The location for this will be over on the right-hand side, in the side section. In the index page, we have two main sections. We have this main wrapper and this contains
all of the contents, which has the white background. Then the aside with
the idea of orders. For now, this just has
a level 3 heading. But what we're going to do
is to loop through all of these orders and place
these into this section. To do this, we'll create a function called
createOrdersList. This function is going to loop through the above orders array. For each one of these
items in the array, we'll construct a div, which looks just like
this section here. Certain parts of this
are going to be dynamic. For example, we'll
have the order number. This is going to be dynamic
along with the name of the pizza and also the
quantity for each line too. You don't need to worry
about these comments. These are just but a visual guide to give us an idea of what
we're going to do. We'll begin down at the bottom
by creating our function. The function is called
createOrdersList , the function keyword. The next thing is to loop over
each one of these orders. We know how to do
this. We can use a loop such as forEach. Grab our orders array forEach. Remember that forEach
is going to run a function for each
item in our array. Places function just inside. We'll give each one of these
items the value of order. Now, we need to
basically construct our elements just like we did above for each one
of these orders. We'll begin by creating
this main wrapper, which is the div. We'll add a class
called order wrapper. We'll get to work
at in our content inside. We know how
to create elements. We use document.createElements. As in the elements' tag name, which is a div inside of a variable or constant
called orderwrapper. Then add our class name by selecting the variable
name of OrderWrapper. The property called ClassName. Just as we see above, this is
going to be Order_Wrapper. So we'll just add a comment. This is the main wrapper. Then after this,
we'll add a comment. This is going to be to
add the order number. This is our first section
inside of our order wrapper. This will be a level
4 our heading. Store this inside of a constant. I remember when creating
elements like thses, we need to create
the actual element and then the content inside. So we need two variables. The first one is orderNumber L, which is shelf elements. So document.createElements. We need a level for heading. Next, the text which
is going to go inside, and this is the order number. The constant name of order number and we create this with
document.createTextNode. Since this is going
to be dynamic because each order has a
different number, which we're going to grab
from this unique ID. We'll place this inside of the backticks to
make this dynamic. First, the text of order, which is this part just here, followed by a dynamic value. The dynamic value
can be inserted using the dollar symbol
and the curly braces. First, we select our
individual order, which is stored
in this variable, and then the property of ID. As always, when creating an
element, just like this, we have two standalone pieces of information and we need to
merge these both together. We do this by
accessing the parent, which is the
orderNumberElement.appendChild, where we'll pass in
our actual contents, which is order number. This now leaves us with our
div, which is the wrapper. Then our first element, which is our level 4 heading, but we still need to add
this heading to our wrapper. Grab the Order_Wrapper and then we'll again use apendChild, which will take in our
order number element. So we've got the first
section just here, and then the next part is to
create our own order list. A quick comment, so we know
what we're doing here of great pizza UL for each order, and the wrapper so
document.createElements. The elements was
an unordered list. Places into a constant
called Pizza list. This now gives us
an unordered list, which is the main
wrapper for all pizzas. But remember, we can
have multiple list items because each order may
have more than one pizza. So because our orders have multiple pizzas
inside of an array, we again need to do a
loop such as for each. To do this, we again jump
into our individual order. We'll select the array,
which is called pizzas. Use the for each loop
to run a function for each item or each pizza
inside of the array, store this inside of a
variable called Pizza. Then we can get to work with constructing each
one of these items. Each one of these items
will be a list item. But first, we need to
create this content inside. We have the quantity such
as one, and then a dash, then a span element followed by the name of each one
of these pizzas. Jump into our function and for each one of
these items we'll create a span element to wrap our title and store this
inside of a constant. This one is order quantity L, so document.createElements. Elements is going to be the span and then next the
quantity of each one of these item,
document.createTextNode. This section needs
to be dynamics or placed in the backticks. Insert a variable. We can access the quantity by
first selecting our pizza. In each one of these pizzas, if we take a look at the array, has a property called quantity. We'll also use the
name of property soon. For this one, this
is pizza.quantity, followed by a dash
just afterwards. Thinking about this, it may
be a little bit easier if we just add this quantity
into our span. Rather than having
them one outside, we'll maybe place this
inside of an element, rather than having this
outside on its own. We'll do this by accessing
the order quantity elements.appendChild that's
in the quantity variable, followed by our pizzaName. For our pizzaName, this
needs to be wrapped also in a span element and
then the contents inside. We need to do exactly
the same as just above, we'll create a constant called pizzaNameL
document.createelement. Element is a span followed
by the pizzaName, which we need to create
with create text node, grab our individual pizza. Then just as we've seen before, we have the quantity followed
by the name property. We can access this
with pizza.name. Merge these two together.
PizzaNameL.appendChild wasn't in the text content,
which is pizzaName. Great, so we've got two
separate spans here. We have our first span, which is wrapping our
individual number or the quantity of pizzas. Then a second span which
contains our pizzaName. Both of these items need to
be merged into a list item. Then the list item needs
to be added to our parent. So let's get to work.
As a new comment just to keep this clear. This one is create a list item. Show the quantity and pizzaName. First elements stored in a
constant called pizzaItem, document.createElement.
This was a list item. We've got our surrounding
list item and then we need to merge our two
elements from above. The quantity which
is this variable, and also the pizzaName. Let's grab our pizzItem. Since we're appending
multiple child elements, we can simply use append and
that's now our first one, which was the order
quantity element. Then the pizzaNameElement.
We're almost done now, we have our list item will be created without two pieces of content inside and now we need to add these to our
unordered list. Your on order list is stored in the constant called pizzaList. Add this just below its list.appendChild wasn't
in our pizza item. We're almost there. Now we have our list items added
to our unordered list. But now the unordered list also needs to be added
to our wrapper. The wrapper is just this
order wrapper variable here. But we do need to add
this outside of our loop. Make sure we locate
the end of our loop. What I did just below
this line here. Select the
orderWrapper.appendChild, passing in our pizza list. This is now our full
order rapid constricted. The next step, or the
final step for this, is to now add this
to our web page. We need the sections
of these two. This section is the aside. We can grab this with
the idea of orders. Just below our order wrapper, use document.querySelector
to grab our orders.appendChild wasn't in our order wrapper. Let's try this out, now
we need to actually call this function
for it to work. Let's grab three orders list, call us at the bottom,
refresh the browser. The good news is we can actually see the orders on the screen, but it doesn't quite
look as we expect. Let's take a look
inside of our code and see what we need to change. We're currently
missing the quantity before the actual pizzaName, which is this list
item just here. We're adding the order number. I think this just needs
to be the order quantity. This looks a lot
better now we've got the order number
at the very top, each one of these
is in sequence, followed by the name
and the quantity of each one of the
pizzas on the order. As you can see, this is a pretty long
function, and ideally, we want to refactor
this into smaller, more focused pieces and
this is what we'll do next.
26. Listing Orders Refactor: As we write our code and
especially as our project grow. We want to regularly refactor
our code where possible. At the moment, our code
works completely fine. This is what we want to
be focusing on at first, but it's also important
to look over things regularly and see if
we can improve things. This video is going
to involve us refactoring this one single
function which you have. Break it down into smaller
ones when possible. It's a good idea to have smaller
functions which focus on single tasks and this helps with readability
and debugging. The way you approach
is, it is up to you and often
personal preference. But the closer we can get a function to do it in a
single task, the better. The first step we're going
to take is to remove all of the order creation process from this function and place it
into a standalone function. We'll take out all of
the contents which is used to create our order, leaving our
"CreateOrderslist" function with a simple task. This task is to loop over all of the orders array and then add
them to the aside section. To do this, create a new
function just above. This one is going to be called
to "CreateSingleOrder". Since this function
is going to be repeated for each order
inside the array, we will also take
in the order as a variable moving down and
jump into our function. Starting with "OrderWrapper"
inside of the loop, drop all of the
contents from here, right the way down
to where we add this to our "OrderWrapper". Cut this out and we should
just be left with our loop. Then the section where
we add this to the aside. Now this is cut-out. Jump into our "createSingleOrder"
and paste this in. Now, if we jump into our loop where we just cut this code out. We can now call
this "Stand-alone function" which was called
"createSingleOrder". "CreateSingleOrder"
also needs to take in the order since we're
passing it in just here, which is then used in the
rest of our function. Now if you think about this, this is just simply
call no function. It's not actually doing
anything with it. All we need to do is we need to return the order from
this above function, store this inside of a variable and then we
can add this to the DOM. First we'll grab
our orderWrapper, which is the full order section, and return this back
from our function. This return value can
then be stored inside of a variable called
"singleOrder". SingleOrder can now be
added to our sidebar. Let's try this out,
"Save" and "Refresh", and everything still
works just like before but this function
is still pretty long. We can also reduce
the size of this two. Again, how you
break this down is down to personal preference. But what I'm going to
do is to outsource the "pizzaList" creation to a new function and this could also be used
in the future too. The pizzaList section
is this loop. We also need to grab
the one order list, which we store this inside of so copy this and also the loop. Then create our function and just above called
"createListOfPizzas". Just wanted to take
in the pizzas. Then "Paste" in our new section. Just make sure the correct
section is pasted inside, which is our unordered list, followed by our four each loop. In just a moment when we get to actually call this function. Remember this is going
to take in all of our pizzas so we don't
need this order section. We can call our pizzas
directly and it's variable. Now we can call
this function from the place where we
just cut out this code.The function name
was "createListOfPizzas". This needs to be passed all
of the pizzas for this order, which we can first grab from our order and each order
has the property of pizzas. Just as we did below, we
need to store this inside of a variable and also return back something
for our function. The thing we need to return back at the very end is
our "pizzaList". The returned value can then be stored inside of a constant. If we call this
constant "pizzaList". It also ties in nicely with our variable name
which we used before, which is then added to
our "orderWrapper". Give this a "Save"
and try this out, "Refresh" and all the orders
still work as expected. Refactoring functions
like this may not always be for you and you can decide
how far you want to go. Nothing is changing in terms of the way the project works. We still only see the orders
on the screen but now we have three clear functions
with a specific task. We have at the very top,
"createListOfPizzas", which is returned back
from this function. This list of pizzas is then added to the
DOM when we create our single order and a
"singleOrder" is then looped over in our last function and then added to the DOM.
27. Element Helper Function: Another refactor we
can do to save us a lot of code is to
look at what we're repeating currently in our
functions and see if there's a way to outsource it into a
reusable function instead. If we take a look at our
code at this early stage, and we'll go up to the
createListOfPizzas function. We already have
some things which we are currently repeating. We're repeating
creating these elements in our three steps. Is part of the quantity. We're creating a span element. We're creating the TextNode, and then we're merging
these two together. We're doing the same
for our pizzaName. We're creating an element. We're creating a TextNode
for the contents, merging them together,
going down a little bit further to
createSingleOrder. We're also doing a similar
thing just here we're creating our text element, which is in a level 4 wrapper. We're creating the
contents and then we're appending these together, and this is all completely fine. This is something
which we need to do to create our elements. For each time we create a new element in any
one of these functions, it's a three-part process
taken three lines of code. It would be nice to
create a helper function to reduce the amount of code, and this is going to be
the task for this video. We'll create a helper
function which is going to take in
the element's name. This is the create
elements section. You'll take in the contents, which is the TextNode, will merge them together, which is typically
our third line, and then we'll return
this new element to whatever calls our function. Let's get to work.
Create a new function which is going to help out
with this called buildElement. BuildElement also needs
to take in it two things. It needs to take in the name
of the elements such as the h4 and also the
content which goes inside. Will pass this in when
we call this function, and the first one is going to be the elementName and the second
one is the elementContent. Inside the function
is going to be pretty familiar from
what we've already done, and in fact, we can copy
any one of these examples. Solving createSingleOrder. Have all three lines of code
where we create the element, we create the contents, and we merge these together. If you want to, we can copy all three
lines, paste these inside. Then we need to make this
a little bit more generic. Beginning with the
first constant. This is for the element. The second one is
for the content, and then inside of both
of these brackets, we can pass in the data which
is passed to all function. The first one is for
the elementName and the second one we can
remove what we currently have, place in elementContent. Change the variable names. This one will be
element.appendChild, passing in the content
to this element. The final thing we need to do, we are currently
create an element. We also need to return this
back from our function, it can be accessed
within our code. Will return the full elements
after it's been merged, and this function can
now replace any one of these three lines of code
which builds an element. If we grab our function name and also the brackets
with the contents, we can now replace any one of
these sections just above. We'll begin with
createSingleOrder, for all order number
elements, the order number, and then the third line which merges both
of these together. I'm going to replace this with
our function elementName. This is a h4. For the content we
can replace this with the exact same content
which we used just above. Grab this and paste this inside. Keep our naming consistent
since we are currently using a constant
called orderNumberEl, and we access in
this just below, also need to store
this inside of a constant with the same name. what happens here is we are building our elements
inside of our new function. We then return this
back from the function, which is then stored
inside of this constant, which we can then use in the rest of our
code, it just below. If you want to, we can remove the free existing lines of code, and then we can move up to our first function which
is createListOfPizzas. We have all three
lines just here. I'll comment this out so we
can use it as a reference. We need to pass in our
buildElement function, passing in our two values. The first value is our span. The second one will
copy our template literal. Now this in. Also for this down
at the bottom, we used in the
original constant name of orderQuantity element. We can copy this and store
it inside the return value. Remove these three
lines if you want to. Next, the pizzaName. These three lines out. We'll create our
constant, which is the same name of pizzaNameEl. Order function is also
takes in a span element, and the content is pizza.name. Remove these. Both
these are the same, we just need to change is over. This one should be
the pizzaNameEl. We'll bring this one over. Good. Now let's save this and over to the
browser and we can test. Everything is still
working fine. Jumping into the
console, we don't see any errors inside of here. Our sidebar has our generated
orders over on the right. We have the order numbers,
we have the pizza names, and also the quantities too. This all seems to now
still working fine, and hopefully you can see the benefits of doing
things like this. Even though our function has
taken a few lines of code, each new element we create will reduce three lines
of code into one. We can also use this many
more times in the future during our project to
save even more code. It's not just about
readability and organization. A single line like this means fewer chances of typing errors, and also an increase in
speed as our program grows.
28. Selecting The Current Order: We have some orders on the
right right get us going, and soon we will create
new ones randomly too. Just before we move on to this, I want to show you
how things will work so you you can picture
what we're going to be doing. Once the game has started, our orders will begin to come
in and show on the right. The chef who is the player, will then click on an
order to begin working on, which will then move this order into the working on section. This section is what we're
going to be focusing on now. Later on, we will then go
even further by clicking on a particular pizza in
the working on section. Then this will display the
method and also set it as the currently working on
pizza inside the kitchen. Now we can just
focus on clicking on an order and moving it into
the working on section. As you may have guessed, to
do this we're going to create a new function just above
our create orders list. It's a new function called
selectCurrentOrder. To call this function, we
need to listen out for a click on any one
of these orders. To do this, we need to move up to our createSingleOrder
function. If we take a look
for this section, we need to add an
event listener to each one of these orders. We can click on
this and then move that particular order
into the correct section. Just underneath where
we add this class. Grab the order wrapper, which is the div surrounding
each one of these orders. Add an event listener. That will listen now for a
click which will trigger our new function called
selectCurrentOrder, gives us the test by doing a console log inside
of our new function. Pass in the event
information so we can get the correct elements which
is being clicked on. Then we log this with e.target. Let's check this
out in the console. Now what we need to do is click
on any one of our orders. You see the margherita. We
see the ham and pineapple. This is not exactly
what we want. We can click on any
of the order numbers, any in the contents. But what we want to do is to
make the whole order section active so it's just one
single clickable area. Better understand
this, let's jump into the Elements tab
and take a look. Now we'll jump into the
aside section which contains our orders and
click on any one of these divs with the
class of order wrapper. What we want is to
only be able to click on this div
with the class of order wrapper and ignore all of the clicks which are on
the nested elements. A way to do this is to
listen now for a click on any one of these
nested elements, such as the list items or
the Level four heading. Then climb up the
element tree until it finds the first div with
the class of order wrapper. To do this, we can
listen out for exactly which element
was clicked on, such as the unordered list. Then we can keep climbing
up the chain to check if the parent element is
this order wrapper. There is a couple of different
ways we can do this. We could store a reference to which child element
was clicked on, such as our unordered list. Then we can keep checking
inside of this loop if the parent element is
this order wrapper. Or an easy way is to use an element method
called closest. The closest method will take an element such as
the one we click on. Then it will keep climbing up towards the document
root through all of the parent nodes until it reaches
a selector match. The selector can be a class, an ID, an element name. Just like we can do
with query selector. Let's take a look
at how to do this inside of our function. First, we'll start
a reference to our elements which is clicked
on, which was e.target. Then we'll find the
closest parent elements with this class
of order wrapper. We can do this by accessing the element which is clicked on, call the method called closest. Well we're passing at the query
which we are looking for. In our case, we want to
look for the order_wrapper. With this being a class, we
add in the dot as a prefix. This inside of a constant
called OrderWrapper. Then we can check this out by logging this to the console. Now if we refresh and we can try to click on
any one of these orders. Make sure the Console
tab is selected. If we try to click
on anything outside, we don't see anything
in the console which is expected by the pizza title. This returns back towards
the parents order wrapper. We can open this up and
check this is Order 1. Let's try a different one. This is Order 3
and also Order 2. Good so now regardless of which part of the order
which we click on, it always climbs up to the
parents order wrapper, to grab the full
contents of this order. Using this method, if
no matches are found, we will get back
a value of null. We can also check against
this before adding our order into the
working on section. Just underneath where we
select our order wrapper, add an if statement where we
pass in our order wrapper, we can check if this
is not equal to null. If we have successfully
clicked on an order wrapper, we now need to grab
this order wrapper and move it over to our
working on section. Step 1 is to grab a
working on section and store this inside of a
constant called the orderDiv. That is equal to document. querySelector, and the
query selector which we need as a idea of working on. Then using append child, we can add our order
wrapper. The orderDiv. appendChild passing in our order wrapper
which was clicked on. Great let's give this
a try. We can close the console and try any
one of these orders. Let's try Number 3 and this now appears in the
sections since we've appended this as a
child elements Order 2, Order 1, and this
all works great. However, we only want
to be able to work on a single order at a time. We need a way of
checking if there is only a single order selected
and placed in this div. A way of doing this is to
first access the working on section and we can check what child elements
are nested inside. See it is better we'll
grab a reference to our div place this
inside of a console log. Then we can chain on the end.children. Jump
into the console. Click on one of our orders
to add this to the section. If we look closely, the
first thing we have is a HTML collection with
a Level 3 heading. After this, we have our
div with the class of order wrapper, which
we just added. We can also confirm this if we go to our index page that is working on section only has
a single Level 3 heading. With this in mind, the number of child elements can now be
used to stop the function. If the number of child
elements is greater than one i.e I we have our Level 3 heading followed by
any one of our orders, we can do this back in our
select current order function. Remove the console log, replace this with
an if statement. We can check if the children. length is greater than one. If it is, we'll simply
return alter this function. We can also remove
the curved braces and replaces with our
return statement. Let's try this out.
Save and refresh. I click the first-order
this works fine. Now we can't add
any further orders. The final thing
to do, we want to move over the order into the working on section is to also
remove the event listener. We no longer need this
section to be clickable, so we can remove this
from our order wrapper. We can do this just before we add this to our div section. By accessing the elements
which is stored in order wrapper called
removeEventListener. The listener we
want to remove is the click events followed by selectCurrentOrder which
matches our event listener. Where possible event
listeners like this should be removed when
no longer needed. It helps keep performance
up since the browser no longer needs to do
an unnecessary task, but also to eliminate
unexpected behavior. Next, we'll cover
selecting the pizza, which we are going to be working on inside of the kitchen.
29. Set The Current Pizza: Look at the flow of our project, we've already done the
first step which was to move the selected
order which we click on to the Order section into
the Working on section. Next, we need to
be able to select the pizza to work on
by clicking on it. This selected pizza
should then show in the kitchen area and
also display the method, all the steps to create this. If you take a look here I've
created some steps which we're going to use to fulfill this objective at the top which is to make the pizza names clickable and then move them
in to the kitchen area. If you want to you
can follow along with these five steps
and try to recreate this yourself or if you prefer to you can
follow along with me. First of all to be able
to click on these pizzas, Step 1 includes
adding a class of pizza name to each
one of these items. Let's jump up to the
createListsOfPizza's function , we'll locate this. And then this is where
we create each one of our pizzas inside of
the span elements. Just before we do any appending, we'll add our class by selecting the pizza
name element variable. Select the ClassList, the method called add, where as a string we'll pass
in the name of pizza_name. That's Step 1, taken care
of. Let's scroll back down. Step 2 is to locate the
SelectCurrentOrder function. Since we may have
multiple pizzas, we need to select all
of the span elements. And remember if we have
multiple elements, we need to use a
querySelectorAll. We can grab all of
these elements using this pizza name class,
which we just added. So let's locate this
function SelectCurrentOrder, then at the top we'll
use querySelectorAll to grab all of these
pizza elements, store these inside a variable called pizzas
document.querySelectorAll. We can grab these with
the new class name which we just created,
which was pizza_name. Remember the objective
is to be able to click on one of our pizza names, such as our ham and
pineapple or all pepperoni. So we can just now free click
on any one of these names. So grab our variable,
which is pizzas. We can use forEach to run a function for each
one of these items. Each one of the individual
pizzas will store on a pizza variable
and then we can add an EventListener to each one of these pizzas, so
pizzas.addEventListener. Event will be a click, which will then run a
function which we haven't created yet called
setCurrentPizza. Okay? So let's create
this function down at the bottom, function
setCurrentPizza. Since we trigger this
using a click event, we can also pass in the
event information to our variable and check this is working
with a console log. So to grab the actual elements
which you clicked on, such as ham and
pineapple or pepperoni. To grab the text,
we can do this with e.target in a property
called innerText, check if this is all
working correctly. Let's try this out, jump into
the console, and refresh. So the first thing
to do is to click on one of our orders, oops it's thrown an error
at the EventListener. Let's take a look at what
could be causing this. We've got unused variable, so this just needs an s, because this contains
multiple pizzas. Let's try this out
now. We can click on our order. That works great. Now if we click on the
text of ham and pineapple, we see this in the
console, Pepperoni. Good, this is exactly
what we want. Now if we go back down
to our setCurrentPizza, obviously, the objective is not to log this to the console, what we want to do is to
save the pizza name into a variable and then add this just after our text
of Currently making. If we jump into the index
page and locate this section, we can see just
afterwards we've got a span with the ID
of current_pizza. So we're going to
store this text into a variable and then
place it into this span. Let's do this inside
of our function with a console log and store this inside of a
constant called pizzaName. Grab our span area with
document.querySelector. Use #, since this was with
the ID of current_pizza. Set the innerText for this element equal to
our above variable. Let's try this out. We don't
need the console anymore. Move any one of these orders over to the working on section. Click on "Pepperoni" and
there's our text displayed, "Ham and pineapple" and
also the "Margherita". Next, we'll use this
selected element to display the required steps inside
of the method section.
30. Split & Join Methods: Previously we set the
current pizza name inside of the kitchen
when this was clicked on. Now we're going to move on
to displaying the method to make this pizza inside
of the methods section. The method is all the steps
needed to create each pizza. We have this at the top
of our JavaScript file. Inside of our index page, we can go into all pizzas array, and this has the pizza name
which you've already used, and also the method too. If we take this Margherita
method as an example, this is going to
have three steps. The first one is
to roll the dough, the second one is
to add the sauce, and the third one is
to top with chees. We will validate the
chef has completed the steps later using
the required steps, but for now, all we want
to do is to focus on displaying this method
inside of the browser. As you can see, this method is all one single string of text, and what we're going to
do in this video is to split each one of these steps up to display
them in the browser. So we need step 1,
we need step 2, and also step 3 to display
these in a formatted way. To do this, we could go into our set current pizza function, but to keep things cleaner
and more abstracted, I'm going to create
a new function down at the very bottom. Jump down to the very
bottom of our project. It's a new function
called displayMethod. This is also going
to need to take in the pizza name so we know
which method we need to get, so pass this in
into our function, and we need to call
this function each time we set the current pizza, ie, this function just above
when we click on the pizza, and this is displayed
inside of here. When this is clicked
on we know which pizza will need to work on. This makes sense to
call our function from inside of there. Let's do this. We'll call our function, which is displayMethod, and remember this
also needs to take in the current pizza name, which you have stored
inside of this variable. Over to our index page, is currently already
has a section called method, which
you can see here. This has the ID of method a level 3 heading up at the top, and then we have two sections. We have the pizza
name followed by the method which we are
soon going to insert. As you can imagine,
this is the section which we're going
to be working with. We can begin up at the first section which
is the pizza name. Into our function, we've already got the pizza
name so we can use documents.querySelector('pizza_name').innerHTML which would have seen the inner text content will be equal to our
variable, which is pizzaName. Before we go any further,
try this out in the browser, I need to refresh and pass over an order into the
working on section. Click on "Pizza" and we can see our pizza name inside over here. This also updates as we
click on different ones. The next step in our
index page is to grab our pizza method and place
a method inside of here. Remember, our pizza object has this method property
which we can access, and to find this we can use the Erase find method
is find method we can use to filter
this particular borne by the pizza name
which we already have, and then we can select
the method for each one. Back to our function bravo full pizzas array
method, which is find, and find is going to
run a function for each value inside of
our pizzas array, and you can place an irregular
function if you want to, but because this is going
to be a simple statement, I'll add this on its own
line with an arrow function. This function is going to
take in the individual pizza, then access each individual
pizza's name on the loop, and we can check if this is
equal to our pizza name, which is passed
into this function. The current clicked-on
pizza name is equal to any one of our pizza
names in the array, we'll store this selected
pizza inside of a variable or constant
called selectedPizza. Next, we'll do a console log and check this is
all working fine, the constant of selectedPizza, and let's refresh this
inside the browser. Jumping to the console. First, click on an "Oder,"
select one of the pizzas. This is ham and pineapple,
and we can see the full among pineapple
object has been returned. Let's try one more.
The pepperoni. Great, so this all
works and we can now filter this down to only return back the method. Try this again. The steps which we
need inside of there, and it's different for
each one of these. Great, so this now returns
back a string of text, and we need to split
up this string and display each step
as a list item. For this JavaScript has a built-in string
method called split, which strings inherit
through the prototype chain. This method will take
our single string, which we have now, split it up at a certain
point which we specify, and then return a new array containing all of
these substrings. This is how it
looks in our code. This is our method, so we
can just remove this from the console log
chain onto the end, the JavaScript split method, and we want to pass
in which section of the string we want to split this apart, and then our case, a full stop is ideal, and this is because if
we look at our string, each one of these steps which we have ends in a full stop. This is perfect for
breaking up our string, and remember this will
return back a new array, which we can store
inside of a constant. Finally, before we
go any further, we'll do a console log passing in our constant,
which is methodSteps. Let's try this out.
Select an order, select the pizza and this now returns a new array with
all of our steps in place. We can also do the opposite
too if we wanted to, and that is to take an array
of values just like we've got here and convert them
into a single string. We can do this with the
method called join. Remember, methodSteps
is an array. We can do the reverse by
passing in the join method. Join is into a single string. We can test this out by
clicking on a Pizza. Have a single string
return back towards and each value is
separated by a comma. If we wanted to, we could change this separator to be
any other character, such as a dash. Try this out. Now have a dash between
each one of these values. This is useful if
you need to join an array of values
into a single string. We know now that we can
loop through arrays and do something with each
value, and in the HTML, we've set the pizza name currently with
this section here, and the next step is to grab our second div with
the id of pizzaMethod. I'm placing our array values. Grab this with documents.querySelector('#pizza_method').inner HTML and here I'm just going to set this to be
equal to an empty string. If a method has already
been previously set, we can clear it out before
adding our new one. Then, we'll list our steps by grabbing our array,
which is methodSteps. Since you have multiple values, we'll loop through each one of these with a for each loop, which runs a function
for each value. We can solve each
one of these values in the variable called method. Then construct our elements. Install this inside of a
constant called elements. We have
documents.createElements plus the elements which
you want to create. I'm going to choose a p element, and next the text content, document.createTextNode, and the contents which
you want to pass in is the text from our method. Placing in the
method inside merges together elements.appendChild
passing in our text. The final step is to again grab our pizza method section,
so we'll copy this, paste this in, and then
we can add this using the appendChild passing
in our elements. We'll save this and refresh, select a new order. Pepperoni. There we go. There's our pizza title and also the required steps for
each one of the pizzas. Good. This is all now
working and looking good. There's just one little
re-factor we can do. Remember, we've created a
new text element just here, but we've also got
a helper function called buildElements, which takes in our
element name, contents, and returns is newly
constructed value. Back down to our loop. We
can store this inside of a constant called steps. This is equal to
the return value from our buildElement function. Passing the p elements. Then the contents, which
is the method we're going to move these
next three lines and pass in our steps. Let's try this out,
select a new order. This all works exactly the same, but we've saved a
few lines of code. This is another good step
completed for our project, and if you are a beginner, I know that can be a
lot going on here, but the important thing
to remember is we've already covered the majority
of what we're doing here. It's just the case
of breaking it down into smaller tasks, making sure each step
works along the way.
31. Adding Pizzas To The Oven: The final stage of moving
this pizza around is to move it from the kitchen
area up to our oven. We've not yet created the pizza, but we will look at
how to do this soon along with checking we've
used the correct ingredients. But for now, we can
use this pizzaName which we have and move this
up to the oven section. Now our oven is going to be an array to store
multiple pizzas, we'll also need some
other variables too. Let's create these both
our functions well up, and just below our ingredients, create our oven with
the let keyword, which initially will
be an empty array. After this, we need to set the maximum number of pizzas
which will fit in our oven, which is the ovenCapacity. I'm going to go for
a value of six. Next, we need to keep
track of the pizzas which we have made for
a particular order. If you take Order 3 for example, it says four pizzas, Order 2 has three
pizzas and we need to keep track of how many
we have currently made. let pizzasCompletedForOrder, which will be initially zero. Now we have these, we can create our first function which will be used to push the pizza
to our new oven array. Down to the bottom, create a
function called addToOven. For now this oven
is going to take in the current pizzaName. We can grab this from our kitchen if we go
to our index page. Down to the kitchen area, remember we have this span
with the ID of current_pizza, and this stores our pizza name. Grab this with
document.querySelector ID, which is current_pizza. Then the innerText. Store this inside of a
constant called pizzaName. Remember that we don't always
have a pizza selected. Currently we've got the value of ham and pineapple but if we refresh this element
is often empty. To check against this we'll
place in an if statement. We can check if the
pizzaName is present. If it is, we'll then add
this to the oven with the array method called push, pass in the pizzaName and then a temporary console log to check this is all working. Let's have the Console,
jump into the browser. We can check this log
in just a second but first we need to actually
call this function. We'll call this
function by adding a click listener to the
button which we already have, which is inside of
our kitchen section and just below our
current pizza. Have this button to addToOven. It currently has a class,
but we can add an ID. Select this addToOven and then grab this oven
in our script. documents.querySelector,
the ID value of addToOven, then we can add an
event listener. Event listener will listen out for a click on this button, which will run a function called addToOven. We'll try this out. With the Console Open, click on one of our
orders. Select the pizza. Currently we're on
ham and pineapple, and trigger our
click listener with oven and we see ham and pineapple
is pushed to the array. We'll try pepperoni. Click on this, and again, this keeps adding
pizzas to our oven. Great. Now we know this works, we can extend this
a little along with adding a
pizzaName to the oven. Currently we just see
inside of the array, but we also need to list
this in the oven section. Along with listing
the pizzaName, we can also set the time
which was put into the oven. This is because later we'll
also add a cooking time too. So for this, create a new object and we do this inside
of our if statements. Just above where it
pushes to the oven, we'll create an object
called pizzaForOven. The first property is the pizzaName which is stored
in the pizzaName variable, which you already have and then a property
called timeAdded. For now a simple
place-holder value is fine, we'll come back to this later. The final step for now is
just to put the constant of pizza for oven into
our push method. Just before we test
this, remember we added a variable called
pizzasCompletedForOrder. We can increase this
by the value of 1. Let's just check this all
works inside the Console. We've got a pizza, add this to the oven and there's
our new object. The oven array is now getting
the pizzas as we can see. Next a function to run through this array and display
them in the oven section. Just after we push our
new pizza to the oven, we're going to call
a new function called displayOvenItems. We call this each time we create a new pizza and add
it to the oven, which is going to update
the user interface. We haven't created this yet, so let's add this just above. To begin inside of
the index page, we need to grab our oven section and if we take a little
look through our code, we've got the oven wrapper
which has the level 3 heading and then this empty
div with the ID of oven, and this is the location
where we add our new pizzas. We can grab this
document.querySelector, the ID of oven and then first we'll set
this to an empty string. This clears out our oven
before we update it. Wrap our oven array, and since this isn't an array
we can use forEach. We run a function forEach
value inside of there, each value is going to be a particular pizza which
we'll wrap in a div. const pizzaDiv is equal to
document.createElement, creates our div section. We'll add a class to
this for our styling, so pizzaDiv.className
is equal to pizza_div. Remember, inside of our
starter files we also have an SVG image for our pizza. This will be used to
display inside of the oven each time we add
a new pizza to the oven. What we can do is we
going to treat this as an image or create a
new image element. We have document.createElement, the image elements with IMG. Then we can set the
source property to be equal to our SVG. This one is called pizza.svg, and it's also in the same folder but we can reference
this by its name. We've got a wrapper
with a class, we have an image, we'll
also need the pizza name. To do this, we can make use of our function called
buildElement to create these photos and store the return value in a
constant called pizzaName. Our helper function,
which is buildElement, this can be a p element and since the pizzaName is
going to be a variable, we'll insert this
inside the back ticks, with all the symbol
and then calibrate this and then remember, each one of our oven
items is stored in this pizza variable so we
can access pizza or name. This is all the
elements we need. Now it's just a case
of adding these to our pizzaDiv wrapper. We'll select this pizzaDiv. We can use append to
add multiple values, add some new image
on the pizzaName. The final section is now
add this to our oven. Just like above, we'll use querySelector to grab our oven, appendChild, placing
in our pizzaDiv. It's a bit going on there, but let's refresh and
test this is all working. Place in a new order, select the pizza, add the
pepperoni to the oven. There's our image which
we created just here, and also our pizza name
below, ham and pineapple. Great, and this is
all now working. As mentioned, we still
need to come back to actually create in the pizzas, but we now know this step
is ready when we do.
32. Starting & Ending The Game: This lesson, I have solved some steps to try
things out yourself. What we're aiming to cover
is creating two functions. One function will
be used to start the game and one to end it. These functions are going to be called from our HTML buttons, which we can see in our index. This is the game control section where we have our two buttons. The idea behind
these two buttons is to clean up the
user interface, to only show the correct things, such as only showing
the end game button when the game is
actually running. We use the start game button to begin the orders
coming in and so on. These functions will also
be useful later on too when we add to our project things
like stats and timers. What we need to do
is to first create our two functions called
startOfGame and endOfGame. Then we can call these by adding an event listener of the two
buttons we just looked at. We'll create a new variable, which is game started, which will be a Boolean. This Boolean will be
true when we start the game and false
when we end the game. We'll do a conditional check
inside of startOfGame to return out of this function if this game started
variable is true. Step 5, remember
that all orders have this class of order_wrapper. I'm going to clear
all of these orders in our startOfGame function. Since we have not
started the game yet and createOrdersList is responsible for
creating these orders. We can also move this inside of our startOfGame
function onto the CSS. When the game start, we'll
show the end button and hide the stop button and then do the reverse when
the game has ended. Not forgetting our end game
button also needs to be removed or hidden when
the game initially loads. As ever, you can try
these out on your own or follow along
if you prefer. Down at the bottom,
remember the first step is to create our functions. The first function
was startOfGame. Then our endOfGame function too. Both of these functions
need to be called by clicking on the two
buttons in our HTML. We'll do this by grabbing
our start and end button, and adding an event listener
to call these functions. These buttons, at the idea
of start and end button. We can grab these now with
document.query selector. Here's the hash. First,
the start button. Add event listener
for a click event, which is going to
trigger a function called startOfGame.
Duplicate this. The second one is
for the endBtn, which will trigger the
corresponding function. Also move down our
event listener from above to keep
these grouped. Actually it's just
wants to be add event listener to make sure
that this is completed. Then before we go any further, we can add a console
log into each one of these functions to check
they're all working correctly. The first one is start. The second one for end. Then jump into the console. Just separate out the
minutes, so we'll just reattaches. There we go. Back into the console and
our two buttons at the top. Start triggers a console
log and also end works too. That's Step 1 and also
Step 2 taken care of. The next one is to create
a gameStarted Boolean. Let's do this up at the top and the rest of our variables. Let gameStarted, which
will be initially false. We can update this variable
from our two functions. Remove the console logs and
when we start the game, gameStarted will
be equal to true. Then inside the
endOfGame function, gameStarted will
be equal to false. Now when, if this game
is started or not, it's going to be
really useful and in particular for the
startOfGame function, we're going to be doing
some cleanup work such as removing any
existing orders. We don't want to use
it to accidentally run this mid game. We can return out of this
function if this is true. If gameStarted is equal to true, we'll then return
all to this function before running any more code. After this, we can
move on to removing any orders from
the previous game. Let's grab our orders
and store this inside of a constant with
document.getElementByClassName. We are using class name
here because remember, we have this class
of order_wrapper. Pass this in brackets. Then we can loop through and
remove all of these orders. If we access the main array
object with a capital A, we can then have access
to a method called from to create an array
from all of these items. When using
getElementsByClassName, this will return back
a HTML collection. Then we can use this to create a new array from these orders. The from method,
passing the orders. Now we have this
converted to an array. We can make use of
array methods such as forEach to run a function
for each one of these. Passing the function, each order will store it in
the order variable. Access this individual order, and then call the
JavaScript remove method. This takes care of removing these orders when we
first start a game, but we also need to actually create them in the first place. Currently we have a function
called createOdersList. If we scroll up to find this. Just to get this
function is responsible for creating all of our orders. Would make sense to call this
when the game has started. Currently if we do
a search for this, we can see this is just called from inside of our program. Instead of calling this
as soon as it loads, we'll cut this out
of place and then move this down to our new
startOfGame function. Now all orders should only show when the user clicks
on this start button. Let's save this
and try this out. Now, since we've moved
our function call, we don't see the
orders, like start. Now they appear
inside the sidebar. The orders are showing and we know this function call
is working correctly. But we can't get to tell if the previous orders
were cleared. Where we used order.remove. This doesn't matter much at the moment since all
the orders of the same. But later we'll generate random orders which
will be different. As a temporary
measure to test this, comment out our function call. Then we can add a
temporary elements into our HTML and check
this is being removed. In order section of P elements. Remember all of these
have the class of order_wrapper placed
in any text inside. Now, if we save this, refresh, we can see all the run. Click on Start. This element
has now been removed. Great, so we now know
this is working. We can remove our
temporary elements and also reinstate
our function call. Then the next step
is Number 7 and 8. This is to use the
style property to show and hide these two buttons. At the startOfGame,
at the top of the function, use
document.querySelector. First grab our start button. Access the style
property of display. We can hide this by
setting this to be a CSS value of none. Duplicate and then
for the end button, we'll reinstate this by
setting this to a value of inline. Copy both of these. Jump into the endOfGame
function and paste these in. Then we could reverse the
values of both of these. The stop button
will display with a value of inline and then end button can be removed with
none. Let's try this out. Refresh. Click on Start, and this removes the end button. Click on end and this
shows the start button. You'll also notice as
soon as the page loads, we do see both of these buttons. Since the game has
not yet started, we only want to show
the start button. We're going to move
this by setting the style property of
display to be equal to none. For this, we've
already got the code as our second value
inside of here. Copy this and then we can call this up at the
top of our code. Anywhere around the
variables is fine. Refresh. The end button
has been removed. We start and we only
see the end button. Now I'm in the game. We
only see the stop button.
33. Introduction To Javascript Math: In this section,
we'll continue with our Speedy Chef
project but we're going to be focusing
on some handy math, date, and time of functionality
which JavaScript offers. Starting soon, we will
look at how we can use JavaScript math to generate
random pizzas and orders. Currently, with this
orders array we have, we only generate these
same three orders each time the game starts, but we can generate new ones with have been more randomness. These orders will keep on coming in as the
game is running. We'll have different
amount of pizzas and also different types of
pizzas for each order. To do this we need two new
functions at the very bottom. The first one is going to
be called generatNewOrder. This will be responsible
as it sounds, for creating a new order
with an order number. I'm going to parse this
in, generateNewOrder. Then this second function is going to be called
generateNewPizza, and generateNewPizza will
create random pizzas, which will be called
from our order function. Test this out with
a console log, parsing in the
JavaScript math object. Just before we can see this, we do need to call our function. So grab this and then call
this function anywhere below. Refresh, open this up, and this returns a
lot inside of here. We have lots of different
properties and methods, and I'm not a mathematician, so some of this is
beyond my knowledge. But you don't need
to be great at math to be able to
use some of this. It's all here to help us. There are some
constants which we can use such as the value of Pi, which you may be familiar with. We have something called
Random if we scroll down, which is a method. It's what generates
a new random number between zero and one. We have something called
ceil and floor to round up or round down and we'll look at these in
just a few moments. There is a method to round
to the nearest number. We can find the smallest
and largest number with min and max, and also lots of others too. Let's go back to
the function and see how we can use them. Jump into generateNewOrder, and just below our console log, let's change this
to be Math.random, which is one of the methods which we've just seen
inside the browser. Let's test this out.
As mentioned before, this will generate a new
number between zero and one. So zero is included, but the value of one is not. We'll only go up to the
value of 0.999 recurring. Often we do need this number
to be a different range, such as the value of 0-10, 1-50, 0-100 and so on. To do this, we can't parse
in any range to Math.random. Instead, we must
multiply this by the amount we want
to multiply it by. If I multiply this
by the value of 100, this will give us a value
between zero and 99. Remember that zero and one
is the original Math.random. Even multiplying that 0 by
100 will still give us 0. Since 0.99 is the highest value, multiplying this by
100 will give us the value of up-to 99. Save. Leaving us with some larger numbers
inside the console. Let's store this
inside of a constant. I can pull this out of our
console log, const random. Then we can round up this random number to the
nearest whole number. It was an example. Rather
than getting 53.4, we can round this up to 54. We do this by, again, accessing the
JavaScript math object, a method which we've just seen inside the console
called a ceil, where we parse in our
constants of random. Now each time we refresh this, it will round this up to
the nearest whole number. We've actually got pretty
lucky there because we've got the value of
100 in the console. This is because the
original random constant will give us
a number such as 99.9. Then this will round
this up to 100. With this rounding up,
any numbers beginning with zero will be
rounded to one. This should give us a
number between 1-100. We can round down by
changing ceil to floor. But you won't notice much
difference inside the console. But this will round down rather
than rounding up even as a whole number between 0-99. Also mentioned when we looked
at the math object was the min and max methods
in the console. Let's begin by finding
the largest number value. To do this, we'll
create a new constant called highest and set
equal to Math.max. Math.max is going to
take in multiple values. We'll go for 1, 77 and 34. Just parse in any random
numbers inside of here, then log this to the console. In my case, 77 is the highest
value inside of here. This is returned
back in the console. As you would expect
in a similar way, we can also store
the lowest value. We have Math.min. Log in any numbers
inside of here. Log this one to the console, and it should return
back the value of one. This isn't always practical
to wrap our numbers inside of the max or
the min function. Often these numbers are
stored inside of an array. Then we need to access
this array and find out which is the highest,
and lowest value. Well, to say this, create a new constant called
numbers and store this inside of an array.
Saved numbers are fine. Count these ones
out. First of all, maybe to reproduce what we had above with
our highest one, and then parse in our
numbers to Math.max. Let's do this and
see what happens. Console log the
value of highest. You may be expecting to
get the value of 77. Well, if we save
this and refresh, we see the value of not a
number inside the console. The reason we get this
is because inside of here we're parsing in an array, rather than the actual values which were contained inside. To extract the values from
this array and compare, we need to make use of the JavaScript spread method
based on the three dots. Refresh, leaving us
with the correct value. Also if you wanted to, this can also be
used with Math.min. It works exactly the
same and that is other methods and properties available on this math object, as we've seen earlier
in the console, depending on your
particular needs. Now we're going to put
this into practice by generating random
pizzas, and orders.
34. Generating New Pizzas With Math: We can use what
we now know about JavaScript Math to generate
random pizzas and orders. First, let's clear out our generate new order function from the previous video and jump
into generate new pizza. We use this function to generate a random pizza from
our pizzas' array. To start off, we'll create a random number
between one and three, to generate the quantity
for each pizza. Remember to create a new random number, we
use Math.random. This will generate a random
number between zero and one. But we also want to make
this between one and three. The first step is
to use Math.ceil to round this value
up, cut this out. We use Math.ceil and inside the brackets pass in
this random number. With this between zero and one, this is always going to round this up to
the value of one. But we want the value of 1-3, meaning we need to multiply
this returned value by 3, store this inside of
constant called quantity. Then do a console log to
check this is working. Say this, and we need to console log this value by calling
generate new pizza. Changes, refresh 1, 3, and 2. Seems to be all the numbers
which we need in our range. This is our random quantity, but we'll also need to
select a random pizza. Just before we do
this, ultimately, what we want to be aiming
for is to be creating a new pizza object, const pizza. This pizza needs to take in
the quantity which you've already created just above and we'll also
need a pizza name. This is a step which
we now need to create. Comment this out so
we get no errors. We take constant
to store this in, for all at random pizza. This is going to select a random pizza from
our pizzas array. Remember the arrays always
begin at the value of zero and it needs to go right through to the
end of our array. We're now going to grab
this with pizzas.length. Using a similar
example from above, we'll copy this without the
semicolon, pass this in. As we know, this will return
back a value from 1-3, but we need a value of zero all the way through
to pizzas.length. To get zero, we use Math.floor, which currently will give
us the value from 0-2. But rather than
multiplying this by 3, we'll multiply this by the
value of pizzas.length. This also means that if we
add new pizzas in the future, this will also be taken care of. Now back to this name property, we can set this equal
to our random pizza. But remember, our
random pizza is selecting one from the array. If we look at the array, this contains an object
with the name, the method, and required steps. We only need to access
the name property, so we can access
randomPizza.name. Finally, since the
job of generate new pizza is to
generate new pizza. This is what we've
done with this object, so we can return this
back from our function. Good, let's try this out.
Rather than calling this directly at the bottom
place this into a console log,
generate new pizza. Call this function, refresh
as our first object. We've got a random quantity of three and the name of
ham and pineapple. Again, one vegetarian, two vegetarians,
and one chicken. This is effectively half of the job done to
generate our new order. We've got all of the
pizzas return back. But rather than console
login, this value, we need to call this from
generates new order. Jump into this
function and we'll begin by creating a new pizzas variable and set this
equal to an empty array. This empty pizzas array
will be used to build a random amount of
items for our order. Because remember, even though
it generates new pizza, it generates multiple values, it's still only one line item. By this, if we refresh,
starts up again. What I mean by this,
if we click on one of our orders is, for example, we only generate in one line, such as one ham and
pineapple or two pepperonis. Pizzas is going to store an array of all of
these line items. In this game, I'm
going to restrict the number of line
items to be up to five, so we don't have too
many huge orders. We already know how to generate a new number
between one and three. Let's copy this, store this inside of a constant
called order item, rather than the value of
three, changes to be five, given us a number
between one and five, which you can change
if you prefer. Now we know we've
got a random number between one and five. Next we'll create a loop to
push each one of our orders, which is generated
from this function into our pizzas array. We'll repeat this for
the value of order item. Pass in a for loop, i will
be initially set to one. The loop will keep running
where i is less than or equal to the above
value of order item, so this is 1-5. Then after each loop we'll
increment i with i plus plus. For our empty pizzas array, use the push method
to add a new value. The new value to push is this value which is returned back from generate new pizza. We should be left with
now a pizzas array, which is set to a number of
line items between one and five and then the random
pizza objects inside. This now leaves us with our
pizzas array for the order, but now we need to construct
our order objects. This creates a new
constant to store this in, called new order, let
this be an object. Each order needs a unique ID. We could go ahead and generate some long random
number or string. Of this case, a simple
order number is fine. What I'm going to do is just
outside of this function, creates a new variable
called order number, and set this equal to our
orders array dot length. If our current amount
of orders is three, this will return back
the value of three. But since we're
creating a new order onto the end of this array, we'll add plus one to the end. There should be
always one more than the number of orders
which we have. Therefore, it's
going to be unique. We can pass this in as the ID, place in at the pizzas
array from above. What do we actually want
to do with this new order? Well, up at the top, remember
we have our orders array which contains the
three sample orders. We need to push to the
end of this array. This is pretty simple.
We'll grab our orders.push place
in our new order, and then finally would increase our order number for next time, so add order number plus plus. This should always be unique. Let's grab our generate
new order function, and we'll call this
at the very bottom and try this out.
Save and refresh. Click on "Start." Then we have our three original orders
from our orders array, and then a random order
down at the bottom. We've got the next index
number of order four. We've got a random number of pizzas and also a random name. Let's refresh and try one more. Order four, this one's a
little bit longer and it's got four separate line items. Remember, we could have
up to five line items. This is good progress
with our game and we will build on this in
the next lesson and generate new orders after a certain time delay and also
revisit JavaScript Math, where we'll look at
drawn with the Canvas.
35. setInterval: We know our orders
are working and also generating randomly as
soon as the game begins. But we also want them to keep coming throughout the game too. But this JavaScript
provides us with a method called setInterval, and setInterval run a function after a certain time delay. It's available on
the window object, so we can simply
call it on its own. So call setInterval, and setInterval is going
to take in two things. First, we pass in a function
which you want to run, and we could pass
in a new function directly just like this, or we could reference an
existing function by its name. The function which we want
to run is generateNewOrder, so pass this in
without the brackets, and we can also now remove
this from just above. Separate it by comma. We
pass in a time delay, and this is the time delay
between each function call. It's also in milliseconds, so 3,000 milliseconds
is equal to 3 seconds. This function will be
called every three seconds. We check this works, move up to our generateNewOrder
function and we can pass in a console log, place in the orders array. Refresh the browser.
After three seconds, we should see a console
log with four orders. Three seconds later,
we see five orders, six orders, and so on. Good. Now we know this works. Earlier, we created
a function called createOrdersList. Let's
take a look for this. Scroll up and here we are. This is our function just here. This function took
the orders array. We then looped over
each one of our orders and placed this into
our orders section. All we need to do now is to call this function each time
we generate a new order. Instead of our console log, which we placed them before, inside of generate new order, remove the console log and replace this with
createOrdersList. Let's say this and see
where this leads us. Refresh the browser. After three seconds, we see our set of
orders come through. We have number 1 all the
way through to four, and then the seconds later, we get a new batch of orders. The problem here is though, we don't just add the new orders which have been generated, we also add in the
existing orders too. This now leaves us with two problems which we need to fix. The first one is that orders
are coming in before we even press the "Start"
button at the top. The second one, as
we've just seen, we only need to
add the new orders rather than add all of
the existing ones too. Fix this issue, we
can clear out all of the orders before we add our
new ones to this section. This can be done by going to the createOrdersList
function and clearing the order section
before we add the new ones. Let's jump back up
to createOrdersList. Here we go at the very top. Before we add our orders
into this section, we'll clear this out with
documents.querySelector. Grab our orders section, inner HTML and set this to an empty string.
Let's try this out. Refresh, give this
three seconds. There's our first four orders. Order 5, this is looking good. Order 6, great. These are all now
coming through but rather than adding all
the existing orders, we are adding it to the end of the ones which
are already there. The next thing we
want to solve is to only add these orders to the sidebar after we've actually clicked on
the "Start" button. For this, rather than directly calling our
setInterval at the bottom, we can remove this, quit this out of place, and wrap this inside of a
function called ordersTimer. Place this back inside. Remember, when we click
on the Start button, this triggers our function
called startOfGame. If we locate this just here, we can then call our function, which was the ordersTimer. Let's say this and try this out. First, refresh and give
this three seconds and we shouldn't see any orders
now in the sidebar. Good. This has stopped
the initial load. Click on the "Start"
button. Now our order is beginning to come through. SetInterval is also useful to create a countdown
timer for the game, so it only runs for a
certain amount of time. As soon as you click on
"Start," we'll begin a countdown timer from a
certain amount of seconds. For this, we need some variables
to keep track of this. Jump up to the top where we have the rest
of our variables. The first one is going to be a constant called gameLength. Set this equal to any number of seconds which you want to. Mine is going to be 300, and the second one
using the let keyword, since we'll update this, is going to be equal
to the countdownTime. Countdown time is going to begin at the initial gameLength. Then we'll reduce it by one
second with setInterval. Initially, we'll set this
equal to gameLength, then we'll reduce it
as the game goes on. We also need to display
this on the screen. We can do this with
document.querySelector. We need to find a
section which is up at the top so next to our buttons. This is the game control
section where we have the start and the
end game button, and we also have a span
with the ID of gameLength. Pass this in, set the innerText. Since we're inserting
the gameLength variable, we need the backticks will say the game length is
insert the variable, which is our gameLength number, followed by the text of seconds. Reload and there's
our text at the top. We now need to do two things. The first is to
create a function to deduct this variable
by one second. Then we can call
this function once every second using setInterval. First at the bottom, create our new function
called countdownTimer, which is going to be responsible for grabbing our variable, which is countdown Time and
deducting this by one second. Each time we do this,
we also need to grab the same elements up at the top and update it
with this new time. Just like we did
at the top, we use document.querySelector, pass it in the same section, which was gameLength,
the innerText , open the backticks. This time we'll say the time left is equal to our
variable just above. This function doesn't actually
do anything on its own. It's simply responsible
for selecting our countdownTime variable and
then deduct in one second. To also assign this to
the existing variable, we also need to
place in an equals, and then this adds
this to the DOM. This function is responsible
for deduct in a second, but we still need to call
this function every second. To do this, we'll jump into
our game start function. Into the bottom, we'll
setInterval once more, pass in our function, which
is the countdownTimer, which will run every second. Let's try this out. Refresh and we'll see our text
at the very top. You can start. Then our
timer begins to count down. However, though, if we
start this once more, you will notice a small issue. If we look closely, the
countdown doesn't begin for the first second. We'll click. We have to wait one second
before the countdown. This happens because
setInterval will not call the function
immediately. Instead, it waits
for the time delay first and then calls
this afterwards. This is a pretty simple fix. All we need to do is to grab our countdown timer and call this once just
before our timer starts. Now, if we refresh, click on "Start," our
timer begins immediately. This is how we can
use setInterval, which is a really useful
method to learn as is another timing function
called a setTimeout. This is what we're
going to cover next.
36. setTimeout: SetInterval which
we just looked at, repeatedly calls a
function or runs some code with a time
delay between each call. Another timing method we have available is called setTimeout. SetTimeout to run a function or some code once
after a time delay. We can use this to run
the endOfGame function once the timer is up. It looks similar to setInterval. Let's take a look at this
down at the very bottom. This one is setTimeout, which like setInterval, also takes in two things. First is the code which we
want to run and in our case, we want to run the
endOfGame function. Separate it with a comma, the time delay before running this function again
in milliseconds. So 2000 is a two-second delay. We can test this
works by jumping into the endOfGame function
and login to the console. Any text, try this out. So let's go for ended.
Open up the console. Gives us two seconds and our function is
now being called. But we want this function to
run at the end of the game. So we can set the
time delay to be the same as the length of the game. We have this stored inside of
a variable at the very top, which is called game length, currently set to be 300 seconds. That's how this works
rather than having to wait 300 seconds for
our function call, I just drop this down to a
small value such as three. Down to the very
bottom, we can now set this as our time delay. So as we know that
the game length is in three seconds with a time
delay is in milliseconds, meaning we need to convert this by multiplying
this by 1000. Okay, let's try this once more, refresh, and after
three seconds, our function has
now been called. So a little bit cleanup
work before we move on, we'll remove our console log and the end of game function. Reinstate the game length
to be at 300 seconds. Good. So this now works fine, but to save this code from running when the
game is not started, we can wrap this
into a function. So create a function
called gameTimer, put this inside and this function can now be called when the game
at first starts. So jump up to our
start of game function , call our gameTimer. Still within this
function, we can pass a message to the user. So what we're going
to do is if we look at these messages area, we'll place in a
message to say, "Chef, our first orders are coming in as soon as the game starts." We use setTimeout, then clear this after a
certain amount of seconds. So the message is not
permanently there. To begin at the very bottom, we'll grab the section
with query selector. Let's take a look what
the selector is for this part in the message area and we have this p with
the ID of message. This is an ID, so use a hash, select the innerText content. My message is going to be, "Chef, our first
orders are coming in." Then, just afterwards we'll
then run a function after three seconds to remove this
message from the screen. Once again, we can
use setTimeout. As we've already
learned, setTimeout and setInterval can either call a function by its name or can pass in a
function directly. We can copy this line
of code, paste this in. In fact, just before we do this, we'll remove these brackets. This needs to be
set to an equals because innerText is a
property rather than a method. The same for our second one. Let's set this one to
be an empty string and then just after our
brackets or the curly braces, we can add the time delay
of 3000 milliseconds. Refresh, click on "Start" and our message is now placed into
the messages area, and three seconds later
it's now been removed. Good. So this is
an introduction to JavaScript timers using
setInterval and setTimeout. You can see how useful it can
be in projects like this. We also have some more
timers to add very soon. But next, we'll take a
look at how to clear these timers when they
are no longer needed.
37. Clearing Timers: We've covered both of
these timers now which was setTimeout and also
setInterval of these methods. Also have a clear count part 2 of clearTimeout and
also clearInterval. These clear methods will stop the timers when we
no longer need them, and this is useful for
when the game is over. We can stop things
like the countdown, so we can restart when needed. Not use up any of the browser's
resources unnecessarily. To be able to clear a timer, we first need to store the original timer
into a variable. First of all, we'll
have our ordersTimer, which contains our setInterval. We reference to this inside a variable called orderTimerRef, that is equal to
an empty string. Now to this next function
we have the countdownTimer, and inside this doesn't
contain a timeout directly. But if we take a look first, this one is called
using setInterval. We'll place available
just above this. This one is the
countdownTimerRef. Also an empty string. Last one is the gameTimerRef. Notice here how we've declared these left variables
outside of each function. This is because if we declare
them inside the function, we won't be able to
access them later on in other parts of our code. We will need to access
these to clear the timers. We will cover this in more
detail in a later section. This first one is going to
be equal to our setInterval. The game timer reference is
equal to our setTimeout. Also know this middle one,
the countdownTimerRef, doesn't directly
contain our timeout. What we can do is go
up to our function, which is this startOfGame,
and set this up there. So this startOfGame function. Set the countdownTimeRef to
be equal to setInterval. These are all now stored
inside of variables. Just before we clear this out, we first need to prove
they are running. Console log can help us with this inside of our
generateNewOrder function. Function generateNewOrder,
place in a console log. It's actually is fine inside
of here, such as order. [inaudible] the
browser and refresh. Start the game. Our timer
begins to countdown. We see our message has now been removed after three seconds. Our orders still keep coming in, and also our console
log here too. But you'll notice if we click
on "End," stop our game, our timer still counts down, our orders still keep coming. To fix this, we can clear all of these timers when we call
the endOfGame function. Take a look for this
and then inside, we call the
clearInterval method, parsing in our orderTimerRef. The second one is
also an interval, so we use clearInterval. This is the countdownTimerRef. For the third one, our
game timer was a timeout. So we use clearTimeout
for this one. Try this once more.
Refresh. Starts our game. See the countdown,
we see the orders. Click on the "End" button. Everything has now been stopped. If we again click on
the "Start" button, everything carries on
from where we left off. We see the timer continues
from where it was paused, and also the orders
keep coming in from where they left off too. But this is fine, we'll
do some cleanup work very soon to fix these issues. The main thing here is our
timers can now be stopped, when they are no longer needed.
38. Introduction To Javascript Date: Many applications and websites require being aware
of the current date. We can track a
user's sign-up date, the time between two events, and lots more, but this JavaScript
has some built-in objects which can help us. To begin, we can call
date as a function, just like this,
with the brackets. Then store this
inside of a variable, that date equal
our date function. Then let's try this out
with a console log. Place in the date variable
and see what happens. Refresh and into the console, which will then return back the current date and also
the time as a string, as does the
constructor function, which we looked at
earlier when creating new objects I placed
in the new keyword. With the new date though,
we can also pass in some values to create
a different date too. Jump into the brackets in
order we can pass in the year, the index number of the
current month, the day, the hours, minutes,
seconds, and milliseconds. We' re placing any
year we want to, then the month index, which also begins at zero. January is zero and
December is 11. Next, the date. Save and refresh this inside the console. This now reflects a date
which we just placed in. This means this
constructor function, it gives us the
option to either get the current date or to set any date we want
to, just like this. Working with date values
like this can be difficult. If we want to compare
two different dates, what we may need
to do is to take the current string just like
we're have in the console, split up our string and then compare each individual piece. As you can imagine, this
is not an easy task. An alternative date format is
to use a method called now. We can move the
contents from date. With the new keyword, call it now method. Let's try this out and see
what happens in the console. This gives us back a
strange looking number. It may look a little strange, but it's a number in
milliseconds since January the 1st, 1970 UTC. If we keep refreshing,
each time we do this, this number will
keep increasing. Although it's still not ideal, it can be a simpler way of
comparing dates or times. For example, when a user
signs up to our website, we could use Date.now to store the current
time in milliseconds. Then on future visits, we can get the current
time and compare the difference between
these two numbers. The milliseconds would need
to be converted to seconds, minutes, and days also, depending on what you
needed to do with it. We'll look at this in
a little more detail in an upcoming video. Available on the dates
prototype is a range of methods which we can use
to also set the date too. Let's take a look at
these in the console. We could move the now method. Access the prototype object, refresh, and open this up. As we've just seen, we
have the date constructor function and lots more
of the use cases too, such as get the hours, get the full date, get the day. We've also got the setters, so we can set the current
date, we can set the time. I'm not going to go through
all of these since they are very specific to
different use cases, but it works just like
any other method. Use them since methods
are on objects. We again need to use
the new keyword to create a new date object. Let's remove this, place in the brackets, add
the new keyword. One of the methods
we've just seen in the console was getMonth. We've got this on
our date variable. Let's see what returned
back inside the console. This returns back the date
of the current month. Again, this begins with zero, so January being zero, and February is
number 1, and so on. GetDay will return the
date of the current week. Let's try this.
GetDay. That's what you've seeing with
the prototype before. We also have access to some methods that also
set the date too. Maybe we'll have an event
which we'll already have saved and we'll maybe want to change the month or the year, which we could do
just like this. Here, we've got the
basic current date. Let's just remove
this. Then below this, we're going to change
the current month to anything which
you want it to. Let's store this inside of a constant called updated date. Grab our current date variable, access one of our set
methods such as setMonth, place an index number of 11, which will be December. Then a second console log just below, again with the date. We should be able to see
the updated version with our second log. Refresh this. We start with March and then set our month to then be December. This is a basic introduction
to JavaScript date. Up next, we'll make use of this date and time
inside of our project, keep track of how long the
pizzas have been in the oven.
39. Setting The Cooking Time: What we learned in
the previous video, the task now is to set
the cooking time for the pizza using JavaScript date. We are going to
move the examples from the previous video. Now inside the add
to oven function, we created a placeholder
date early on. Let's take a look for this
function, at the oven. Here we set the time
added to be a string. This can now be
updated to make use of the current date dynamically. As we've seen, we access the date object and
the method called now. Remember date.now will
be in milliseconds, so we can remove this
from the oven after a certain amount of
milliseconds have passed. But first before we
do this we need to set the cooking time
which we want to apply. This variable is perfect, so jump up to the
rest of variables. Set up a constant
called cooking time, which is equal to 20 seconds. Now to the very
bottom of our script, here we can place in a new
function which will loop over all of the pizzas inside
the oven once every second. We'll then remove any
pizzas which had been in for our set cooking time. Function, check oven. To check the oven once every second we can
use a set interval. If you wanted to,
you could create a separate standalone
function or we can place in the
function directly. This function is
going to loop over all the items inside
of our oven array. This oven array is stored
in the oven variable, loop over using for each. Again, for each takes in a function which has access to each one of our
individual pizzas in the oven. At the end of our set
interval brackets, we can place in a
time delay between each one of our function calls, which will be every second. For each item or
each pizza inside of our loop facing an if statement. This if statement
is going to check the time difference
every second. We can say if date, but now, which is the current
time in milliseconds, then deduct the cooking time. Remember the cooking
time is in milliseconds. Let's convert this,
multiply this by 1000. In its current
calculation will give us the time 20 seconds ago. We can check if this
value is greater than the current pizza.time
added property. If it is, it's been in over
20 seconds and we'll need to remove this from the
oven. Access our oven. The shift method, which
is going to remove the 1st item from our array. We also need to access the
1st item because remember, the 1st item is going to be
in the oven for the longest. This is always going to be
the next one to remove. After we've removed this
item from the array, we'll then need to reset the oven contents
with the function, which is display of an items, which will effectively
redraw the oven. We can also keep track here of the number of pizzas
we have cooked, By increasing a variable. The variable of complete pizzas, an increases with plus plus. We haven't created this yet, so we jump up to the top with
the rest of our variables. Let completed pizzas to
begin at the value of zero, and this section
will be useful later on when we add
start to our game. Finally, we've actually
created this function, but we've not yet called it. What we need to do
is call our check oven function when
we start the game. Jumping to the start
of game function, places in down at the
bottom. Let's try this out. Refresh the page. Start of game. Click on any order, add this to the oven,
add a few more. Give this 20 seconds,
and our pizza should then begin to be removed. The 1st one gone, or 2nd
one just afterwards. Followed by our
final two pizzas.
40. Introduction To The Canvas & Co-Ordinates: We can still stay within
our project for this one and take a look at some
examples down at the bottom. This video will cover some
of the canvas basics. Then in the rest
of this section, we will draw our pizzas and show the different
toppings as we add them. First, let's cover an
essential part and this is to understand
using co-ordinates. There is a HTML element
called Canvas which allows us to draw to it
using JavaScript. We set the width and the
height of the canvas, and then we can navigate
this pixel-like grid using co-ordinates. The x-axis runs left to right, and the y-axis runs
from top to bottom. Using these x and y positions, we can move to any location
on the canvas and x,0, y,0 is the top left corner. We can then move across or down any amount we want to just
like we see in this example, where x is equal to 10
and y is the value of 3. We'll soon see how understanding these co-ordinates are essential to drawing on the canvas. Back to our project and
into our HTML file. We have a canvas
elements already setup. If we take a look for
our kitchen area, have this canvas elements
with the ID of pizza area. It has the width of 250 and also the height of 250 pixels too. Jump into our script and let's go down to the
very bottom of our script. Create a constant called canvas
to store this inside of. Just like any other elements, we can use something like
document.queryselector. As we've just seen,
we can pass in an ID. This one is equal to pizza_area. Then we can call a method
called getContext. Grab our variable, call
the getContext method. Passing in a string of 2D. In this context is
an object containing properties and methods we can
use to draw to the canvas. We're only going to
be needing the 2D features for these examples. Install this inside of a
variable or a constant, which is generally called CTX, but this can be named
anything which you want to. When using the canvas, two concepts which
are important to understand is stroke and fill. Stroke is like a pencil line
or an outline and fill, just like it sounds, fills
a shape with a solid color. To draw a rectangle, we have strokeRect and also
the fillRect method. Let's take a look at
strokeRect to begin with. Although drawn features are
available on the context. To draw the outline
of a rectangle, we'll use strokeRect, and then we pass in four values. The first two just like
we've seen in the slides, is the x and the y location for the top
left corner of our rectangle. If we want this to
begin at the top left, we set this as 0,0. Then we have the width and the height of the rectangle
which you want to draw. This could either be a
rectangle or also used to draw a square if we're passing equal values. Let's
take a look at this. We'll say this and
jump into the browser, give this a refresh and
we see the outline of our 200 pixel by
200 pixel square. As well as also passing in hard-coded values
just like this. We can also access our
canvas variable from above. Pass this in and access
the width property. We've already set these both
inside of our elements. We've got 250 pixels for
both of these values. We can insert these
as variables. Try this out, refresh and
because this is 250 pixels, this is now a little bit
bigger than we originally had. Using these variables is
really useful and we can also provide calculations
based on their size. For example, if we
only want this to be half the width of the canvas, we could divide this by 2. If you want this to be a solid rectangle or a solid square, we can change stroke
to be fillRect. This will fill this with
the default black color. The standard black generally
doesn't look great, and we can also change
this color to this. Let's duplicate our rectangles and we'll make the first
one to be a stroke, and the second one
we'll just make this a little bit different. Let's go for 100 by 100. Places, 20 pixels in from
the top left of the canvas. Now we have a stroke
and a filled rectangle. We can provide different
colors for each one of these, starting with the stroke. Again, access the context. Since we're using a stroke
and pass in the strokeStyle. Set this equal to a CSS-like
value, such as green. I'm going to see to the green
color has taken effect. Then just above our
filled rectangle. We can also access the
context and provide the property called fillStyle to affect this second square. This equal to any other color. Save this and refresh. This is how we can set
different colors for each one of our shapes. This comes API also has an arc method to help with
drawing arcs and circles. As always, we access this
on the context ctx.arc. Unlike the squares
which we've just drawn all the rectangles where the starting location is
based on the top left, with a circle, this is
based off the center. We can access the canvas
width for the x-axis. Divide this by 2 to
places in the middle of the canvas and also the
same for the height. This is the starting
location for our circle, but we still need to provide
some more information. The next one is the
radius of our circle. If you're unfamiliar
with radiuses, this is basically half
the width of the circle. The value from the center
point to the outside. Let's go for 100. Then the last two values at
the start and the end angle. These values are in radians
rather than degrees. To begin, we'll add
a starting angle of zero and then the end angle, a value of two radians. Let's give this a save and see what happens inside the browser. If we reload, we don't see
any drawings on the canvas. This is because we need to
say if we want this to be a stroke line or a solid
fill like we did above. Just afterwards
access the context. Hold the fill method, refresh, leaving us with
a nice smile-like curve. Or we can just do the line
with a stroke with ctx.stroke. You can also see that now
we're doing the stroke line. This will also inherit the
stroke style of green. To go full circle in radians, we need to multiply
this value of 2 by math.pie which is the degree
equivalent of 360 degrees. There's our green
circle and as you can see now we've
got a full circle. This is also placed
in the center of the canvas because of
our x and y locations. Just before we
actually draw this to the canvas using stroke or fill, we can also set some
additional properties such as the line width
and also the color too. Let's start with the
line width property, which we'll set to a
pixel value of five. Then also available on the
context is the strokeStyle. This is the same value we used above to set this to be green. But now we'll override this
to be a different color. Before, I mentioned this is a CSS-like property so as well as just using the words
like green or hot pink, we can also pass
in the things like the x-value and set this
to be a different color. I'm going to go for f5cf89. Try this out, leaving us with a 5-pixel line in
a different color. Alongside these methods
to draw preset shapes, we can also draw
freehand lines too like a pencil tool
if you've used a drawing application
in the past. To demonstrate this, we can replicate this
green outer rectangle or square and draw
this free hand. We can comment out
the first rectangle, which is these two lines here. Then draw this ourselves
using co-ordinates. These are all available
on the context. The first method is
going to be moveTo. MoveTo is basically like
picking up our pencil, moving this to a certain part of the paper which we
want to begin from. This will move our co-ordinates without actually drawing a line. Just like our original square, this will begin in the
top left of the canvas. To actually draw, we use
the method called lineTo, which will draw a
pencil line from our starting location to location which we
set inside of here. Just like above, we can place in hard coded values
just like this or we can access the variables such as canvas.width
and canvas.height. Make this a full width of
the canvas to go right across, access the canvas.width. Since we don't want to
go down on the x-axis, we can leave this at the
value of zero. Let's refresh. We don't see the
line on the screen just yet because we need to call ctx.stroke to draw
this onto the canvas. This is our first
part of our square. The next part is to
go downwards and draw this vertical line from
our current location, access ctx, the lineTo. We still want to remain over on the right-hand
side which we can access with the width of 250 or, again, using the
canvas.widthproperty. Pass this in, canvas.width. Then to go down to the
bottom of the canvas, we can access this with
canvas.height. Duplicate this. We'll stay at the same
height value of 250. But now we need to move
over to the left-hand side, which is the x-value of zero. Save, refresh. Just
one more to go. But now we need to move up
to the top left corner, which is the position of 0,0. Good. This is r squared
now on the canvas, we can also add a method
at the very beginning, hold begin path, also
available on the context. We'll place this at
the very start just before we begin to
draw our shape. This is useful
because it clears out any existing paths from above, so we don't get any conflict. It also means the starting
position is remembered. We can use closed path to return back to this location
at any point. To see this, we can remove
our last line, refresh. Rather than draw this manually, we can now return back to our starting location
with ctx.closepath. It should work exactly
the same as before, returning back to the
original location. As well as creating squares,
rectangles, circles, and also freehand drawing, we can also write text
on the canvas too. First, we can set the font
which we want to use, ctx.font, and set this
equal to a string. The string can take
in the size of the fonts and also
the font family too. The method to draw our
text is ctx.fillText. As you've probably
guessed, there is also a corresponding
strokeText method too. The first value is a
string of our text, I just say Canvas API. Then a x and y location, which is the baseline
of our text, so 20 and 200, Save and Refresh. There's our text and it's also pink because it will inherit the previous fillStyle property, which will have from above. Canvas can also be used for
lots of other things too, and also other
methods which we can explore and we can also even add images to
the canvas which we can edit by accessing
the pixels. In the upcoming videos, we're going to use a canvas
to create the pizzas and also display the toppings
which we added in the kitchen.
41. Setting Up The Ingredients: In the top of the
script section, we have an ingredients
array with some ingredients which we
need to make out pizzas. This strings, all
match the ingredients which are needed in the
required steps from above. So if we go to the pizzas array, we see the required steps array. This contains the strings
which match our ingredients. If you change any of these pizzas to use
different ingredients, make sure the
ingredients array is also updated to match too. This will be really
important soon. What we'll do in this
video is to loop through all of
these ingredients, and then display them as
buttons in the kitchen area. Once a pizza has been selected, the chef can then
follow this method, and make the pizza
using these buttons. Also making use of the canvas, each button will draw the
ingredients onto the pizza. Let's now scroll down to
the bottom of our project, and clean up the examples
from the previous video. So we don't need anything
such as the text. We don't need the
freehand section. We don't need the circle. Don't need any of these
squares or rectangles. But we do need to keep
access to our canvas, and also the 2D context. After this create a new function which is called listIngredients, which will loop over all the ingredients array from above, and display this
inside the browser. To access our ingredients array. Since this is an array, we
can use the forEach method, which runs a function for
each one of these values. This function is also
going to take in an ingredient which
is each array value. Then we need to create
a button element for each one of these ingredients. So we could create one
manually or remember, we also have access to our helper function
called buildElement. This takes in two things. The first one is the type of elements we want to
create, a button. The second one is the
text content to display. This is the ingredients
from our array, store this inside of a constant
called ingredientElement. Then for our styling, we can also add a class called ingredient to access
our constants, and add the class
name of ingredient. The final stage
is to add this to the DOM,
documents.querySelector. The query selector
which we need. Let's take a look
at the index page. We need this div with
the id of ingredients. Inside here we'll place in all of the contents
of our array. Pass this in as a string. We use appendChild passing
in our constant from above. So we could call this new
function immediately. But it probably
would make sense to add this to the
gameStart function, take a look for
the start of game. Inside of here, we'll
call it from the bottom. Save, and then
let's try this out. Refresh. Click the Start button, and all of our
ingredients are now placed in the kitchen
at the bottom. For the next step, we
need to be able to click on any of these ingredients, and then do something. This something is
going to be a function to keep track of the steps which we've taken down
to the very bottom. Create a new function
called stepComplete. Then a second function
which is going to draw the ingredients onto the canvas. Function, make pizza. This stepComplete function needs to be called when the user clicks on any one of our
ingredients from just above. So for this, just before
we add this to the DOM, we can add an event
listener by accessing our ingredientEelements.add
event listener. This now for a click, which will trigger a function
called stepComplete. Since this is a click event, it's also going to take in the event information which
we can store in the variable. This stepComplete e
function can also now be used to disable the
button once it's clicked on, and also keep track of
these steps we've taken. So step 1 is going to disable the button when
the user clicks on it. So we can't be clicked on twice. I'm going do this by accessing our elements with e.target. We can set an attribute. Attribute is disabled
with a value of true. Then step 2, we need to access the actual text
content of our button. So we'll know the
ingredient name. For example, we need to
grab the roll dough text, the pizza sauce, or
the cheese, and so on. Store this inside of a
constant called step name, access our elements
with e.target. Then the inner text content. Before we go any further, let's check if this is working. Place in a console log.
We have step name. Onto the browser, and into
the console. Start the game. Click on any one of our buttons, such as cheese, pizza sauce. These buttons are also
now disabled too. Keep track of the steps
which we've taken, all the buttons
we've clicked on. We need to store this inside of a variable containing an array. So jump up to our variables. At the bottom let
completed steps to be equal to an empty array.
Down to the bottom. We can now remove
the console log, access this variable
of completed steps, access the push method, and then we can push a
new value to this array, which is our step name. The last thing is to call
our make pizza function to draw a new ingredient
to the canvas. Of course, it doesn't
do anything just yet, but we can place this
inside of the function. So make pizza. Pass in pizza
ingredient of step name. Now since we pass
in an ingredients, we can set this inside
of our function, and test if this is working
with a console log. Let's just remove any other
console logs we have. So we've got the order wrapper, got the oven, text of order. Then refresh, start up again, and click on any one of these
ingredients. There we go. There's our new ingredients
from make pizza console log. This now leaves us
ready to actually draw a certain ingredients
to the canvas. For example, if the make pizza function
receives pizza sauce, it will draw some red
sauce on our pizza. This is what we'll
move on to next.
42. Drawing Circular Ingredients: Down at the bottom
of our script, we currently have this
makePizza function and this is being called each time I add a new ingredients. It's also been past
this ingredients. What I'd like to do
now is to create a switch statement to handle which ingredient has
been clicked on. We can then draw this ingredient onto our pizza by the canvas. Since we have different
ingredient options, as we just mentioned, we'll replace this with a switch statement to handle
each one of our cases. Passing the ingredient
which is being clicked on, and then we'll create
a case for each one. The first case,
this is going to be a string and if we
scroll up near the top, we have our ingredients
right here and each one of the cases is going to match
each one of these strings. Back down, the first one in
capitals was for ROLL DOUGH. We'll handle what to
do in just a moment, but for now we'll just break
out of this and we'll just copy each one of
our ingredients. The second case, this
one was PIZZA SAUCE. The third one for CHEESE,
and then PEPPERONI. Next one was for
HAM and PINEAPPLE. I'm just going to leave this as the set of ingredients for now and you can add all
the rest if you prefer, but I'm going to move
on because this will be really long otherwise. Now inside of each
one of these cases, we can set up some
canvas drawing functions to represent the
particular ingredients. For the dough, for
the pizza sauce and also for the cheese, these can be reasonably simple, we just need to
draw some circles. We'll start with the roll dough. We know from previous videos
that to draw a circle on the canvas we use ctx.arc, then we provide the
starting location which is going to
be in the center. canvas.width divided by two and also the same
for the height. This is the starting
x and y position. The next one is going
to be the radius. Let's go for 100 pixels. Just like earlier, to create
a full circle in radians, the starting position
is zero and then the end position is two multiplied by the
value of Math.Pi. Remember that from earlier
videos when we used ctx.arc, it didn't actually initially draw anything onto the canvas. What we need to do
is to either use ctx.stroke or ctx.fill
to draw this, but in fact, we're going to
actually use both of these. The reason being is we'll
use the fill method to draw the full pizza base
onto the canvas. We'll also do an
additional stroke method, which is also going to add a 15 pixel outer circle
onto this dough. The reason being,
we'll set this to be a slightly different
color to give us the effect of
the outer crust. First of all, what we'll
do is we'll concentrate on the crust and we'll do
this with ctx.lineWidth, set this to be a
value of 15 pixels, then we'll set the color with strokeStyle passing in a
string which is going to be a hex value of f5cf89 and to finally draw this,
we use ctx.stroke. This should now give us an
outline based off our arc. Let's try this out, refresh, start up again, select any one of our pizzas, the roll dough, and we now see the outer circle of our pizza. As mentioned, this is the crust, so we also need to
add the fill method to also fill the inner
part of the circle, but a slightly lighter color
just after the stroke. Put the color for our fill
method and this one is going to be f5d69d, then fill the circle
with this color. Let's try this out. Jump in
again and start over again, select any one of our
pizzas. Roll dough. We shouldn't have this black
color so let's take a look. This fillStyle actually just
needs the hash just before. Start. Select one of our pizzas, roll dough, and it's
now looks a lot better. Now, down to our pizza sauce, which is also going
to be a circle but slightly smaller than our dough. We don't have any
conflicts from above. We'll begin this section
with ctx.beginPath. This also needs a circle, so we can copy the ctx.arc
method from above, which also is going to
need to be in the center of the canvas. We do need
to change the color. This is going to be filled,
so we'll use fillStyle, and this one will be ed4434, finally, call ctx.fill. Next, we'll also move
on to the cheese, which is still a
circular join function. We'll copy all of the
pizza sauce section, paste this into our cheese area, make the circle a little
bit smaller so we can still see the sauce
around the edge, but we do also need
to change the color. This one is f7bc4d. Let's save this and try this out before we go any further. Again, we can select any pizza, pineapple, roll dough. This is fine as we've seen. Next we'll try out
the pizza sauce and this is all fine and this covers the same fill section
as the above dough area, but we can still see the
stroke outline from above. Next, we'll have the
cheese, which is a slightly smaller circle, so it doesn't fully cover
up all of the tomato base. Good. We'll leave
this one here for now and in the upcoming video, we'll take a look at
the next ingredients, which is going to be
a little bit more complex than these circles. Rather than creating circles
like we're just doing, we're going to involve multiple pieces at
different locations. We'll see you in the next one, where we'll take a look
at how to do this.
43. Drawing Multiple Pieces: Going off my version of
this switch statement, I still have the pepperoni, the ham, and also the
pineapple pieces to create. Since these are all
separate pieces, we're going to be drawn
lots of different shapes onto our pizza-shaped Canvas. To begin, in the pepperoni, just underneath the case, since we're using multiple
pieces for these ingredients, we need to create an
array of locations. Const, pepperoni positions, which is going to be an array. In fact, this is going
to be an array of arrays because this is going to be an array of
multiple values. Also, each one of these
values needs to have an x and a y location for
each one of these pieces. The first one, and of
course these all need to be kept inside of the
dimensions of the pizza. But these are all
random numbers. We can add these in and play around with them if you
want to afterwards, or you can copy along with mine. The third one is going
to be 147 and 57. Remember that each one of these sub-arrays is going to
be new piece of pepperoni. The fourth one, you can add as many or as few these
as you would like to, 116, 134, 125, 190, 162, 165, 190, 85, 192, 142, 150, 115, 76, 95. I want to start a
couple more pieces. Let's go for 80, 190, and the final one, 61 and 135. Make sure this is just before the break clause and before
we do anything else, let's think about exactly
what we need to do here. A piece of pepperoni is
just a small circle. We know how to draw
this using the arc. But the difference will
need to repeat this arc for each one of these locations. This is where a
loop can come in. What we'll do is we'll grab our constants of
pepperoni positions. Then we'll call a for
each loop on this array. Pass in a function which takes in the
variable name of piece. Piece is going to be each one of these individual pieces of
pepperoni which we'll draw. To do this, we'll begin with ctx.beginPath without any existing paths
which may be drawn. ctx.arc, draw each one of the circles for
the pieces of pepperoni. Then remember, this
takes in the x and the y location for the
center of the circle. Since what needs to be based
on the above locations, what we're going to do
is to access our piece. The first index
position of zero, and for the y location, it'll be peace and
then the index number of one. Pass this in. Piece, access the
index number of zero, the y location is this peace
and the index number of one, size of 10. Let's create a circle
it as zero and then math.Pi multiplied by 2. Let's give it a color
with ctx.fillStyle. Just before we draw this, which is equal to a string, I want to go for the
hex value of BD 3611. Then finally, we'll
draw this with ctx.fill give this our
solid background color. Let's give this a try.
Jump into the browser. We need to start our game and select any one of our orders. Also, pizza, we'll
roll the dough, add the source, the cheese. We know this is working,
next the pepperoni. There's all of our
circles which are all based of our array, which we created inside of here. Good. Now down to
the ham and you can also give us a go if
you want to yourself, you feel confident doing so. This is going to be based off a similar approach to above. First, we need to create
our ham positions, which is an array which
also contains subarrays, contain now x and y locations. Let's first create these
and store them inside of a constant called
hamPositions, our main array. Then create our
random locations. The first one, the
second one of 108, 74, number 3, 147, 47, 130, 124. Again, you can make
these completely random and change these if you want to, 125, 160. Make sure that these
all stay within the pizza Canvas area, 159, 145, 197, 82, 202, 132, 158, and 90. We're almost there
now let's keep going with the final two, we'll do 90, 140 and the last
one of 105 and 135. Just like above,
we'll loop through these with a forEach loop. We'll select our ham positions called a forEach
loop on the array, which takes in a
function each piece. But this time,
rather than creating circles using the arc method, I'm going to use the
fillRect method to draw these as a rectangle. First, we'll set a color
with ctx.fillStyle, and set this equal to a
string, which is f58c8c. Next, ctx.fillRect. Draw a solid rectangle
on the Canvas. Just like above, this
is going to take in the x and y positions, which we can access
just like this with our array positions x. This is the piece and the
index number of zero, which corresponds to each one of these first array values. Then the second array value of piece what's in the
index number of one. Remember with rectangles, this is the x and y location for the top left corner of the
rectangle or the square, we'll also need to set the size, which we want to
set to eight pixels wide, 32 pixels tall. Let's try this out before
we go any further. This is for the ham. Click on this and
we have lots of pink ham pieces now on pizza. You can play around with
the positions if you want to make it look
a little bit better. But I'm going to move on to the next one, which
is pineapple. To save little time, a lot of this is going to be similar to our ham position so let's copy all of this section,
including the for-loop. Paste this just below
our pineapple case. Of course, it's going to be
the pineapple positions. Change it for the loop. Make play around
with these values afterwards if you want to. For the fillStyle, we'll change the
color of these to be yellow color of ebe534, make them a little bit
different in terms of size, 12 and 18. Create the cells inside
the browser start. Our pizza. The pineapple
also works too. Just like mentioned,
you may need to change its position so we don't
have any overlaps. One last thing before we wrap up this video is we're also
going to make sure that the pieces of
pineapple and the ham a placed on the pizza
at different angles. Currently, it looks
a bit boring because everything is facing
the same direction. Let's mix up this angle a little for each one
of these pieces. We can do this with rotate. That starts in the ham section. Just before we draw
this with fillRect. We can access rotate, which is also available
on the context. This rotation value is
in radians, not degrees. A way to convert this to a
degree is just like this. We can pass in our
initial value. If we wanted this to be rotated by a small value such
as five degrees, we can convert this to radians
by multiplying this by math.PI divided by 180. This makes it a lot simpler
to work with degrees if it's a lot easier to wrap around
this inside your head. But we don't want to keep this
hard-coded value of five. The whole point of adding
this rotate for each one of these pieces is to make it a slightly different
value for each one. We can do adding in Math.random, which will place in a
slightly different value for each one of our positions and to make
it a slightly larger angle, because remember Math.random
is only between 0 and 1. We can multiply this by 2. That's also copy
this rotate method, and we can reuse this
inside of the pineapple. Just before we use fillRect, let's try this out, refresh
and starts up again. The pizza sauce, the cheese, pepperoni, next to ham. If you take a close look, these pieces are slightly
out of alignment. Also with the pineapple too, but with a pineapple,
as you can see, we have a small issue. This is when we use
multiple ingredients with rotation values. This happens because of
the rotation at transform. When we rotate, the Canvas will rotate from the canvas origin, which is 0,0, all the top-left. If we add multiple rotations, each one adds on to the previous
one, which we have set. We set one for the ham, and then we add
to this by adding the rotation for the pineapple. We can fix this by
resetting the origin for each new ingredients using something called set transform. For this, inside
of our function, I'll make pizza function. Just before the
switch statements. We want to call this
ctx.set transform, which is going to
take in six values. The six values will be 1, 0, 0, 1, 0, 0. These six different values
can be a little confusing, especially as they are not
particularly grouped together. We can see the first value of one and the fourth
value is also one. Both of these relate
to the scaling, which can make an item
larger or smaller. The first one is the
horizontal scaling, and the fourth one is
a vertical scaling. We don't want any of
these values to be increased or decreased
so we keep these at one, which is the original size. The second of third value
both relate to the skewing. We don't want to skew any
one of our pieces either. We're keeping these as zero, which means nothing to be set. The things that we are interested
in is these final two. These final two we
set in the horizontal and the vertical translation
to be back to zero. Each time we select
a new ingredients, the rotation transform, which will look before
the reset back to zero, meaning we don't get a buildup
of these rotation values. Let's try this with
the transform now in place. Select an order. Now what we're looking
for is both the ham and the pineapple pieces to be back in the original locations. This is all working fine now. As mentioned, you can create
more of these cases for different ingredients if you prefer or if you'd like
some extra practice. The next, when you are ready, I will see you in the upcoming
video where we'll clear out this Canvas once the
pizza has been completed.
44. Clearing The Canvas: When we're finished making the pizza, adding the toppings, and then finally
add it to the oven, we need to clear
the existing Canvas ready to make the next one. As you may have
guessed, we create a function which is
going to cover this, and we'll do this at the
bottom of our script. Create a new function
inside of here to cover this called clearCanvas. The first task I'm going to do it inside this function if we go into the Start here and we'll
select one of our orders. Remember, when we'll
make each one of these pizzas and we click
on the ingredients, it then renders the
button disabled. What we'll do is we'll select all of these buttons to begin. We'll loop over
these buttons and then we'll remove this
disabled attribute. The first one is to setup
a constant called steps. This is going to
loop over all of our buttons with the class
name of ingredients. Document.getElementsByClassName. The class name attached to each one of these
buttons is ingredients. A little bit more space
for this and if you remember from the
early videos when we use getElementsByClassName, rather than returning
back in array, this will return back
at HTML collection. We have HTML collections. We can't use any of the array
methods such as for each. What we're going
to do is to grab our steps and then we'll
convert this to an array, which will mean we
can use our forEach to loop over each one of these. We'll do this with Array.from. This will create a new
array from our steps. Place this in. Now
we have an array, we can use array methods
just like forEach, pass in our function. We'll have access to each
one of the elements. From these, elements, they can remove attribute passing
in the string of disabled. This now it should loop over all of our ingredients and remove the disabled
attribute so that are available for selection
on the next pizza. Next, as we looked at
in the previous video, we sometimes have an
issue where we have any rotation values for
the ham and pineapple. What we're going to
do is to grab or set transform at the top of the switch statement,
which is just here. The current values inside
of here will reset our transform if any existing
rotations are taking place. The final step is to clear anything which is
currently on the Canvas and we've used this in the
past with ctx.clearRect. We begin at the top left of
our Canvas, which is zero, zero and to clear the
full size of the Canvas, we can access our variables with canvas.width and
also canvas.height. That's it. That's our
function now complete. The last thing to do is to call this function
from addToOven. We'll select this and take a look for the function
called addToOven. Just under displayOvenItems, paste this into
our if statements. While here there's also one
quick step we can introduce, which is to grab our variable
which is completedSteps. Reset this to be an empty array. Let's give this a try.
Refresh the browser and it starts up again.
Select an order. Currently on ham and pineapple, so we roll the dough,
add the sauce, cheese, ham, and pineapple. Add this to the oven and now our Canvas is
being cleared and all of the ingredients
is still available for selection on our next pizza. I'll try one more,
the pepperoni, add the ingredients,
add this to the oven, and this all works as expected. This is now it for our work on the Canvas and you can
play around with things, you can extend things, and you can even add the extra ingredients if you want to. Now let's now move on
to the next section where we'll finish
off this project.
45. Wasting Pizzas: This new section is going to involve finishing
off our project. One of the things we
need to take care of is to check the chef has completed all the
required steps before adding the pizza to
the oven. So i.e. if the pizza is wrong,
it will be wasted. That is what we're
going to cover now. Later on, we will also display
some stats to the user, so we can keep track of these wasted pizzas
inside a variable. Let's jump up to the
top of our index.js. Scroll down to our variables. Using the let keyword, create a new variable
called wastedPizzas, and we'll keep track of how many of these we've got wrong. Then it go back to the
bottom of our script, and we'll create a new
function to handle this. Set this up, call
the wastedPizza. Before we forget, we'll grab our wastedPizzas variable from the top and we'll increase this and use this with
the stats later on. Next, we'll clear any steps
ready for the next pizza. CompletedSteps, reset this
to be an empty array. Then finally, we'll
call clearCanvas. This was the function which was set up in the previous video, which is going to
reset the canvas and also remove the
disabled attribute. Over to the index page. Inside the main kitchen area, we also have a button which
we can link this up to. We'll add an EventListener to this element which has gone
to call our new function. Below our function
documents.querySelector. This button. If we just take a look,
this had the ID of waste. Add an EventListener, where we'll listen out for
the click event, which is going to call
our above function. Let's try this out, save and go back to our game
and start over. Select an order.
We'll do this wrong, we'll just click on these
in the wrong order. Click waste. Now a
canvas is now being reset and we see we don't have this item inside of the oven. This now sets us up for
the upcoming video, where we'll check
if the chef has taken the correct steps
to make this pizza. If not, this function
will again be called, but this time automatically.
46. Checking Steps: This wasted pizza
function which we just created will also be
useful once again, because we're now going to
check that the chef has taken the correct
steps, or i.e., added the correct
ingredients onto the pizza before adding
this to the oven. If they got this wrong, it'll be then again, a wasted pizza. Just to say this, if
we go into our game, select any one of our pizzas. Currently before we add
any of the toppings, we can click on the oven and add this without the
correct ingredients. We can also keep going and
add in more of these too. To check the ingredients, we can create a new function. I'm going to do this just
after we add this to the oven. Take a look at our function
called add to oven, which is a little
bit further up. It's been awhile
since we've seen this one. This is add to oven. Just after this, we'll
create our new function, which is the steps completed. Inside here, we also need
to take in the pizza name, which we are currently
comparing against. We can check if we have
the steps correct. So passing the pizza name, which we'll add in when
we call this function. This function will
return back either a true or false
Boolean depending on if these steps
are correct or not. It's true or false value will determine if we
can add the pizza to the oven and we'll call this from our add
to oven function. Jump inside of the if
statement where we check if we have a pizza name, create a constant to store back the returned value from our function which we'll
look at in just a minute. We'll call this can add to oven. Remember, this will be either
a true or false value, and this will be
equal to our function called steps completed. Remember this also needs to
take in at the pizza name, which we have stored
above in this variable. Just below this,
we then create in our pizza object to
go inside the oven. Now we're pushing this
to the oven array. But just before we do this, we only want to make
sure this happens if the can add to oven
variable is true. What we'll do is
we'll cut out all of this information from our object all the way down to
completed steps. Pass in an if statement to check if can add the oven
is equal to true. If it is, paste this back in. Our pizza is now able to
be added to the oven. Let's do a quick test
and check this works. We'll go into our new
function, and for now, we'll simply return
the value if true. We should be able to add any of the current
pizzas to the oven. Let's try this. Click the
oven, and it's works. But now if we stop the
game and return false, hopefully should now block all pizzas from being
pushed to the oven. I'll select one,
and this now blocks all pizza from going
into this section. This means we can now
add some real conditions to check against inside of here. To do this, we need to grab the steps from all pizzas array. To begin, we need to actually
select our pizza objects. Here we'd been passed
at the pizza name and if we take a look for our
pizzas array up at the top. We're going to use
this name to find our particular pizza
and then return back the full object so we can access these required steps. Let's go down and do this now. We can move the
return statements, access our full pizzas array, and then use the JavaScript find method to search via
this pizza name. To do this, we'll
run a function for each one of the items inside of our array and store this inside of a
variable called pizza. This find method is going
to return back a new value. We want to check if the
pizza which we are currently accessing has a name property which is equal to
our pizza name, and then it we'll store this
returned value inside of a constant called pizza objects. The next thing to do is to grab the steps from this object, and again store this inside
of a variable or constant. This one is called
the steps required, and we'll set this equal
to our above pizza object. Filter this down to only
save the required steps. Now if we go back up to our pizza object at the
very top of the script, this is the required
steps property which we just accessed. This is an array of
all of the values. We now have all of these values which it takes to build a pizza, and also if you
remember from earlier, scroll down to our variables. We also have a variable
called completed steps, which is added to as a chef clicks on each one
of the ingredients. We now need to compare these
two arrays and check that all of the included steps
have been taken care of. Also, if we think about this for a pizza to be ready
to go into the oven, not only did the completed
steps need to be correct, but they also need to be
in the same order too. For example, if I have
the dough, the cheese, and the sauce in that
particular order, it technically would
match a margarita. But we don't want the
source to be on top of the cheese or the dough
to be added last. We can check this
using a JavaScript array method called every. Back down to our function, what I'm going to do is to type out this function
and then we can talk about what is going
on just afterwards. We can access our
steps required, which is the array of all of
the steps required to create a pizza and then call this method which we just
mentioned, which was every. This will run a
function for each one of our array values. Passing in as arguments, the element, the index, and then return back a value, which is if the
element is equal to the completed steps
as in the index. Also finally, we'll just
store this returned value inside of a constant
called check steps. Just to explain what
was going on here, we're going to loop through
all of the required steps, and then we'll store this
inside of the element value. For example, on the very
first required step, which is generally role dough, this would be the
value of this element. The index number would
of course be zero. Then below we check again
for the very first one if this roll dough value is equal to the first value
in completed steps. If this pizza has been created, both of these value should
be equal to roll dough. On the second loop,
this would generally be equal to sauce. Then we check if the source is the second value and so on. Every method will return
true if all of them are a match or false if a
single one or more fail. This is the main test
to check if the pizza is correct before
going into the oven. If we wanted to, we could
simply use this as our one and only check but
if we use this, the chef doesn't know exactly which ingredients has
caused the issue. He won't know if he used
too many ingredients, not enough ingredients
or the wrong ones. To help with this,
I'm going to set up multiple conditions so we can give different
error messages. This will pass in
some if statements to perform some checks. But the first one,
we'll check if too many ingredients
have been added. We want to know if the length of the completed steps is greater than the length
of the steps required. If completed steps, the length is greater
than the steps required. If it is for now
we'll just place in a console log and
we'll say too many. Copy this, paste this in below, and changes this to
the less than symbol. We can check if not enough
ingredients have been used. If this happens will say not enough inside the console log. The third one we'll check if both of these
values are equal, so just make sure this
is spelled correctly. We'll check if the
completed steps is equal to the required steps. We'll also check if the
ingredients are incorrect. We can do this with
our checkSteps variable and checking if the value is equal to false
by adding an exclamation. Basically we've taken
the correct amount of steps but the ingredients
are incorrect, but this will log to the console the value of wrong ingredients. Let's check this out
over in the console. Open up the developer tools.
We'll start up again. Select one of our
pizzas and let's go for pepperoni which
is a simple one. We need to roll the
dough with pizza sauce. Hopefully this one will
add too many ingredients, will click on too many options. Try to add to the oven,
I see too many, refresh. Let's do this once
more, margarita. We'll only click on one
of these ingredients. At the oven this is not
enough ingredients, and then the third and final
one we'll check if we've included the wrong ingredients with the right amount of steps. Let's test this. We know that the pepperoni has
the roll dough, it has the sauce, the cheese, and pepperoni, so we need to take
four steps but make sure that these are incorrect. Pepperoni, I will add
some ham onto this. Make sure we are at four steps. Click on the Oven and we can see we've used the
wrong ingredients. Good. We have our messages and these also now need to
be shown to the user, and we can do this
if we go over to our index.html. Let's take a look. For our Message area we have this surrounding div and
our messages for the chef, and then this p with
the id of message. We could write these
messages directly into each one of our IF
statements and I'll put these on the screen but instead let's create a
standalone function to handle these since we will be repeating these multiple times. Let's add our function
called showErrorMessage. Pass in the message which
we want to display. if that's all we'll
select our Message area which we've just seen inside of the index page with
document.querySelector. Pass in the id of message, set the innerText content
equal the message variable. Now once the chef has made a mistake we want this
to disappear after a certain amount of time and we can do this
with a setTimeout. Pass this in which
takes in a function , separated by comma. The second value is going to be the time delay in milliseconds, so let's say two seconds. Open up the function body. All we'll do inside
of here is copy this Message area,
paste this in. Let's set this to
be an empty string. We can now call this
function called showErrorMessage and place
console logs just above. Paste this in, add in a string. Also you have used too many
ingredients. Copy this. The second one you have not
used enough ingredients, and the third one you've
used the wrong ingredients. Let's try this out. Again, we'll do the same
test as before. Select an order, so
we'll do too few. You've not used
enough ingredients , then we use too many. That's good. Then the
last one when we use the wrong ingredients,
so pepperoni. I'll add four steps; roll dough, pizza sauce, and peppers,
and there we go. The last thing to do
is to make sure that these pizzas only add to the oven if the ingredients
and the steps are correct. Currently as we looked
at earlier this controlled with
this IF statement, and this will only add to
the oven if this is true. We need to cut this
true or false value, return it back from
overblow function called stepsCompleted. Currently this function is
running and we're doing all of our checks but we're not yet returning a true or false value, so we need to add this
at the very bottom. We'll only want
this to return true also if the oven capacity
has not yet been reached. Earlier we set up
variables called oven and also ovenCapacity which
we can see just here. I have the oven which
is an empty array and also the ovenCapacity too. We set this to only
hold six pizzas. We also want to do a check
to make sure that we don't have too many pizzas in the oven before we
return the value of true. We'll cut this out. Add an IF statement
where we'll check if the oven.length is less than the oven capacity which
is currently six. If it is we'll then return true, if not we'll return false. We're almost there now but we'll also do need to take care of what happens if we've
used the wrong ingredients, because if we mess up a
pizza we also want to run our wastedPizza function
which we created previously. We can do this inside
of our IF statements. If we've used too
many ingredients this is a incorrect pizza, run our wastedPizza function, then return out of this. The next step is
middle one is to check if we've used not
enough ingredients. However, we don't want to run the wastedPizza
function because they can still have the chance to add the correct ingredients
afterwards. All we do is return out of this. For the last one where
I've used the wrong ingredients we again want to run the wastedPizza function,
return out of this. This should now leave
us ready to give this a try when we created the wastedPizza function
down at the bottom in the previous video.
Let's take a look for this. This also clears the Canvas too. We know if this
function is being run and we'll also know if the return value
of true has been triggered because we should
see a pizza in the oven, so we'll refresh
and try this out. First of all let's
select the pepperoni. I'll give this a go for
the correct ingredients : the dough, the sauce, the cheese, the pepperoni, and this the oven. This is good. The is [inaudible] now
being cleared so we'll try one more, the
ham and pineapple. For this one we'll
make it incorrect. We'll add the wrong
ingredient, so not too many. This all seems to
work in because this pizza is not
added to the oven yet the canvas at the bottom is still cleared and ready
to add a new pizza.
47. Completing Orders: When we've completed all the
pizzas on any given order, it would be a good
idea to complete the order so we can move
on to the next one. This is what we'll
focus on in this video. Just before this, I want
to fix a small issue. This issue, if we go
over to the browser, and Start our game. Click on any one
of these orders. The issue is we can click
on these ingredients before we've even selected
one of our pizzas. Not only does this leave our project feeling a
little bit strange, it also means we can't add
a pizza to the oven because we check these
selected ingredients against the clicked on pizza. To fix this, all we need
to do is to check if the current pizza section is set when we click
on any ingredients. To do this, take a look for
the stepComplete function, so I'm going to do a search. This is the one we
need, just here. First, before we check
if a step is complete, we can do an if statement
at the very top. This is going to check if
we have a pizza selected. Where we can do this,
if we go to our Index.html into
the kitchen area, just here, we can see we have this span with the
ID of current pizza. When we select one of
the pizzas or order, it's then placed
in this section. We can check if it has any contents inside
of this elements. We do this by selecting this
with document.querySelector. This is an ID, so
pass in the hash. The ID was current_pizza. We could check if there is any
inner text inside of here, or if it's equal to
an empty string. If this is empty, we have an issue, we've
not selected a pizza. What we're going
to do is to access our function called
showErrorMessage, pass it in a string of first, select a pizza you
would like to work on, and then, we return out of this, so no code below is run. Let's give it a test. We
can go over to the browser, Start our game, and
select one of our orders. Now before we select
one of our pizzas, we'll try to add in ingredients
such as roll dough, and we see our error
message just here. The step is no longer
marked as complete as we can see by the fact that this button is not disabled. Now onto completing our order. For this, for example,
this has three pizzas. Then we want to make
all three of these, and then complete our order. Now for this, before we let
the player complete it, it would be good to
first check that they've actually created enough
pizzas to complete the order. Step 1 will be to store somewhere the number of
pizzas on each order. There is many ways we
could approach this, but I'm going to store
it as an attribute when we create the order. Take a look for the function
called createListOfPizzas , which is this one just here. Inside of here where we built
our particular elements, we have a span with the
current pizza quantity. For example, this could be two margheritas or one pepperoni. Now, we can add the quantity of each order line to a variable. Let's create this variable just above, so we can
keep track of this. whole total pizzas, and we'll initially set
this to be a value of zero. We'll then reset this each
time we create our list of pizzas by setting this
to be a value of zero. Then, just below
this, we can add the current pizza quantity to the existing total
pizzas variable. Just to clarify what
we're doing here, if we have an
order, for example, this first one has three pizzas, this one has three,
and this one has four. What we're doing is
we're looping over all of the pizzas, and then adding in this
quantity to our variable. Then we have this
total for the order, which we can reference
against in the future. As mentioned before,
we are going to use this total pizzas quantity, and add this to our
order as an attribute. We can do this when
we create the order. We need to take a look
for the function called createSingleOrder,
which is just below. Just below our pizza list, what we'll do is we'll
grab the order wrapper, we'll set attribute, which
we've used in the past. The attribute, this is going
to be a custom attribute, it's not one of the built-in
ones which we already have. Generally, when adding our
own custom attributes, we have the data prefix. This is data, total pizzas, and then the value, which
is our above variable which we created
called total pizzas. What we're basically
doing here is each time we create a new order, we're adding a data attribute
called data-total-pizzas, with the total number
of pizzas on the order. If we save this,
we can jump into the developer tools and
check this is all working. Refresh, and then
Start our game. Now if we select
one of our orders, take a look for the
class of order wrapper, and you can see each one has this new attribute called
data-total-pizzas, and the quantity for each
one of these orders. This means we now
have the total number of orders we need to create. Earlier, if we take a
look at the variables, we also created a
variable called pizzas complete for order,
which we have here. This is increased each time we add a new pizza to the oven, giving us the two values which
will now need to compare. To do this, we also need to create a button
to complete the order, which the chef can trigger
inside the kitchen. This button will
trigger a function, and we can set this in the
select current order function. Take a look for this,
select current order. I'm going to create a
button inside of here, because this is where we set the current order which
we're working on. Down to the bottom, just
under this last line of code, but still within
the if statement, and below this, and we'll create a constant called
completeButton. From early, we have the
function called buildElement, which will create a
new HTML new for us. The first button is the type of element
we want to create, which is a button and the value which is
the text of complete. Then, we can access this button, add a class name
of complete_btn. This button needs to trigger
a function so we can add an event listener, so
completeButton.addEventlistener. Well, it's now for a click,
which is going to trigger a function which we'll create in just a moment called
completeOrder. Finally, we'll
have our orderDiv, which is this current
working on section. We grab this, and then
we can add appendChild. Pass it in our Complete button. We'll test this all by adding a simple alert inside
of this function. Create the function
called completeOrder. Now I alert with the
text of complete. Let's jump into the browser
and check this works. Refresh and click
on the "Start". Select one of our orders. There's our new
Complete button which will trigger an alert
when clicked on. Good. Now we know this works
and it's been triggered, we can remove this
alert, and then, we can do our comparison
between the total number of pizzas on the order and the number which we
have currently created. First, we'll grab
our current order, we stored this inside of a
constant called currentOrder. I'm going to grab this with
document.querySelector. Now, how do we go
about selecting the current order which
we're working on? Well, if you remember
from earlier, when we clicked on
the "Start" button, all of the orders
inside of the sidebar, if we select one of this
class of order_wrapper, and then, when we click on one, it's moved over to the
working on section. The working on section
also has an ID too. If we scroll up, this is
the section, just here. We can grab the order
wrapper which is contained inside of this
working on section. We can do this by passing
it in a CSS selector. The working on section, grabbing our section just here. Then we can grab
our section inside, which has this class
of order wrapper. We can do this just
like with CSS, use the greater than symbol, and with this being a class,
we use the.order_wrapper. Select this stored order. Remember inside of
this order we added the total number of
pizzas for this order, inside of a custom attribute. We can use get attribute, pass it in the attribute
name which we gave this, and this was data-total-pizzas. Store this inside of a constant called
totalPizzasOnOrder. Then, we can finally
do our check below inside of an if statement. We've got the total number of pizzas which we should have, and we also have the
variable stored from earlier called pizzas
complete for order. We can check if
pizzas completed for order is less than the value above called
total pizzas on order, if it is, we have an error, so we can pass it in
our function called showErrorMessage with the text of "You have not
made enough pizzas to complete this order." Then return out of this. Let's give this a try.
Save this and Refresh. What we need to do now
is to Start our game. For our first-order, this has three pizzas, so click on this. Now if we try to add
one to the other, and let's go for the
ham and pineapple. We'll create the very first
one. Add this to the oven. We only have one,
click on "Complete". Now we get the error
message inside of here. This now all seems
to be working. We can move on to actually removing the order
once it's complete. Just below the if statement, we want to grab
our current order which we have stored from above, and remove this
from the working on section with the remove method. This will actually remove
the order from this section, but this will actually only
remove the order which includes the title
and the pizzas, we still need to take care
of removing the button. The way way can grab
this button is remember this complete order function is being triggered from our
click handler on the button, so we can pass it in the actual, put in information
with the event. We can access this
with e.target. We stored this inside of a variable called
Complete button. Just like above, we can
access this variable, and call the method called remove to remove
this from the DOM. We're also going to add some
stats to this game later on, such as the number
of completed orders. We're going to
prepare for this by creating a variable
of at the top. With the rest of
them, we'll say let completed orders to be an
initial value of zero. Then go back down to our
complete order function where we can increment
this by the value of one, so completed orders, plus, plus. Now, we have this
order completed, we can also reset this
variable called pizzas completed for order. We can reset this to
be the value of zero, so we're ready for
the next order. Let's finally save this and go over to our game, and
we can try this out. Click on "Start". What we're
looking for is to create the correct amount of pizzas
to complete this order. First, the ham and pineapple will complete this,
so we have one, and we also have two
pepperonis to create on this. We'll try one more. We should now have three in the
oven and three which also associated with this order. Click on "Complete",
and this now removes our order from
the working on area.
48. Removing Orders: When testing out this game, so far you may have
noticed an issue, when we click on
the "Start" button, we get the orders coming in, and then when we click
on one of the orders to move it into the
working on section, when the orders are regenerated, the same order is still
placed in the list. We want to remove order 1
since we're working on this. This happens for each
one which we click on. To fix this, we could
remove this order from the orders array. To help identify which
order we've clicked on, we can add an order number
attribute to each one. In a similar way, where we go into the createSingleOrder
function, remember early we added this custom attribute
called data total pizzas. This helped us identify how many total pizzas
were on the order, so we could compare this to
the amount which we created. In a similar way to this,
we're going to also add a new custom attributes
with the order number, so we can identify which
one we've clicked on. If we take a look
above, we already have access to this order ID. It's going to be pretty
straight forward to do this. What we're going to
do is to duplicate this line to create
a new attribute. Copy and paste this one in. We'll create a new attribute. Again, with the
custom data prefix, this one is data order number. Passing this order ID. Then before we go
any further let's save this and try
this in the browser, by opening up the Developer
Tools. That's our game. Now if we select
one of our orders, this is your orderWrapper. We have the data
orderNumber of one, we have the orderNumber of 2, 3, 4, 5, and so on. With this now in place,
we can move over to our code and remove this
order from the orders array. A good place to do
this will be in our function called
selectCurrentOrder. Take a look for this.
This is a function which is run when we click
on each order. Using this constant
of orderWrapper, we already have access
to the click on order. Inside of the if
statement below, we can retrieve the order number attribute from the orderWrapper, and then get to work
with removing this. Down at the bottom,
we'll start by retrieving this order number, install this inside of a
constant called orderNumber. Set this equal to
our orderWrapper. Dot getAttribute. [inaudible] customer
data orderNumber. Now we can access
our orders array. We can use the JavaScript
array method called Filter, which can take in a function, and to keep this short, I'm just going to place in
an arrow function. In each order, in
the order variable. Then we want to check
if the particular order ID is not equal to our
orderNumber above. This is going to filter
out all of our orders and only let through the orders which have not been clicked on. This will give us
a new array with all of the values
except this one. What we can then do, is set our orders to
be equal to this, which will override
the original array. Let's go back over to
the browser and try this out. Start the game. Select our first order. Now let us regenerate. Now when our orders
are now regenerated, we no longer see order
1 in this sidebar.
49. Updating The UI & Stats: During creating this game we've kept track of some numbers, such as the completed pizzas, the wasted pizzas, and also
the completed orders too. These are now going
to be used to create a stat section at
the end of game. This area will appear in
place of the method section. So we'll hide this
section and replace it when it's no longer needed
at the end of the game. The stat section is actually already inside of
our index page. If we go into here right at the very top inside
of the main section, we have this area where
we're going to update all of these three areas
with our variables. This will also reflect
our start and end game functions and move over all of the areas which are
showing and hiding elements into some
custom functions. Currently, our stats
area is not being displayed because if we
go into our style sheet, we set this to be the
display type of none. Let's jump into our index
page and if we take a look, it'll start off game
function just here. This area is responsible
for showing and hiding our start and end buttons
once the game has started or ended and
also if we scroll down, it also adds this message
on the start of the game, which is then removed
after three seconds. Also the end of game
function will then reinstate the Start button and then
hide the End button. To re-factor these
two functions, just below startup game, we'll move these
into a new function called Startup game UI. Then scroll up. We'll cut out our two areas which
are showing and hiding the buttons into our UI. Then also our message area with the set timeout for this out. When this is in and
then we can call a startup game UI function just above in place of the
code, which will cut out. In a similar way just
below the end of game, create a new function. This one is end of game UI. Now what we need to
do for this one is to copy a cutout both of
these lines at the bottom, and these into the new function and then call this in place. Let's try this out, over to the browser and click
on the "Startup game". This is now removed and
we see the end game. Now if we click on "End", the start button
is now displayed. Nothing different, just
a re-factor to keep our code a little
bit more organized. Now we can also use these two new functions to
show and hide the stats area. We'll begin with
the startup game UI at the bottom
document.querySelector. Grab the original
method section which we can hide by accessing the style.display and set this equal to the
string of block, duplicate this,
and then we can do the opposite with our stats. We want the stats
area to be hidden, will change this to
be the value of none. We do this because
we only want these stats area to appear at the end of the game but
we'll do this now by reversing both of these values, instead of the end of game UI, the method area is going to
be hidden so we can reveal the stats area by setting this
to be the value of block, save this and try this out. Go to the game, we see the method area so
we can see exactly what we're doing with each pizza and the game and we
now see our stats. As we've seen before, this
stats area is just the HTML, so we need to take
care of adding our variable values to
each one of these spans. Jump into our index.js inside of the end of game UI
document.querySelector. The first one of our spans at the idea of completed orders, where we can set the inner text content to be equal to our
variable with the same name. Copy and paste this
two more times. The middle section
was completed pizzas. Again with the matching
variable name. The third one was wasted pizzas and wasted
pizzas variable. These free variable
values also needs to be reset at the
start of each game. The start of game UI it's
resets all of these values. The first one was the
completed orders zero, the completed pizzas zero, and also wasted pizzas 2. Let's try this. We'll need to create some new orders
start up the game. Ham and pineapple in the
oven, and pepperoni. Also waste some of
these pizzas so we have some stats, end the game. Don't see any
updates in the stats let's go over to our
code and check this out. Where do we update
this? We update this in the end of game UI. These three lines.
We can see here that the text editor has highlighted an issue with the three dots. We've got complete orders
which should be completed, check the variable.
Complete all this. This looks like the
right one, the result. Order number 1, which
is ham and pineapple, add this to oven and
then two pepperonis , complete the order. Next one we'll try
to waste an order, we'll add some
pepperoni and ham, add this to oven,
this is being wasted. Now, click on the "End", see one completed order,
which is correct. In terms of completed pizzas remember that this variable is only updated when the actual
oven has finished cooking. Once the timer, which is
set to be 20 seconds, only after this cooking
time has finished will this be added as
a completed pizza. We wasted one pizza
too, so this is good. Almost now done with this video, we do have a couple of
things to also fix. If we click on the
"Start" button, we'll see exactly what this is. We take a look at in
the kitchen area, we've got 11 at
different ingredients and also the timer is on 271 seconds but if
we click on "Start", the time we will continue
from there rather than restarting and the ingredients
are also doubled up. Let's fix these two issues
over in our start of game UI. First, we'll reset
our ingredients by selecting the HTML area
with query selector. Select the ingredients and reset this by setting
the innerHTML to be equal to an empty string. Also will take care of the countdown timer
by setting count down time variable to be equal
to the game length. Try this out. It's
also the game, ham and pineapple,
add this to the oven. Now if we end, we start the
timer that has been reset. We only see the single
set of ingredients. Finally, just to finish
off the stats area, I've added in a fade
animation to our CSS. If we go into our style sheet, let's take a look for fading. Creating a simple
animation to fade in the start area at
the end of the game. All we need to do
is to add this as a class name to our element. To look the end of game UI, duplicate any of these,
grab all starts. This time we'll set the class
name to be equal to fading. Good, let's give us one final
test inside the browser. Start the game, select an order, complete the first
one, pepperoni. Complete a couple of these pizzas to
finally onto our stats , end the game. Good. With that in place, I'm now going to call
this project done. But it's probably a few
things which can be improved and a few little issues which
may also need to be solved. But this project is about
learning JavaScript. I think it's given
us a lot to practice with and a good chance to show how all of these
little things which you've been learning
during this class, all work together to create
something much bigger.
50. Introduction To Scope: An important but
often confusing part of JavaScript is scope. Scope at most basic is a way to place
variables into groups. Each group allows a
certain amount of access. This means we can keep
some control over what has access
to our variables. This makes sense
because variables hold all of our pieces
of important data. Why would we want
them to be changed to accessed when they
don't need to be. Here we see a simple example. We declare a variable
called score. We then have a function
to update the score, log it to the console. All is completely fine here. But what about this
though, we decide to add a bonus of 10 points
to the score. This variable is
created inside of the function and then
log to the console. Again, no problems here. The console will
show a value of 11. The problems begin
when we try to access this bonus variable
outside of the function. This would cause an
error with a message along the lines of
bonus is not defined. We know it is defined because we see it's not accessible
because of scope. This goes back to these groups
variables are placed into. Since the bonus
variable is created inside of the function
unlike the score. Accessing this is
restricted to all function. But as we know with JavaScript,
that's never just it. There are also a few other
things we need to understand. For the first time
in a long time, we can jump into a new section, and this is number 9 and then
jump into the first lesson, which is introduction to scope, which has an index.html. To control the
level of access to a variable it depends
on a couple of factors. As we've just seen,
the location in our program where we declare the variable and also the type of variable
which we create too. We can create variables
with var, let, and const and these also
have an effect on scope too. This is the same example
which we just looked at, where we declare a score using the var keyword outside
of the function. Then inside of the function
we update this variable. We add a bonus and then
log this to the console. Let's jump into the console. We can confirm what
we've just seen. Open us up and we
expect the value of 11. This works because
the score variable is declared at the top level of our program and this
doesn't mean it needs to be physically placed at the
top, just like it is here. It just means it's not nested or declared inside of
something else, such as a function. This means it's in
the global scope and it's globally accessible. If we try to access
the bonus variable outside of the function
of where it was declared. We haven't declared
inside of here. Let's see what happens
down at the bottom. Placement console log, a variable of bonus, which then returns back an
error of bonus is not defined. This bonus variable
is restricted to only being accessed inside
of this function. Because this function
also has its own scope. This can be useful for
variables when no, we don't need to use elsewhere. It gives us the safety of
knowing other sections of our code cannot
modify these values. This function scope
also means we could create multiple
variables with the same names, but inside of different function and if we need it to do this, they would be
completely independent. We can also see
this access level inside of the
browser's console too. If we first remove
this console log refresh to clear this browser. Then if we type the word score
inside of here and enter, this returns back
the value of one. This is correct because we have the initial value
of one and then our function runs and increases this to be
the value of one. This all works because
this variable is global. But what we try to access
our bonus which is scoped to this function.
Let's try this out. Enter and we see the same error message which
we had from the console log. Along with these functions,
these global variables are also available in other
blocks statements too, such as for loops
and if statements. Let's try an if statement
just below our function call. If score, place in a console log with the value of score and this should
work as expected. This also applies to other
block statements too. Block statements are
basically sections wrapped in curly braces so we have the
function which we have here. I have the if statements, which also has
these curly braces, but also things like for
loops and while loops too. Recap. JavaScript
has the concept of scope to allow us to set how much access we have to a
variable like this example, score is declared in the
outermost section of our code, meaning it's in
the global scope. Global variables like this
can be accessed anywhere, including inside of the
code blocks wrapped in curly braces i.e functions
and if statements. The opposite is
true though when we declare a variable
inside of a function, this has function scope, and will not be available
outside of this block. Therefore, protecting
the variable from being updated or removed
from elsewhere.
51. Nesting Scope: So far we see that scope only
appears to work one way, from outside to inside. Variables declared at the top, such as this global
level variable, are available inside
of other functions or blocks such as this
function here. We can carry on by nesting other statements
inside of others, and the scope continues
in the same way. Access is passed from the
top and down to each level, yet new variables
declared inside of new blocks are still restricted. You're effectively not passed back up to any higher levels. When we seeing this in the previous video
where we try to access this bonus variable outside of this function and in
the global scope. Scope nesting is referred to as lexical scope in JavaScript
and many other languages. Let's take a look at what
this looks like in action. To begin, we have the
same setup as previously. First, let's change
this console log, include the text of
inside function. We do this so it's a little
bit more clear because we now going to be
extending this code, then it's still inside
of this function we can nest an additional block, such as an if statement. An if statement, where we
can check if bonus is true, and if it is, based on a new console log with the
text of inside if statements. Then we can append onto the
end the variable of bonus. Now we are inside a
nested block statement. We try to access
our bonus variable from the level above, so what do you
think will happen? Well, let's try this out. Make sure you were inside of the nesting scope section and this is open in the browser. Refresh. We can see as expected, the inside function code will run because there's
nothing to stop this. Then one level down inside
of the if statement, not only does the
console log run, but we also have access
to this bonus variable. This means that nested
statement blocks can access variables from an outer or
a higher level of scope. But what about the
other way around? But we can try this out
by adding a new variable inside of our most deeply
nested statements, so var bonus2, any number is fine
for this test and we can access this in
the higher level, which is this inside function, write add the value of bonus2, save and refresh, and now we can see we get
the value of undefined. Meaning we can clearly see this doesn't work the
other way around. We get undefined so JavaScript is aware that this
variable exists, but it's assigned
an undefined value. As a quick side
note, if bonus2 was declared with let or
the const keywords, it would throw an error rather than having
an undefined value. But more on this one soon. The nesting can also continue
as deep as you want to go. Let's try adding a
new block statement, such as a while loop. This again, also has its own curly braces to enclose the code and create
a new inner scope. First, let's remove the bonus2
which you no longer need. We can remove this from the log, and then inside of our most
deeply nested section, we can go one further. Creates a new variable called numbers which you're
going to loop over and set this to be an array of any values
inside of here. While loop we also need an initial value which
we can store in i. Set this to zero. We can keep running
this while loop, while i is less than the
length of our array, which we can access
with numbers, the land inside of our code
block or our scope section. Placing a new console
log with numbers i, which will print out each one of these numbers individually, so don't get stuck
in an infinite loop. We can increment the
value of i each time. Let's test this
out. Refresh. As it will fall console
logs from just here. Again, from this most
deeply nested section, we still accessing our
variable from a higher level, and we can go up even
further and test our bonus variable and try this out inside of our
deeply nested section. Paste this in, and
there's our value of 10. Also going off the
earlier example, you can probably tell
what's going to happen if we tried to do things
the other way round. If you create a new variable
inside of this while loop, so jump inside of here. But the log with the
new variable of bonus3, which is equal to any value, and then we can
try to access this at the top level
of our function, so add this inside of our
console log bonus3, refresh. There we see the
value of undefined. This is how we can use scope
in nested block sections. We've also seen some
examples about how scope is only accessible
from one way. Nested sections, such
as our while loop, in access any variable
from a high scope, but it doesn't work
the other way around.
52. Block & Function Scope: Jump into the next file which is the block and function scope. We don't get too confused about what we're now
going to look at. I've kept the same function
example as previously. With this example here, we have multiple
nested variables inside of this function
at different levels. Functions behave differently
to other statement blocks. Going forward each
time I mention blocks, I'm referring to the code
surrounded in curly braces, such as these if statements
and also our loops. With a function like this, we cannot access the variables nested inside from outside. If we try to place a
console log just after our function call
access our bonus3. You see bonus3 is not
defined and also the same for things at other levels such as
the regular bonus. We also have the
same issue but it doesn't matter if
the variables at the top level of a function are nested inside of other blocks. Even if we declare
these variables using let or const it will
also be the same. [inaudible] var from let the same results and
also the same for const. Changes back to var. This
is how a function behaves. It has its own scope
and we know exactly where we're at with the
variables declared inside. But it's not straightforward
with other blocks. If we change this from a
function to any of the block, such as an if statement,
let's see what happens. Move the function
keyword and we can say if the score is equal to 0, remember score is declared
in the global scope. We still have the console.log for the bonus at the bottom. Now if we try to refresh, we also need to remove
our function call. This bonus variable
is now available. But what about a
deeply nested variable such as bonus3? Let's try this. Refresh and this
one also works too. We can see here
the blocks behave differently to functions
regarding scope. This happens because
block statements don't have their own scope. Well, that's not strictly true because it's true for this
example which we see here. But as a JavaScript developer, we have multiple
choices depending on how we declare our variables. Variables declared
with the var keyword, which we've deliberately
used so far, will always behave like this. But using const or let
inside of these blocks will remove access from
the outside and sculpt these to
their current block. If we wanted to make sure
this bonus variable was only available inside of this block and not from outside
which we have here, we need to make sure we
declare this variable using the const or
the let keyword. Now let's try the bonus. We no longer have
access and also, the same for const. Just as a recap when we declared variables
inside of functions, we didn't have this choice
of whether we wanted to restrict the access to inside of the function block or make sure it's
accessible from outside. But when we use other blocks, such as an if
statement or a loop, these blocks give us the
choice of whether we want to use scope in or not.
53. Hoisting: Earlier in the course, I briefly mentioned a term
called hoisting, and promised to come back to it. This is what we're
going to cover now. We've already seen some
examples of hosting already. Let's take a few examples first
to see what is happening. Title files in this
hoisting section contains some examples and the
first one maybe familiar. This was an example
we used earlier in the function section, and up at the top,
we have two arrays. We have the bread and
also the brownies. Then we have a function setup
in two different styles. We have the function declaration and also the
function expression. Both of these do the same
thing that you're checking if an ingredient is present
in the selected array. Why am I showing you this
same example over again? Well, notice here
how we're calling the function in the
console log up at the top before the function has been created. We call it check. Since our code is read
from top to bottom, we get into the checkAllergies function call before
it's even been created, whether that's in
the declaration or the expression version. Let's go over to
the browser and see what happens with
this console log. Let's see if we get back a
returned value from this. This works with the
function declaration, but what about the
other way round? We comment out declaration
and reinstate the expression, refresh and this returns back
an error saying we cannot access our function
before initialization. Below this, we have Example 2, which is a really simplified
version of the issue. We're creating a
console log and login the value of the variable
before it's been created. We've seen inside the console
before that this works. We just remove this
with the error refresh. We can see this is
working perfectly fine. This works even though
we access our name variable before it's
been declared and it gets even more
weird if we try to reassign a name value
before it's declared. Over the top, just
above our console log. What we're going to
do is to reassign or update this variable with a new value before
it's even being created. This also works. We'll see the new updated value
in the console. Although these
examples are varied, they are all showing
a fundamental thing. We can often access variables before
they're even declared. This leads us to a
term called hoisting. Just quickly before
we jump into this, I want to show you
something else, which is a variance
of the last example. What about at the very
bottom if you had a console log passing
in the value of role? We then declare this
variable without initializing it with
a value and then we'll give this a value at the very end. Let's
test this out. Save and refresh. You can see this slightly
modified version will return back the
value of undefined. Looking at these two examples, it may seem pretty strange because we're looking
at how we can access variables before they
are declared and also since both examples
are pretty similar. Hoisting is often described as the process of JavaScript, taking our variables and
function declarations and moving them to the top of the program or to the top of their scope, meaning they are
declared and ready to access at anytime
during our code. This would explain a lot
of what we've just seen. None of the code
physically gets moved around or moved to the
top of our program. It's the process of
our variables and function declarations
being placed in memory at compile time. The human readable
code which you write, such as JavaScript, can
be read by a computer. It first needs to be
converted or compiled behind the scenes into a set of instructions which
computers can understand. It's during this compile phase, our code is first
read or passed. Our variables and
function declarations are then stored in memory. Just to clarify, during this compile phase when this
program is first passed, variables like this one are
first stored into memory, which is why we can
access them before we appear to have
even created them. There's no hidden magic
behind the scenes, no moving around our code
to the top of the program. It just simply our
code being read and a reference to all variables and functions stored in memory. This explains a few
things from our examples. First, if we take a look at this function
example at the top. The first example, the
function declaration. Function declarations
are hoisted. This is why we can call this function before
it's even declared, which we're seeing
with our console log. However, function
expressions are not hoisted, and this is why we've seen
an error in the console. For Example 2, the
very first one. Since this variable name
of Chris is hoisted, this is why we can
access it before it appears to have
even been created, although this was different
for the second version, which returns back undefined. Why do you think
there's a difference between these two variables? Well, an important part to understand is what
exactly is stored. To better understand this, let's take a quick step
back to variable basics. Earlier, we looked at some of these variable related keywords and a declaration is when
we declare a variable, which you want to use
and then give it a name. Once we then pass
it an actual value, this is referred to
as initialization. You have declaration
and initialization. Back to this example
for our role variable. Here we've declared
an empty variable and then after this, we have initialized it
with the value of dev. At the early compile phase, only the empty
variable declaration is hoisted and
stored into memory. However, though
the following line where we initialize it, this value of dev
ignored at this stage. At this stage, it's assigned
the value of undefined. This is why we see
undefined in the console. However, with the previous
examples since we do initialize this
with a value of Chris, at the compile phase, the variable will be assigned this value rather
than undefined. Also to make things
even more confusing, we need to remember
here that we're using the var keyword for
both of these examples. It's behavior is also different with the let or const keyword. If we change our role down
to the bottom to be let, let's see what happens
inside of here. We login the value of role, refresh and now
rather than the value of undefined being
assigned to this variable, we'll now get an error
inside the console. This error says we cannot access our role before
initialization. This is because when we're using the const or let keyword, it first needs to
be declared with a value before we can access it. If we don't first
assign it a value, it still will be given a value of undefined
behind the scenes, but we still must declare it before we try to
access it in our code. This can be a tricky subject to understand especially
in the beginning, because hoisting applies to
all three of these keywords, it applies to let,
var and const. However, var does
things a little bit differently like
we've just seen. But even if you don't fully understand what was
going on at this stage, having a basic understanding that variables are hoisted can save you a lot of trouble in the future if you run
into a similar error.
54. Temporal Dead Zone: The temporal dead zone
is something which sounds more difficult
than it actually is. Most of the concepts surrounding it has been
covered in previous videos. Looking at the code in the script section down
at the bottom, we have a console
log and a variable. We know that a variable
created like this with the var keyword will find access before it's
been declared. This is because the variable is hoisted to the top of its scope. We can see this by accessing
this first and I'll put this inside the console so everything works
completely fine. But as we also discovered, if we use the newer
let or const keywords, the behavior is different. Another way to look at
this would be maybe to initialize a variable
before we use it. If we remove the value and
then initialize this about the top, name equals Chris. Let's see what happens
inside of here. We still have an error since
we are trying to access our name too early before
it's even been initialized. But what about if we switched
around lines one and three? Well, if we place one at the very bottom and then move the let keyword
to the very top, this instead returns back
the value of undefined. This is also touched upon
in the previous video. If we don't assign a value to a variable using the let
keyword before we use it, instead, it will be initialized with the value of undefined. Const, let, and var all get
a value of undefined if we don't set the initial value before we try to
access the variable. The difference is var can be accessed before it's
even been declared, just like we've seen
in the previous video. How does this all relate
to the temporal dead zone? Well, it's simply name given
to something we've already seen when using the
const and let keywords. It's the name for a
period of time from when we enter any given scope, right up to when a variable
is initialized or accessible. A variable may exist, but it's not yet initialized. If this happens,
it's said to be in the temporal dead zone and we cannot access it or
use it how we want to. If this all sounds a
little bit confusing, let's simplify an example to try and give you a
better understanding. What we're going to
do is we'll remove our top line given
the console log and we'll say let name
equal to be a value. If we try this out,
we know this won't work as we've seen in
previous examples. This is because from the
beginning of the current scope, we`re entering the
temporal dead zone. Let's add a comment,
temporal dead zone begins. This is all at the top
level of our script, so it's in the same scope. It's not nested in an
inner scope such as a function or an if statement. Then when our variable is
initialized on line 14, the current temporal dead zone ends and the variable
is now accessible. Its beginning and ending zone also happens to any
other scope too. For example, if this
was in a certain block, such as a function or an if statement surrounded
in the curly braces, the same would also apply. Looking at this example, it may appear that
the code order is the determining factor, since the variable
declaration happens after we try to access it in
the console log. However though, this
is not the case. As mentioned earlier, the
temporal dead zone is a period of time from when
we enter any given scope, right up to when a
variable is initialized. It's the time which is
important and not the order of the code since the word temporal actually
relates to time. We can see this if we wrap
this log into a function. Create a function about the
top. Let's say get name. With this console log up inside a function and then
call our function. However though, we may expect
this to cause an error, just like we've seen previously. Looking at the code order, the console log still happens before we
declare our variable. It's understandable that you
may be expecting an error. However though, this
is not the case because the function is actually called outside
of the temporal dead zone. The temporal dead zone
is not the code order, it's the time it
takes to actually make this variable accessible. There are some complicated
things to grasp here and also in the past few videos. I don't expect you to fully get everything
on the first try, but I do think it's
important to know that these things exist
because one day you may run into an issue because of them and it may just
trigger you to think about these concepts and how they could be
affecting your code. How do we make our lives
easier and minimize errors? Well, a simple way could
be to put const and let variables at the beginning
of their required scope, whether that's at the
top of an if statement, the top of a function, or even at the top
of our scripts. That way, they will always
be ready to use when needed.
55. Closures: When we create
functions, they don't always need to be
stand-alone functions. They can instead be nested
to in the starter project. In this closure section, you can see inside of here we
have an empty script area. Now we're going to
create a simple function to demonstrate the
effect of closures. Jump into the script section and create a regular
function inside here. I will name this one the
outer inside with a variable. We'll say outer value. I set this to a string of outer. Now we can nest an additional
function inside of here. I'll just blow our variable
creates a new function. This time with the
name of inner. We'll do what we did just above we'll create a new variable. This one can be in a value
equal to the string of inner. Then inside of this
inner function we'll create two console logs. The first one is going to be for the
value of inner value, which is contained inside
of this same function. We'll duplicate this and
placing our outer value. We know from earlier videos
that when working with scope and when we nest scope inside of functions or
brackets like this, we can still access variables
created in outer levels, such as this one here. Basically, an inner scope can access variables
from an outer scope. For this inner function
to actually run and place our console logs
inside the browser. We need to call this
inside of here, so called our inner
function makes you this is nested inside
of the outer function. Then finally we'll call
our main outer function, we'll also do this
inside of a console log. Make sure you're
placing the brackets. Then test this out by
jumping into it the console. We see inner, we see outer, which is our two console logs. Then the third one
down at the bottom is the value of our function
which is undefined. We can ignore this undefined value at the moment
because we have not returned anything
back from this function. But we now know this is
working since we have our two console logs meanwhile we can access both variables from inside of this
inner function. We can first access
the inner value because it's local
to this function. We can also access
the outer value because of the scope chain. Scope or lexical scope,
which you mentioned, allows this inner function
to access our two variables. What about this
inner function was pulled out of this
current setting. I'm not talking about physically moving the inner
function elsewhere. But instead, what
about if we stored it into a variable and made
use of it elsewhere? Well, to be able to do this, instead of calling the inner
function at the very bottom, we instead return it, so we'll remove this,
return the value of inner. The important part to
understand here is we're returning the actual
inner function. When we call this
outer function. We can see this
inside the console. We still log in this
and also running this. If we refresh this,
we'll now see this inner function
inside the console. Bear with me here on this one. What we're doing here at this stage is we
call it an outer, which returns the
inner function. This inner function is effectively a stand-alone
piece of information. It has no reference to this outer function or
any content inside. Let's grab this inner value, which we can see inside
the console log. Install this inside of a
constant called interact. Now instead of directly, I'll put in the outer function. We can place in our
constant refresh and we can still see we have access to this inner function. All we're doing here is creating
a variable which stores a reference to this
returned function. This variable can now
be used elsewhere. Now what we have is a function reference
independence of the original outer function. This now leaves us
with a key question. This inner function which we
have stored in a constant, access this variable
from outside. Well, we can find out by
actually running this function by placing the brackets
just after this constant. Say this, remember we're still trying to access
its outer value. Refresh, and we still see
our two console logs. Meaning yes, we can still
access these outer variables. This may seem pretty strange. We are accessing variables
which seem to make no sense with what we've
already learned about scope. This all relates to
something called closures. A function, just like
our inner function here, we'll also remember what
variables it has access to. In our case, it's
this outer value. This is what's called a closure. Closure is putting a fence around everything
we have access to. Remember all of these variables wherever this function may
be called in the future. This can be useful now
because instead of needing to create an outer value
as a global variable, we can instead restrict
this access to inside this function and still allow any nested inner
functions to use it. Also, note that this
term of closures can only happen inside of functions. This is still not clear. Let's take a look at another example using the player score, which needs to be updated. We'll just comment out all
of this and at the bottom. Create a new variable. Let's scroll to be equal to 0. Then create a new function
called update score. Then inside here, what
we'll do is re-assign our score with the value of
the current score plus 10. Log this to the console. At the bottom call our
function. Let's test this out. This should be a
value of 10 since we have our original score of 0. We then call our function
which adds 10 to the original score and then
prints out the new value. Also, if we update
this multiple times by calling our function
more than once, they should also work too. This is all fine, it
works as we wanted to, but score which we
have outside of a function is classed
as a global variable. It can also be updated
from other areas too. Instead, it will
be better to have more control over
this variable and only update it inside the functions created
for this purpose. You may think, let's
move this variable into the function. We
can drag this down. This technique creates
a function scope and his variable cannot
be accessed from outside. This solves one of our problems, but it also creates another. Let's see this inside
the browser, refresh. We get back the value of
10 free times because the variable resets back to zero at the beginning of
each function call. Also if this was a real game, we may also want access to the score variable
outside of this function. Maybe do something like display on the screen to the user. To help with this, we can
make use of a closure to nest inside a function to
update the score variable. We have the update
score outer wrapper. Then we can create a new
nested in a function called increase with our score
variable inside and also the console log which are the inner
function, just as before. Also, store this function
into a new variable. The first one, we'll say const, new score is equal to
this returned value. We could still
call this function multiple times if you wanted to. But this time we
need to reference this new score, so it changes. Also leaving in
the brackets just afterwards so we actually
run this function. Then try this by refreshing. Now, this has increased
by the value of 10 each time we
call our function. This now solves the
problems which we had before with the original
update score function. Before we made these changes, we reset the score
variable back to zero. This meant that as
we've seen before, each time we call this function, the result was always
the value of 10 and it wasn't adding this to
each one of the calls. Closures will solve
this by remembering the score variable in memory
each time it's called. A score variable also now has narrower scope so we cannot be updated accidentally
elsewhere like we could if it was
a global variable. Yet if we still
didn't need to access this score variable from
outside of this function. We can return back a value and store this inside
of a new variable. Meaning we could still access
this score from inside of the closure and display
it to the end-user.
56. A Little Bit Of Background: This new section comes with quite a few buzzwords such as: callbacks, asynchronous,
synchronous, and also some concepts
which may be difficult to understand without
knowing a little bit of background as to
why we use them. This video is going to
try to explain these in simple terms and what
exactly each one does. Starting with synchronous code. Each line of code is
a bit like a task, and each one is
completed in order. Line 1 will be read, wait for this to be processed, and then move on to line 2. We'll also wait for
this to finish before then moving on to
the third line. On a tiny program like this
it doesn't really matter because operations are
quick and easy to process, and you wouldn't notice any waiting time between each one. But in extreme cases if one line never finished
processing or took a long time, then we will be stuck. On the browser we can see
some simple examples of this, so jump into this
new section which is number 10, Asynch-JavaScript. In a little bit of
background section we have two examples, but now we'll focus on example
1 which is uncommented out and also the
HTML up at the top. We have the button and
the level 3 heading. Ignore the image
for now, this is related to the second example. All we do here is grab both
of these first two elements. We query selector, we
select our button. Add an EventListener
which will listen out for the click and this function
will trigger an alert, and then just afterwards we'll
grab our heading and set the innerText
content effectively given us two stages
to this function. Alerts can be good for example
like this because they block the execution of the
code until we close it down. Now if we open this up
inside the browser; copy the path to this,
paste this in, and refresh, we see our button which will now listen
out for the click. I'll click on this, we'll
then see the pop-up which is the very first
line of our function, and then if we click on
this button it closes down. The next line of the function
is then called which will set the innerText
of the heading, so we have a clear order
of things running. We have the alert,
and then once this finishes it moves down
to setting the text. With this example,
each line will not run into the previous
one as completed. This is how synchronous
code works. One of the issues here with
synchronous code is that a single line of code can block the rest of the
program from running, and this can be avoided by
using asynchronous code. The code is still read
in order line by line, however an operation
doesn't have to complete before moving
on to the next line. Effectively, all of the code is run and then set off one by one. We need to handle what
to do when each task is completed and this is
the key part here, and then in what to do
if the code comes back a success or if
there's an error. Looking at our second example, let's just uncomment
out this section. Here we have something
called fetch. This is the Fetch API, and remember from
earlier we have certain web APIs such as Canvas. This Fetch API allows
us to fetch things across a network which
could be another website, a database, a server, or an external API
which someone else has created and the important thing here is the fetch
call is asynchronous. It will run and then allow the rest of the
code below to run too before it completes
or it gets any data back. The main idea here is we
get data from somewhere else over than inside
of our own application. This example selects
an image from Wikipedia and then stores the return value
into a variable. Let's begin by logging the return value of this
variable to the console. Just uncomment out
this first example, and then place in a console log with the value of the variable which is imageSource
of the browser. Jump into the console and we'll see something called a Promise. We get back a
Promise and it's got the result in
brackets of pending, so what do you think
this could be all about? Well, we will look up
Promises in more detail soon, but it goes back to
the important point which I mentioned before. When using asynchronous
code or async for short, we need to handle what happens
if the code comes back a success or if
there was an error, and a Promise can
handle this for us. When we first asked for
this image from Wikimedia, we don't know at this time if the Wikimedia site is down, running slow, or if there is a connection problem between
us and their server. This console log below was immediately called
after our fetch, so it was run before the
image even came back. This is why we get the
result of pending because we never had an image to
actually log to the console. As another example if this is pending and we don't
have an image, what about if we try to
maybe delay the console log by three seconds? Well,
let's cut this out. We can place in a timeout
which will trigger a function, and let's say three seconds. Inside the function place another console log from before, refresh, and you can see
after three seconds, our promise is now fulfilled. We have a response, so waiting three seconds
this gives us the time to get back the information or the
image which we need. Instead, if this was
synchronous code, the rest of the
code would need to wait on this image to come back for three seconds even if
we didn't need to use it. Or alternatively, if we
never handled it like this, the line which immediately
follows our fetch call may try to access our image which
you don't have back, and therefore causing an error. With this in mind, we'll look at promises and other ways to handle a response in
the upcoming videos.
57. Callback Functions: This video is going
to introduce you to something called
a callback function. A callback is just
the name given to a function which is called
after something has happened. The anomaly used
as a next step to take once something
has completed. From what we've
previously covered, we know that async code can
take some time to complete. This could be getting
data from a database, or even saving to a
database as an example. Let's see how this
can affect our code. The code in this
example begins with an empty, unordered list. Now, our script has
an empty users array. A typical app which has users would probably have
a function to make a call to our database and update this user's
array with the result. We have a function to simulate this and add two new users. All we're doing inside of
this function is to access our users array and placing
it to user objects. Then once we have our users
back from our database, which we assimilate in here, what we'll probably
want to do next is to create a new
function which grabs all of our users loops over each one and then it displays
them in the browser. Let's take a look
at how to do this. We will create a function,
let's say listUsers. Under listUsers is first going
to access a users array, we'll loop through with forEach, which will run a
function for each one of our array values or passing
the variable name of user. Then run that before we have
this empty unordered list, we can create a new list item
for each one of our users. Const elements is equal to
document.createElement, create a new list item, and then add the
text content inside, which is the username. Documents.createTextNode
where we can make use of our user variable and access
the name property. As ever, we need to create
our three-part process, which is to create the elements, to create the contents, and then at the third part
is to merge these together. To grab the parent elements, we call appendChild, add in the name,
and then finally, add this element to
our unordered list. First grab our unordered list with document.querySelector, add it in our unordered list and then appendChild,
which is our elements. This is it for our
list user function. We can call this below and check this all works in the browser even the names of our
two array values. This works completely fine.
We don't have any issues with functional data because we
know it's in this same file. But realistically,
what we'll probably do is to get our users
back from a database, and this database call would be asynchronous and
may take some time. If all this went well, the time delay will be small, but still a delay nevertheless. To simulate this delay, we could also add a setTimeout
to our both function. I'll grab our users,
let's cut this out, place in a setTimeout
call, and then a function. I would just add one
second delay to this. Open up the function body
and paste in our user array, and now let's see what
happens inside the browser. Refresh, and even
after one second, we don't see our users
inside the browser. Now, user have been listed since we are
immediately Lupin of the users down at the bottom because our function is
being called immediately. This is effectively
happening before the one second delay
from our timeout, meaning that when we try
to access our users, it doesn't have any values. This is a common thing
to have to deal with, when working with data
stored externally, we basically ask for
the data which we need, but we need to also make sure
the data is returned and available before we
can safely access it. It needs to be in order
so things don't break. Even though we're calling the functions in
the correct order, so we're created our users and then we're
listing our users, the time delay is
causing the issue. We can also confirm this
with some console logs. First of all, if we place in one just after where
we create our users, place in the first one and we'll say this is the
getusersFromDatabase function, and then also placed
one it inside of our list user function with
the text of list users. If we didn't have
this time delay running from the
top to the bottom, you would expect to see
the first console log, followed by this one just here. But if we save and refresh,
the opposite happens, we see the list of users cold before
getusersFromDatabase, and we know this
is the wrong way round for what we need to do. It would be nice if this
list user function was only called once the code in a timeout was deemed
to be a success, and we can do this by
introducing a callback function. To do this we'd move the
list uses function call, pull this out, and
then passing in the callback function into
getusersFromDatabase. This callback function
will be then taken in as a parameter from
create our function. Like any other parameter, this name is up towards. It doesn't have to be callback, we'll place this in it just so it's clear what we're doing. Now, we can call this
callback function at the end of setTimeout. Just after our console
log we'll call this as a function and then we can go back to the console and see
which order these appear. Refresh, and we get an
error inside of here. We say callback is
not a function. Callback. This just needs to not have the function
calls since we're just passing a reference to this
function. Now, refresh. Things appear to be running
in the correct order. We get back our users
from the database, we then list now users can also see these reinstated
back in the browser. This solution is
asynchronous callback. Remember, from the
last video that synchronous refers to
the code which runs in order and one operation needs to finish before moving
onto the next. Using this example
that we've just done, once the users have successfully came back from the database, which we simulate
with our setTimeout, we can then use our coal back
the list users function, and this way if things are
kept in the correct order. We can also pass in
additional arguments to this function too. Let's say we wanted to only get the users which had
the role of admin, we can pass this in
except the parameter. Now, when we call our function as passing the role of admin, so this is completely fine. We can pass in as many arguments as one, two to the function. We must ensure that the
callback is always last. This method of passing
in functions as function parameters
is nothing new. We can see this if we go
back to a previous file. Let's open up the sidebar, jump into the previous one, and inside the first example where we added an
event listener. We first listen of to click, then as the last parameter
which triggered a function. Here we pass it in a callback
function which we want to run once something
has been clicked on, and also consider a
array methods too which we've looked at
something like for each, which we have in this
current example. Each element inside
of our array, we then run a function inside
which is also a callback, meaning this technique
is nothing new. When we talk about callbacks, these are generally a more older traditional
way of doing things. We'll look at more
modern ways next. But callbacks are really
important to be aware of, since there's still
plenty of them around and still in use today. Callbacks too get a bit hate in the JavaScript world and not
because they don't work. It's more because how things
can easily get messy. This example that we
just use is not too bad. Because all we have
is a single function, which then calls back
a second function. But the problem lies
when we have a callback, which calls another
callback function, which calls another callback
function, and so on. Even looking at this example, the list of users function may also need its own callback too. Then that callback may also need its own callback and then
we're stuck in a long chain. We can compare this thing
to our own lives too. We have an order of tasks which you want to complete
in a certain order. Say we want to drive the car. But before we can
actually drive the car, we need to walk to the car, before we walk to the car, we have to find the keys, before that, we have to
get dressed and so on. This is a series of
tasks which rely on the previous one to
be first completed. This is comparable to the
callback functions being called once the previous
code is successful. It's this multiple callbacks which can result
in one big mess. To see an example of this, we've create some small
demonstration functions. Let's comment out
everything from above. Now, create some new functions. The first one is getOutOfBed, placed in the console.log, says out of bed, and then duplicate this
three more times, giving us four functions. The second one is to find our
keys and just console.log. The next one is walk to car, and the last one is
to drive the car. Will say yey, since
our task is complete. Using what we know from our previous callback
demonstration, each one of these
functions needs to be passed to the next
function as a callback. Then call this inside
of each function. The first one getOutOfBed. This is going to
take in a callback, which will run at
the very bottom, passes into the second one
too, call our function. We don't need to add this to our last function since it's not going to be calling
any further functions, since this is our end result. It's actually triggered
this chain of calling all of these functions, we're going to create
one more function to call all of these. Create a new function
called completeTasks. We can call this function,
which is completeTask passing in the first function which you want to call back. Our first one in our
order is getOutOfBed. What we're going to do now
may look pretty complex, but it's basically the
same setup as before. We'd get users from database. Only this time there is
more than one function in the chain, which
we'll callback, and then inside of the
completeTask function, we can then nest each one of our functions to call inside. The first one is get out of bed. Pass in a function. Let's go over to the console
and see what happens. If we refresh, we see
the text of out of bed inside the console and
if we take a look up, this is the console
log at the very top. The next party is going to be key to understanding
all of this. What we'll be doing
here is we're creating our first function
called completeTask. This is then called
on our first step, which is get out of bed, then
we pass a function inside. This function is accepted
as the callback, and this function is then called at the end
of this function. Basically once this
function has finished, it will then call
our second function, which is nested inside. This next section, which
we're going to call can be our next stage,
which is findkeys. Let call this function, findkeys also needs to take in
a callback function , so pass this in. This should now run our
second console.log. Got a spelling mistake, so we'll just change
this, findkeys. Now we have our
second console.log, so will run in this section just here and then it's going to run this next function is next
function is nested inside. Passing our next stage, which is walk to car. Walk to car also takes
in a callback function. We'll save and refresh, send prints out the
text of walking. It will then run our next
function, which is inside. The final stage, is
to drive the car. This is the very last stage, so it doesn't take in
a callback function. We can simply call this
as a regular function. Refresh. This code is run alongside all of
our other functions. If you want to, we could also
pass in additional steps or additional code into each
one of these functions too. Just to clarify, all
we're doing here is creating a main
function which is run, and then it's function is then calling additional
functions inside. Each one of these functions, we'll wait for the
code to complete inside before calling
back the next one. If this is starting
to look a bit messy, well, it probably is, because it is a
mess, and that's why some alternative ways of dealing with this
are being created. But just because
there's new ways, it doesn't necessarily mean
this is still not relevant. Callbacks is still an
important part of JavaScript, both currently and
you will also see this around in legacy code too. You will probably
hear this kind of thing also called Callback ****, the Pyramid of Doom,
Nested callbacks. Also plenty more names too. All of these names point to the same complexity of nested
callbacks just like this. In upcoming videos,
you will discover some alternative ways
of handling this, including using callbacks
asynchronously with promises.
58. Promises: We know now that making an asynchronous request
can take some time, maybe just a few milliseconds, or maybe a few seconds, minutes, or never
even complete at all. Often when we're talking
about this thing, it refers to fetching
something from somewhere else, such as a server, some kind of datastore, an API, or an external website. A promise is great for
handling this outcome. Promise is an object
and this object represents the result
of an async task. The result could be a failure, we may not get back from the
server what we asked for, or it may take a little time. Promises hold onto this task and promise to tell you in the future when it
knows the outcome. We've already seen promises and some of the outcomes already. A few videos ago, we
looked at a simple example of fetching an image
from Wikimedia. I'm going to use the Fetch API. Fetch is an async operation, and we're seeing a promise
returned back in the console. When we tried to
access the promise too early before it finished
grabbing the image, the results were
seen as pending. We then added a small time
delay with a set timeout, which was altered in the
promise being fulfilled. These are two of the three available
states with promises. Pending is the
initial state when we don't yet know if the task
will complete or not, then fulfilled when the
task was deemed a success, such as when the data has came back and it's now ready to use. We also have a state called rejected when the
task has failed. We don't have the
data we requested, and we need to do
something about it. You may also hear the word
settled with promises. Settled is when we know
the fate of the task, meaning it's either
fulfilled or rejected. We don't need to
know anything about the pending state since we can't tell what direction it
will go with the result. But we do need to handle
a success or a failure. Over to the starter files, which is the promises section. Here, we have another example
using this Fetch API. This is fetching data
from an external API, and this is just
an external URL. This URL is going to
select a random dog image. We can copy this, and paste
this inside the browser. We can see we have a result
in a format called JSON, which stands for JavaScript
Object Notation. It's a syntax for exchanging
data over the web. It looks like a
JavaScript object, but it's actually
language independent, meaning it can be created and passed by other languages too. We can see we have a message, which contains a URL for
a particular dog image, and the code of success. We can copy this image
and paste this link in, which then gives us access
to the particular image. I've installed the
browser extension to make this look a
little bit prettier. Yours may look a
little harder to read, but the data should
still be exactly the same. Back over to the editor. As we know, the fetch
call is asynchronous, so we can handle the outcome of fetching this image
with the promise. First, we can handle
the fulfilled state, which is a success, and we do this by chaining onto the end of the fetch call. We can remove the semicolon, chain onto the end.then. Then is a method which we
can chain onto a promise. Remember, when we
use the Fetch API, this will return back a
promise as the response, meaning we have access
to the then method. Pass in a function. With this being a
asynchronous callback, we'll only run the
code inside of here. Wants to know the task was
completed successfully. Of course, we probably
want to access the data we get back
from the fetch call. This can be passed
to our function, so passing any variable
names such as response. Then we could log this to the console and check if this works. I'll save into the browser and open up the console.
There's a response. This successful promise returns
a response object which contains details of what we get back from the server
or from the API. This object contains things
like the status code. In our case, it's 200, which means it's all good. Down to the URL low, we see the actual URL which
we requested in the fetch call rather than the URL of the image
which we get back. The reason we don't see this is because all the data which we get back is stored
in this body section. If we open this
up, we still don't see the URL of the actual image. Instead, we see a
readable stream. This goes back to the JSON
format mentioned before. To read this JSON format which is stored in the body section, we have a JSON method
which we can use. Inside the console log, we can add onto the
end the JSON method. Then let's try this one
out. Save and refresh. We're back to a promise which has the current
state of pending, meaning the task has
not yet completed. This is because the
JSON method also returns a promise too which
we again need to handle. There is a couple of
ways we can do this. First, we can remove
the console log wrapper and leave response.JSON. Since this JSON method also
returns back a promise, we can also chain
on the then method. Pass this in, which again
it runs a function. Also this function, it takes in the data from the response, so passes into a data variable. Then we can log this
data to the console. Let's try this,
save and refresh. Now, we've successfully
passed our JSON content, which is stored in
the body section. This returns back the
actual image of the dog, which is stored in the message, and also success as
the status code. This is the same response
which we've seen when we pasted this into the
browser earlier on. Everything here
returns a promise, the fetch call
returns a promise, and also the JSON
returns a promise. But another way of doing this, which I personally prefer, but it's completely up to you, is to not nested then
calls like this. If possible, my preference
is to keep them all at the top level
for readability. To do this, we need
to cut out the nested then method which
we've just added in. We'll leave it in
response.json inside of here, and we'll cut out the
second to last set of brackets all the way up to
just after response.json. Cut this out. Now, the function should just
left with response.json. Make sure the semicolon
is removed from the end, and we can paste
this onto the end, giving us two then
methods in a row. However, though,
if we go over to the browser and refresh, we see the message of undefined. This is because we
are trying to access the data from our
previous method. But first, to do this, we need to return this back. This all works just as before. We can also chain on as
many of these then methods as 1, 2, and each one will wait
on the previous result finishing before running
the code inside. We'll keep full control over our async task and the order
in which the code is run. We can also filter this down
just like any other object. We could access the message, which is the URL of the
image which you need. We can click on this and open
this up inside the browser. We can also make use
of this image URL and pass it to an image element. First, add an empty
image element just above our script, and then in the second
then method when we know we have access to this URL, we can remove this console log, select our image elements
with document.query selector, and then set the
source attribute to be equal to data.message. Let's save this and
refresh the browser. Each time we refresh, we'll get different random image
back from our API. This is the fulfilled
state handled. But what about if
there's a problem and we get back a rejected state? This will happen if there is
an error fetching the data or completing the task
which we asked for. This we chain onto the
end a catch method. Here, we are chaining
the event method , remove the semicolon, and then chain onto the end of the catch method which we'll
run if there's an error, we pass that error to
this callback function. A lot function with the error
based on a console log. Let's start with a string
of there was an error. We can also add onto the
end the error message too. If we test this and refresh, we don't going to
see anything inside the console because the image is being returned
back successfully. To actually test this,
we need to jump into the developer tools and
go into the network tab. I'll select this,
and to do this, we can actually turn
off our network. Let's make this a
little bit bigger. Chrome has this drop-down
which you can see here. We currently not
using any throttling, but we can also switch this to a slow connection
or even offline. If we do this and jump into
the console and refresh, switching off this
network means when now don't get the
image which we want. The promise should
now throw an error. With all of this, the final
thing which we're going to look at is the
finally method. The finally method will run
regardless of if the promise was fulfilled or if
it was rejected. This can also be chained
onto the end too. Like with all of
the above methods, we also need to
remove the semicolon as in the finally method, which again it runs
a callback function. Place in a console log with the text of,
I will always run. With the network
still turned off, we can refresh and
see this message. Let's turn the network back on, refresh, jump into the console, and we still see
this console log even when the promise
was a success. The finally method is
useful to follow up with some code which we need to run after the
promise has settled. Alternative would be to
place this code into both the then also
the catch sections. But this would result
in duplicated code. This is promises and
how it can be really useful with async JavaScript. This whole thing is possible
because the fetch method initially returns a promise
to kick off this whole chain. But what about things that
don't return a promise? Well, next, we'll take a look
at how we can handle this.
59. The Promise Constructor: In the previous example, we could begin to
use promises because the Fetch API returns a promise. But what about functions
which don't return a promise? To kick things off, let's write a simple
custom function, and this is going to run and set some data after a time-out. I will jump into the
promise constructor file, which is currently empty
inside the script to create a variable at
the top called data. Set this to an empty object. Create function called getData. Then inside pass
in our setTimeout, which is going to
take in a function. As a function Then
just after this, we'll run this after 2,000
milliseconds time delay. After two seconds, all we want to deal with this function is to set our data object
to have a value. We'll reassign this to be a new object where we'll pass in a name property
equal to a string. This will call getData to
actually run our function. Then at the very end we'll
log the value of data, we can see what we get back. Parsing our data. We can also go for the name too. Into the browser, open up the file and
jump into the console. The console see the
value of undefined. This is probably unsurprising
because we access the data object inside the console log before we've
even set the name property, for the data takes two
seconds to be set. This is a bit a simulated
request to a database, free to handle what
happens between requesting the data
and getting it back. No promises are good for this, but this function doesn't
return a promise by default. For this, we can create our own using the
promise constructor. Constructors is something
which we've already looked at with the new
operator in the past. We've looked at things
like new array. We've looked at new object
and also new function. These all create a new
instance of an object. Remember the promises
are also objects too. We can also use this new operator to create
a new promise ourselves. First comment out
our example for now, which is everything except the data variable. Remove this. Then we can begin to construct
our new promise below. Just with the examples
we've seen before, we'll use the new keyword
to create a new promise and this constructor takes in a new function which
is going to run. This function takes in
two optional parameters, a function to run if the
promise resolves on one, if the promise has
been rejected. These are names of our choice, but resolve and reject
are pretty descriptive. What it's been our
own custom promise, we set exactly when
and where we want these resolve and reject
functions to run. The first step is to store our promise
inside of a variable. We'll just call this promise. I can call this
in just a moment. But I will simply call resolve
to resolve this promise. Then afterwards we
can make use of this promise variable
which is below the coldly, then catch and finally methods which we
looked at previously. We can access promise.then, and this method is exactly the same as we looked at in
the previous video, which takes in a function
which is going to run, the code was a success. With this in mind,
we'll place in a console log with
the text of success. Allow this and make sure no
semicolon is on the end. We'll also chain onto
the end of the promise. The catch method, which will
also take in a function. Remember the catch method
will also be pass the error, which we can place
inside a console log. Let's go over to
the console now and see which one of these
has been logged. We see the value of success, which is unsurprisingly because we've resolved this promise, meaning it's been successful. This result function can also take in a fulfillment value
which you want to pass on. Let's say a string of success. Then this success
message will also be passed down the chain if
this has been successful. The successful section
is the then section. We can pass this
into our function. We can store this inside
a variable such as response and then we can
log this to the console. Refresh, and there we go. This is handy if you need
to pass some custom data or message to the
result section. But going back to the
earlier example of fetching some data
which we had above, this would be a better example than just simply
sending a message. What we're going to do
is to copy and paste this timeout section from
inside the function. Then we can paste
this just above the results section and
uncomment out all three lines. We can then move up
the results section to be inside of the timeout. It will also set the data and
then resolve this promise. This means that the
promise should now only be resolved after the
data has been set. We can test this by
login to the console. Jump into the then section, if this has been
successful with a new console log the
value of our data. Refresh. Give this two seconds. See the message of success, and our object has been
updated with our string. This is how we can handle
a success with a promise. But what about rejecting? Well, since this is our promise, we can make it do
whatever we want to. This example, what about successfully resolving is the
name of Chris has been set. I'm rejecting if the
data object is empty. We can jump into our timeout just below where
we set our data. We can place an if statement. We can check if Object.keys
has in our data object. We can check if the length of
this is greater than zero. The name being the
key property here, we're checking if the length of this object is
greater than zero. If it is, this
means this has been set where we can pass in
the resolve function. A success message. If not, just after the if
statement will pass in reject, which will have passed
into our function with the message of rejected. Let's try this. We'll have
a data property sets, so we should expect the value of success. Gives this two seconds. This has been a success. If we want to check
the rejected state, we can check if Object.keys
is equal to length of zero, which results in a
rejected promise. There is a bit going on here, but the idea is pretty simple. If a function by default doesn't return a promise, we
can create our own. This can resolve or reject at any point we feel it should, giving us total control of
the behavior of our function.
60. Handling Multiple Promises: Once we understand
how promises work, handling an AsyncTask
becomes a lot easier. But what about if we have
more than one AsyncTask running at the same time, this can cause various issues. Do we wait on the first task to complete before moving
onto the next one? As an example, if we had 10 AsyncTask running
at the same time, if we waited on each one finishing before
moving on to the next, the delay from each
could really add up. Also, as an example, what if task number 10 relied on the value of task number 3, but task 10 finished first. Well, to help with some
of these problems, we have access to some
useful promise methods. We've covered some
methods so far, including then,
catch, and finally, and these upcoming
methods are going to deal with multiple promises, starting with a
method called ALL. The ALL method loops over
multiple promises and returns one single promise and a single promise results
to an array of the result. It's useful for gathering
multiple pieces of data and maybe aggregating
them into one single result. Let's take a look at how
this works in the starter. Have two separate
promises stored into variables. Promise number 1. This again is a simple example
that we've seen before where we fetch an
image from our API. If this has been successful, it then runs a function which will return back the
value of the response. Along with this, we'll
have promise number 2, which is using the
promise constructor that was seen in
the previous video. This will resolve
after two seconds. I've deliberately kept them
simple for this example. But imagine we wanted to know if both of these were a success. Rather than having to check
each one individually, well, for this we can
make use of promise.all. At the bottom, access promise, capital P. The ALL method. We're going to pass in an array containing both of
these variables, so promise 1, promise 2. This promise.all
method will take in any iterable value
such as an array. We've looked at looping over arrays plenty during this class. Also, this ALL method will
return a promise too, meaning we can chain onto the end various methods
such as then and catch. We'll do exactly the same
as we've done previously. We'll chain on then, which
takes in a function. This function can also
take in the results. We'll log this to the console. Afterwards, if
there was an error, we can also chain in.catch
onto the end as as an ALL function which takes
in the error, place in log. Then let's give this a go. Over to the browser, make sure
the current page is open. Refresh. After two
seconds we get back, return, and an array
with two results. The first one is
our random image, which is stored
inside this object. If we open this up, we can see that this one
has been a success. The second one though,
after the timeout, is shown as undefined, since the promise does not
actually return anything. If we want to, we could pass a third parameter to setTimeout. Let's jump in and do
this. Separate by comma. We'll pass in a simple
string as a result, which is an optional
parameter to pass to the function which we're
calling, which is resolved. Now, after a refresh, we should see this value
inside the console. We can also test a
failure and see what happens if one of these
promises is rejected. We can do this by instead of
resolving, we can pass in, reject, and this one out and
then I'll catch section, the console log will run. We see this value of results
which we'll pass in here. But one thing
you'll notice is we don't see the random image. Even though we're
passing in both of these promises to promise.all, we only see the
result of rejected. This is because when
using the ALL method, it will only resolve if all
of the promises it takes in results successfully
or if a single one fails. We're either successfully
getting an array containing all of the
promises like we've seen before or the first
one which is rejected. However though, there
may be occurrences when this is not the
behavior which we want. We may still want all
of the promises to be returned regardless of if
there was a failure or not. For this, we can replace
all with all settled. All settled is still
going to take in an array containing
all of the promises. But now, if we save
this and refresh, give this two seconds. Rather than seeing
the single failure, we see all of our promises
inside of the array. The first one was fulfilled.
We'll see all value. Then we'll see the second
one which has been rejected. We could use these results
in any way we wanted to, such as looping over
them to handle what to do if there was a
failure or a success. For example, we maybe want
to know which ones have been rejected so we
can call them again. Or even maybe only show a user a certain section if the
user request was a success. Something else we
have is two methods called any and race. Both of these will only
return back one value. First, let's take a look
at any which we can use in place of all settled. Save and refresh and we'll
see what we get back. This time we don't
see the rejection. All we see is our
successful image. This is because any
method, as it sounds, will resolve as soon as any of the promises passed to
it has been fulfilled. Which basically means
the first promise, which is a success. This doesn't always mean
that it's going to be the first one which is
passed in the array. It just means the first
one which has completed. The race method however, if we take a look at
this, Save and Refresh, this also returns back
the same random image, but this will return back the
first promise passed to it, which has been settled. Settled could even be
fulfilled or rejected. In our case, it's a random image because this one comes back faster because it doesn't
have two second time delay. Regardless if the
promise passed to it was a success or a failure, the first one back will be the one which you see
in the console. To recap, the race
method returns the first promise it encounters which resolves or rejects. The any method will also
return one promise, but this one has
to be fulfilled. They're both very
useful to have if any of these
situations comes up.
61. Async / Await: With the arrival of ES 2017, came a new way to handle
asynchronous code. Often we've looked at
previously is redundant, it's all still something
which we need to know, particularly for what we're
going to look at now, which is something
called async/await. Async/await is actually
promises behind the scenes, but it was created
to look simpler, and be easier to
read for developers. It looks simpler because we go back to something
more familiar, which is a function. In starts files we'll have
an example image where we'll return back a promise which
we've seen previously. If you were to instead use a regular function
to set this image, we'd do something like this. This probably will create a
function called set image, and then inside, we could again make use of
our fetch call. Paste this in and store this inside of a constant
called response. Then, let's try to do a
console log for the response, and as you all know
from previous videos, here we're trying to
access this async code immediately just below before it's potentially had the
chance to come back. To run this, call our
set image function, refresh the console, we get
back to the pending state. We can see the response,
which is our URL, which we originally
calling inside of here, rather than our actual
image, which we get back. What you may be thinking
we could do next is to use response.json.
Let's try this out. Store this inside of a
constant called image, which is equal to response.json. We can pass this response. Now, look this image, the console. Let's
try this again. This time we get an error, saying response.json
is not a function, and this happens
because the image data is not yet available. We've promises we
know we have to chain onto the end
of all fetch call, then methods, which then awaits the successful response
before running a function. However, though we're not
using promises this time, we're going to use async/await. For this, we have
two simple steps. To convert our regular
function to an async function, all we need to do is to pass the async keyword
in front of it. Once you do this with a mark, which line of code we
want to await on the data returning before we move
down to the next one. Now in case we want to await
on the image coming back, and this will effectively
pause our function and wait for the data to return before moving it down to the
rest of the code. Let's save this and
see what happens. If we now refresh the
error is now gone. The promise is still
independent states, but if we open this up, we can access the data which
we need inside this object. We can now see if we copy
this image with quotations, we now get back our
random dog image and the message of success. The reason why we need to dive into this promise is because we're trying to access our response.json on the
line just below. We've still awaited our
image to come back, but then we've logged to
the console response.json. But remember from
previous examples, the json method will also
return a promise too. To deal with this, all we
need to do is to await our json method to finally finish before we log
this to the console. Now if we save this, try this again in the browser, rather than having
to jump inside the promise and into the object. Now, directly see the
correct information we need inside of the console. We've now returned the URL
inside this message property, which we can use to set our image elements.
Let's grab this. We can remove the console log access
document.queryselector. Wasn't in the image where we can set the
image source to be equal to the image.message. There we go. This is how we can use async/await with this
regular function style. But there is also
different types of functions available too
which you've looked at, including function expressions
and arrow functions. Just like this. With a
function expression. Say let func as the variable name and say
this equal to our function, which is simply going to
return a string of hey. The arrow function equivalent
would look just like this. Here we have our
function expression and then we have
our arrow function. Async/await can be used
in either one of these. The way to mark our
function expression as asynchronous is to
just like above, passing the async keyword just before the
function keyword, and also the same for
our arrow function too. We can place this in right
at the very beginning, and then we can use
the await keyword anywhere inside of
the function body. Async/await is a clean
and powerful solution to handling async tasks. But we need to be careful to
not get too carried away. In this example, we're setting the image
on the next line. It makes complete
sense to await on the data coming back
before we use it. But imagine if we had
something like this. If we duplicated this response, and we asked for our
two separate image. What we're doing here is
we're making two requests for two independent images
at the same time. But the problem we
currently have is the second image is
not going to be called until the first one has returned because we
use the await keyword. Now this will cause
an unnecessary time delay in the code below. Here, we're trying to
access our first image, but we still need to wait
on the second image being returned before this
line even runs. We use a wave, but we
need to wait on the data, which we need to and after. But we still don't want
to block the rest of the code from running,
if we don't need to. Handling multiple
requests like this is something which we'll look
at next in more detail. Along with soon looking
at error handling too.
62. Handling Multiple Awaits: At the end of the
previous video, we discussed the effects
of multiple await calls. Here, we have three,
have response, responds 2, and response 3, which all make the
same fetch call. We know, for example that if
the second image needed to wait on the first image data coming back before being run, this was completely fine. However, though if image 2 doesn't rely on the first image, it shouldn't block this
code unnecessarily. With multiple requests
for async data like this, it brings us back to
a previous video, where we looked at how to
handle multiple promises. What if we want to know if one
fails or if they all fail, how would we then go
about handling this? With promises we
could use methods such as all and all settled. But with async code, these promises also
under the hood. We can still make use of
these methods such as all. For example, down at the very bottom inside of our function, we can access promise to all, which takes in an array. We can pass in response, response 2, and also response 3. Let's store this
inside a variable or a constant called result, and then log this
to the console. An error, so that
needs response. This still returns a promise, but remember the all method will resolve into one single promise, if all of the promises it takes
in resolves successfully, or if a single one fails. With this in mind,
we can also use the await keyword to make sure they all resolve first before
moving on to the next line. Pass in await, it
is before this. Now we're awaiting all three of these promises to now complete. This means we can now remove
the await keyword before each one of these free
independent calls. Let's try this out, refresh. We now get an array with all
three separate responses. We can go into
each one of these. But one of the problems
which we still have is, if we look at the URL, this is the image
URL which we are requesting inside a fetch, not the actual JPEG image, which is coming
back from the API. If you remember, to access this actual
image which we need, this is contained
inside of the body, and to read the
contents of the body, we again need to make
use of the JSON method. The JSON method will read
the contents of this and turn it into a JavaScript
object which we can read. As we can see we've got an array with three separate values. What we can do, is
we could remove our console log and
loop over all three of these values with the
forEach method, so results. For each function, store
this inside of value. Now we can log this value
to the console and call the JSON method for each
one of these values. This now gives us back our
three separate promises. But if we open this up, we see a familiar object
which we need to dive into to get our
actual image back. This happens because as
we've seen previously, the JSON method will
also return a promise. To wait on this data coming
back before we access this, we can use the await keyword, and because the await keyword can only be used
inside of a function, we can mark this as async. Reload. Which then gives us back our three
images, which we need. This all method now means
we can do something after all promises have
been successful, or if a single one fails. Speaking of failure, the subject of the next video is looking at how we can handle
errors using async await.
63. Error Handling: This video is going to
show you some ways to handle errors in
your async code. Code is great when
all things go well, but when things don't, we
need to handle what to do. Some of the promise
methods we've looked at give us some error handling. For example, the all method
fails if one promise fails, so we have some options there, but we also need to handle
all other situations too, either a general error handler, or handling each
specific request. In the starter project, in the error handling file, we have an empty
image element at the top and also a
single function. Nothing new here
for this function, this is just grabbing an image
URL using the Fetch API. We wait on the data coming back, which is stored in response. We then extract this
data via JSON method, and then return
the image property which contains the URL. Just before, we
use this function, let's take a look at
another simple example. Just below, let's create a
new function with any name, and all this is
going to do is to return any simple string. After this, we'll console
log the return value, we call our function,
opens up in the browser, and there's our return message. Nothing unexpected here, but if we mark this
function as async, so just before the
function word, mark this as asynchronous, and this time we
get back a promise. We're just going to leave
this function there, but just bear this in
mind for a moment. As soon as we mark a
function as async, it will then return a promise. We know we've got a function
to grab the image URL, so let's create a second
function which is actually going to set the image URL
through this image element. This can also be async,
call this setImage. Then inside, all we're
going to do is going to grab our image elements
with document.query selector and set
the source to be equal to the return value
of the above function. I start with image element, and set the source equal to our above function,
which is getImageURL. We need to call this. The
getImageUrl function is async, so we need to await
on this promise being returned before we
can actually use it. We're going to wait this
function returned value. This is all enabled
since we've marked his function as async. Let's call this just
below, setImage. Earlier when handling
errors using promises, we simply chained on a catch
section to the promise. This setImage function is
also asynchronous too, which also returns a promise just like we've seen before
with the simple example, meaning we could
chain onto the end, then all the catch block
which is going to run next. We can chain this on the end, making sure there's no
semicolon, add catch. Catch will run a
function which takes in the error message
and place it in a console log the message of "noooo" and also a second
one with this error. Now, let's see what happens if we try to call this function. Do we get back the error we'd catch or do we set the image. This particular
call was a success, so we see the image
returned back. To test out the catch block, we need to switch
off the network like we've done previously, so jump into the Network tab. Under here, we're going to set the presets to be offline, now we can refresh, we
don't see the image. Jump into the console. Our first console log and our second console log
have failed to fetch. We see this works
fine and we're mixing the syntax of async
await with promises. If we wanted to just stick
with the promise syntax or move the error handling
into the function itself, we could use something
called try and catch. Try and catch, as it sounds, will try to do something,
and if it works, that's all great, if not, we catch the error and handle it in any way which you want to. Let's remove the catch
section from setImage, and then inside of our
setImage function, we create a try-block
and below a catch block, which is also going to
take in the error message. This is reasonably simple, all we need to do is to move our code which you want to run inside of the try
block, so cut this out, paste this into the try section, and then inside the catch area, we can place in the
same console logs, so a string and also a second console log with the error message which is
passed to this function. Let's save this
and try this out. Bear in mind, my network
is still disabled, so if we try again, we
don't see the image, you will see our two error
messages inside the console. Let's try to switch
this back on, return the network, the
image is now returned, and we don't see any
of these console logs running inside of here. This try and catch section
is running synchronously, so we'll run the very first
section which is try. If this all works,
that's completely fine, but if it fails, it will then move down to the next catch block and run the code contained
inside of there. On top of this, we also
have a finally section 2. This works just like when we chained finally onto
the end of a promise. Finally will always
run regardless if the promise was
fulfilled or rejected. At the bottom, pass in finally, and we'll just place in
a simple console log. Let's try this,
refresh and we see the console log with the
text of always runs. Just as a quick side note, the catch or the finally
block need to be present. It must have at least
one of these in place, or just like this
example, we can use both. This is fine for
general error catching, but what about if
we wanted to know which part of the
try code failed? At the moment, this is
pretty simple because we only have one line of code. But what about if this try
section did more than this? For this, we could chain a
catch block directly onto the async task and
to see an example, I'm just going to copy
this full function, comment this out for reference, then paste it in below. We can remove all of the error handling
which we just added, which includes the
catch, the finally, and also the try-block leaving
our simple async function. With this, we can then
remove the semicolon, we can then chain catch onto
the end, inside of catch, we could pass a function
directly inside, or if we wanted to
reuse this function, we could make a separate
function inside of our code. Let's create a function just
above called handle error. We'll just grab our two error
messages, paste these in. The error will also take
in the error message, then we can call this
inside of catch. This gives us a separate
reusable function, which we can then chain onto the end of multiple promises. Let's try this
out. We should now see our two error messages. If there's a failure. If not, we see the image,
this works fine. Let's try and turn the
network off once more, go to the console, there's our error messages
inside of here. Of course, a console
log wouldn't be enough in a
real-world application, we would maybe want to hide
the image if there was an error or even add a place
holder image in place. But the key here is to do
something rather than just see the website or the
application breakdown. Handling errors is a big
part of async JavaScript, and we see some common patterns here which you can
use in your projects, which will really improve
the functionality and user experience.
64. Leaving So Soon: This first project which we find in the last section
of this class, is called Leaving So Soon, and it contains
some starter code so we're going to focus
on the JavaScript. This project is what is
called a exit intent pop-up. An exit intent pop-up
is basically a way of grabbing the user's attention if they try to leave your site. We have some simple HTML, have our title,
and then we've got this pop-up section
inside of a div. If we go over to the
project and refresh, all we currently
see is this title. The idea is we have a
simple website like this and you may have seen these types of websites
when browsing online. If you try to move the
mouse up to the top, and try and close
down the browser, or to even search or
navigate away from the site, you may often see
a pop-up appear. This pop-up could
contain information. It can contain a voucher code, or just generally anything to
keep the user on your site. We don't see this pop-up section because if we take
look at this div, it's got the ID of exit pop-up. Inside of our style sheets, if we take a look
for this section, the display type is
currently set to none. If we change this to
be flex and refresh, the background will
fade out because of the CSS animation, which we have just
at the bottom. Our pop-up now appears with
all of the text inside. The contents inside
of these websites or inside of this
pop-up is irrelevant. The idea is we want to
toggle this display setting once a user's mouse leaves the top of the browser. For now, let's go back
into our style sheet, and I'll hide this by default. Then, we get to work
inside of our script, which is already linked
down at the bottom. Let's open this up. If you think about what we
want to do here, there's not a lot of tasks
which we need to run. Let's start by creating
a function which is going to run when the mouse
has left the window area. Once the pop-up is actually displaying
inside the browser, we also need a way
to click on an X in the corner and
close this down to. Create a second function
called closePopup. We'll start with
our first function, which is leftWindow
and for this, we need to listen out
for a mouse event. We're going to
select the document which is our full webpage, and then listen out
for when the mouse leaves with the mouse out event. Let's grab our documents. We can add an event listener. Events. We want to listen
out for is mouse out, which is then going to
trigger our above function. Remember, with events
this also takes in the event information which we can access inside of a variable. Let's begin by logging
this into the console. Log the event information
into the console, refresh, and we
can see if we move the mouse outside
the documents area, this mouse event is now fired. This will also work if you
go for the other side, and also the very top
two. Jump into here. The mouse coordinates
which we're interested in is this clientX
and the clientY. This is a pixel value
which will tell us the mouse location once
this event was fired. Just like when we looked
at the Canvas early on, X is the left or
right direction, and the Y is top to bottom. Since we're trying to
watch out for the mouse, leaving the top of the
browser to close this window, we're going to be interested
in the clientY direction. We can filter this down,
e.clientY. Let's reload. Now, if we go to the top, we can see as soon as we pass the top of the browser area, we then begin to get
a negative number, meaning the very top here
is the value of zero. We need to listen out for
maybe a 20-pixel area, which will display all pop
up as soon as the user moves the mouse up to the top
area of the browser. We can remove the console log. Place in an if statement. We can say if e.clientY is less than any value of your choice, I'm
going to go for 20. As we've seen inside
of these style sheets, we can grab our
elements with the ID of exit-popup and toggle
the display type. Grab this with the
document.querySelector. I send the ID of exit-popup. Set the style property
of display to be equal to flex.
Let's try this out. Refresh and now move to the top, and this one will
trigger our pop-up. You may think this
is all working fine and this is
all we need to do. We also need to
take some steps so that we don't irritate the user. Need to activate this cross so it toggles our
closePopup function. But probably only want
to run this code to activate the pop-up after
a certain amount of time, and we also want to
make sure this is only triggered once on
the user's visit. To make sure this is
only triggered once, we'll do the reverse, and we'll remove the event listener
from the documents. From early videos, we
know that we need to copy the same information
from addEventListener. We can't test this out just yet because we also
need to be able to close the window down before
we can reactivate it. We'll do that now we'll go
into the closePopup area, and then do the opposite by setting the display type
to be equal to none. To activate this function, we need to listen out for
a click on our cross. Inside the index page, the span contains
the cross area. Let's grab this
inside of our script, under the bottom,
document.querySelector. We'll select our span.
Store this inside of a constant called closeBtn. Grab the elements, add
an event listener. We want to listen out
for the click events, which will run our
above function. Let's try this out.
Move to the top to activate the pop-up.
Click on the cross. We can also see if we move
to the top of the browser, our pop-up isn't reactivated because we removed
the event listener. All good, this is
working pretty well. But one quick
addition we'll make, is to make sure that
the pop-up isn't activated as soon as the
user visits the site. If there are, for example, typed in something at the top and then moved straight away, you don't want the pop-up
to appear immediately. What we'll do is we'll
place a setTimeout to only activate this function
after a certain time delay. Call a setTimeout. Pass in a function where we
can move our mouse out event, which triggers our function. Will only call this after a
time delay of three seconds. Try this out so we can move our mouse up for the
first three seconds. Then, after three seconds, our function is active. Pretty simple project, and we can do quite a lot with a small amount of
JavaScript code. It's something which
you may find useful when building websites
in the future. As will be our next project, which will be an image carousel, which we'll build
completely from scratch.
65. Image Carousel- Setting The Images: In our next project,
we're going to build in a image carousel. This is going to
be the end result, and it's completely
built with JavaScript. What we'll have is a main
large image at the very top. We can cycle through the
bottom images to replace this using the left
and right arrows. Also, we can skip to any one of these images by
clicking on them, and placing these up
to the top section. This is going to put
together a lot of the skills that you've
learned and also better see how things work in practice with a real-world app. Let's start this over
in the starter files. If we jump into
the next section, which is the image carousel, we have some starter code. We've got a very
simple index page, we'll be injecting all the
contents by JavaScript. All we've got is an empty
div with the id of carousel. We'll place in all
image contents, and then we'll link
to our script. The script is inside
the carousel folder, which contains our images, our style sheets, and also the JavaScript
to make this work. Provided with this course is five different
images which you can replace and also some basic CSS. We've just got some
basic styling. For example, we set the
main carousel to be a certain width and
also place it into the center with margin 0 auto. We've got some hover effects. Other than that, some general
styling along with the small and the large
class we have here. We'll be adding these smaller than large class to each one of these images so they
fit nicely on the screen. Let's start. We jump into
our empty carousel.js. We'll begin by grabbing this carousel div
section and also create an array with
all of the images. Jump into the carousel.js. We'll start by creating
an array of our images. Each one of these is going to
be a string which points to the URL inside the
images folder. If you have used
your own images, remember to change
the names inside of here to match the ones
which you've placed inside. The first one, this is
in the carousel folder, and the file path is
images/beach.jpg. Let's duplicate this to give
us five different images. The second one was
elephants. We've got grass. We've got lake, and
the final one is town. Next, we'll grab a
reference to our carousel. We'll store this inside of
a constant called wrapper is equal to
document.query selector, plus in the id of carousel. The next step, we're going to
create a function which is going to grab all these images, will loop over these and
place these onto the screen. So create a function
below called setImages. The first step inside of this function is to
grab our wrapper, and we'll clear out
any existing contents. This is so we can repeat the process of
setting our images, but sometimes they'll be
in a different order. We'll clear out any
extra images by grabbing our wrapper and setting the inner HTML to
be an empty string. Before I forget, we'll call
our function from below, and then it grab our
images and create a for each loop to loop over
each one of these, images.forEach, plus
inner function. We'll create a reference to each one of these and we'll call it image source, imageSrc. Open up the function body, and then inside
here, we're going to create a new image element, store this inside a variable called elements,
documents.createElement. Write an image element. Then we need to set the
image element source to be equal to this variable. Grab our element, set the source attribute to be
equal to our image source. We can then grab our wrapper, which we stored
inside this constant, then we can add to
this new element, wrapper.appenChild, plus in our newly
constructed element. Let's go over to the browser
and see what we have. We've got our five different
images from this loop, but if we take a look
at the final version, we don't just want
five random images placed inside the container. What we want to do is to make the first image large
and then create a separate section
down at the bottom with the remaining small images. To do this, we'll create a wrapper for all the small
images down the bottom, and then it will
section this off the place a small
images inside of here, and the large image at the top. First, let's jump back into our function and create a new wrapper for the four
small images at the bottom. Just after where we clear
our upper and create a new constant called
the smallImageWrapper. This is equal to
document.createElement. This can be any
element you want to, such as a div or a section,
it doesn't matter. Then grab our section, and we'll give this a unique
ID of smallImageWrapper. Now what we want
to do is loop over all five of our
images in the array. The very first one needs to
be placed into the wrapper, and then all the four
remaining images need to be placed inside of
our small image wrapper. The way we can do this
is by first selecting our full wrapper and checking if it has
any current contents. If it doesn't already have an image element assigned
to this wrapper, this means it's a very
first image in the array. What we can do
inside of our loop is we can place in
an if statement, and we can check if our wrapper doesn't
have any child nodes. We can
say!wrapper.hasChildNodes. The code inside of here will
only run if this wrapper is empty and it doesn't contain any additional
images inside. This will always be the
case if we are looping through and on the first image. If this is the case, we
want to set the class. If we look at our style sheets, we want to set this
class of large because this has a width of 100 percent. If not, we'll add
the class of small, which gives you
this smaller size. It's add a class, we can grab our elements.classlist.add, place in the class of large, and also the class of carousel. This class of carousel, if we again go back into
the style sheet, will turn the cursor
into a pointer when the user hovers over the image. This one should only apply to the very first element
inside of our wrapper, else it should apply for
the remaining four images. We'll add a class list of small. We'll add all small
and also carousel. We still get the hover effect, but this time since
we are dealing with the four smaller images, we don't want to add these
to the main wrapper. Instead, we want to add them to our small image wrapper
which we just created. So let's grab this, open child, placing in our elements. Let's save this
and try this out. There we go. There's
our large image and the four small images
in these separate div. Just as a recap, what we've
done here is we've created one large wrapper for
all of our section. We've created a function
called set images. As soon as this runs,
it will clear out any existing contents
from the wrapper, which means that when we're
loop over our images, if this wrapper is empty, this means it's a very
first element in the array, therefore we give
it the large class. If not, it's the remaining four images where we
add the cluster of small and also place this
into a separate div. This is a good first
stage for our project. In the upcoming videos, we'll take a look up
various other functions such as reordering these images, how to swap the images around, and also how to place in the
arrows to go left and right.
66. Image Carousel- Creating The Arrows: We're at the stage now inside
of our project where we've got all five images
displayed on the screen. We have a large feature
image at the top, then of four smaller
thumbnails below. We have some
improvements to make, we want to be able
to click on any of these smaller images and places into the featured
section at the top. We also want to be able
to rotate these around by adding a small arrow to the
left and also to the right. Let's start with this by adding the arrows once the images load. To do this, we can place these into a standalone
function and call this. Let me call our images. Right at the bottom
of this function, we'll call our
function, which we're going to call setArrows. This is the function
which we'll create next. Just below this sets up our
function which was setArrows. SetArrows doesn't need to take in anything as a parameter, so what we need to do here is we can create
some icons using HTML entities and store these as the inner HTML
of a span element. What we need is two constants, the first one is the
left arrow just equal to document.createElement
as in a span. The reason we're using
the span is because by default this is an
inline element, so we can place these inline
with our four images. The left arrow, let's
access these constants and set the inner HTML
to be equal to our HTML entity and the
code for the left arrow is the ‹ Both of these arrows, you
need to click on these and reorder all our images. We'll select our
constants and add an event listener is
now a free click, which is going to
trigger a function. We've not yet created
this function, but it will be called reOrder and just so we don't
throw any errors, we'll create this
function above. This doesn't need to
have any contents, we'll come back to this. Just underneath
our event listener , we'll do pretty much the same, but this time for
the right arrow, we'll create the span
elements with document. createElement, set
the inner HTML, rightArrow.innerHTML. The HTML entity code for
the right arrow is ›, which is just one up
from the left arrow. Add an event listener. This one is also listening out
for a click event, which will trigger
the same function. Those are our two arrows created with the contents
and the event listener. But what we now need to do is to also add both of these
to a certain section. If we go back up to
our first function which was setImages, we have this smallImageWrapper. This is a div section
which contains over four small thumbnails, so we'll add both of
these into this section. The way we can do this at
the beginning and the end, is to use a JavaScript
method called appendChild, which will add this to the end of the div placed on the right. Then for the left arrow, we can use the prepend method, which will add this to the
very start of our div. Let's do this at the bottom
of our setArrows function. Let's first grab the section, so on the inside of a constant
called smallImageWrapper, the document.querySelector, this has the ID of
smallImageWrapper. Grab this section. As
I've just mentioned, we'll place the right arrow into this contents
with appendChild, like we looked up previously. This will add this
to the very end of our div section
after our images. Place in our right arrow, then let's add our left
arrow onto the start, we'll use the prepend method
and insert our left arrow. Let's try this. Now after refresh we still don't see
our arrows on the screen. Let's take a look
in the console and see if this gives us any clues. Inside of here we can
see we cannot read properties of null,
reading appendChild, so when we try to append a new element to our
single image wrapper, we seem to be getting an issue. The reason this happens
is because we've got a smallImageWrapper which we
are creating up in the top. When we set our images, we've got the main
wrapper that contains our full carousel and then, we have our smallImageWrapper, which we are trying to select
by this ID inside the loop. But each one of our images, we're creating a
new image elements stored in this variable, we add in either the
large or the small class, and if it's a small image, we then add in these
four small images to this smallIimageWwrapper. But currently,
we're not actually adding a smallImageWrapper
to the DOM, all we're doing is creating it and we're adding
our four elements, but we're not actually
placing this inside the DOM. At least then, we're able to select
down at the bottom. The only time
something is getting added to the DOM is when we add to our wrapper
this element. Adding the element directly to our wrapper is completely
fine if this is the large image because
it's going to go directly inside of
the main carousel. However though,
instead, when we're dealing with a small image, we want to assign these smallImageWrapper
to this section. The way we can do
it is by selecting our element and set this equal
to our smallImageWrapper. This smallImageWrapper
will contain all of our four small images and then we'll add this
to our main wrapper. Now if we save this and refresh, we can
now open this up, we see our two arrows jump into the elements and
if we go into the body, we see our main wrapper
with id of carousel. We can open this up. We see our first image with
the class of large. Just to clarify, this
one gets assigned because as soon as we start looping through
all of our images, we check if this
section is empty, if it is bigger
with the class of large and add this
to our wrapper. If not, the four
remaining images will get the class small and then add
to our smallImageWrapper, which will then be passed to our elements and then we
can add these to the DOM, which we can see if we look
at this div just here. We can open this up. We have our span element which we
just added with prepend, we have our four
images and then, the final span which we
added with appendChild.
67. Image Carousel - Re-Ordering Images: Previously we set up our
left and right arrows and also linked these to a
function called re-order. We did this by creating our arrows inside
of this function, using our for a click event, which in it triggered
our function. What we're going to do now is basically create a new array. This new array is going
to be constructed by rearranging the order
of all of these images. We'll do this inside
of our function by first taking in the event
information from the click, and the reason we're doing
this is because we need to determine which one of these
buttons has been clicked. We need to know if it's the left button or the right button. We can do this by adding an
ID to each one of these, so the leftArrow.id is
simply equal to left. Then go down to the
rightArrow.id right. Now we can access this ID by the elements information
from the event. We know from previously, by doing a console log, if we log the value of e.target, we get the actual elements
inside the console. Click on any of these get the right button, and
then the left button. We can also filter this object
down, grab just the ID. This now gives us a
unique pointer as to which button has been pressed. We can now remove this
from a console log, store this inside of a
constant called direction. Now what we're going
to do is we'll loop over our images array
up at the very top. We'll make sure we cover
all five of these images. We'll then determine if the left button or the right
button has been clicked, and then we use this
to reorder our images. These new images will be stored inside of a new array
called newArray, which will have the initial
value of an empty array, and then we can loop
over all of our images. Do this with a forEach loop, pass in our function. Then inside of
this function it's going to take in two things. First, as ever we'll
pass in a variable name, which is going to be each
image on that particular loop, and then we can also
access the index number. For the first loop, this will be the first item, which is our beach with
index number of 0. Then this will be incremented
one on each loop. To rearrange these images, we need to first check if the direction will
begin with left. If not, we'll add an
else section which will run if the direction
is equal to right. Let's think about
what we want to do if the direction is left. If this left button
is clicked on, we need to loop through the
original array which we are doing and for the very
first item inside of here, we want to push this to
the end of our new array, and then for the remaining four images down at the bottom, we want to shift these
back one position. We can do this inside
of the left button. We'll check if the index
number which will have access to just here
is equal to 0, so i.e it's the main
featured image at the top. If this is the case,
we want to push it to the very end of our new array. We can do this by
accessing this variable. We can position this
at the very end by accessing our
images.length property. For this example, this should be our new array in
index Position 5. We'll set this to
be the first image. Grabbing our first image from the original array or pushing it to the end of our new one. We'll also call it
newArray.shift in case there is anything at the
beginning of this array. This is the very first
image taken care of, but what about the
remaining four? Well, for the
remaining four images, all we need to do is grab the current index number
and shift this back by one. We can do this inside of
an else section just after if, placing else. This will be the case for
the remaining four images. All we want to do is
to grab our new array, select the current index
adopt the value of one, and set this equal to
our current image. For example, if our current
image is index Number 3, this will be moved
back to Number 2, and this will be the same for
the four remaining images. Before we go any further,
let's try this out. Currently our function is being called and it's
reordering the array, we will also need to override the original images
array with our new one. We can do this at the
bottom of our function, set our images array to be
equal to the new array. Also once we reset
our images array, we need to call the
set images function, which is actually looping over this array and displaying
them on the screen. We'll do this at the bottom. Call this function, and
let's try this out. Refresh. If we click on
the "Right Arrow," we get an error because
we haven't handled this case yet. Let's try left. We have the elephants, which has now moved up to
the first index position. Let's click "Left" again.
This has moved to the end. This all seems to
be working fine. Just to recap, we're grabbing
the very first image and then pushing this at
the end of our new array. Currently it's the beach,
this goes to the end, and the remaining four
images will be moved to the left by one index number. Next we'll take care
of the L section, which is if the right
button has been clicked. What we're going to do
in here is basically the opposite of this
line just here. We need to, rather than remove one index number
to move it to the left, we need to add one to move
each one to the right. We'll paste this in, all
at an index number of one. This time rather
than checking if we are accessing the first image, we need to check if we're
accessing the last image. If it's the last image, it
then needs to be removed from the end of the array and
placed at the very beginning. We can do this inside
of an if section. If the index plus one is
equal to images or length. The reason we've done
this, because remember index numbers begin at 0, but when we're accessing
the length of the array, this will be the actual number. Currently this is 5, but the index number
only goes up to 4, meaning we need to plus 1. Check if this is
also equal to 5. So if this is equal to 5, this is our very last
value in the array. We need to set this to be
at the very beginning. The very beginning
of our new array is going to be index Number 0, which we can set to
be the current image. Also just like we
did when we removed the very first item
from the array, this time we need to remove the very last one, newArray.pop. Let's save this and
try this one out. To the right, we should see the houses moved to the top and the beach
moved to the left. Good. Now the lake, the grass, the elephants. This now works completely
fine in both directions.
68. Image Carousel- Swapping Images: Our next and final task
for this project is to allow the user to
click on any one of these small thumbnail images, and then this will
be swapped with the main featured
image at the top. The small image
will be placed in the large section and
the large image will then drop down to
the thumbnail area. For this one we create
our small images of any set images function
jump into the else section, where we add the class of small. What we'll do here is we'll add an event listener to each one. Just after we add the class. Before we append
this to our wrapper, we'll select our elements, add an event listener, where we listen off the click, which is going to
trigger a function which will create in just a
moment called swapImages. Down to the bottom. Our
function will swap images. Inside of here we've
added the event listener, which is going to
trigger this function, each time we click on
one of the small images, but just as an extra
precautionary step, we'll add an if statement
to return out of this function if the large
image has been clicked. What we'll do is since this
is an event, click "Event", we'll parse in at the
event information, we'll access the
elements with e.target. Then we can check if
the classList contains, using a contains method, the class of large. If it does, we'll return
out of this function. This is just for our precaution. Next, what we'll
do is we'll grab using the event.target
information, the actual image which
has been clicked on. All we need to do is grab
the source attribute, find out which one
of these images needs to be placed in
the featured area. We can do this,
install this inside of a constant called image source, set this equal to e.target. We use getAttribute, where
we'll grab the source. Now we have this image source, which is the file path. We can use it to grab the
index number of our array. We'll grab our images array. We'll then use the
method called indexOf, and then parse in our source. So the images, indexOf, as in our variable name,
which is image source. This is going to return
back an index number which you can store inside of a constant called
selectedImageIndex. What we have now is
the index number of the clicked on thumbnail. Now we need to swap this
with the image which is at index position 0. But before we actually
make this swap, we need to first
store which image is currently in index position 0. We'll store this inside of a
constant called firstImage. Set these two images
at index number 0. Then we can reassign the
original first image, so images 0 is going to
be now equal to images, and parsing this
selected image index. This replaces the first image with the clicked on thumbnail, and then we need to do
the reverse by, again, selecting our images at
the selectedImageIndex. Swap this with our first image. This is a reason why
we originally start off firstImage because
on the next line, we're reassigning
the first image. Therefore, it gives us the
wrong value at the bottom. At the end we'll call
our setImages function, to re-update the browser with
the new order of the array. Let's try this out
for the grass. The beach drops
down to the bottom, we'll try this one. Good. This now completes
our image carousel project. I hope you've enjoyed
it and learned some new tricks along the way.
69. Follow me on Skillshare: A huge congratulations
from me for reaching the end of this class. I hope you've really enjoyed it and gained some
knowledge from it. If you've enjoyed this class, make sure you check
out the rest of my classes here on Skillshare, and also follow me for
any updates and also to be informed of any new classes
as they become available. Thank you once again, good luck, and hopefully, I'll see you
again in a future class.