[MUSIC PLAYING]
SPEAKER: Welcome back, everyone, to Web Programming with Python and JavaScript,
and last time we took a look at JavaScript, that language that
ran inside of a user's web browser client-side
and allowed us to do a number of things to make our web pages more interactive.
JavaScript enabled us to display alerts, to be
able to manipulate the DOM, the structure of the web page,
in order to add content or see what content was already there.
And it also let us respond to user events.
When the user clicked on a button, or submitted a form,
or typed something into an input field, we
could have JavaScript functions run that responded to those events
in order to make our web pages more interactive.
Today, we're going to continue that conversation, in particular,
taking a look at user interface design, looking
at some common paradigms in terms of user interfaces
and how we can leverage JavaScript to be able to achieve those goals to create
interactive user interfaces that will be very valuable when users
are interacting with our applications.
So one of the more common paradigms, especially nowadays, in web programming
is the ID of single-page applications.
Thus far, if we wanted to create a web application that
has multiple different pages, we've generally
done that via multiple different routes in our Django web application,
for example, where you go to slash something to get one page
and slash something else in order to get another page.
But commonly using JavaScript, we have the ability
to create single-page applications, where the entire web page is really
just a single page, and then we use JavaScript to manipulate the DOM,
to replace portions of the page with things we want to replace.
And this has a number of advantages, one of them being that we only
need to make modifications to the part of the page that is actually changing.
If, for example, you have five different pages,
but the general layout and structure of the page
is pretty similar, when you switch between pages, rather than load
an entirely new page, you can just load the part of the page that is changing.
And this is especially helpful for applications
that are changing quite frequently.
So let's take a look now at how we could implement, for example,
a very simple single-page application.
So let's imagine, for example, that we want a single-page application that
just displays three different pages, but all included in the same page.
I'll go ahead and create a new file that I'll call a singlepage.html,
inside of which we'll include our usual HTML tags.
And inside the body of this page now, I'm
going to include three different sections of the page
to represent the three different pages I might want to display to the user.
So I'll have a division whose ID is page one that maybe just has a heading that
says, this is page one-- and you can imagine there's more content on these
pages, as well--
a div whose ID is page two--
we'll say, this is page two--
and then one final div whose ID is page three and has a heading that
says, this is page three, for example.
Now, right now, if I were to open up singlepage.html, what we'd see
is we see all three pages at the same time.
Now, that's probably not what we want.
What we really want is by default to hide these pages
until we want to view the pages one at a time, for example.
So one thing I could do is use CSS to be able to toggle whether or not
something is visible, adding some style tags to my page,
to say that by default, all of my divs should have a display property set
to none, meaning they're not visible.
They're not displayed on the screen.
Now if I refresh the page, I don't actually see any of the three headings
that I had there before.
But what I'd really like is for some buttons,
now, to allow me to toggle between these three pages,
so I'll give myself three buttons, one button that says,
page one, one button that says, page two, and one button that says,
page three, for example.
And I need some mechanism for these buttons to know,
when you click on this button, what page should be displayed, so I'll go ahead
and use data attributes, which we saw last time with JavaScript,
to add some additional information to these particular HTML elements,
where I'll give the first button a data-page value of page one,
the second one a data-page value of page two,
and the third one a data-page value of page three.
Here, again, just providing information so that later,
when I write some JavaScript, I can have the JavaScript code look
at the data page attribute to say that when you click on this button,
you should let me see the div whose ID is page one.
That's what this is going to allow us to signal.
So now let's go ahead and write that JavaScript.
What I want to be able to do is to be able to say,
I would like to show page one and hide the other two, or show page two
and hide the other two, or show page three, for example.
And so to do that, I'll first write a function that will let me do that.
I'll right a function called showPage that
takes as its argument what page I want to show.
And so what should this function do?
Well, what we're going to do is we're going to say document.querySelector,
and I want to get the thing that has a particular ID, the ID
of whatever this input happens to be.
This page is going to represent the ID of the div that I want to show.
So I say, get me the thing that has this ID,
and then using a template literal, I'll say, all right, get me the ID of page
whenever element has that particular ID.
And then I'd like to change its style property.
Which part of the style?
Well, I want to change its display property.
And instead of none, which was the default here, where I said,
don't show it at all, the other option for a div
is block, meaning it shows up as just a block that is
on the page that is actually visible.
And so now I have this showPage function.
And I can test it, in fact.
If I go into my browser, refresh the page, I now see three buttons.
The buttons don't do anything just yet, but what I can do is in the console,
if I actually just try running this, I can run the showPage function
and say, like, showPage page1, for example, press Return.
And page one now appears on my page.
And if I [INAUDIBLE] showPage page2, then page two will become visible.
And that did half of what I wanted.
Page two is now visible, but so is page one.
So I probably want it such that if I ever show a page,
I hide the other pages first.
Like, hide all the pages, and then show page two, or hide all the pages,
and then show page three.
So how could I go about doing that?
Well, first, I might want to just, when I show a page,
first hide all of the other pages, hide all the pages.
So to get all the pages, I'll do document.querySelectorAll,
get all of the divs, which is what I'm using to enclose the pages
and now, for each one of those, again, effectively
creating a loop, where I'm looping over each of the divs.
For each div, let's go ahead and set the div.dot.display property equal to none.
And so what this showPage function is now doing
is it is first querying for all of the divs, which
are simulating my pages inside of this single-page application.
And for each one of the divs, we're going
to pass it as input into this function, which is the argument to forEach,
again, using this arrow function notation, which is just a shorthand
way of expressing a function, where I'm here saying that for each of the divs,
we'll go ahead and modify it style property,
setting display equal to none, meaning don't show any of the divs.
And then show only the div that was requested.
So now this should solve the problem of multiple pages appearing
simultaneously.
And if I go back to this page and I write, showPage page1, then page one
appears.
But if I run showPage of page two, then page two appears,
but page one disappears.
And likewise, when I showPage page three,
that shows page three, but not the other two.
So I can manipulate which page is visible all via the console,
but now what I'd like to do is get these buttons to actually work,
where if I click on one of the buttons, that has the effect of actually
displaying the requested page.
So in order to do that, well, I want to attach some event listeners
to these buttons, which means I need to wait until those buttons have loaded
onto the page.
So we'll use document.addEventListener DOMContentLoaded, again,
waiting until all of the content on the page has been loaded.
And then only then will I say, let's go ahead
and querySelectorAll for all of the buttons.
And for each one of those buttons, let's go ahead and attach an even listener
to each of those buttons.
So I'm querying for all of the buttons and saying for each of the buttons,
I would like to do this with each button.
And what I'd like to do is say button.onclick.
When the button is clicked on, go ahead and run this function.
I'd like to show page--
and which page do I want to show?
Well, I want to show whatever page is in the page part of the button's data set.
And to get it the current button, the button that has been clicked on,
recall that when we're inside of an event handler,
we can take advantage of the JavaScript keyword "this,"
which refers to whatever element has received the event, so whatever button,
in this case, that was clicked on.
So I can say, this.dataset.page to mean that for each of the buttons,
when the button is clicked on-- we're saying button.onclick for each
of the buttons--
run this function when the button is clicked.
We'd like to show a page.
Which page do we want to show?
We'll take this button, the button that received the event,
access its data properties, access its data page attribute, which
are down here, either page one, or page two, or page three,
and go ahead and just call the showPage function that we wrote a moment ago.
So now that we've done that, we've attached these event handlers
to the buttons.
So now if I refresh the page, I can click on these buttons
and toggle between any of the three pages.
And now, the interesting thing here is that we now
have the ability to just, in a single page,
allow myself to simulate the idea of having multiple pages, all enclosed
in a single HTML file, but not needing to consistently make
additional requests to a server in order to get access to that information.
Now, sometimes, though, it might be reasonable to want
to reach out to a server when you need new information for a page,
for example.
You might imagine that each of these pages contains a lot of text.
It's going to be inefficient if immediately we're
loading all of that data into HTML and just showing and hiding them
when we need to, because maybe we're loading
more information than the user is ever going to actually care about
if they're never going to look at page two or page three.
So one thing we might imagine doing is loading this data dynamically.
Last time, when we were talking about JavaScript,
we saw how we could use fetch to say, go ahead, and request
some additional information from a web server.
Last time it was currency exchange rates.
But then we used that data that came back in order
to fill in something onto our page.
And likewise, we could do a similar thing here,
that if we have the general structure of a single page
and we want to load new content, rather than load entirely
new HTML content and reload the entire page,
we can just ask our own web server for what part of the page
needs to change and then just replace that part of the page.
And so that's what we'll take a look at now,
now combining Django for our web server and JavaScript
for writing the client-side code to be able to generate
a single-page application.
And so for this, we'll go ahead and go into an example I had an advance called
singlepage1, and inside of singlepage1, this
is just the Django application with a single app called singlepage.
And what we'll notice is-- we'll go to URLs first.
There are two URLs, one default URL that just loads the index function,
and then a URL for loading different sections of a page
that I might want to dynamically load, for example.
So I have sections slash some particular number.
And if we look at the views for what it is these URLs else are actually doing,
the index function just returns index.html.
And then what the section function does is
it first makes sure the numbers between 1 and 3,
and if so, responds with one of these just strings of text, for example.
So how does this actually work?
If I go into singlepage1 and run the server, if I go to this URL,
/section/1 section, for example, what I get is this block of text.
And if I go to /section/2, I get that block of text, section/3,
a different block of text altogether, so just different text.
And I'd like to incorporate this text into an existing HTML page,
for instance.
So here, I'll go into index.html, this template that gets
loaded when I go to the default route.
And inside of index.html, what we'll see is
I have a showSection function that behaves
very similar to the showPage function we saw from a moment ago.
But instead, what showSection is going to do
is it's going to fetch what text I should display
on the page from my own web server.
I'm fetching from /sections/ fill in a number here, a number like 1, or 2,
or 3.
When I get the response--
in the past, we've seen how we can convert that response into JSON data
if it's unstructured data.
We can also just convert the response into plain text.
Then I'll take that text, console.log it,
just so we can see it in the log output, but then go ahead and querySelect
for the content of the page, something that has an ID of content,
update it's inner HTML, and set it equal to that text.
So what this entire function is now doing
is it is going to reach out to my server,
figure out what text content belongs in the new section,
and fill in the part of my page accordingly with the text that
comes back from that HTTP request.
And then down further below, inside of the page,
we'll see that I have a Hello!
heading, three buttons that toggle between the different sections--
each of them has a data-section attribute this time for which section
should be loaded--
and then a div that is initially blank just for the content of the page.
So putting this all together now, If I go to the default route,
I see Hello! plus three buttons to give me
a choice between three different sections.
And if I click Section 1, what's going to happen
is JavaScript is going to query/section/1, ask for the text.
It gets that text back, and it's going to fill it
in into the page, section one, section two, and section three, so very
similar to before.
But unlike what we had before, where all of the text
was being loaded into the HTML page all at once,
now we're using asynchronous JavaScript to only dynamically load
information when we need it.
When we click on a section, then it's going
to make the request for what content needs to be filled in,
and it's going to fill it in.
And everything else, these buttons, this heading--
and you might imagine, in a more complex website,
you've got a lot more going on around the edges of this web page.
All of that stays the same.
We don't need to reload any of that information.
We're only reloading the portion of the page that
actually changes as we toggle between these various different section
headings.
Now, this seems to be an advantage in some ways,
that maybe we can be more efficient about how we run
our single-page applications like this.
One thing we seem to lose, though, is the notion
of maintaining state inside of the URL, that generally,
the URL gives you an indication for what page you're on.
You're on something like /1 if you're on section one,
or /2 if you're on section two, /3 for section three.
But of course, we're staying on the same page in all of these examples.
Whenever I click a button, section 1, or 2, or 3, the URL is never changing.
URL stays the same.
It turns out there's a way in JavaScript to manipulate
that URL, taking advantage of what's known if the JavaScript History API,
where I can push something to the history, meaning update the URL,
and actually save that inside the user's browser history so later on,
they could potentially go back to that.
And to do that, I'll show you yet another example inside of singlepage2,
which is very similar, except inside of index.html,
I've added a couple additional things.
One is that when I click on a button, meaning
when I click on section one, or section two, or section three,
I've added this line here, history.pushState.
What history.pushState is going to do is it
is going to basically add a new element to my browsing history, where I first
specify any data associated with the state.
So in particular, I'm storing a JavaScript object
representing what section number is being represented here.
Next is a title parameter that most web browsers actually ignore,
so that can generally be the empty string.
But the third argument here is what should go in the URL,
and what I want to go in the URL, in this case,
is something like section followed by the section number.
So I can go to /section1, or /section2, or /section3, for instance,
and those will appear in the URL bar when I click on a different page.
Then what I want to be able to support is the ability
to say, when I go back through my history,
if I click the Back button in my web browser,
I'd like to go back from section three to section two
if that was the page I visited previously.
And there turns out to be an event handler for that, as well,
window.onpopstate, meaning when I pop something off of the history,
like, go back in my history.
We have the ability to take some event as an argument.
And if you look at event.state.section, which I've run console.log on,
so we can take a look at it in a moment, we'll
see what state was stored associated with that part of the user's history.
And I can go ahead and show that section.
So all in all, when I run this web application,
going into singlepage2 this time, when I run the server, I see Hello!,
three sections for buttons.
When I click on one of those buttons, not only do I see text,
but I also see in the URL bar that I'm now on /section1.
That has been pushed onto my history, and I've
updated the URL to reflect that, too.
I click section two.
That updates the URL, as well.
Section 3 updates the URL, too.
And when I've pushed things onto my history,
I've associated some state with them so that I can go back if I ever need to.
And in fact, if I open up the JavaScript console now and I go back,
for example, back to section two.
What you'll see is that what gets logged is the number 2.
When I print out what is the current section that's
associated with this URL, it's saving that state,
that I should be loading section number two.
And so it does load section number two here.
So there's certainly nothing wrong with the original paradigm of just loading
different pages dynamically using Django, like, make a request,
and get a response.
But oftentimes, as you begin to imagine applications where a lot of things
are changing on the same page simultaneously,
you might imagine social networking websites, where a lot of things
stay the same.
But new posts might be added, and you might be looking
at different parts of the same page.
Being able to dynamically load information,
request additional information, and then display it on the page
can actually be quite powerful and a way to make your web pages a little bit
more interactive.
So that, then, is how we might build the single-page application,
taking advantage of JavaScript to asynchronously load new data
and then taking advantage of this History API
that let us add things to the URL, add things to the user's browsing history,
such that we could go back to them later by listening for window.onpopstate.
And it turns out that window object that we get access
to in JavaScript is quite powerful.
It represents the physical window on the computer screen that
displays all of their web content, and there's
certain properties of that window we can look at that allow
us to enable some interesting features.
So for example, your window is really described
by what the user actually sees inside of their window in Google
Chrome, or Safari, or whatever web browser they happen to be using.
And there are a couple of properties that might be of use.
Something like window.innerwidth will represent
how wide is the window, which might be useful to know
to know the size of the user's screen, for example, to know how many pixels
wide the window happens to be.
And just as there is a window.innerwidth,
there is also a window.innerheight that represents the height of the window,
as well.
Now, "window" represents the physical part that they're actually seeing.
We've also seen another variable that JavaScript gives us access to,
and that is this document object.
So what is the difference between the window and the document?
Well, the document generally represents the entire web page,
but if web pages are long, oftentimes a web page
doesn't fit entirely inside of the window,
that you generally have to scroll through an entire web page.
And the window is only showing you one portion of that page at any given time.
So you can represent the document as this big vertical section
that goes beyond the window.
There might be part of the document that is
above the window, part of the document that is below the window, as well.
So window.scrollY is another variable you have access to on the window,
and window.scrollY represents how many pixels far down have you scrolled.
So if you're at the top of the page, window.scrollY is 0.
You haven't scrolled at all.
But as you begin to scroll, if you want to know how far the user has scrolled
on the page, you can look at window.scrollY
to figure out the number of pixels the user has
scrolled in the y-direction, the up and down direction.
And the entire height of the page is represented
in document.body.offsethight.
That represents how tall the entire height of the document is.
And we talk about all this, in addition to
things like window.innerheight and window.innerwidth,
because using all of these values together,
you can begin to do some interesting calculations.
So one thing you might want to detect, for example,
is, has the user scrolls down to the bottom of the page or not?
That might be something you care about knowing.
And it turns out there isn't an event listener that does this automatically,
but we can calculate it in order to try and figure this out.
If inner height is the height of the window,
scrollY is how far vertically the user has scrolled,
and document.body.offsetheight is the entire height of the document, you
can ask yourself, what needs to be true if the user has scroll
to the bottom of the page?
And well, if the user has scroll to the bottom of the page,
well, then scrollY, plus the inner height,
meaning the amount that they've scrolled,
plus the height of the window--
that must be at least or equal to document.body.offsethight,
meaning the amount that they scrolled plus the window takes you down
to the bottom of the page, to the end of the page,
to whoever tall the document happens to be.
And using that mathematical comparison, we
can actually detect when the user has reached the bottom of the page,
and we can actually try now put that into practice.
So I'll go ahead and open up an example that I have here called scroll.html,
and all scroll.html has right now is 100 paragraphs.
Inside of the body tag I have a p for paragraph, paragraph 1, paragraph 2,
so on and so forth.
I have 100 paragraphs inside of the body of this HTML page,
and that's all that really is there right now, such
that now if I go ahead and open scroll.html,
I see that I have 100 paragraphs that I can scroll through.
And what I might like to do is detect when
I've reached the bottom of the page and maybe do something when I do so,
something like change the color of the page, for instance.
So might I go about doing that?
Well, I'm going to need some JavaScript, so I'm going to add some JavaScript.
And I'll add an event listener for window.onscroll.
Onscroll is an event that listens for when I'm scrolling through the window,
and when I scroll through the window, we'll go ahead and run this function.
We'll just use an arrow function as a shorthand here.
What do I want to calculate?
Well, I want to calculate if window.innerheight,
meaning the height of the window itself, plus window.scrollY,
meaning the amount that I've scrolled--
if that is at least document.body.offsethight,
well, that means I must have scrolled to the bottom of the page
or maybe even a little bit further if there's a little wiggle room
to scroll past the end of the page.
So if this is true, well, then I've reached the end of the page.
And then we'll go ahead and say, document.querySelector body.
And let's go ahead and change its style, in particular,
change its background color and change the background color to green.
Otherwise, if we haven't reached the end of the page,
then we'll take the body of the page and change its background color to white.
So what we're now doing here is taking advantage of the properties
we know of this window object, saying when we
scroll the window, let's check to see--
if we add this up and it's at least the height of the entire document,
we've reached the end of the page.
Go ahead and change the style of the background to the body accordingly.
Otherwise, change the background to white,
or leave it at white if it already is.
So now if I take a look at this actual HTML page and reload scroll.html,
we'll see that the background is initially white.
But as I scroll down, once I reach the bottom,
we'll see that the page changes to green.
It's white before I reach the bottom, but as soon
as I get to the bottom of the page, it turns to green.
And the reason why is because the height of the window here plus however much
I've already scrolled from the top of the page up until now--
that together is equal to the entire height
of the document, which means we're able to detect the fact that I've
reached the end of the page.
And as a result, we can change the color of the background to green.
Now, this in itself is not a particularly practical use
of detecting when we scroll to the end of something.
We probably don't usually care about changing the background color
when you reach the end of the page.
But there actually are real applications,
and you might imagine this in the context of websites
that allow for things like infinite scroll,
that if you're on a social networking site that has a whole bunch of posts,
you scroll to the bottom of the list of posts,
and then it generates the new set of posts, as well.
Or you're looking at news articles, and you're scrolling through news articles.
And once you reach the bottom, it'll load a whole new set of news articles
without you having to go to another page.
How is it doing that?
Well, it's a combination of the same types
of ideas that we've been talking about, number one, the ability
to detect when you've reached the end of the page,
using JavaScript to detect that you're at the bottom of the page, and number
two, to be able to asynchronously, using JavaScript, load additional content,
fetch some additional page that has some additional content,
some additional news articles, some additional posts, and whatnot
and then take that information and manipulate
the DOM to add that information to the existing web page.
And that ultimately is what's going to give us
this power to be able to support something like infinite scroll.
So let's now go ahead and try and see what it would
look like to implement infinite scroll.
I've already started to create a sample application
inside of this application called scroll,
and I've got an app called posts inside of it.
And what the posts app does is it's has got a couple of URLs.
It's got a default URL that just loads an index row
and then a posts route that loads this posts view.
And so let's look at what these do.
Index-- all it does is it's going to load a file
called index.html, this template.
And if I make a request to /posts, I need to provide two arguments.
I need to provide a start for what post I want to start with,
an end for what post I want to end with, and then
it's just going to generate some sample posts that just say, like,
post number one, post number two, so on and so forth.
In practice, you could actually use social network posts in place of this,
but this is good just for demonstration purposes.
So what this is going to do, if I go into scroll and run server,
is that if I go to /posts and say, start equals 1 and end equals 10,
for example, then I get a JavaScript object that looks like this.
Recall that a JavaScript object is just a convenient format for passing
information back and forth in JSON format,
and what we have here is a JSON object with a key called posts that gives me
all of the posts, post number one, post number two,
all the way up to number 10.
And it's giving me those posts because I said, start at 1, end at 10.
But I could have specified other numbers, as well.
If I had said something like, start at 20, and go to 28,
then it's going to give me post number 20 through post number 28.
I can specify the range of posts that I want.
So this now is an API that I have implemented effectively
that allows someone to get access to a variety of different posts
by hitting this particular URL, this end point, so to speak,
and passing in parameters, passing in what post they want to start with
and what post they want to end with.
And then they get all of this data back presented to them
in JSON format that can then be used.
And what's nice about this is that now, when we're loading posts,
rather than have to just guess at how many posts we need to load
and then require someone to go to another page,
we can just do something like load the first 20 posts.
And now what we'd like to do is if they reach the end of the page,
go ahead and load the next 20 posts by hitting this API end point,
getting the next 20 posts, and then filling that in into the HTML page.
So let's see now how that actually works in practice by taking
a look at that template in index.html.
So we go into templates, index.html, and there's a fair bit of JavaScript here.
But look at the body first.
The body just has a div for all the posts
that initially is going to be empty.
Now, here's what the JavaScript is going to do, and we'll walk through it.
We start with the first post, so counter is
going to keep track of what post we need to load next.
By default, we're just going to start by loading post number one.
We have a variable called quantity that's
going to tell us how many posts are we going to load at a time.
Let's just say, load 20 posts at a time.
So start with 1 to 20, then 21 to 40, 41 to 60, so on and so forth.
And when DOM content is loaded, go ahead and just
call this function that's called load.
And what the load function does is it figures out
what the start and end should be.
It fetches all the new posts.
And then for each of the posts that comes back,
we call this add post function.
So we're asynchronously asking for new posts,
and what the add post function does is it creates a new div,
populates the post inside of it, and adds it to the DOM.
So now that we have these parts, the ability to load new posts,
as by fetching from some URL all of the posts that we care about,
and then for each of those posts that comes back, add something
new to the DOM, as by creating a new HTML element
and inserting it into the page, we have the ability
to dynamically load all of these posts.
So if I go, not to /posts, but just to this default route,
I'll see that we have something like 20 posts that all show up,
but just 20 posts, because every time I call the load function,
that is going to load the next set of posts, for example.
And so what I can do is in the console, if I try running the load function just
by calling it myself, press Return, after a second or so,
the next set of posts show up, 21 all the way through 40.
I call load again.
The next set of posts show up, 41 through 60, 20 posts at a time,
all using that asynchronous JavaScript.
But now what I'd like to happen is for all this to happen on its own
without me having to intervene and manually write JavaScript calls.
I would just like to say, well, the same type of logic
as before, window.onscroll.
Let's go ahead and say, if window.innerheight plus window.scrollY
is at least document.body.offsethight, meaning
if I have scrolled to the end of the page,
we'll then just go ahead and call the load function.
That's all these lines are doing.
Every time I scroll, we check.
Did we scroll to the end of the page?
And if we did scroll to the end of the page,
then go ahead and load the next set of posts.
So now I refresh the page.
I see post one all the way up through post 20.
Now watch what happens when I get to post 20.
If I scroll to the bottom, after a second the next set of posts appears.
I scroll to the bottom again.
I'm at 40.
And then after a second the next set appears.
Every time I scroll to the bottom, more posts are going to load after that,
allowing me to effectively implement this idea of infinite scrolling
by taking advantage of some JavaScript techniques, where I can check for when
I've got to the end of the page and then dynamically do something
as a result of that, something like load some additional pages onto the screen
and so here, too, a lot of power to be had inside of JavaScript.
And a lot of where the power of user interface comes from
is from how it is that the user interface interacts with the user,
thinking about what the there is going to do
and how the page should interact as a result, something like user scrolls
to the end of the page, and they see some new pages show up, as well.
And one technique we can use for just making HTML elements a little more
responsive, a little bit more interesting
is by adding some animation to them, as well, the ability for things
to move around and change their properties in some way.
And it turns out that CSS has support for animation.
CSS has already given us support for things like styling elements,
saying we want this element to be this color and this size, for example.
But it also gives us the ability to animate those properties, as well,
to change the size of something or change the position of something
over some amount of time.
And so let's now take a look at an example of what that might actually
look like.
I'll go ahead and create a new file, and I'll call it animate.html.
And inside of animate.html, I'll go ahead
and start by including our usual HTML.
Title is Animate.
And what I'd like to do is just add a little bit of animation, using CSS,
into this particular page.
I'm going to start with just a heading, a heading that says
something like "Welcome!" for example--
it's just going to display a welcome message--
such that now, if I open animate.html, here's what I see, just a message
that says, "Welcome!"
But now let's add some CSS to it.
Let's go into the style tag, and for each one for this heading,
I'd like to apply a particular animation to it.
And I first need to specify what the animation's name is going to be,
and I can pick a name for the animation.
I'll say something like grow, for example.
I'll set the animation's duration to be two seconds,
and then the animation fill mode is what direction should the animation move in.
Should it go forwards, or should it go backwards?
We'll generally want our animation to go forward
so they're making some sort of forward progress according to some rules
that we're going to specify.
So here I'm saying we're going to animate all of our headings
using an animation called grow.
And now we need to define what that animation actually does,
and to do that, up above, in style, I'm going to say, @keyframes grow.
And what this is going to allow me to do is
specify some keyframes for this particular element,
meaning where should the element start, what should its style properties be.
And then at the end, what should its style properties be?
And CSS is going to take care of the process of figuring out
what needs to happen in all those intermediary fractions of seconds,
for example.
So what I can say is something like, go ahead and grow from,
meaning what should its initial properties be?
And maybe initially I wanted to have a font size of 20 pixels.
And then we'll say, to font size of 100 pixels, for example.
So all in all, what this is saying is I would
like to apply an animation called grow to all of my headings.
This animation should last two seconds and go forwards.
And what is the grow animation going to do?
Well, it's going to mean at the start, anything
that obeys the grow animation will start with a font size of 20 pixels,
and at the end, it will grow to a font size of 100 pixels.
And I have now defined what it is that that animation means.
So now, if I go ahead and refresh this page,
animate.html, you'll see that "Welcome!" changes size.
Over the course of two seconds, it goes from smaller to larger by obeying those
key frames.
I told it to obey this particular step, a set of instructions
where it goes from a particular font size to another font size.
And as a result, we see the effect here on the page.
And it turns out you can do more than just manipulate size.
You can manipulate just about any CSS property you want.
So if I tell the heading that it should have a relative position,
meaning its position should be relative to other elements or other things
in its parent, I can say, you should change your position from being 0%
from the left side of the screen to being 50% of the way
from the left side of the screen.
And at this point, grow is probably not the best name for this animation.
I'll call it move instead.
So animation name is move.
And so now with this animation is going to do is it's going to say,
when you run the animation, go from being
right next to the left side of the screen to being about 50
away from the left side of the screen.
So I can go ahead and rerun this, and we see
that's the animation that takes place.
It goes from the left all the way back up to about halfway across the screen.
Refresh the page, and it goes ahead and does the exact same thing.
And it turns out we don't just need to specify a beginning point and an end
point for an animation.
We can specify various different keyframes for different points
within the animation that we would like to capture,
something like at the beginning of the animation,
have this set of CSS properties.
Maybe halfway through the animation, have a different set of CSS properties,
and then at the very end, have yet another set of CSS properties.
So I could say something like-- if I want the heading
not just to move from left to right, but also to move back again, I can say,
at the beginning, at the 0% point, when you're 0% of the way
through the animation, you should be 0% away from the left-hand side.
When you're 50% of the way through the animation,
you should be 50% away from the left-hand side,
and then when you're done with the animation, 100% of the way through,
let's go back to 0% away from the left-hand side.
I now have three keyframes, beginning of the animation, middle of the animation,
back to the beginning of the animation again.
And the effect of this is if I refresh the page, we go to the right,
and then we go back.
We're able to move one direction and then move back.
And there are other properties we can use to manipulate these animations, as
well.
I can set the animation iteration count, for example,
to 2 to mean, rather than just do the animation once and then stop,
do the animation twice and then stop.
So I refresh.
It goes to the right, and then it goes left.
And then it repeats that a second time.
And it turns out, if you really want, you
can set this to infinite to mean never stop performing that animation.
It's consistently going to have this heading move to the right
and then move left, according to those keyframes that I've specified.
And so if you ever see things moving around on a page,
interactive in some way, there are a number of ways to do it.
You can animate things using JavaScript, for example,
but there are many cases where CSS alone is pretty good at just
creating these types of animations.
And while this animation right now is just running forever,
we could use JavaScript in order to control that animation, as well.
So let's see an example of what that would look like.
I'll go back here.
In the body of the page, in addition to a heading that says "Welcome!"
I'll go ahead and add a button that just says, click here, for example.
And now what I'll do is add a little bit of JavaScript.
I'm going to add some JavaScript so that the button can now
control the animation, decide when the animation is going to start and stop.
And so what we'll do inside of the script is to first say,
document.addEventListener DOMContentLoaded,
meaning wait until the DOM is done loading, as we've done before.
And let me now get that h1 element, document.querySelector h1.
And initially, I'm going to set its style.animationPlayState
equal to paused.
So animation placed it as a property of the style that
lets me decide if the animation is playing or paused,
and I can control that using JavaScript.
Rather than just say, run infinitely forever, I can say,
the animation-play-state should start out
as paused by first getting the h1 element then modifying
the animation-play-state property of that particular element.
But now what I'd like to happen is anytime someone clicks on the button,
I want to change the animation-play-state.
So I'm going to say, document.querySelector button, meaning
get that button, and when someone clicks on the button,
let's run this function, where if the current animation-play-state is paused,
we'll then go ahead and set animation-play-state equal to running.
And otherwise, if it's already running, then
let's go ahead and set the animation-play-state equal to paused.
So all in all, what this function is going to do
is it's going to get me the heading, pause this initially,
and every time the button is clicked, run this function, where
the function says, if we're paused, go ahead and start running the animation.
Otherwise, go ahead and pause the animation
by modifying that animation-play-state property of the heading.
So now if I refresh this page, right now we have "Welcome!" plus a button
that says, "Click Here!"
And initially, everything is paused.
There's no animation happening.
But I click here, and that begins the animation, which
would go on indefinitely until I decide that I want to stop it, at which point
I click it again, and the animation pauses.
And I can control when to start and when to pause that animation, as well.
And so this can be helpful and nice when you
want to create something a little bit more interactive,
something animated on the page, but this is especially helpful
because it means that you can gradually change CSS properties over time.
Rather than just immediately change something,
you have the ability to animate something,
to make it work a little bit better.
So let's take a look at an example of how
you might put that idea into practice.
Let's go back to our posts example, where we had this infinite scrolling
list of posts, but imagine now that we want the ability to hide posts
when we're done with them.
So I've prepared an example called hide, which
is very similar to what we had before.
But this time I've just added one extra button,
and the button says, "Hide," on every single div.
Right now, clicking the Hide button does nothing.
We'll go ahead and implement that in just a moment.
But first, to see how this worked, if you go into hide,
go into the index.html template.
The only change that's been made here is what happens when I add a new post.
Recall, again, that what this application does
is it loads posts from a server, and then when it gets to those posts,
it loops over each of the individual posts, which is just a string of text.
And it adds that string of text inside of an element onto the page it via this
add post function.
And what the add post function is going to do here
is first create a new element, create a div in which to store that post,
give it a class name, because that's how we're going to animate it, and then
set its inner HTML equal to the contents of the post, something like post
number one, post number two, post number three,
and then add a button that just says, "Hide."
And then we're going to go ahead and add that to the DOM, as well.
So that's what add post is now going to do.
We're generating some HTML using this JavaScript code
and then adding that HTML to the page.
And now what we're adding is a div that has not
only the contents of the post as text, but is also
going to give us access to a button that ultimately, we hope,
is going to let us hide that post, as well.
So how do we actually get the hiding of the post to work?
Well, what we want to do is somehow detect
when the user clicks on one of those Hide buttons.
So there's a number of ways we could do this,
but one way is just to listen for anytime anyone
clicks on the document as a whole.
Anytime anyone clicks on the document, I might like to ask something like,
what did they actually click on?
And it turns out that with most event listeners,
the function the event listener takes in can
take as an optional argument the event itself, which
is a JavaScript object that contains information about the event that
happened, like the click event, or the scroll event, or the keydown
event, or the keyup event, for example.
And one of the properties you get access to is event.target, which is like,
what was the target of the event?
In this case, what was the thing that was actually clicked on?
And I'll go ahead and save event.target inside of a variable called
element, where the idea now is that whatever gets clicked on, that is
the event's target.
We're going to save that inside of element.
And what I want to know is, is element--
is that one of the Hide buttons?
I want to know, is it a Hide button?
I could have also attached an event listener to each of the hide buttons.
This is just an alternative way of doing it
that I'm showing you for sake of demonstration,
where we say when we click anywhere in the document,
figure out what was clicked on, and save it inside of this variable.
And if it's a Hide button, then it's going to have a class of hide,
because I gave every Hide button a class of hide.
And so what I can say is, if element.className equals hide, well,
that means that what was clicked on is something with a class of hide.
We can assume that it is, in fact, a Hide button.
And then what I want to do is I can do something like element.remove
to say, go ahead and get rid of that element.
So now what does this do?
If I refresh the page, let's try it.
Post number one-- if I hide it--
I want to hide post number one.
That didn't quite work.
It was close.
It got rid of the Hide button, but I didn't
want to get rid of the Hide button.
I wanted to get rid of the whole post.
So what's going on here is it seems to be that if the element's class name is
hide, meaning I clicked on a Hide button,
element.remove just removes that element.
It removes the Hide button, but it doesn't
remove the post that contains it.
And if you think about this in terms of the DOM, the post is a div,
and its child element is the button, this Hide button.
And so you remove the button, but it doesn't also remove the post, as well.
If you want to remove the post, as well, you
need to remove not the element, but the element's parent.
And in JavaScript, it turns out there's a way to do that, too.
Rather than element.remove, I can say element.parentElement.remove to say,
take the element, get its parent, and remove that.
So now I refresh the page.
Now I see post one.
I want to hide it.
I hide post one, and now I see post two.
And post one has gone away.
If I want to hide post three, I hide post 3.
Now post 3 is gone.
Now I go straight from post two to post four.
So this works, but it's also not immediately obvious what's going on.
Because all of the posts are the exact same height, when
I get rid of post one and three, it's not immediately obvious to the eye
that they've gone away, because post two and four--
they look almost exactly the same.
You really have to be paying attention to know that the hiding worked.
And so this can be a time where animation can actually
be quite helpful.
So what I can do is say something like, let's go ahead
and give this post an animation associated with every post.
We'll give it an animation name called hide,
an animation duration of two seconds-- we'll say it'll take you two seconds
in order to hide--
and an animation fill mode of forwards.
I want to go forwards with the animation.
And initially, I'll give the post an animation-play-state of paused, meaning
initially, I don't want the animation to be running, because I don't
want to hide all the posts immediately.
Pause this animation.
Later we'll go ahead and run the animation
in order to actually hide the post.
Then I need to define what does it actually mean to hide the post.
And I'll say, well, at the 0% mark, what does it mean?
Let's give yourselves an opacity of 1.
Opacity is a CSS property that just controls how opaque
or how transparent an HTML element happens to be.
And at the end, 100% of the way done with the animation,
we'll set opacity to 0.
So initially, we can fully see the element.
At the end, the element is totally transparent.
And now what I need to do is actually trigger this to happen somehow.
So this is probably going to happen inside of my event listener,
where instead of removing the element right away,
let me just take the parent element and set its animation-play-state
equal to running, for example, meaning when I click the Hide button,
go ahead and run the animation that will change
the opacity from 1 to 0 over the course of a couple of seconds.
And then, if I really want to, I can add another event listener to say,
take the parent element, add event listener.
There is an event called animationend, which
happens when the animation is over.
And then I can say, when the animation is over,
we'll then go ahead and remove the element.
So all in all, rather than just immediately
remove the element when I click on the button that
says "Hide," what I'd like to do is say, if you click on a button
and the button is Hide, go ahead and get the parent element, not
the Hide button, but the post itself.
Set its animation-play-state to running, meaning run the hide animation,
and then add an event listener to the parent, to that post of the whole,
to say, when the animation is over, go ahead and remove that entire post
from the DOM altogether.
So what is the effect of all of this now, of having this animation
and running it?
Well, now, if I refresh the page, I see all these posts.
If I try and hide post number two, for example, you'll
see that the opacity changes, and then it slowly disappears.
And then only after it's totally transparent,
the post disappears entirely.
So I can say, hide post number four.
It disappears, and then post number five jumps up to fill its place.
And I can do that for any of these posts,
triggering the animation when I click on the Hide button.
And so this is part of the value of what animation can do
is to be able to make our user interfaces a little more
pleasant from the perspective of the user
by not immediately getting rid of a post, but by having a nice fade-out
so it disappears nicely.
Now, even this is not perfect animation-wise.
One thing you might notice is that it jumps up as soon as the post is gone.
If I hide post number three, I hide it, it
disappears, and post five jumps up very abruptly in order to fill its place.
What I might like is to be a little bit clever, to somehow shrink
the size of the post after it's gone so that the post doesn't jump into place,
but it slides a little bit more naturally into place.
And so there's some additional things I can play with here.
Maybe I want to say, let me make this animation a multiple part animation.
So here, instead of just from 0% to 100%, setting the opacity from 1
to 0, maybe in the first 75% of the animation that
will take care of reducing the opacity, going down from 1 all the way down
to 0.
But in the last 25% of the animation, we'll still end with an opacity of 0,
but anything that creates vertical space I want to reduce down to 0.
So the height should be 0 pixels.
The line height, which is how high the text should also be 0 pixels,
and any padding I want to go away.
It turns out I've added some margin to the bottom of the post.
I want to make that go away, as well.
So I want to get all of those to 0 from whatever their initial values happened
to be, that initially, the height is, like, 100% of what the height could be,
likewise, for line height, 100% of the parent.
Initially, I have, like, 20 pixels of padding
and a margin at the bottom of 10 pixels.
And I want all of that to still be true 75% of the way through the animation,
but it's only in the last 25% of the animation
that I want to set all of these vertical height properties
down to 0, when I remove all the height, remove the line height,
remove all the padding.
And the effect of this as will have an animation now where for the first 75%
of the animation, the only thing that changes is the opacity. .
The opacity goes from 1, fully visible, to 0, fully transparent.
And then in the last 25% of the animation,
the post is already transparent.
You can't see it.
But it's still taking up physical space on the page,
but we're going to now reduce the height of that post
so that now you won't be able to see it at all.
So now, if I refresh this page, here, again, are all the posts,
but now, if I click Hide on a particular post,
we'll see that it first fades out.
And then its height shrinks so that the next post
slides very nicely into place.
I can do that again, hide the post, transparent,
and then it slides into place.
And this, again, is just an application of this idea of CSS animation,
using properties of animation to make our interfaces a little bit
nicer to use, a little bit clearer, visually,
to the user that one post has gone away, and the rest of the posts
have now scrolled up in order to take their place.
So now we've been able to use JavaScript to create
a lot of nice user interfaces.
We've been able to create single-page applications,
to create infinite scrolling, to be able to create some animations, as well
and use JavaScript to be able to control them.
But one thing you might be realizing at this point
is that our applications are starting to get fairly complicated.
There's a lot of JavaScript code needed to manipulate
a lot of different parts of our application at the same time.
And you can imagine that as web pages start to get more complex,
and as you want to start making them more interactive and more dynamic,
there's going to be a lot of JavaScript code required in order to keep
everything in sync, in order to make sure that all of the elements
are updated when they should, so on and so forth.
And it's for that reason that in recent years, a lot of JavaScript
has now turned to some JavaScript libraries or frameworks that
allow to more efficiently and more effectively
create user interfaces that are more interactive and more reactive.
And one of the most popular of these is a framework known as React.
React as a JavaScript library that is going
to enable us to be able to design user interfaces that
are very interactive, where the content of the web page updates
automatically based on some underlying state.
And what we'll do now is take a look at a brief taste of React
to get a sense for how frameworks like it can actually work
and can help us in designing some interactive and useful interfaces
for users to be able to interact with.
React is ultimately based on this idea of declarative programming,
a particular style of programming which is
different from the types of programming you might be familiar with,
more classical programming styles like imperative programming.
In imperative programming, you generally give the computer commands,
tell the computer what to do.
For example, if we had that counter program from before
and we wanted to update the counter from one number to another number,
in the view, the HTML that the user sees,
we would include something like a heading
that just has the 0 inside of it.
And then the logic in imperative programming
would take something like this form.
It would be like, first, document.querySelector h1
to get that each h1 tag, get it it's inner HTML, parseInt.
We'll take the string and convert it into an integer.
And we can save that inside of a variable called num, for example.
And then after that, if I want to increase it,
I would take this variable num and just add 1 to it, num plus equals 1,
add 1 to it.
And then if I want to update this heading in order
to replace the 0 with a 1, for example, well,
then I would need to say, document.querySelector h1, set
the inner HTML equal to that number, for instance, in order to say,
num is now 1.
Go ahead and replace that in the view.
But this is a fair amount of code to do something fairly simple,
just increase a number by 1.
And the reason why is because we have had
to be very explicit about what instructions we're
giving to the web browser.
We're saying, first, grab the h1.
Figure out what number is inside of it.
Add 1 to that number, and then replace it inside of this tag.
What declarative program is going to allow us to do
is it's going to allow us to just describe what state should
be displayed on the page in what form.
In declarative programming, in our view, the HTML-like code
that we're going to be writing, we're just
going to say something like, h1 and then in curly braces, num to mean,
fill in the number here.
And this is what the React syntax is going to look like.
And then the logic code, if we want to increment that number by 1,
is we just need to say, num plus equals 1, add 1 to the number,
and the effect of that is that since we have declared
that inside of this heading it should be whatever the value of the number
is, when we increment the value of number,
React is effectively just going to update the view so
that the number updates, as well.
And so this will be some of the power that React gives us.
React lets us divide our application into a whole bunch
of different components, where a component is something
like this thing here that is keeping track of some sort of count
along with a button that might manipulate it and then
make that component based on some underlying state, some underlying
variables that represent the state of the application,
something like the current number.
And then we can manipulate that state.
And when we manipulate the state, that will
have an impact on what the user actually sees,
and React will handle the process of updating that user interface for us.
There are a number of ways to get react to working on our web page,
but the simplest is probably just to include these three JavaScript
packages inside of our web page.
So we're first going to include React itself,
which is going to be the library that's going to allow us to define
these components and how they behave.
Then is ReactDOM, a special package that's
going to allow us to take react components
and insert them into the DOM, the Document Object Model, that represents
the structure of the entire page.
And then finally, Babel is going to be a package
that we're going to use in order to translate code
from one language to another.
It turns out that when we're writing React code,
we're not actually going to be writing JavaScript.
We're going to be writing in an extension to JavaScript known as JSX,
and JSX is going to be an extension of JavaScript that
looks a lot like JavaScript, but has some additional features.
In particular, it has the ability to effectively allow
us to represent HTML inside of our JavaScript code
in a way that's much easier to read, and it's
going to be convenient for us to deal with.
Browsers, on the other hand, don't understand JSX automatically,
so what we're going to use is a tool like Babel
to convert that JSX code into plain JavaScript
that our web browsers are ultimately going to be able to understand.
The best way to get a feel for this kind of thing is just to see it in action.
So I'll go ahead and create a couple of React applications
just to get a sense for how it is that you can use React
in your own applications, as well.
So I'm now going to take a look at react.html,
which just contains the basic structure for an HTML page,
that so far, all it's doing is including those three JavaScript packages we
talked about a moment ago by using these scripts tags to say,
I would like to include those JavaScript files inside of this web page, as well.
Now, inside the body of this HTML page, I'll just have a div whose ID is app,
and it's going to be an empty div.
It's not going to have anything inside of it initially,
because what we're ultimately going to have
happen is we're going to have React put something,
in particular, put what React calls a component
inside of that part of the body.
So now here comes some JavaScript, and we're
going to put this inside of script tags.
But because this isn't strictly speaking going to be JavaScript--
it's going to be an extension to JavaScript called JSX--
I'm going to add an attribute to the script tag
to say its type is equal to text/babel to mean Babel should translate
this code from JSX to plain JavaScript that the web browser is
able to understand.
Ultimately, this is going to take some time,
and in practice, you probably want to do this compilation before users end up
using the page, but for now, this will make our lives a little bit more
convenient.
And what we need to do inside of here now is define a React component,
and all a component is just something on the web page
that has some view, something the users can see,
when the component gets rendered to the screen
and maybe has some underlying properties in state that we can manipulate,
as well.
And it turns out that every React component
is organized into a JavaScript class.
In the same way that Python had classes when
we were talking about object-oriented programming,
JavaScript, too, has classes that we can create that, in this case,
are going to represent React components.
So here, I'm going to create a new class that I'm just going to call App,
but you can call it anything you want.
And it's going to extend React.Component,
meaning I'm creating a new class called App,
and this app is going to be a React component.
And every React component needs a function or a method called render,
and what render is going to do is return what HTML should display
when you look at this component.
So here I'm going to return, and now this
is where the JavaScript JSX part comes in.
Rather than just include JavaScript here,
I can actually include HTML-like syntax as what I return.
So I can return something like a div that says, "Hello!"
for example.
So when I render the app component, what's
going to display on this page is a div that is just going to say, "Hello!"
That's what this entire class called App is doing.
It is a React component, and when I render the React component,
we are going to return a div that just says, "Hello!"
And then the last step is to say, now that I have this component,
let's go ahead and render it into the DOM.
Let's render it into the page.
And to do that, I call ReactDOM.render and say,
I would like to put the App component into document.querySelector
app, the element whose ID is app.
So here is that element whose ID is app.
And what I'm saying is in that part of the page,
go ahead and insert the app component, and the syntax
for creating a component kind of looks like the syntax
for creating an HTML element in the sense
that it has these angled brackets.
Effectively, I have created this app component,
and I would like to insert it into the page.
So now, if I open up react.html, here's what I see.
I see a div that, in this case, says "Hello!"
because that's what was inside of my component.
And if I change the component to say something else,
like if I added an h1 at the top of it that just said, "Welcome!"
for example, well, then now, when I refresh the page,
that's what the component now looks like, big h1 that says, "Welcome!"
Underneath it, it's got a line that just says, "Hello!" for example.
So that then is an instance of our very first React component,
but what's really powerful about React is the ability
to compose components inside of each other,
to have a component that is inside of another component, for example.
So let me define, now, another component that I'll call Hello, which
will extend React.Component.
And when I render the Hello component, what should happen?
Well, I'm going to return just an h1 that says, "Hello!"
So the Hello component, when I render it to the screen,
just displays a big heading that says, "Hello!"
And now, inside of my app, I can just render a Hello component,
and maybe I'll render a second Hello component.
And maybe I'll render a third Hello component.
I can reuse components as much as I want to say, passing in Hello, Hello, Hello.
And now when I refresh the page, I see three of those Hello components
appear on my screen.
And the reason why that happened is because up above, I
defined a component called Hello, and all
the Hello component does is when it is rendered
to the page, it displays a heading that says, "Hello!"
And then what my app is going to do is it is
going to have three Hello components.
Each one of which is going to be rendered as just Hello.
Now, these components aren't super interesting if they all look exactly
the same, and it's for that reason that these components can have additional
properties or attributes-- or in React terms, they call them props--
associated with them.
Maybe instead of Hello, I would like to provide additional information,
like, say hello to Harry, or Ron, or Hermione, for example,
where I might want to say, much as you could with HTML,
provide attributes, in this case known as props, to hello,
where Hello name equals Harry, Hello name equals Ron,
Hello name equals Hermione.
It turns out that in React, if I take a React component, something like Hello,
and I pass it the equivalent of what looks like HTML arguments
that you can put as parameters to a particular HTML tag,
like attributes on an HTML element, that you can access
those attributes, otherwise known as props, inside of a React component,
that I can access Hello.
And then in order to substitute a variable inside of React,
we use curly braces, much as you do in Python, and I can say, this.props.name.
So now what's going on is that I have passed in as a property of each
of these Hello components a name, and in order
to access that name inside of this class, I can just plug is,
using these curly braces, this.props.name.
This, in this case, refers to the current object, the Hello object.
I access its properties, and I access the value
of the name property, which is Harry in the first case, then Ron, then
Hermione, for example.
And so now, all in all, when I've go ahead and open this page, what I see
is three different components, each parameterized by a different property.
One says, "Hello, Harry!"
One says, "Hello, Ron!"
One says, "Hello, Hermione!"
And I've been able to abstractly represent
that as just three hello components, each of which
has a different property for what name I would
like to provide to that component.
So this, then, is what these components can look like,
but where components start to get more powerful
is when we can begin to express state inside of those components, as well.
So let's go ahead and taking a look at an example of how we could do that.
I'll now go ahead and create a new file called counter.html,
and here we're going to reimplement that counter program from when we first
took a look at JavaScript, where you click a button,
and that's going to increment some sort of counter.
And before, we had to separately maintain a variable called counter,
and every time we update that variable, we had to then say,
also, manipulate the DOM in order to reflect those changes.
That's going to start to get tedious, especially
if we need to start making many different changes in response
to what's happening.
It'll be nicer to be able to declaratively just say,
what should be displayed on the screen is whatever the value of the variable
happens to be inside of the component's state.
And so we'll see how we can do that now.
I'll go ahead and just copy the same basic structure
from react.html inside of counter.
But we no longer need a Hello component.
We're just going to have a single component, which
is going to be this App component.
And by default, let's go ahead and render
an h1 that says 0 and a button that says, "Click Here"
that's going to count.
Or maybe, instead of "Click Here," we can instead
have it say, "Count," just to make it much more clear.
So now I have a counter that is going to first render this App component,
and all the App component is going to do is display a heading that says 0
and display a button that says, "Count."
So let me now open up counter.html, and here, then, is what I see,
number and then a button that says, "Count."
Of course, nothing does anything just yet.
That's going to be this next step.
And now the first step is going to be to say that I don't just
want to display 0 inside of this h1.
I really want to display whatever the value of some internal state
of this component happens to be.
And so how can I do that?
Well, the first thing I need to do is when I first create a new app,
I need to give it some internal state, and it turns out
that in React, when I create a new component,
it's going to call a function called the constructor
to make the new component, which takes as an argument
the props that were passed into it in case you want to do something
with the props.
And because this is an object-oriented programming language,
we need to start by saying, super props, just to say,
make sure that this all gets set up as a React component,
no need to worry too much about that line.
The interesting line will come next now.
Inside of the constructor, when we create
this component for the first time, I'm going
to set this.state equal to a JavaScript object that can contain whatever keys
and values I want.
So I can add any state that I want into this.state, anything that
is about the state of the component.
So for example, I can add a variable like count, which will be 0.
So initially, when I create this application for the first time,
it is going to have some sort of internal state, some variables that
is keeping track of, and it will have one property called count,
and its initial value will be 0.
And now, instead of displaying just 0 inside of this h1,
I want to display in curly braces so that I plug in a value here.
The value I want to plug in is this.state.count, where now I'm saying,
you look inside of your state, and whenever the count happens to be,
that should be what gets displayed, instead of the h1.
If the count ever changes, I don't need to manually then say, go ahead
and update the h1.
The h1 will update automatically based on whatever the state is,
and React is going to take care of that for me.
So if I look at the page now nothing, appears to have changed,
but the number that I see is based on the number inside the state.
If I change it to something else, for example, and refresh the page,
then what I see in the h1 is just a reflection
of what happens to be inside of the application state.
But we'll go ahead and start that state at 0,
which is why 0 is the first thing that you should see here.
Now what I want to happen is when the button is clicked,
I would like for some function to run.
So I can say, onClick--
and react happens to use capital C click for a lot of these event handlers--
and when you click, we'll go ahead and run
this.count, which will be a function that we'll write in just a moment.
And so let's go ahead and write that function.
Count is going to be a function.
We use arrow function notation here.
And what is the count function going to do?
Well, it's going to update the state.
And how do you update the state?
React gives you access to a function called this.setState.
And it can work in a couple of ways, but one way it can work
is it can take as its argument a function
from the old state to the new state.
So I can say, the old state we'll call state.
And then the new state is going to be what?
Well, the new state is going to have count
set to whatever state.count plus 1 is.
And so how does this logic work?
Let's take a look at it again.
Inside the render function, we're saying,
go ahead and render a heading that just displays this.state.count.
Look at the internal state of the component, get access to the count,
and that should be the big heading.
Then we have a button called count, and when you click on the button,
we're going to run the this.count function.
And the count function is down here.
It's a function.
And what does the function do?
The function is going to update the state.
This state takes the initial state and then generates
a new state where we're going to update the count
to be state.count, whatever the count used to be,
plus 1, adding 1 to whatever it was.
And now that's all we need to do.
I refresh the page.
It starts out as 0.
Every time I press Count, the count is going to update,
incrementing by one each time.
And again, the reason it does that is because I
don't need to say, update the h1 in order to reflect the new change.
Anytime the state changes, React is going
to handle updating what it is the component looks like because I'm
saying that when you render the component,
it should be based on the value of this.state.count.
And that's going to, then, enable us to be
able to much more easily modify what the page looks like just by updating
the internal state.
And if the state appeared in multiple places,
for example, if underneath here I said, the current count is this.state.count,
then I refresh the page.
It says, the current count to 0.
And every time I press Count, it's going to change in both places.
I don't have to manually go into two different places
and change what the value should be.
React is going to take care of the process of updating that user
interface for me anytime the underlying state of the application changes.
So now what we'll do is take all of this, and begin to put it together,
and build a more interesting, dynamic, interactive web page
using React and using state to be able to control
how the application is going to work.
And the application we'll build will be a bit of a game
where we'll ask users to solve addition problems.
We'll ask them what is 1 plus 1?
And they answer, and if they answer correctly,
we'll increase their score, for example, type of game
you might see on the internet that's really just
going to be some text field, some text that we're going to display,
but ultimately, some variables that we're going to need to keep track of.
And so we'll see an example of how we can put all of those pieces
together by taking advantage of React to leverage this declarative programming
style.
So in order to do this, I'll go ahead and create a new file.
We'll call it addition.html.
And we'll start it off with the same type of code that we had from before.
I'll copy it from counter, but then we'll
clear out the contents of the app component,
clear out what we're returning.
So now we just have an app.
And what state do we need to keep track of inside of this game?
Well, one thing we might now want to keep track of, for example,
is the current user's score.
So we'll go ahead and give them a score that initially is going to be 0.
And maybe on the page we'll display the user's score.
So maybe I'll say, in a div, your score is this.state.score.
So that's all this application is going to do right now.
It's going to display a score.
Initially, I set the score equal to 0, and when
I render this component, when it displays on the screen,
we're going to have a div that says, your score is.
And then we're going to plug in here in curly braces this.state.score.
It's going to get us whatever the score happens to be.
So I go ahead and go to addition.html, and here's what I see.
I see that the score, in this case, so happens to be 0.
So now what I'd like to do is display some sort of math problem
for the user to be able to solve, because this
is what I want this game to be about.
So I might display a div that just says something like, 1 plus 1, for example.
So we'll go ahead and refresh that.
There's a math problem.
And now I also want there to be a place where the user can type in a response,
for example.
I'll just use input field for now, refresh the page,
and this is what this now looks like.
We have some math equation, some input field, and then some score.
And now when we begin to design a react application,
the thing to start to think about is what
should be included inside of the state of the application, what things
could change in this application.
And just looking at this here, there are a couple
of things that are going to change.
One thing we already know is going to change is the score.
The score starts out at 0, but ultimately, the score is later
going to increase to something else.
What other state is being stored inside this application?
What other things are going to change?
Well, these two numbers are going to change, presumably,
that after I answer the first question, you'll probably want
to show me a different math problem.
And so this 1 and that 1 are probably going to change two other numbers,
so maybe those should be part of the state, as well.
So we can do that, too, by updating what we're storing inside of this.state.
Maybe I'll initially store inside my state
that num1 is going to be 1 and num2 is going to be one.
And these are going to represent the two numbers
that I would like to add together as the problem
that I display to the user who is playing my game, for example.
So down below now, instead of just saying 1 plus 1,
I'm going to say, this.state.num1 plus this.state.num2.
So now, rather than just literally using 1 and 1, I'm saying,
the numbers that should be displayed in this part of the page
are reflective of whatever is stored in the state.
And if the state changes what those numbers are,
then we should change those numbers, as well.
So now I refresh the page.
Nothing appears to have changed, but if I later
change what these two numbers are to different numbers, then the problem
changes, as well.
So I'm able to manipulate what appears on the screen based on the underlying
state of the application.
And it turns out there's at least one other part of this page that
could change, as well, and in particular, it's
not just the score and not just what these
values that are the two numbers that we're adding together.
But the contents of this text field you can also
consider to be part of the state.
It is something about the application that is going to change.
And in particular, it's something that I'm
going to care about knowing about, because later on,
when the user presses Enter in order to submit their answer,
I want to know what the value of what's been typed in here
is to be able to figure out if they answered the question correctly or not.
So what I can do is I can add to the state something
like the response, which initially will just be the empty string.
But then I can say, this input's value is going to be this.state.response.
And again, nothing appears to have changed,
but now I'm controlling whatever is inside this text field
based on whatever the value of this.state.response is.
If I sent a responds to something like "Hello!"
for example and then refresh the page, well, then "Hello!"
is going to show up inside of the input field.
It is part of the state, and as a result, it is reflected in the view
that users ultimately see.
So this seems reasonable.
I have two numbers.
I'm keeping track of a response, and I'm also keeping track of a score.
We can go ahead and try and play this game now.
I refresh the page.
It's saying, 1 plus 1, and now giving me an option to type in a result.
Let me see if I can type in the number 2.
And though you can't see it, I'm pressing the number 2,
but nothing's changing in the input field.
I'm typing the number 2, but nothing is happening.
So why might that be the case?
Well, React is very literal.
You tell it what you want, and it's going to do it.
And what we said is that the value of this input field
should be whatever this.state.response is.
And this.state.response is always going to be just the empty string.
It's going to be nothing.
So it doesn't matter what I type while this input field is selected.
No text is ever going to appear here, because I'm not
updating this.state.response.
So if I want this text field to work, I'm
going to need to update the response inside of the state
in order to reflect what the user has actually typed in.
So let's give this a try.
And the way I can do this is go back to this input
field whose value right now is this.state.response,
and I'm going to say, onChange.
Let's go ahead and run a function.
And we'll do this.updateResponse as the name of the function.
updateResponse is just a name I chose.
It's arbitrary.
But what I'm saying here is anytime the value in the input field changes,
anytime someone tries to type in something
new, go ahead and call the updateResponse function.
Let's go ahead and now write the updateResponse function.
And remember that event handlers can usually take the event itself
as an argument.
And this is going to be helpful, because event.target.value is going to contain
whatever it is the user typed in.
Event.target is the input field that I was typing into, and .value will get
at me what it is the user typed in.
And what I'd like to do is say, this.setState.
And here, all I want to do is set the response equal to event.target.value.
So what's going on here?
I have an input field.
Every time the input field changes, meaning every time I type something
into the input field, the onChange handler in React
says, go ahead and call this.updateResponse,
meaning update the response inside of the state.
Here, then, is the updateResponse function, and what it's going to do
is it's going to take the event, the onChange event, get its target--
what changed?
It was the input field--
and get at that value--
what is the actual text that was typed into the input field
when I triggered this event--
and go ahead and update the state, setting response equal to that.
Now, this time I didn't need to provide what
the prior state is, because the prior state isn't
going to influence this new state.
We're just going to replace response with whatever the value of what
was typed in actually is.
And so we can actually test us now.
I refreshed the page, and now I have an input field
where if I type something like 1234, then it's going to actually appear.
And how do I know that it's actually keeping
track of this inside of the response?
Well, I could try just displaying it on the screen.
I could add another div that says, "You have typed,"
and then this.state.response.
That is going to just display on the screen what it is that you have typed.
It's going to say, this.state.response, no matter what
this.state.response happens to be.
So now, if I refresh the page, you have typed nothing so far.
But as I type in numbers, not only does the input field change,
but this line changes, as well, because this line
is dependent upon the value of this.state.response.
So it updates dynamically along with the rest of the page, as well.
If I delete everything here, then this field also goes away.
It turns out, I probably don't need this,
because the input field does enough for me
to know what it is that I have typed in.
So now what I'd like is some way of detecting
that I've entered the problem, I pressed Return,
and I'd like to see whether or not I got the answer right or not.
So in order to detect that, we'll go ahead and add another event handler
to this input field, onKeyPress.
We'll go ahead and run a function called this.inputKeyPress.
It can be called anything we want, but basically
what we're saying is in the input field, when you press a key,
run the inputKeyPress function.
And what the inputKeyPress function is going
to do is the first thing it wants to do is check,
is the key that was typed in the Enter key?
So what is going on here again?
Every time I press a key, regardless of what key it is,
it will call the inputKeyPress function.
But what we really care about is just a scenario where
the user presses the Enter key, in which case, inputKeyPress will be called.
And it turns out, with a KeyPress event, you can access event.key,
which is a property of that event that tells you
what key was actually pressed.
And if the Enter key was pressed, then we want to do something.
What might we want to do?
Well, we want to check to see if the answer is actually correct.
Well, how do we know what the answer is?
Well, the answer is whatever the user typed into the text field,
and we know that's stored in this.state.response.
Now, technically, since this.state.response might be a string,
we'll go ahead and convert this into an integer first.
So parseInt is a function in JavaScript that takes a string,
converts it into an integer.
And we'll store that inside of a variable called answer.
And now how do I know if they've correctly solved the addition problem?
Well, the two numbers that were being added
were this.state.num1 and this.state.num2.
So the question that I could ask as a condition is something like,
if answer is equal to this.state.num1 plus this.state.num2,
well, that means they got the question right.
So this means user got question right.
And then we can add an else case that is,
user got question wrong, for example, so two different scenarios,
one for when the user correctly answers the question, one
for when the user does not.
So then the question is, what should happen
when the user gets the question right?
Well, we want to update the state, so this.setState,
and we take the old state.
And what do we want to modify?
Well, we probably want to modify at least the score, setting
the score equal to state.score plus 1, increasing the value of the score by 1.
So the logic of this now is that we get the answer, whenever
it is the user typed in.
If the answer is equal to the sum of the two numbers
they were trying to add together, then we'll
go ahead and update the state, setting the score equal to the score plus 1.
So if I try this now, it says 1 plus 1.
If I type in an incorrect answer, press Return, nothing happens.
I type in the correct answer, press Return, and my score increases
from 0 to 1, exactly what I would expect it to do.
Now, there are a couple of things I also probably want to happen.
One is I should probably get a new question, otherwise,
if it just stays 1 plus 1 forever, I can just keep pressing return
and just keep making my score as high as I want it to go indefinitely.
So I probably want every time I press Return, give myself a new question
to answer.
And so to do that, what needs to change in the state?
Well, the state is keeping track of a couple of things, num1, num2, response,
and score.
We've updated score.
Now let's also update num1 and num2.
So I can say, if they get the question right, we update the score,
increasing it by 1.
And let's also set num1 equal to a random number.
So Math.random gets us a random number between 0 and 1.
If we multiply it by 10, that'll give us a random number up to 10.
And because this could be a real number, something after the decimal,
but I only want to deal with integers, we'll
go ahead and take the ceiling, Math.ceil,
to make sure we get a whole number, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10.
And we'll do the same thing for num2.
Go ahead and generate a random number.
And you'd only know this by looking at JavaScript's documentation.
They happen to have a function called Math.random and Math.ceil that
do what I want.
And now I'm updating the score and also updating the two numbers.
So we try again, refresh the page.
Score is 0.
I type in the correct answer, press Return, and my score changes,
and I get two new numbers.
I type in the correct answer, press Return.
The score changes, and I get two new numbers.
All of this is updating automatically because I am updating the state,
and then React is taking care of the process of saying,
the state has updated.
Let's now update the interface, as well.
The other change that might be a nice user interface optimization
is that after I press Return, you probably
want to clear out this input field for me.
And so in order to do that, we go ahead and set the response back
to the empty string.
So every time I get the correct answer, the answer
is equal to the sum of the two numbers I'm trying to add,
we'll increase the score by one.
We'll set num1 and num2 to new random numbers,
and we'll clear out the response.
And so now I type in the right answer, press Return,
score increases to new answers.
What if I type in something incorrect?
I type in something like 5, press Return.
Nothing happens at all.
And that's what I would expect, because user got the question wrong.
I don't change anything about the state.
But maybe I do want to change something about the state.
Maybe when you get a question wrong, I want to decrease your score by one.
So we could say, this.setState.
And this time, your score becomes state.score minus 1,
and for good measure, we'll also clear out the response field.
But we won't replace num1 and num2 to give the user another chance
to answer the same question.
So now I refresh the page, 1 plus 1.
I type in the answer.
I get it correct.
If I type in an incorrect answer, watch what happens.
My score decreases by one, and the input field clears out.
So I now have the opportunity to type in another problem.
Now I type in something else, something like 11, get it right.
I get it right, and every time I get it right,
the score increases, and the numbers change, as well.
And so this is the big idea of what we can begin to do with React.
Just by manipulating four pieces of state, this number
here, this number here, what's inside of the input field,
and what the score happens to be, we're able to create
a very interactive and dynamic web application,
where we're able to update the interface of what the user sees based
on the underlying state, based on what happens
to be going on inside of the application,
whether they got the question right or whether they got the question wrong.
And what nice things we can add now are really
just aesthetic changes, things to make this look a little bit nicer,
look a little better.
So I can add some style, just say, let's go to the style section of this page.
And maybe for the entire app, the div whose ID is app,
let's center all the text and maybe use a sans serif font.
Let's refresh that, just see what it looks like.
That looks pretty good.
I might like for this math problem to be a little bit bigger.
So how do I do that?
Well, here's what I'm rendering.
And this div is where the problem is, so I'll just
go ahead and give it an ID of problem.
And then I can, in the style section and page,
say, for the thing that has an ID of a problem,
go ahead and give it a font size of 72 pixels.
So now I refresh that, and this looks even better,
that now I see a big math problem up top and then an input field
where I can type something in.
I type in a number, press Return.
I get a new problem.
I type in something wrong, press Return.
My score goes down, and I have another opportunity
to be able to answer the question correctly.
And this seems to be working pretty well.
Now what might I like to add?
So it's entirely up to us now in terms of what
features we might want to add here, and using React, and leveraging those tools
to be able to do so.
Maybe I'd like for some way to win the game.
Like maybe once I get to 10 points, let's say the game should end,
and we can just say, we've won the game at this point.
How can we go about doing that?
Well, I'll go back to addition.html, and let's look at the render function.
Right now, the render function always returns just one thing.
It's going to always return the problem in input field and the score.
But maybe I can add some additional logic.
This is a JavaScript function, so I can add any JavaScript logic that I'd like.
I can say something like, if this.state.score
equals 10, then rather than return this thing, let's return something else.
Let's return something like a div, maybe, whose ID is winner,
and that div is going to say, you won.
And now I can say, for the div whose ID is winner,
let's set the font size to 72 pixel, and let's set the color to green,
for example.
So now if I refresh this page, I see this math equation.
If I answer it, answer, answer to go all the way up to 10 points--
I have a score of 9 now, last question.
If I get the question correct, the score is going to update to 10,
and when I do answer the question, press Return,
the entire screen changes and now says, "You won!"
And again, the reason why it's doing that is because of this render
function here.
The render function determines what should be displayed to the user,
and renders based upon the value of the state.
But I can ask a question.
If this.state.score is equal to 10, then render something.
Otherwise, render something else, that in this case,
I am returning a div that just says, "You won!" for example.
And otherwise, I'm not going to end up inside of this if condition,
in which case, we just return this normal div that includes a place where
we have the problem, an input field where you can provide some information,
and then a div where you can display your score.
And all of this is just based upon that state,
and given the state of a particular component,
React knows how to render that component, how
to display it to the user so the user can see what that interface ultimately
looks like.
And React is just one of many libraries that do this type of thing.
Other popular ones include Angular and View, where all of these
are just these web frameworks that make it easier
to be able to create applications that are able to respond
to some underlying state so that you, the programmer,
don't have to worry about constantly having to manipulate
various different parts of the page, especially
as you imagine websites like Facebook or Twitter,
where there are many things happening on the page at the same time.
Every time a new tweet comes in, you might get a notification
and see a new post in your main area of your news feed.
So these are the types of things that you might want the application
to be able to more easily handle for you,
where you describe what the state is.
You describe what the page should look like based on that underlying state
and let the library, whether it's React or something else,
begin to handle the process of doing that for you.
And the world of user interfaces is changing pretty quickly,
that a lot changes in user interfaces in terms of the technologies
and the tools that are quite popular.
But they're really based on the same set of underlying ideas,
the idea that we can use JavaScript in order to manipulate what it is the user
sees on their page in order to detect what's
happening based on particular events, like scrolling
to the bottom of the page or typing something into an input field,
and then responding to those particular events
by providing some sort of function that gets called
anytime a particular event happens.
By mixing that in with other features, like the ability
to asynchronously request information from an external server or the ability
to do computations based on the values of the state, like we saw within React,
we have the ability to create very interesting,
engaging, dynamic user interfaces very, very quickly, all just
using the power of combining Python and JavaScript.
That was Web Programming with Python and JavaScript for today.
We'll see you next time.