[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.