[MUSIC PLAYING] BRIAN YU: OK, welcome back
everyone to web programming with Python and JavaScript. And so we'll pick off where
we left off two weeks ago. So we've been working with building
web applications in Flask using Python. And then last time, we
introduced JavaScript, which was a language that was able to
run inside of our web browsers, which allowed us to run some
code on the client side, as opposed to running everything on
the server, which allowed us to do things a little more dynamically,
create more interactive user interfaces. And today, we'll continue
to build on those ideas, continuing to work with
Python and JavaScript, in particular, to take a look at
some modern trends in web application development, and how we can
use Python and JavaScript to be able to achieve those goals as we go
about building our web applications. So the first thing that
we're going to take a look at is single-page applications
or single-page apps, which are becoming increasingly popular
nowadays, where so far in the projects that we've built, we've been
building a lot of web applications that have multiple pages. In the first project, you
were building an application that dealt with interacting
with a library of books, where you had one page where you
might search for different books. And you get another page where you might
look at individual details for a book and write reviews for that. And in the current
project you're working on, you may building out
multiple pages, where you have one page where
people type in their name, for instance, and another
page where people are looking at messages in a given channel. And what single-page apps are all about
is taking content that would ordinarily be on multiple pages or
multiple different Flask routes, you might think of
them and combining them into just a single page, where that
page will pull in new information from the server whenever it needs
additional information in order to render in the browser. And so last time, we took
a look at a technology called AJAX, which allowed
us to asynchronously ask the server for more information
whenever we wanted to include additional information on our web page. And we took a look at
a currency converter, for example, where we
could type in a currency and then retrieve information
about the conversion rate of that currency from the server
and then display that information right inside the web browser. And so we're going to
take the same approach as we start to build
single-page applications and looking at how we can
start to build those out. So we'll start by taking a look at
an example of an application that is not a single-page application or a
multi-page application, so to speak. And so we'll take a look at
this multi-page application, which is just going to be
a Flask application that has three different pages. And so this looks very similar
to the Flask applications that we've been dealing with. This one happens to be very simple. It's a Flask application
with three routes. The default route,
which is just slash, is going to return for us the first
HTML page, just called first.html. Then we have this second route, /second,
which is going to return us a second HTML page, second.html. And a third route, /third, which is
going to return for us a third HTML page. In this case, just called third.html. So it's a very simple page that has
three different routes, each of which will display a different page. And so if we wanted a
web application that displayed content on
three different pages, this might be how we would do it. And so if we were to then run this
multi-page web application by going into multi-page and doing a flask run. And then taking this URL and
pasting it into a web browser. What we get is something like this. This is the first page. And if we look at the
code for the first page, we'll go to multi-page and
go to the first HTML page. What we have inside of first is
we're extending a layout.html file. Recall that in Flask when
we're generating templates, we can have HTML files that are
extensions of other HTML files that inherit from them,
so to speak, so that we don't need to repeat ourselves
if there's common code that appears on multiple different pages. So this first.html file is
extending this layout.html file. And inside the body of the
page, just displays a heading and then the contents that I actually
want to display inside of the page. And if we take a look at
this layout file, what we see is that inside of the web page
body, it has this navigation bar, which is just an unordered list
where we have one list item for each of the pages I might want to go to. First page, which will link me
to the first page, second page, and third page. And then this empty body
block that eventually I'm going to fill in with whatever my
first.html or second.html or third.html file wants to include there. And so in the case of first.html,
for instance, that body block is being filled by this
heading and that text. And so what I get is this first
page, where this navigation bar was defined inside of layout.html. And then the contents
of that page, which is just defined by my first.html
file, which is extending or inheriting from that layout file. And then if I click to second page,
we'll notice a couple of things. The URL changes to /second because
I've now clicked on a link that took me to a new route that rendered
a different HTML page. In this case, second.html. And in the contents
of this page, I have a whatever was inside the second
page, which was presumably some other amount of text. And likewise, if I click on third
page, that takes me to the /third route and displays the contents
of the third page there. So none of this is anything new. This is just an extension or
looking at how we can use Flask, as we've already seen it, to be
able to link between different pages and create a web application that has
three different routes that take me to three different pages. But now let's take a
look at how we can give this same idea of a
three-page application and pull it into a
single-page application that's just a single page that will display
all three of the pages worth of content when I click on the page
that I want, in particular. And so how is that going to work? Let's take a look at
multi-page or single page zero. And let's look at application.py
inside of single-page zero, which is going to be a
single-page application version of the multi-page application
that we've already been creating. And so the first thing
we do at the top is just define a default route that
is going to return index.html. And we'll see what's inside of
index.html in just a moment. But for now, it's just going to be
returning for us some HTML file. But then down below, we have a
couple interesting things going on. Starting on line 9, I
define a variable called texts, which is just going to be a list
in Python of three different strings. Where on this first line, that's what
I want to appear on the first page. On the second line, that's what I
want to appear on the second page. And on this third line, this is what
I want to appear on the third page. And then down below, I have these three
routes, /first, /second, and /third, each of which returns a
different one of those strings. Notice I'm not returning a whole HTML
file that has a header with a title, and then the navigation bar,
and then the text underneath it. I'm just returning the string that I
care about for that particular page. So in /first, I'm just
returning that string-- that first item in my list of texts. /second returns me the second item
of index1 in my list of texts. And likewise, /third will take me to
item at index 2 which is the last item in my list of texts. And so far, this seems like
a lot of added complexity. How does this all tie together now? Let's take a look at index.html
and see how we might actually use this inside of an HTML file. So what I have inside of index.html-- I'm skipping the JavaScript
for now and just going to the HTML contents of this page. I have this navigation bar,
which looks similar to before. It's an unordered list,
where each list item is going to be a link that I can
click on-- first page, second page, third page. But unlike the multi-page application,
what's different about these links? Anything immediately
strike you as different compared to what we had before? Yeah? AUDIENCE: [INAUDIBLE] URL. BRIAN YU: Yeah, I don't have a URL these
href attributes that would normally be the link of the page that I want
to go to when I click on that link, those are missing. They're just empty
strings for right now. And notice also I've given them all
a class and called them nav-link. We'll see why that's
important in just a moment. And then I have the
body of the page, which for now is just going to be empty. There is nothing inside
the body of the page. So how does this get filled and
how do these links end up working if none of them seem to link anywhere? They have an empty href attribute. Well, that's where the
JavaScript comes in. And so let's take a look at
some of this JavaScript code, starting with this function
here called loadPage. So this is a JavaScript function called
load page, which takes as its argument a name, and that's going to be
presumably the name of the page that I want to load-- the first page, the
second page, or the third page, for instance. And so what we see in here is
reminiscent of what we did during last lecture, when we were
talking about AJAX, about trying to get additional
information from the server when my index.html file wants
to load, the first page-- get that first string of
text from the server-- well, my index.html file right
now doesn't know anything about what that first piece of text is. So I'm going to need to ask the
Flask server for information about what it is. So in order to do that, I create
a new HTTP request just like so. I'm going to create GET request. And what route am I going
to make the GET requests to? It's going to be to slash and then
recall that this dollar sign and curly brace syntax is the template literal
syntax in JavaScript ES6, which basically says plug-in the
value of the variable name here. And so if I'm trying to load_page where
the page is first-- the first page-- then what I'm really going to be doing
is making a GET request to /first-- the URL /first-- which we defined
inside of application.py earlier. We can go back to that in a moment. When that request is done, here's
the function that I want to run. First, get the response text--
whatever came back from the server when I made this call to the server. Save it inside of a
variable called response. And then take the body of my HTML page. Remember, querySelector will
search through my document, finding me something
that has ID called body and then filling in the
inner HTML of that body block with whatever that response is--
whatever this variable response is-- which is whatever text
came back from the request. So this function now will allow
me to load any of those pages-- that first page, the
second page, or third page, get the contents from the server, and
then just fill it in to the body block. Then when is this
function actually used? Well, it's used in a couple of places. What we have here is that when
the done content is loaded, when my web application is done loading,
the first thing that I'm going to do is load_page first. Call this load_page
function that's going to get me information from the server
and go ahead and load the first page. But then in addition to
loading the first page, the next thing that I
need to do is make sure that when I click on any
of those links, those links will load the subsequent pages. And so I'm going to
do a querySelectorAll, selecting for all of the things
that have a class of nav-link. Recall the done below. I said that each one of these
URLs has a class nav-link. So if I want to select
all those links, I can just select for everything
that has class nav-link. And now I've got a JavaScript array
of all of those individual links. And for each of those individual
links, I want to apply some logic and here's the logic
that I'm going to apply. Namely, when that link is
clicked on, on the click event, I'm going to run this
function where I'm going to load the page link.dataset.page. And recall that
link.dataset.page is going to get me whatever the data-page
attribute is of this particular link because I can use the
data attributes to be able to store whatever information
I want associated with these links. So each of these links is going to store
for me which route I want to access. In this case, first
and second and third. And so how does this
actually work in practice? Well, if I go ahead and
go into single-page zero and run this application, what I
get at first is just the first page. Now recall that originally the body
here-- this body of the page is empty. And yet, when I load the
page, it fills automatically with the contents of
that first piece of text. And the reason why
that was able to happen is because immediately when
the DOM is done loading, I called load_page first to say
load the contents of the first page, asked the server for whatever the
contents of the first page is, and then plug it into the body. And now if I click on
the second page, then I see the contents of the second page. The third page on the third page. And never am I making a
request for a brand new page. I'm just pulling information
that I need from the server and plugging it in to where I need it. And so why might this
single-page approach be advantageous over
a multi-page approach, like the one we saw
at the very beginning? What advantages does
this have that might make it useful for some web applications? AUDIENCE: We don't have
to reload the web page. BRIAN YU: Exactly, we don't
need to reload the page. So if there is a lot of content on this
page that isn't changing, for example, this navigation bar just really stays
the same, no matter which page I'm on. Then I don't need to constantly be
reloading the entirety of the page. All the HTML header information and
this navigation bar information-- I can just load the actual
information that needs to change. In this case, namely
just the text that is going to appear in the
actual content of the page. So that's certainly one advantage. What's a disadvantage? Why might this single-page
approach be not as good? Or what's something that
I lose in doing this? Yeah? AUDIENCE: [INAUDIBLE] could be wrong. I just waste [INAUDIBLE]. BRIAN YU: Yeah, so this might be a
little bit of overkill, for instance. An example like this,
certainly it might be, where I'm just loading
an individual paragraphs. And it wouldn't have
been all that much work to load the entirety of the HTML page. So that's certainly one
trade-off to be watching as well. And one thing you may notice in terms
of how this page operates differently than the multi-page
approach is that recall in the multi-page
approach, when I clicked on an individual link, the URL changed. The URL when I clicked on the second
page, took me to /second as the URL. And when I clicked on the third page,
that took me to /third whereas here in this single-page approach,
when I click on individual links, notice that up here in the
URL bar, nothing's changing. I'm staying on the same page and so
the URL doesn't necessarily change. And maybe that's what you want. But maybe in many other
applications, it's useful for the user
of the web application to be able to see the URL as
an indication of where they are in terms of navigating your website. It might be helpful for the user to see
a /second up in the URL so that they know, in fact, that they are on the
second page or see /third to know that they are on the third page. And a lot of users will utilize
that URL for that purpose of providing that additional information
and context about where they currently are. And so how can we recreate
that functionality of allowing for the URL
to change without actually needing to reload the whole page? Well, luckily, in the latest
version of HTML, HTML5, we have access to what's called
the HTML5 History API, which is a feature of HTML5 that allows
us to manipulate the browser's history effectively and update the URL
to reflect different URLs that I might want to create. And so the way that this works is that
we can push URL states into the web browser's history. So I might start out
on the /first route. And when the user clicks
on the second link, that takes me to presumably the second page,
even in a single-page application, even if I'm not trying to access some
new HTML page, I can, using JavaScript, tell the web browser that
I want to update the URL. Push this new state that I
want to be at URLs /second. And likewise, when I click on the third
link, I can push this new URL, /third, so that I can update the URL,
depending on what the user is clicking on or depending on what's happening
inside my application at any given time in order to allow for this
additional functionality. So let's take a look
at how we might expand upon this single-page application
idea to be able to allow for changing the state of the URL. So let's now look at single page
one and look at index.html there. So most of this is ultimately the same,
but there are a couple of differences. In particular, when inside
of my load_page function when I'm trying to load a new page from the
server, when the request is downloading after I fill in whatever
information I got from the server into the body block of HTML
page, now what I want to do is I want to update the URL. And so I'm going to
do a couple of things. The first thing that I'm doing--
and this is just for good measure aesthetically-- is that
every web page has a title. It's the thing that appears at the top
of the web page and the web browser. And so I might want to update
the title of the web page to reflect the new page that I'm on. And so I can just set that by
using document-title to get at whatever I want to call the page. And then history.pushstate is how
I manipulate the HTML5 history by saying I want to push
a new URL, in particular. The first argument
here is any data that I want associated with me pushing this
new URL, and we'll see in a moment why that's going to be useful. But for now, we don't
really care about it. So we'll just call it null. The second argument is the title of
the page that we're trying to push. And the third argument,
in particular, is the URL that we're going to try to push. In this case, the title
and the URL are the same. But this is ultimately
what is going to affect what URL gets changed on the URL
bar at the top of the web browser. And so using just this line, I'm
able to update the current URL that I'm at in order to reflect
whatever page that I happen to be on. And so if I go into single page
one and run this application, what I get is I start out
on /first immediately. And so my URL is /first. And notice that when I click on second
page, the URL changes to /second-- almost as if I had clicked and gone
to an entirely different page when in actuality, all that was happening
was that inside of the code here, after I loaded the second
contents of the second page, filled it into the body, I pushed
this /second route to my history. And as a result, the URL
is updated to reflect that. And so that allows us to
update the URL in order to make it reflect whatever we
want, but this isn't quite perfect. Any ideas as to what
might not work about this pushing different things to
the URL and expecting the user to have a similar behavior to as if
this were a multiple page application? AUDIENCE: Does the Back
button work on the browser? BRIAN YU: Yeah, great question. Does the Back button work? What happens when I
press the Back button? Well, in this case, if I press the
Back button, I go back to /first. , Certainly, the URL changed, but
nothing changed on the page. I stayed on the second page. So I clicked the second page link. The second page loaded. But as soon as I clicked the Back
button, the URL changed back to /first, but the contents of the page stayed
on the second page because nothing in my JavaScript code,
of my index.html page, says anything about what to do
when the Back button is pressed. What should happen then? We don't really know. So that's going to be the next step. How do we fix that in
order to allow ourselves a situation where we can allow the back
and forward buttons on our web browser to work as we would expect them to? So the idea here is that after
we go to first, second, third, when we want to go back, for
instance, to the previous page, if we think of this set of URLs
as a stack-- just one link, followed by another link,
followed by another link-- when we go back, that's
effectively popping whatever was on the top of
the stack off the stack. And so now we're no longer on the third
page, but we're on the second page instead. So we went from first, second,
third to just first and second. And in order to do that, we're going to
use an additional feature of the HTML5 History API. We used push state as
a JavaScript function that allowed us to add some
new URL to the stack to update whatever the URL was in the URL bar. But we're now also going to
handle the popstate event. What happens when I try and
pop something off the stack? What happens when I try and click
the Back button in order to go back to whatever page I was on before? How do we handle that situation? And so we handle that
in a single page too. So let's open up index.html
for single page 2. And so inside of this
load_page function, when I push the state, as I did before,
in addition to pushing information about what the title of the page
is and what the URL of the page is, I'm also going to take advantage
of the fact that when I push State, I can push data on top this stack. I can store information about
this page that might be relevant if I ever later need to go back. So in this case, what information do I
need to store about this page in case I ever need to go back to it? Well, I'm going to store this JavaScript
object that has two values in it. One is whatever the
title of the page is, which is just this name value, which
is going to be first, second, or third. And the second thing that I care
about is storing the actual text content of what was on this page. Whatever came back in
this variable responds this is information that I will
need to know in case I ever go back. Because if I go to the second page, and
I fill in the contents of the HTML page with the second paragraph, and I
go back to the first page, well, I need my web page now to know what
text to fill in to the first page. And that information
would have been lost if I hadn't tried to store it somewhere. And so by taking advantage
of this data that I can associate with
history.pushstate, this allows me to store this data
with the URL that I'm pushing. And now when the state is popped-- in other words, when someone presses
the Back button on the web browser to go back to whatever page
they were on previously, then this is what's going to happen. So we're going to handle this popstate. When someone, on the window, tries
to popstate-- in other words, press the Back button on this window-- here's the function that should run. I get as a parameter, e, for
the event that just took place, and I can get the state of whatever
was just popped off with e.state. And so I'm going to store
that inside of data. And so this data is going
to be that JavaScript object that contained a
title, and that contained the text that I wanted on that page. And so what should I do
with that information? Well, with data.title,
that title that got pushed onto the stack,
well that should become the new title of my HTML document. So I'm going to set document.title
to be whatever that title was. And then what should I do with the text? Well, when I go back to that first page,
whatever that first page's text was, whatever was inside of
data.text, that's what I want to fill in to the body of my HTML page. So I go document.querySelector
body to get whatever the body block
of that web page is, and I fill in the inner HTML
of that HTML element with whatever that text response was. And so what all of this is going to
do, and what all of this is saying is that if I ever press the Back
button, this onpopstate event, then get the information
associated with that page, e.state, the state that
I'm trying to go back to, take the title of that, set
that to be the document's title, and then fill in the body of the
page with the text of whatever was on inside of that JavaScript object. And so how does that
manifest itself in practice? If I now try to go into single
page 2 and run that application, now I start on the first page. And if I go to the second
page, I go to /second. And notice, in particular, the
title updates to be second. This is document.title,
the title of the web page. And if I click on third,
now I'm on the third page. And notice the documents title
also updates to be third now. And now if I click Back-- I've built up this stack
first, second, third. If I click the Back button, that's going
to pop third off the top of the stock. And now what I'm left
with is second, and that's going to trigger that onpopstate
event, which does two things. The first thing it does is it
changes document.title to be whatever the title was-- in this case, second. And it changes the text of this page--
whatever was inside that ID body block-- to be whatever the text was stored
inside of that JavaScript object. And I can go back again. I go back, and now
I'm on the first page. And the first page's
text is there as well. So by taking advantage
of the HTML5 History API, the ability to manipulate
URLs, the ability to use this popstate
event to figure out what should happen when the
Back button is pressed. We can begin to create these
single-page applications that allow the user to stay on
the same page and just pull in whatever data from the server
we need without actually needing to reload the page and
reload any of the content that we already had on the page. Again, if we ever were to need it. Questions about any of that so far? AUDIENCE: What happens if
you press the forward button? BRIAN YU: Great question. What happens if I press forward? So if I press forward, it sort of
does the same thing in reverse. So it keeps track of not
only the previous states that I was in but also in the
states that I went back from, such that if I press
forward, now it's going to get me whatever I
just popped off the stack and sort of bring that back for me. And so now the title updates the
second, and the text updates the second. And that's all again because
of that same onpopstate event. So the backwards and forwards buttons
will work exactly as intended-- great question. OK, we'll change gears a little bit. And we'll start to talk more about this
idea of the window and the document that we've been dealing with. So we've dealt with this window
and document variables a fair bit, where we did window.onpopstate
just now to say here's what should happen when you go back. And we've used this document variable a
whole lot to try and use querySelector to try and extract information. Window and document are just
examples of these JavaScript objects that we can perform operations
on and access properties of. And in particular, we have access
to information about their size and about their position. So for instance, if we take
a look at this example here. This is just a generic window. If I wanted to get the
size of this window-- the size of various
components of this window-- well, window.innerwidth
would be a variable that I could use to get for me
whatever the width of the window is, just in terms of number of pixels. I can use that information to
get at the width of the window. And likewise, I can use
a window.innerheight to be able to extract for me
whatever the height of the window is. So just in terms of number of
pixels, how tall is the window. But of course, the
window is not displaying the entirety of the HTML page. If I have a long HTML page that I would
need to scroll through presumably, then what I really have
is this longer document. This gray area you can think
of is just the document, where inside of the document,
only a potentially small component of that document is actually
visible inside of the window. And so there are other properties
you can access as well in order to get at information
about the document. So document.body.offsetHeight
offsetHeight will get you the displacement
height of any HTML element. And so the offsetHeight of the body
of the HTML page just gets me-- really the entire height
of this entire document-- of which the window's height is
probably only a small portion of that. And likewise, I can also access
variables like window.dot.scrollY to say how far down along the
page am I currently scrolled? If I scrolled down 50
pixels window.dot.scrollY will be 50, for instance. And so you can use all
of this information to be able to look at the current pixel
values of things about your window. And this is helpful if you
ever want to use information about where the user
scrolled on a page are or how big the current window is to
be able to do different things inside of our JavaScript code. So for instance, maybe I wish that
there were a way in JavaScript that I could tell when I
was at the bottom of a page when I had scrolled down all the
way to the bottom of the page. Just in terms of these variables here,
and you might not need all of them, but giving you access to
innerWidth, innerHeight, scrollY, and document body offsetHeight. What would have to be true about the
relationships between these values for me to know that I had scrolled
to the bottom of the page? Take a look at the image and
what scrollY, and innerHeight, and offsetHeight mean. And see if you can come
up with a way that I could tell that I was at the
bottom of this HTML page-- that I had scrolled all
the way to the bottom. AUDIENCE: If scrollY
equals window.innerHeight. BRIAN YU: If scrollY
equals window.innerHeight. OK, so that's a good thing so far. So if scrollY were equal
to window innerHeight, that would mean the amount that
I've scrolled down now is equal to however tall the window is. So that would mean if my
window was originally here, and I've scrolled down. I've scrolled down one
full window's worth. But it's possible that there
is more information still below-- that the body is larger
than two window's worth of content. So we'll have to make some slight
modifications to be able to detect when we're at the bottom of the page. Yeah? AUDIENCE: It'd be innerHeight
plus scrollY equals [INAUDIBLE] BRIAN YU: Yeah, exactly. Yeah, so if the innerHeight-- the
window.innerHeight plus window.scrollY were equal to the
offsetHeight of the body. In other words, if the
amount that I've scrolled plus however tall the
window is is the same as the height of this
entire document, then presumably I must have
scrolled to the bottom. So that's an excellent
intuition, and that's correct. That's how we might be able
to using these variables, figure out if I have, in fact,
scrolled to the bottom of the page. And so let's take a
look at that in action and then look at some
practical applications of that. We'll take a look at scroll.html
and what scroll.html is going to do is inside the body of this
page, inside of scroll.html, I just have a whole bunch of paragraphs. I have paragraph one, two, three, all
the way down to paragraph number 100. So a whole bunch of
paragraphs that presumably I would have to scroll through. And what I have up inside
the JavaScript code-- if we go up to the top are whenever
window.onscroll, whenever I scroll, the first thing I'm going
to do is I'm going to log some information about these heights. So if you're using this
in your own web browser, and you look at the console
inside of Google Chrome or whatever web browser
you're using, you can see some of these values,
which can help you compare them. But then here if window.innerHeight
plus window.dot.scrollY is greater than or equal to-- or really just
equal to would have been fine-- the offsetHeight of the body-- that
same equation that was just described-- then let's go ahead and take the
body and change its background color to green and otherwise let
the background color be white. So the idea here is that when I'm
scrolling, if I get to the bottom, I want to change the
background color to be green because I've detected now that I've
reached to the bottom of the page. And so if I go ahead
and open up scroll.html, I see a whole bunch of
these individual paragraphs. And if I scroll-- right now the background's white-- if I scroll all the way to the
bottom, as soon as the amount that I've scrolled-- scrolled plus the height of the window-- is equal to the entire height of
the document-- in other words, I've reached the bottom-- the page turns green. If I scroll up, and now
it's white again because I'm no longer at the bottom of the page. I scroll back down to the bottom. Now the page turns green. And so I'm able to detect now that
I've reached the bottom of the page by using JavaScript and by comparing
things about the window and about the document. So this is all interesting. But why is this at all practical? What might be a use
case where it might be relevant or useful to be able to
know when the user has scrolled to the bottom of the page? Yeah? AUDIENCE: Would that load
more than a lot of pages have? BRIAN YU: Yeah, exactly. A lot of modern web applications
now have this either load more or this automatic
loading, where when you scroll to the bottom of a
page of tweets, for instance, it will automatically load a whole
bunch of additional tweets for you to continue to scroll through. And so what we're going
to do now is take a look at how we can use JavaScript to
build out that kind of idea-- that idea of infinite scroll, where you
can scroll to the bottom of the page and then load the next
set of information. And this has a lot of advantages. Why would it be useful to
dynamically load new information when you reach the bottom
of the page, as opposed to just loading it all
once at the beginning? Yeah? AUDIENCE: Performance
[INAUDIBLE] it would be really slow to load everything all at once. BRIAN YU: Performance-- certainly,
that if there is a lot of content, and the user might not ever want
to look at all that content, there's is no reason
to load it all at once. And even if they do want to
look at all that content, it's a little bit nicer
to load it in increments, rather than have them wait
all this time just to be able to see the first couple of posts. And so let's try and implement
this infinite scroll now with this ability and knowledge
of how we can detect when we've reached the bottom of the page. So I'll go ahead and go into post 0. And post 0 is just going to be
simulating a social media feed, where I have some number of posts, and
really an infinite number of posts that could continue to infinitely scroll. And so how is this going to work? Well, I have this default route
that renders index.html, which we'll take a look at in just a moment. This is the Flask application. And now I have this /posts route. And this /post route is going to be
a route that I can access via AJAX or just by making an HTTP request
that will get me some posts. And in particular, I need to
provide as arguments whatever the start and end of these posts are. And maybe I want to get posts
1 through 20 for instance. These ors are just
default values in case I don't specify what
the start and end are. But if I say I want posts 1 through
20, then what I'm going to do is generate this list of posts. And the way I'm going to generate
this list of posts for now, since I don't really have a database
connected to this web application, is just call each post number i. So I'm going to loop
through from start number to end number and just
add to this list of posts a string that says post
number 1, post number 2, post number 3, so on and so forth. Then you would never actually want to
include this in an application but just to simulate the idea that it might take
time to get data from the web server to query from the database,
I'm going to do time.sleep(1), which just means take a one second
pause before doing anything. And then finally, I'm going to return
a JSON response of all of those posts. So this /post route is going
to be how I access new posts. I'm going to ask for posts1
through 20, presumably. And then later, ask for 21
through 40 in the next step. So how does this work? Let's go ahead and look
at post0's index.html, which is where the interesting
stuff is going to happen. Inside the body of
this web page, there's very little actually in the body. I have a div, just an HTML element
that's going to store the posts. But right now, just stores id
equals post, and that's it. So nothing actually inside this element. And so what I'm actually doing-- well, let's start by looking
at this load function. So inside this load function, I'm going
to define a start and end variable, and they're defined in terms
of a counter and quantity. So I've defined up here
let counter equal one. So the first post that I want to
load, that should be post number 1. And then I have this
second variable called quantity, which is just
going to be how many posts am I going to load at any given time? So we'll just say, in this case,
let's load 20 posts at a time-- load the first 20, then the next
20, then the 20 after that. And so I define the start and end value. I update the counter. So that the next time when
I start loading new posts, it's going to start from 21, presumably. And now I'm going to
make an HTTP request. And this is very similar to the
HTTP request we made before him. We're going to make a
post request to /post. And then when we've finished
making that request, we're going to get the data that
came back from that response. And then for each of the
items in that data-- remember, the data is just going to be this long
JavaScript array of individual posts. For each of them, we're going to
run this add_post function, which we'll take a look at it a moment, which
is going to add a post to the DOM-- add a post to the window that
the user will actually see. When we make this request,
we need to make sure that we tell the HTTP request what the
start point is, what the end point is, and then we send the request. When the request comes back, this
add_post is what gets called, and so add_post is going
to be called 20 times. Every time I try and
load 20 new posts, it's going to be add_post post number
1, add_post post number 2, so on and so forth. And to do that, I want to
add HTML content to the DOM. So I'm going to define
a new variable, post, which is going to be the result of
creating a new div inside of my page. I'm going to give it
a className of post, and I'm doing that because up at the
top of the page, if you took a look, you'd find some CSS styling code, where
I've given the post a background color, for instance, just to distinguish it. And then the innerHTML
of the post is content. Whatever the contents of this post is-- presumably, it's post 1 or post number 2
or post number 3 or so on and so forth. And then I'm going to add that
post to the DOM so querySelector posts and go ahead and
add to this new post to my div that contains
all of those posts. So that's ultimately
how new posts get added. Whenever I run the load
function, that's going to make a request for 20 new posts. For each one of them, I'm going to
call the add-post function, which is going to add a new post to
the contents of my HTML page. So when now does this load
function ever get called? Well, it gets called twice. It gets called once up here on line 25. I add an event listener. When the DOM content is done
loading-- in other words, when the page loads for the first time,
go ahead and call that load function. Load for me the first 20 posts
without me having to do anything. But then also on scroll, whenever
I scroll the page, let's check. If this equation holds-- in other
words, if as we talked about before, I've reached the bottom
of the page, then go ahead and call the load function. Go ahead and load the
next set of 20 posts. So this load function is what
loads us an additional 20 posts, and this condition is what
makes it such that we're only going to load the next 20
posts if, in fact, we have reached the bottom of that HTML page. So how does that ultimately
work in practice? Let's go ahead and go
into post zero and run. And now if I go to this
page, what I see immediately is that I have these individual posts
that were all dynamically loaded. And if you look at the scroll bar,
there is not too far to scroll. And so what you'll notice is that
I'll go to the bottom of the page-- and this will happen fast because
it'll be like half a second before the next one is loaded. But notice that I get to the bottom
post number 20, and that was the end. Then half a second later, because
I reached the bottom of the page, it queried for the next 20 posts,
and now the next 20 posts are here. I scroll down again. I'm at 40. Half a second later, the next 20
posts appear underneath that as well. And so now I've recreated this
idea of this infinite scroll, where I can continue to scroll
to the bottom of the page and render the next set
of posts after that. Questions about anything
we've seen so far? And how we were able to make that work? AUDIENCE: The counter,
that's a JavaScript variable? BRIAN YU: Correct, counter
is a JavaScript variable. AUDIENCE: [INAUDIBLE] BRIAN YU: It was enclosed, so it
was inside of the script tag still. So this whole JavaScript content
was all inside the script tag. And in practice, what
you would probably want to do is move this all out into
a separate JavaScript file-- separate .js file-- just to clean things
up so that we don't have JavaScript and HTML all together. But because this is inside of the script
tag, it will be a JavaScript variable. In this case, just a global variable
that all the functions have access to. OK, so that was being able to
create infinite scroll just by allowing us to check if
we're at the bottom of the page and then render the next set of posts. What might be nice now is if we can-- in addition to creating
new posts, maybe we would want to allow for us to
be able to hide posts as well. If we have a bunch of posts, and
ones not interesting to us-- maybe we don't care about post number 27. We can hide that post, and then the
other posts will just take its place. So how might we go about doing that? Let's take a look now at posts
one and look at index.html there. So most of this is exactly the same. But what will notice is first of all,
inside of this add_post function, we've needed to add a
little bit of content. So when I add a new post, in
addition to just creating a new div, which is going to be storing
the contents of this page, I'm also going to create a new button
using document.createElement again. This is going to be called hide. I'm going to give it
a className of hide. If you look at the CSS,
I've added some properties to make the Hide button go to
the right side of the post just for aesthetic reasons. And the contents of that button--
what is that actually going to say? Well, it's just going to
say hide, in this case. And so I've now added to the
post to add this Hide button, and I've added that
Hide button to this div. What should happen when
the Hide button is clicked? Well, I have this code here
that says hide.onclick. This is the function that I should run. This refers to whatever this
function is being called upon. In this case, the Hide button. And in JavaScript, I also
have access to this attribute called parentElement, which gets
me whatever HTML element contains the element in question. And so if this is the Hide button,
then what is this.parentElement? AUDIENCE: Post div. BRIAN YU: The post div. Yeah, exactly. So I added the Hide
button to this post div. So the Hide button is now the child
of the HTML element of the post div. And so if I have the Hide button,
the parentElement of the Hide button is the post itself. And so I'm just going
to call .remove on that, which is a built-in function that just
takes an HTML element and removes it altogether. And so when the Hide button is clicked,
that's going to remove the post. And so with just that small addition,
now if I go into posts1 and run that, now each one of my posts now has
a Hide button associated with it. And if I click Hide next to post
3, for instance, 3 goes away and now it jumps straight
from post 2 to post 4. And if I scroll down to the bottom
20, I get the auto scroll new, posts appear, and I can
hide those posts as well. Those go away and the newer
posts replace it as well. Questions about any of this so far? Yeah? AUDIENCE: Just go back to code
and was just wondering about-- where you have those constants
defined, but you're citing-- I guess I'm just confused
about constants [INAUDIBLE].. BRIAN YU: Great question. So the question is about these const. I have const post and const hide. We learned that constant
variables can't be changed, yet it seems like I'm calling add posts
multiple times-- time and time again on the first post, second,
third, or so on and so forth where these Post and Hide buttons
are referring to different things. So why is that allowed? And the answer has to do
with variable scoping. So because this variable exists only
inside of this add-post function, then the next time the add-post function
is called, when I say const post, this is an entirely new
variable called post, and it has nothing to do
with the original one. So const post would mean that inside
of this same scope, if I tried to change post equals
something else down here, later in the function, that wouldn't be
allowed because then post would refer to this same variable post that I
said wasn't going to change because I called that a constant variable. But if inside of the entire body of
this function, post will not change, then it's OK for it to
be a constant variable because the post variable is
born when I first define it. And the post variable dies
at the end of the function. So when add_post is done being run,
the post variable goes away entirely. And the next time I run line 67
here, this is a fresh variable, and so it is OK to let
that still be constant. AUDIENCE: I understood that [INAUDIBLE] BRIAN YU: Yeah, no problem. AUDIENCE: But I was wondering
about-- so you're setting-- I don't know what you would
call that-- like a number of-- so even though it's a
constant, you're able to assign values to whatever that dot is. BRIAN YU: Oh, great question. So even though it's a
constant o line 68 and 69, I'm still able to modify the className
and the innerHTML of the post. Yes, you can. And so it's a constant
variable in the sense that it's always going to
refer to the same thing. It's always referring to whatever the
result of creating the element div is. So I've created this new div
element and post will always refer to that development. I could never make post refer to
some other div element, for example. But if it's referring
to that div element, I can still change the
properties of this div element by assigning the className
or innerHTML attribute of it. So that is something
you are allowed to do. You just can't change. I could never have a line that was like
post equals something else entirely. Yeah, great questions. AUDIENCE: Is it like a
pointer, the div [INAUDIBLE]?? BRIAN YU: Yeah, you can think of it as
analogous to a pointer in that sense. That it's pointing to the div object. It will always point
there, but I can still change the information that's actually
within that div object that can modify its innerHTML and so on and so forth. OK, so what we've maybe
noticed at this point is that as I started
to use JavaScript to be able to build a more complicated user
interfaces, to add things to the DOM later, this code in general is just
starting to get a little bit messy that I have to create a div
and assign its className to be post assign its
innerHTML to be the content. Really what I would
like to be able to do is just write the HTML
for all of this code. Write a div and set class equals post
and inside of it have the contents. But of course, I am not
currently able to do that because I don't in
advance know the exact HTML that I want to put into the DOM. I don't necessarily know what
the contents of all of the posts are going to be. And so this is where we're going to
introduce a new idea of JavaScript templating, where we can build
templates in JavaScript that allow us to really write
exactly what HTML that we want and maybe substitute in different
aspects of those templates in order to customize
them to our liking. We've already seen this a little bit in
the context of the backtick symbols-- these ES6 template literals,
where, for instance, in some of our previous examples
we were able to plug in into URL, for example, like dollar
sign and then in curly braces, plug in something here. We're going to be building
on those same ideas. But this time, taking
them to the next level. And so there are all sorts
of JavaScript libraries that are designed for this very purpose. I'm trying to make it easy to create
templates in JavaScript that you can then allow to yourself to build
HTML elements that you can then insert into the DOM. Some popular ones include Mustache and
Underscore and Lodash and Handlebars and all sorts of other ones. For this class, we're just going
to take a look at Handlebars, which is one such example of
these templating libraries it's by no means the only
one, and different ones have their different
advantages and costs. But once you've seen one, you'll get
the general sense for what they do and how they work, and the other ones
become very easy to pick up after that. And so what we're going
to be building now is a set of applications
that allow us to roll dice. Maybe we want a dice simulator
program that rolls dice for us, and that's what we're going to be doing. So let's take a look at
the example of dice zero. And I'll go ahead and run
this application first to show you how it works,
and then we'll take a look at the code that actually makes it work. So if I refresh this page-- oh, sorry. This is not a Flask application. I'm just going to open up dice.html. So this is just an HTML
page-- no Flask involved. Just HTML and JavaScript. And what I have on this
page is a Roll button. And when I click Roll,
it says you rolled a 2. I click Roll again. You rolled a 2. I rolled a bunch of 2s. But now it shows that I'm going
to keep rolling different dice, and I'm going to get a
different value each time. And each time it's going
to add that to the DOM. And so we'll take a look at how we
could build something like this using a templating library like Handlebars. And then look at how
we can use Handlebars to extend this to make it even more
powerful and even more functional. So let's take a look at dice.html. And inside the body of this HTML
page, I have a button called Roll. And then this unordered list of rolls,
which is going to start out empty. So nothing too crazy so far. A button and then an unordered
list that eventually I'm going to be adding things to. So what needs to go
inside of the JavaScript? Well, here is the code. The first thing that I'm going to
do is define a JavaScript template. This is going to be
the Handlebars template that I'm going to create
that I'm going to use whenever I want to create a new role. This is the template for what
should be added to the DOM anytime I roll the dice? So in this case, I'm going
to say Kant's template equals the handlebars.compile is just how you
compile or create a brand new template. And this template resembles
a lot like the Jinja type templates that we used when
we were working in Flask and think of it as sort of the
client-side analog to what the Flask templates were like on the server. And this template is just going to
be very simply, li-- a list item. You rolled a and then
these double curly braces. Just like in Jinja, they
meant plug in a value here. They mean the same thing in Handlebars. Plug any value here. We're going to plug in a value
and then close the list item /li. And so that is this template that
we've now created using Handlebars, and we'll use that, eventually. When do we use it? Well, whenever the Roll
button is clicked-- so we select the Roll button--
the thing that id roll. When it's clicked, run this code. And so here is roll, which is
equal to this equation here. And so math.random is a built-in
JavaScript function that gives me a random number between 0 and 1. And given a random number between 0
and 1, that's not actually what I want. I want an integer between
1 and 6 inclusive. And so I'm first going to take this
random number and multiply it by 6. Now I have a random real
number between 0 and 6-- 0 and 6 but not including 6. 0 up to 5.99999, presumably. But that's still not quite what I want. So I'm going to add 1 to it. Now I've got a random number
between 1 and 6.99999. And by taking the floor
of that-- math.floor is another built-in
function in JavaScript. That's going to give me one,
two, three, four, five, or six. And so this is a common
paradigm that you'll see whenever you want to generate
a random number in some range, JavaScript built-in
only lets me generate a random real number between 0 and 1. But by doing some math to it,
by multiplying, by adding, by taking the floor, I can convert that
to a random number in whatever range I happen to want. And so Roll will give
me some random roll. And now I need to take that roll value,
one, two, three, four, five, or six and actually add it to the DOM. And so what is the content of
what I'm going to add to the DOM? Well, I'm going to run this template. This template you can think
of now as a function that is going to take arguments that define
what to plug into this template. And then give back to me the HTML
content that I actually care about. So I'm going to run
this template, passing in this JavaScript
object that says, here is the value that I want you to plug in-- where this value matches
up with this value here. And the value is roll. Whatever this variable
roll was, the result of doing the math.random and then
the mathematical manipulation. And so that will give back to me
li you rolled a 2 /li, for example. And I'm going to take
that content and just add it to the HTML of my
unordered list of rolls. So the result of that is that
I can now click the button that generates the new template. And notice this value 4-- or
whatever the value of the roll was-- gets plugged into this template of list
item you rolled a plug in a value here. So this is just a very simple
example of an HTML template. And if I wanted to make this application
more interesting, more dynamic, maybe instead of just plugging
in a text value of just roll-- you rolled a one, two,
three, maybe I will care about putting in
an actual image of dice. And so I have an inside of
dice1, a whole bunch of images. And so I have an image for the one,
two, three, four, five, and six. These are just PNG
images that I happened have stored in the same
directory as this HTML file that I'm going to work with. And using that, I'm going to try to
actually plug in the image of the dice, instead of just the text value. And so let's go ahead and
look at dice1 dice.html. This is all going to
be almost identical. The only difference is
in what the template is, what it is that I'm compiling. And I'm going to say you rolled. And then rather than just plug in a
value here, what I'm going to plug in is an image. So image source equals. And the source, I usually use
enclosed in quotation marks, but I have to be careful because this
whole JavaScript template is itself already in quotation marks. I need to escape these
quotation marks to make sure JavaScript knows treat
this as an actual quotation mark and not the end of the string. And so I say slash, quotation
mark, where is the image stored? Well, it's image 1.PNG or
2.PNG or whatever the image is. So I'm going to plug in the value
here now in the file name of the image that I want to display and
then close that image tag. And so the result of this is hopefully
going to be that I say you rolled. And then it's going to actually
plug in an image there. And so now if I go ahead and
open dice.html, if I click Roll, you rolled a five. You rolled a two. You rolled a six. And so now I get the image
actually placed in there as well. But this still isn't perfect
because this is still a little bit messy that all of the contents
of the JavaScript template that I want to insert here has to
be enclosed inside of this string. And we're starting to notice this
string is beginning to get pretty long. And as a result, it's a
little bit inconvenient. I have to worry about it getting long. I have to worry about these
quotation marks or whatever. I would really like to do
is just write pure HTML and then figure out a way to
get Handlebars to compile that. How do we make that work? Well, let's take a look at dice2 now. So what I'm going to do here is,
first and foremost, before any of my actual JavaScript, I have
this additional script tag. And this script tag has
an ID called result, it's going to represent the
result of performing a die roll. And it has a special type. This is just a type that's
defined by Handlebars, and Handlebars knows how to look for it. It's typed as
tech/x-handlebars-template. So inside of the script
tags that are going to be HTML code that is going
to represent the handlebars template that I want to create here. And so in particular, what do I have? I have a list item,
and it says you rolled, and then it has this image, which
has an alt value and a title value. These are so that if I
hover over the image, then it will show me the
text of what number I rolled. And this is also useful for people
that are looking at a browser that doesn't support images, for instance. They'll be able to see the
alternate value text there as well. And the source of the image is an image
and then plug in a value here, .PNG. Notice that I'm still able to use this
Handlebars template syntax of plug in the value here into the
HTML, even though I'm not really inside of a Handlebars.compile. We'll see why we're allowed
to do that in just a moment. But the advantage here is that
I'm able to literally just write the HTML that I care about,
plugging in values whenever I need to using the double curly braces
without needing to worry about escaping the quotation marks, without needing
to worry about making everything in one long string. I've just written here
inside of these script tags the HTML code that eventually I'm
going to want to add to the web page. So now what happens here? Well, rather than handlebar.compiling
a big long string, representing the entirety
of what I want to compile into the template, what I can say is
document.querySelector pound result innerHTML. In other words, get the
thing with id result, which is that script block up above. Get the innerHTML. event. And that's the thing you should compile. In other words, I'm saying get the
contents of this script tag here. This is the template that I want to use. So go ahead and use
that as the template. And now when I call in the template,
I can plug in individual values there as well. So if I go into dice 2 and open that up. Now when I roll, I
see you rolled a six-- different things I can roll. And hovering over the image, I
get this alternative text as well. I hover over the image, and a six
shows up or a four or a six or four because that's the title text
of the image that I've created. And that was all just substituted
in via the script tag. And so using this, I can begin to
factor out some of the templates, put them into different script blocks,
and that makes it a little bit easier. Yeah, question? AUDIENCE: Yeah, the assignment,
you used [INAUDIBLE]---- is it part of the document? [INAUDIBLE] BRIAN YU: Good question. Why am I allowed to use
document.querySelector? Document refers to really everything in
the entire contents of the HTML page. So it includes things that are
outside of just the body section. So even though the script
is outside the body, I can still do the
querySelector with the document. AUDIENCE: OK, [INAUDIBLE] been doing
this this way or doing anything like [INAUDIBLE] BRIAN YU: Great question. So what's the difference
between doing things this way and doing things with
Jinja as we saw before? So Jinja was that templating
language we used with Flask, where we would likewise use the double curly
braces to say plug in a value here. The difference is the Jijja templates
are generated on the server. So we needed a Flask server running. And when I requested a
page from the Flask server, Flask could use a Jinja
template to plug things in it and then hand that back to the client. What we have now is templates that
are being rendered and generated on the client computer. So there is a performance
benefit there because we don't need to ask the server
to render the new template and then send it back to us. We can do it all just in
the client's web browser. And in an example like
this, where there actually is no Flask server involved at
all, it's just this HTML page. That allows me to do the
same sort of templating thing whereas I couldn't use Jinja
here because I don't have a Flask server that's running at all. I'm just looking at an HTML
page but good questions. OK, so that was nice. But what might be helpful
now is just like in Jinja, we saw that we could not only plug in
values, but we could loop overvalues. We could say if I want to
print everything out in a list, I can iterate over all
the elements in the list and apply some sort of
templating logic to it. Handlebar supports the exact same thing,
as do many other JavaScript templating libraries. And so maybe what I want to do now is
let's take a look at dice three first. What I want to support is the idea
that maybe instead of rolling ones, I want to be able to roll
eight times, for example. And I want to be able
to click roll on those in order to get eight different
rolls, or I want to be able to type in five to get five different rolls and
specify any arbitrary number of rolls that I want and get those
generated on the page. How is that going to work? And notice here in addition to
getting all the rolls, that are just laid out one after the
other, we also have a nice total at the
end that just tells me the total count of all of those dice. How might we go about
doing something like that? Well, let's now just take
a look at dice three, which is going to be
fundamentally very similar but let's take a look at what's
different about this template. So inside of the template-- it's just called result again-- we have you rolled, and then we
have the special syntax pound each. This pound is what in Handlebars
is called a block helper. There are a whole bunch of built-in
block helpers like pound each that does a loop. There's a pound if for
doing conditions, just like we could do conditions in Jinja. And if you weren't satisfied with
the built-in block helpers that are given to you by
Handlebars, Handlebars makes it very easy to let you
create your own helpers as well and use them inside your code. But for now each is all we need
to, and each is going to loop over a variable called values. And each one of those values
Handlebars calls this. So for each one of
these individual values, we're going to include an image
that is going to be that number.png. And then at the end, we
have total, and then we're plugging in a value called total. So in order to make this template
work, we need two things. We need to pass to this
template a total, which is some number representing
the total number of rolls. And we also need to pass at
a JavaScript array called values, where each item in that array is
the number of whatever the actual roll was. And so when we use that
template, that's what we're going to have to bear in mind. So what goes on in the code
to allow that to function? Well, down here in the
body, I have an input field. Its id is counter. That's just going to be the place
where I can type in how many rolls I actually care about. I have a button just like before and
an unordered list just like before. So what happens now when I roll? Well, rather than just keep
track of a single roll, I'm going to keep track
of an array of rolls. So I'm defining a variable
called rolls, which is just going to start
out as this empty array, and it'll also keep track
of the total as I go. So after I've gotten this
counter of how many rolls I want to run just by getting at the
value of whatever that input field is,. Here's the for loop that
actually lets me repeat these random rolls over and over again. I get a new value that is
the value of that new roll. I push it onto the end
of this array of rolls, and then I update the total to reflect
the fact that that's a new roll that I want to add to my existing total. So at the end of this, I
have a variable called total, which is the total value of
all the rolls that I've done. And I have this array of rolls that is
going to be each roll in this array. Finally, I run this template,
passing in all those values. This is going to be that
array of different rolls, passing in that integer total. Save the result as this variable content
and then add that content to the rolls unordered list that I
have in the HTML page. So if I now open dice.html,
that's what allows me to now say, here's the number of
rolls that I want to roll. And I can plug-in any number. And that's what allows me to loop
over, adding one image for each one. And then at the end, displaying a total. So you can see how using and taking
advantage of these Handlebars templates, you can begin to build more
complicated, more sophisticated user interfaces, all by just defining
your templates the right way and allowing them to do
whatever we want them to do. Questions so far? One final thing before
we take a quick break. Let's take the same idea
of templating and apply it to our infinite list of
posts, as we did before. Remember that before we had to
create a new element for the dev. We had to create a new
element for the Hide button. And that was starting to
get a little bit messy. How can we begin to
simplify that process? Let's take a look at post2 index.html. Now what I have is rather than
having to create those elements using document.createElement, I just have a
script template and Handlebars template that defines a div that is going to
be a post, and then the contents, using curly brace,
curly brace, contents. Now one strange thing you'll notice
about this particular template is that I needed to enclose
it inside of this raw block. Any ideas why I needed to do
that here when I didn't before? It's a little bit subtle. So this double curly brace syntax
really has two different meanings, depending on when we're looking at it. When Flask is running
our web application and tries to render
a template, we recall that the double curly braces was Jinja's
way of saying plug in a value here. Likewise, in Handlebars, when we're
using Handlebars in JavaScript to try and create a template,
this double curly braces also means plug in a value here. And because the Flask
stuff happens first-- because we render this HTML page
using Flask and Jinja before it ever reaches the client-- before
Handlebars ever starts running. If we just had double
curly brace contents, Jinja is going to try and plug in some
value called contents into this page. But of course, that's not what we
want because contents isn't something that should be plugged in by Jinja. It's something that should
be plugged in by Handlebars when we go about in JavaScript
creating this template. And so this raw block is Jinja's way
of saying, ignore the contents of here. Don't try and mess with
the templating but just leave it alone and let it get
passed back to the client. So that template allows us to create
the post and add the Hide button. And as a result of that, now rather
than need to create new elements, all we need to do is
create a new post template, passing in the contents
of it, and then add that to my do that contains
all of those posts. So no need to do all those
document.createElement and assigning a class
name in the innerHTML. All of that is taken care of for us
just by this HTML email template. And so if I now go into
post2 and run that, the result is very much the
same as what we've seen before. We have this infinite list of posts. We're going to scroll through them. It generates new posts as well. And so that's taking what
we've learned from JavaScript and templating with Handlebars and
applying it to this situation as well. When we come back from the break,
we'll take a look at some animation and how we can begin to do a
little bit more with graphics and actually making these
websites even more dynamic. We'll take a short break first. OK welcome back. So we've been talking about how we
can build web applications using Python and JavaScript. In particular, lately,
using JavaScript to help make our web pages a
little more dynamic, to make them responsive to
when a user clicks on something or when a user scrolls through
something to allow the website to update itself in response. So we're going to
continue along that train of trying to make our
websites more dynamic, allowing for things to
change and be manipulated as things happen on the page. And in particular, we're going to
get into the topic of animation. And so there are all
sorts of different ways to animate different elements
on the HTML page using graphics and so on and so forth. We'll look at a couple of them today. And what we'll begin with is
by taking a look back at CSS, which we've used previously in
order to style different aspects of our website-- to be able to change
the color of a particular element, or add a background to
a particular element, or change the size of an
element, for instance. But what CSS also allows us to do-- in
the latest versions of CSS, at least-- is have what we call CSS
animation or the ability to change from one CSS property
to another CSS property over some duration of time,
while the page is running. And so what that's
going to allow us to do is allow us to begin to
animate content on our page in order to allow it to
change its style properties. And at first, this might seem like just
interesting things that are cool to do, but we'll soon see
practical implications for how being able to manipulate
the style using CSS animation can actually be very
relevant and helpful as we go about building our web applications. And so the first thing we'll
take a look at is animate0.html. So animate0.html is a very simple
HTML page that inside of the body just has an h1 heading
that says welcome. And what's interesting is
what's contained inside of the CSS code-- so inside of the
style block of this particular page. This is where all of the relevant
CSS animation is actually happening. What we're doing up here
with this at keyframes grow is defining an animation-- a CSS
animation that we're calling grow. And what happens inside
of the grow animation? Well, we're going to go from
something to something-- from some set of CSS properties to
some other set of CSS properties. And in this case, we're going to go
from font size 20 pixels to font size 100 pixels. So grow is a CSS animation that
causes the font size of whatever it is applied to to grow. An so now we have this
h1 styling that is going to be applied to that welcome
heading that we have down below, and we're giving it a couple
of its own CSS properties. First, giving it an
animation name, grow. And because this lines
up with the grow up here, that's saying that we
want this h1 to have this keyframe animation--
this animation of font size 20 to font size 100 to be applied to it. Animation duration is just how
long that animation should last. In this case, we're saying 2 seconds
seems like a reasonable amount of time for an animation. And then animation film
mode is a special CSS property that means in what
direction should the animation go? And by going forwards,
were sort of saying once we reach the end of
the animation, we should keep that state that we end up in. In other words, if we move from 20
pixels to 100 pixels in font size, then after the animation is done
running, we should stay at 100 pixels, instead of going back
to 20, for instance. And so by defining
these three properties, we're now telling are h1 heading
to undergo this grow animation. And the result of that is
if we open up animate0.html, which is just an HTML page. We have added no flask backend, no
Python here, no JavaScript even. Just a CSS animation. And now when I open up animate0, we see
welcome over the course of two seconds, grow in size. And it stays in that
size as a result. So that was just a very basic introduction
to what CSS animations can do. And while they can't be used for all
CSS properties-- for many properties, including the size of text, for
instance, we can apply CSS animation. Questions so far? OK, let's take a look at one
more example before we start to look at some practical uses of this. Here's animate1.html, which likewise
defines just this h1, welcome. And here's the interesting
style code that's happening. We've defined this keyframe
animation called move. And what's going to happen
in the movie animation? We're going to move from
left 0% to left 50%. And what this means
is that left is a way of indicating the relative
position of an HTML element. And this h1, you've noticed we've
given a position of relative, meaning we're going to define the
position of this heading relative to other parts of the window. In particular, at the beginning,
when we start to move animation, it's going to be 0% of the window
away from the left edge of the screen. In other words, it's going to be
at the left edge of the screen. And then it's going to move to being 50%
away from the left edge of the screen. In other words, the left
side of the HTML element will be aligned with the
middle of the window. And so this is not a change in
size but a change in position. We are allowing ourselves to change
the position of the HTML element, moving it from the left side of the
screen to the middle of the screen just by applying this
move animation to it. And so very similar to what animate0 was
doing, except instead of changing size, we're not changing the position of
this relatively positioned element. And so if we open
animate1, the result is that over the course of a couple
of seconds, that welcome moves from the left to the center. We're going to watch that again. It goes from the left up until 50%
away from the left edge of the screen. And so that's what allows us to begin
to build up this vocabulary of ways to animate HTML elements and move
them from one place to another. But what might be
helpful here is not just defining a starting and an endpoint
but also some intermediary points in that animation. Now say we want to start it in
some place, move it somewhere else, and then move somewhere
else at the end of it. And so in addition to just saying
move from something to something else, we also have the ability to
define at what percentage of the way through an
animation should we be applying particular CSS properties? So what does that look like? Let's look at animate2.html. And in this move keyframe,
we're doing something slightly different rather than
from something to something else, we're saying at 0% of the
way through the animation, we should be 0% of the
way from the left edge. At 50% of the way
through the animation, we should be 50% away from the left edge. And at 100% of the way through the
animation, when we're done with it, we should be 0% away from the left edge. What is this going to look like if I
try and apply this same move animation to the heading that we had before? Based on these three keyframes
as part of this animation. AUDIENCE: [INAUDIBLE] middle [INAUDIBLE] BRIAN YU: Go to the
middle and come back. At 50% of the way, it
will be at the middle. When it's all the way
done, it will come back. And so if I open up animate2.html,
it goes to the middle halfway through and then it comes back. Excellent. So that was all well and good,
but what we noticed so far is that we don't have
a whole lot of control over when this animation takes place. We open up the page. And because the CSS
properties are already assigned at the beginning
of the web page being run, it's just automatically going to
start performing the animation, moving welcome to the middle,
and then moving it back. But maybe we only want animations to
happen at a certain point in time. When we decide that we
won't be animation to run, the animation should run. And when we don't want it
to run, it shouldn't run. And to do that, we'll need
a little more than just CSS. We could using CSS delay the animation. Say don't have the animation start
until 3 seconds after the page loads, but that's not quite what we want. What we want is the ability
to programmatically control when the animation starts and stops. And to do that, we'll need a
programming language like JavaScript. And so one additional CSS property
that these animations have is they have a property called
animation play state that that property can be
either paused or running. And so if the play state of
a CSS animation is paused, nothing is going to happen. But if we use JavaScript to change
it to running instead of paused, now the animation will be running. So let's take a look at animate3.html. The CSS is still the same. We have this move animation that's going
to go from the left to the middle back to the left again. And then in each one,
we have animation name is move, all this
additional information. We have also
animation-iteration-count infinite. So animation-iteration-count
is a CSS property that allows us to specify how many
times an animation should run. Should it run 2 times, 3 times, 4 times. Infinite just means keep running. So it'll go to the center and
to the left again to the center and to the left again, over and
over and over again infinitely. And here is the JavaScript that we want. So we're going to first
select this h1 element and set its
animationPlayState to paused. So right off the bat, when
we first load the page, we're going to pause the
animation because we don't want it to run when we first load it. What we do want to happen though
is we want to attach a button to do something useful. So we have this button. We're going to say when the button
is clicked If the current PlayState state of this heading is
paused, then go ahead and change the PlayState to run it. Otherwise, change the
PlayState back to paused. And so what that's going to mean
is if the animation is paused like it is to start out with. And we click the button, then it's
going to change from pause to running. If it's running. And we click the button, then it's
going to change from running to paused. And in the body here, all we have
is a button that says click here and a heading that says welcome. So if we open ue3.html. Now we have the ability
to just see welcome, which right now is not doing anything. Its playState is paused. But clicking on the button causes
the playState to change to running and now welcome is going to bounce
back and forth between middle of the page and left side of the page. And for it to click here, now
I've paused the animation again. Now it's paused. But I can start and stop it at will. Whenever I decide to click on it, I
can start and stop that animation. So now I have programmatic
control using JavaScript over when that animation happens. And so this is fun. But now let's actually I a case where
it might be nice for a user interface to build in some of this animation. And so one thing you
might have noticed is we looked at that infinite scroll list
of posts earlier before the break where when I ran this application We saw this infinite list of posts. And right now, there are only 20, here. But if I ever were to hide a
post-- if I hide post number two. Post 2 goes away. And now I have post1 one. Jumping straight to plus 3? What was less than ideal about that
usee Interface experience just then? Any thoughts? AUDIENCE: You didn't really
see the posts going away. BRIAN YU: Yeah, you didn't
really see the post going away, especially because all these
posts are the same length, and they're all the same
size, like if I hide post for you really need to be
paying attention to notice that a jump from plus 3 to post 5. So this is a case where animation
might actually come into play and help the user interface if
we can actually animate something to mean the post is going Away, that might make it a
more enjoyable user experience for whoever is looking at the page. So let's take a look now at post3
and look at index.html there. And what you've noticed
here is that we're going to add an animation here
called hide it inside of the CSS. And it's going to go from
opacity one to opacity zero. Opacity is just a measure of
how transparent something is. Opacity 1 means you can see it entirely. Opacity 0 means it's
completely transparent. You can't see it at all. So if we apply this hide animation
to our posts, To all of our posts are going to have class post. And I say this is
going to have animation named hide the animation
duration of two seconds will take two seconds to hide it. It's going to move forward. So it goes transparent,
it will stay transparent. And a place state is going to start
out is paused because at first, we don't want it to be running. Otherwise, all the posts are
going to disappear right away. So when does this actually happen? Well, when the Hide button
is clicked, and this is a slightly different way of applying
the logic of when the Hide button is click but basically I'm saying
when anything is clicked figure out what with click
the element that was clicked. And if that element has class name hide
in other words, if we are clicking. on a Hide button. Then we should take the
Hide buttons parent element. In other words, the post itself change
its animationPlayState to be running. Now we want the animation to
run-- this hide animation. And now we can also add
these eventListeners. So we can say, all right,
for this entire post, at an eventListener that says
animationEnd-- in other words, when the animation is done
running run this function, which just removes the parent element. So in other words, what all of this
is doing is that when a Hide button is clicked we get the parentElement of
the Hide button-- this entire post-- we start running this hiding animation. When the animation is done running,
when the thing is totally transparent, now we remove the element altogether. And so the result of this is that
if I go into plus 3 and run that. Now I see all the posts. If I try and hide posts
number 2, for instance, over the course of two seconds, it goes
transparent, and now the post is gone. So you have this animation that
changes the transparency of the post. That makes it very clear that
that post is disappearing. I had one additional thought for how to
make this user experience any better. Do you guys have any thoughts
on how we can improve this even more make it a little smoother? Yeah, it'd be nice if we're
sort of used to the idea that things will sort
of slide into place. Right now, when I click the Hide
button on posts6, it disappears. And now as soon as the element's
removed, everything pops up. And the reason why that
happens is that when we change the opacity of the
element, the element is still there. It just now has become transparent. And as soon as we remove the
element, now everything sort of bumps up to fill its place. So we never got the idea that things
were sort of sliding into place. And so let's try and build
that out now with a little more sophisticated animation by taking
a look at post 4, index.html. And so this one's a
little more complicated, but we'll walk through it slowly. The way that we're going to
simulate the idea of hiding the post is by shrinking the height of the post. So if we can get the post to be
of height 0-- to have no height, to have the text not have any
height, to have no padding or spacing around the
post, that's effectively meaning that this post
no longer has a height. And that will simulate the
idea of sliding into place. And so at 0%, when
this animation begins, we're going to start at full opacity,
and all of this height information is going to be its maximum
height is going to be 100%. The line height to 100%. If you looked at the
CSS later, you'd notice we have 20 pixels of padding
and a 10-pixel margin. So all of that padding
and margin is still there. 75% of the way through the animation--
for the first 75% of the animation, we're just going to do
the transparency effect. We're going to cause
the post to fade out. And so the result is that
everything is the same as before, but now the opacity is zero. Now the post is totally transparent. And now at 100% of the way
through, the opacity is still zero. It's still totally transparent. But now the height of
this element is zero. Its line height is zero. It's got no padding and no margin. And so over the course of the
last 25% of this animation, we're just going to have
this transparent post take up less and less space. So the height is going to decrease. Any spacing around it
is going to decrease. And that's going to ultimately
give us the effect of things sliding into place. And so now if we go ahead and go
into posts4 and sorry, run that. Open the page. We get all the posts to load. And now when I click
hide, it first fades out, then the height shrinks and
the posts slide up into place. Again, click Hide. The first 75% shrinks, and
then they slide up into place. And the sliding happens because
that height gets decreased. And at the end, that post
just goes away entirely. And so using CSS animation, we've been
able to create a more enjoyable user experience that makes it clear what's
happening-- by showing the fade out, by showing the sliding up into place. And so these are just some of the
possible practical applications of using CSS animation in order to
make our websites more dynamic, more interesting, more engaging, and
appealing to whoever is using it. Questions on anything so far
about how any of that worked? Yeah? AUDIENCE: So is that built
into CSS [INAUDIBLE]?? BRIAN YU: Yep, it's in
the latest version of CSS, and all modern browsers nowadays can
understand these keyframe animations and make them work. Great question. OK, so that was CSS animation. There are all sorts of
other ways to do animation. We'll take a look at one
such example right now, and that's the idea of svgs
or Scalable Vector Graphics that are going to allow
us to really draw whatever we want onto the canvas of the page. So far, when we want to
put something on the page, we've only really been able
to create HTML elements like divs, which are just rectangular
segments, or buttons or text, for instance. But maybe we'd like to be able
to get whatever arbitrary shapes that we want onto the page as well. So in order to do that, let's
take a look at circle0.html, which is a very simple HTML page that
is going to define this svg element-- this vector graphic element-- where a vector graphic is
just going to be a graphic that's defined in terms
of lines and angles and shapes that we can
programmatically define. And so inside of this svg,
and I'm saying this vector graphic's going to have a
height of 800 pixels, what I have is a circle element. And so this just happens to be
one of the vector graphic elements that each HTML understands and supports. And we're saying this circle will
have a center x-coordinate of 200, a center y-coordinate
of 200, a radius of 50, and we can apply CSS
styling to it as well. We can say fill it with
blue that's just CSS there. And so we're using this svg element
to be able to create a graphic, in this case, a very simple graphic-- just a circle. So if I now open circle0.html,
what I get on the page is just this blue circle that's located
200 pixels away, 200 pixels away. And it's located there. It's got a certain radius of 50, I
believe, and it's colored in blue. And so that was using just this
code that web browsers nowadays can understand. They know how to look at svg, look at
the shapes and lines that are defined by the svg element, and fill it in. But what we might like to do is
have a little bit more control over this, as usual, where we
can define the HTML element here. But it would be nice if we could
programmatically create a circle, such that if we wanted it in a for
loop that created 28 different circles, for instance, we could do that as well. Or if we wanted to apply
animations or transitions to it, we could control that using JavaScript. And so we'll see
examples of that as well. So what circle1.index.html
is-- either it's going to be using JavaScript to
be able to create programmatically that same circle we saw before. And to do that, we're going
to use a library called D3. D3 is a very popular
JavaScript library that that's especially useful for visualizing data. It's good at creating graphs
and charts and different types of interactive graphics that are
interesting and cool to look at. It's got tons of features. We're not really going to look
at most of those features. But we're going to use
D3 just for the sake of being able to create svg elements. So being able to create
graphics and animate graphics, which is one thing that
D3 is commonly used for. And so in the body right now, I've
just defined the svg element-- nothing inside of it. And what we have here
inside of the JavaScript is I am creating this variable svg. D3.select is just D3's way of getting
access to an HTML element to work with. And you only would know about this by
reading the D3 documentation, which is available online. And I'm selecting the thing that has
is svg, which is the thing up here. And so that's my svg element--
this HTML element here. And now I can use these
special D3 functions and say, svg.append, add something new
to my vector graphic 800-pixel canvas here and go ahead and add a circle. I can specify specific
attributes just like before-- set its x-coordinate to 200,
y-coordinate to 200, radius to 90, and then fill it in green this time. And so this allows me to
do all of the same things that I was doing before but now
programmatically using JavaScript. And this gives me a little more
flexibility, a little more control, where if I wanted the radius
to be some random number, I could use that
math.random function that we used before to just
generate a random number and give it a random radius every time. So I get a little more precision
over what I want this to look like. And so if I open up circle1.html,
now I've got a big green circle here. And there are all sorts of
other things that I can append. In addition to circles, I
can also have rectangles, so I can append a
rectangle, for instance, give it an x and y-coordinate,
give it a width and height. And as a result of, that when
I open up a rectangle.html, you get a big rectangle. No need to worry about all the
different things you can create. You can create an oval. You can create arbitrary
shapes, where you define where the different various points are. But the idea here is that
programmatically, you can begin to create some of these
graphical elements that can do things. And we'll see examples of
how those work soon too. So we've been able to create
images that appear-- circles, or rectangles, or other shapes as well. What we might want to do
using JavaScript and using D3, in particular, is to animate those
things-- cause them to move around, cause them to do things. And so let's take a
look at circle2.html. It's going to be very similar
to what we had before. So we define our svg =. Then we're going to append
to this svg element a circle. Give it an x and y-coordinate
of 200 and 200, a radius of 50, fill it in as blue, and save that
circle inside of a variable called c. Now what D3 allows us to
do is apply transitions to these svg elements, which allow
us to animate those elements, allow us to cause them to change
their properties in some way. So c.transition-- so as I want to
apply some transition to the circle, duration says how long
should this transition last? This should be one second. And all of these repeated lines,
each of which starts with a dot, they could all be one line. But oftentimes in D3,
you'll see a common paradigm to separate these repeated
calls onto different lines just to make it clear one line at a
time-- what's happening at each step. So this transition, or this animation,
is going to last one second-- 1,000 milliseconds. And what should change about it? Well, let's go ahead and change the
x-coordinate to be 500, instead of 200. Change the y-coordinate to be
500, instead of a few hundred. And change the fill color
to be red, instead of blue. So we're going to move the circle
and also change of style-- change its fill color. So when I open circle2 now,
we saw the blue circle. It moved to the other
side of the screen. It changed its color from blue to red. And so that was an animation
that I could create. And that happened right away. It happened as soon as
I opened up the page. But there are different ways
to delay animations as well. And I'll just give you a taste for this. No need to worry about
all the specific syntax. But just to give you a sense for
what's possible with these animations, and we'll see where
we can go from there. So let's take a look
now at circle3.html. So in circle3.html,
we're also going to start by adding a circle to our svg element,
giving it an x and y-coordinate, giving it a radius of 50, filling
it in as blue, same as before, and saving all of that inside
of a variable called c. Then down here, we're going
to apply a transition to it. It's going to last one
second and .delay says, how long should you wait
before during the transition? So here is one way to put
off a transition until later. We can say delay it by a second
and then run the transition. And that transition is just
going to change the x-coordinate. It's going to move to the right from
x-coordinate 200 to x-coordinate 500. And in the case of navigating
the pixels on the HTML page-- 00, the origin-- that is the
upper-left corner of the HTML page. We can also cause
transitions to happen, only when something is
clicked on, for instance, like an onclick type event Handler. And the way you would write that in D3-- and again, this is all
syntax you could look up on D3's documentation, will
just give you a taste for it-- is say on click-- in other words,
when this button c is clicked on-- run this function. d3.select this is special D3 syntax
for get the thing that was clicked on. We're going to apply a transition to it. That transition is going
to last 30 seconds. And what's going to
happen in that transition, we're going to change the fill
color of the circle to be red. So if I open up circle3.html, the first
thing that happens is after one second, the blue circle moves from
x-coordinate 200 to x-coordinate 500. And then if I click on the blue circle,
then over the course of three seconds, it changes its color from blue to red. I've added this event Handler that
allows me to manipulate it in some way. And so how can we begin to use this
to create potentially a user interface that we can actually click
on and interact with? Well, let's take a
look at stoplight.html. stoplight.html is going to
be inside the body of it. All it contains is this svg element. All the interesting stuff happens in
the JavaScript-- happens in the D3. So we first get our svg container
where everything is going to go inside, and I'm going to add a rectangle first. This rectangle is going to
have an x and y-coordinate. It's going to have a width
of 200 and a height of 500, so it's going to be
taller than it is wide. And we're going to fill it in as black. And so what I'm going
to try and create here is create one of those
traffic stoplights with the red, green, and yellow lights
that can go on and off, for instance. And we'll try and see if we can animate
that using these vector graphics. So I'll define a variable
called red and that red is going to represent the red light circle. And so I'm going to go ahead
and add a circle to this svg. I figured out by just drawing it out
where the x and the y-coordinates should be, how big it should be. And I'm going to start by
just filling it in as gray. It's not going to be
filled in to start with. Same thing is going to be true of the
yellow and the green light buttons. Those are just going to be circles
that I add to the svg container. They have an x and y-coordinate. They all have radius 75, and they're
all going to be filled in as gray, to begin with. Then when the red button is clicked,
here's what I'm going to have happen. Change the red style to be
filled in with the color red. That red circle is now
going to show up as red. And the other two circles-- those should be filled in as gray. Maybe they were already gray. Maybe they weren't. But we're going to change their
color to gray no matter what. And likewise, for the yellow
button, when I click on that, change the yellow button to be yellow
and change the other two to be gray. And when the green circle is clicked on,
the green circle should now be green, and the other two should be gray. And so using just this JavaScript code
of adding these rectangles and circles and defining what
happens on click events, without any additional work needed,
now I want to open up stoplight.html. What I get is a stoplight. This was the black
rectangle that I created, and then I added three circles
of specific x and y-coordinates. And when I click on individual
circles here, I click on the red one, the red light turns on. I click on the next light,
now the yellow light turns on, and the other two turn gray. And I've been able to create a sort of
stoplight-like user interface, where I can click on something and
cause its color to change. And just by clicking around,
I can manipulate these events and manipulate the CSS properties
of these individual elements. Questions on anything so far for
how we're able to build this out? And a lot of this is just figuring
out how many pixels things should be and figuring out how
big things should be. But this is the general sense for
if you want to user interface that isn't just buttons and sliders and
people typing into text fields, you can build whatever user interface
you want just by drawing it out for yourself, by defining what shapes
you want in the user interface, and what should happen when
you click on various elements. All right, so let's
try and actually build a sort of interesting
application using D3, using the ability to add
lines and circles to our page. So what we're going to
try and do is create an application that allows us to
draw on a canvas on the web page. Sort of like a whiteboard
that lets me draw, we're going to get a web page
that lets me draw whatever I want. So let's go ahead and
look at draw0.html. So in draw0.html, we
have this svg area that's just going to be where the vector
graphics are going to be stored. And inside of the script tags,
here's what we have going on. So we have a variable called svg,
which is just that whole svg container. I have this function draw_point, which
we'll come back to in just a moment. But in particular, look at this. At the very bottom,
whenever this event happens, svg on mouse move-- in
other words, whenever the mouse moves over
the svg canvas, we're going to run the draw_point function. And so what happens in
the draw_point function? Well, the first thing I
need to do is figure out where the mouse is
currently, and D3 happens to have an easy way to get this. We do d3.mouse this, which just
gets me the current position of the mouse in the svg element. And we'll save that in
coordinates, which is going to be an x position and a y position. And now to simulate the idea of drawing. Now that I know where the mouse is,
let's go ahead and just put a black dot right there where the mouse is. So we're going to add
to this svg a circle, setting its x attribute to be whatever
the first part of the coordinates are. The y attribute is going to be whatever
the y part of the coordinates are. Radius is going to be 5. We're going to fill it in as black. And so we've taken our svg element. Whenever someone moves
their mouse over it, we're running the draw_point function. The draw_point function
figures out where the mouse is and adds a small
circle, just 5 pixels in radius, and it places it there. And so when we run that, by opening
up draw0.html, what we'll notice is that as soon as I start to move
this cursor, the mouse is moving. And as a result, every
time I move the mouse, it's putting a little dot right there. And if I move the mouse quickly, because
the mouse move event is only fired every at certain intervals, and it's
not continuously fired constantly, you'll actually see that you
can see these individual dots if I start to move them more quickly. That each time this mouse
move event is fired, it's just going to
drop a dot right there. And the result is that I
sort of have this ability to paint whatever I want just
by moving the mouse around. Questions about how I was
able to make that work? OK, so this is nice. Let's try and make some
incremental improvements of this. Figure out what code we
need to change, what events we need to change to make it better. One thing that's immediately obvious
is that I have no way to stop drawing. I'm just moving my mouse. I'm not clicking anything. But whenever the mouse is
moving over the svg container, it's drawing right there. So what I might like to do is make
it so that I need to click and drag in order to actually draw a line. If I'm not clicking, then
it's not going to do anything. So any thoughts as to intuitively
or instinctively what might I need in order you get the idea of only
drawing when I'm clicking and holding as opposed to just always? Recognizing that I have this mouse move
event, but I've been using already. Yeah? AUDIENCE: [INAUDIBLE] BRIAN YU: Yeah, so we
can look for events of when the mouse or when we click
down on the trackpad or on the mouse and when we click up and do
something interesting there. So let's take a look at what
one example of what we might do. So here we have a very
simple thing going on, where this is similar
to the prior example, except I've added a Boolean
variable called drawing. It's just going to keep track of
whether or not we should actually be drawing right now. And by default, drawing
is going to be false. And inside of this draw_point
function, if drawing is false-- in other words, if we shouldn't
be drawing right now-- then return out of this
function-- don't do anything. But otherwise, if we should be drawing,
then go ahead and do the same thing we did before. Go ahead and drop a point right there. Then at the bottom, here
are these event handlers. Whenever the mouse moves, we're still
going to call that draw_point function. But now we need to make sure
that the drawing variable is set to the right value,
true or false, depending on what's currently happening. And so what I'm going to say is on the
mousedown event-- when I click down-- go ahead and set the
drawing variable to true. And when I click the mouse
up-- when I'm done clicking-- set the drawing variable to false. That way, if I click
down, drawing is now true. Now when I move the mouse, drawing
is true as I draw the points. So it's going to draw the line. But as soon as the mouse comes
up, drawing is now set to false. So even though I'm
going to move the mouse, and the draw_point
function will be called, I'm not actually going to see any points
getting placed onto my svg canvas. And so the result of that is
that if I open draw1.html, I can move the mouse around over
the canvas now and nothing happens. But it's not until I actually
click down and actually start drawing that I get these lines here. And so again, if I move
the mouse more quickly, then you start to see that these
lines aren't actually connected. And so this isn't ideal. And there's not a ton that we can
do in terms of addressing this because we can't actually change the
frequency at which the mouse move event is fired. The mouse move event is
fired by the browser, and so we don't really
have control over that. So what might be a
good heuristic that we could use to sort of solve this
problem from a UI standpoint of, if I move the mouse
quickly, now suddenly I get all these individual points,
as opposed to a smooth connection? Yeah? AUDIENCE: Store the
previous point [INAUDIBLE] BRIAN YU: Yeah, excellent idea. So we can store the previous point
and then draw a line between them. So that's what we are going
to do, such that whenever we have two points that
are next to each other, and we're trying to connect
them, since we know that there's a possibility that there
might be a gap between them, let's just connect them by a line. It might not be perfect, but it will
at least give a sort of a better sense that we're actually
drawing on this canvas. And so as our final
example, let's take a look at draw2.html, which is going to be
our most sophisticated application yet using D3 and JavaScript. And it's going to have
a bunch more options. So inside the body of this
HTML page, we have a heading. We're just going to call it E-33a draw. And I also have a bunch of
these select dropdowns now. I'm going to allow myself to be a little
more flexible from the user experience perspective of being able to
choose what color I want to draw in and how thick I want the thickness of
my brush stroke to be, for instance. And so this select, which is just
going to be called color-picker, has a whole bunch of options. Option black, red, blue, green--
here are the possible colors that you can draw in. And here's a select dropdown,
called the thickness-picker, where I have a whole bunch
of different thicknesses. We're going to default to
selecting a thickness of three, but you could select anything
up until thickness 10, in fact. And I also have a button called
Erase, which will presumably erase everything that's on the screen. So I'm giving myself more options
now by letting the user choose how they want to go about drawing things. So what's the code that's
needed to make this work? Well, all of it is going
to be inside of draw2.js. I've separated it into a
separate JavaScript file just for the sake of keeping
things in different places because our HTML file is starting
to get a little more complicated, and it's often cleaner just
to keep things separate. So let's look at draw2.js. When the DOM finishes
loading, we're going to keep this Boolean
variable, draw, which is going to be false to start out with. Same as before-- this is keeping track
of should we be drawing now or not? Or equivalently, is the
mouse currently down or not? We'll keep track of two arrays-- an
array of points and an array of lines because our lines are going to
connect our points together. And we need to keep
track of both of them because when we later go
to erase the entire thing, we want to make sure we
get rid of everything. So we need to keep track of
everything we've created. And when we render the page-- and render is the first thing
that we're going to do-- we're going to go ahead
and add these events. So whenever I mouse down, whenever I
click down, here's what should happen. Set this draw variable to true-- because now I want to
actually be drawing this-- figure out what coordinates I'm at,
and then call this draw_point function. And draw_point, unlike last time,
is going to take three arguments. The first argument is going to be
the x-coordinate, where I actually draw a point. The second argument is going
to be the y-coordinate of where to draw the point. And the third argument is going to
be a Boolean variable indicating whether or not I should also draw a line
connecting it to the previous point. So when the mouse goes
down for the first time, I want to draw a point
where the mouse went down. But I don't want to connect
it to any previous point because this is the first
point that I'm drawing. There's nothing to connect it to. Meanwhile, when the mouse
goes up, we're going to get the draw variable to false. And when the mouse is moved,
if we're not drawing, return-- just exit out of this. Get the coordinates
and then draw the point at the x-coordinate
and the y-coordinate. But go ahead and set this
third variable to true, meaning I actually want
to connect this new point to the prior point with some sort
of line, as was suggested before. This is what happens when
the Erase button is clicked. We're going to go ahead and loop over
all of the points and remove each one. Loop over all of the
lines and remove each one. And then set both points and
lines to just be empty arrays. And finally, how do we
actually draw a point? Well, the first thing
we're going to want to do is figure out the color and the
thickness that we want to use. So I can get at the
color-picker's value, save it inside of a
variable called color. Get at the thickness-picker's
value, save it inside of a variable called thickness. Those were the two dropdowns
that we were using before. And now recall that draw_point
takes in three arguments x, y, and connect, where connect is a
Boolean of should we connect this to the prior point or not? And if we should connect
this to the prior point, then we're going to get the
most recent point, which is just the last element in this
array of points that we have, and we're going to create a line
that's going to connect those points. And so the starting x and
y-coordinates of this line are going to be the x and
y-coordinates of the last point, and the line is going to end at the
x and y-coordinates of our new point. And we're going to add that
line to an array of lines. Then finally, over here, we're
actually going to add the point. So we're going to add a circle. It's going have x and y center. It's going to have this particular
thickness and this particular color that was whatever the user
selected in that dropdown. And then we're going to add
that to our array of points as well so that we can
keep track of all of them. Then we make sure that we actually do
the rendering that causes everything to render. And the result of all of that-- and you can take a look at
the source code example. It's posted on the course's website-- is that if I open up draw2.html,
I get a nice heading. Right now, I'm on color
black and thickness 3, and I have this Erase button. And now when I start drawing,
even if I move it quickly it's still going to connect everything. And if I move things very
quickly, you can actually see that there are lines that are
connecting these individual points. But I can also change the
color and the thickness. And that causes things to be
painted in a different color, different thickness-- changed again. And likewise, whenever I
press the Erase button, that takes all of the
points, all of the lines, and just causes them all to disappear. So I'm left with a fresh canvas
that I can continue to draw. And so all of that was made possible
by using svg's vector graphics, where I can add points and lines
and have them respond to events-- have them respond to people clicking
on things, dragging on things, mouse down events, mouse up events. But all of that,
ultimately, is what allows me to build what seems to be a pretty
functional application for drawing things in different colors
and different thicknesses. Questions about any of that? OK, so that sort of concludes
our look into JavaScript. Starting next week,
we'll take a look back at the back-end looking at Python
again, moving away from Flask and moving into a different
web framework, known as Django, which is a little more powerful,
and we'll see what it can do. But that's it for front-ends
for today and have a great day.