Jonas: So let's now finish
building the pagination feature.
And for that let's actually think
about some different scenarios.
And to do that,
I will actually come here to the demo.
So the buttons that we want to render down here
are not always the same.
So, in this first scenario we are on page one.
And so therefore,
only this next button here is showing, right.
But there is, of course, no button to go back.
Now, if we had actually less than 10 results,
so basically only one search page,
then there would be no buttons at all.
Then if we are on some other page,
which is not the first page,
well, then we have a button to go back
and one to go forward.
So that is basically the third scenario.
And then if we go all the way to the last page,
then we only have a button to go back.
And so we have to account
for all of these different scenarios, okay.
So let's go back
and basically implement a new view
for this pagination.
So, here in the views,
we create a new file called paginationView.js.
And here let's actually copy some code from here
so we don't have the same work
over and over again.
Then of course, this one here is called PaginationView,
and the parent element is not this one,
so let's go check out or markup.
And so here we already are had the right element.
So you see that after this unordered list of results,
we have this div element with a class of pagination.
And so then this one here already contains the template
for a button to go back,
so to the previous page,
and one button to go to the next page.
So we're going to need this soon
but for now let's just complete this setup here, okay.
And so now just as all the other classes,
we will have a generate markup method here,
generateMarkup,
because remember, that is the method
that the render method is going to call
in order to generate the markup
for the view that we're working on, right.
So again, every view, at least every view
that renders something to the user interface
needs a generate markup method.
So that's this one here.
And so now let's account
for these different scenarios that we just saw.
So let's say the first one is, we are at page one,
and there are other pages.
Then the next one is we are on page one,
and there are no other pages.
Then there is the scenario that we are
on the last page,
and also the scenario
that we are on some other page, basically.
So other page, like this, okay.
So what data do we need in order to figure out
if we are on page one,
and if there are other pages?
Well, for that, we are going to need, basically,
this entire search object here.
So this contains all the search results,
and also the current page and the results per page.
And so we will need all of this data
in order to compute
which buttons should be displayed, all right.
And so let's actually go to our controller,
and already call the render function
so that we can parse in the data that we need,
and so that we can then use it.
And, well, when do we actually want
to display the pagination?
Well, it's actually right at the moment
when we are also displaying the search results.
So we will actually do that right here
after displaying the results.
So we get the search query,
load the search results, render the results,
and then also render the initial pagination buttons.
And so now here we actually need to export this view,
so that we can then import it into the controller.
So just like we have been doing in all the other views,
we say export default,
and then a new PaginationView object.
And so then we come here and import that.
So results becomes pagination,
and now of course that view
has a render method on it.
So paginationView.render,
and here we need to parse in
basically the entire search object.
So state.search.
And actually that's model.state.search.
And so now,
through the magic of this view class here,
we now get access to this data
that we just parsed in, in this._data, right.
So right here, this._data
is now the entire search results object basically.
So, in order to figure out if we are on page one
and there are other pages,
we need to know how many pages there are.
So, let's compute that.
So const numPages,
and how do we actually know the number of pages?
Well, basically we need the number of results
divided by the number of results per page, right.
So for example if we had 60 results,
and 10 results per page, then, we had six pages.
So, the number of results comes from this.data.results.
So let's check that in the model here again.
So results, again, is the entire array,
which contains, so all the search results,
and then we also have results per page,
which is this configuration constant
that we set to 10, remember.
So let me copy this,
and then here we will divide that.
So this.data.resultsPerPage.
And this is a very important variable,
so let's start by logging it to the console,
and with this, we will then also know
if we correctly hooked everything up here.
So basically, if this render here
of the pagination view is already doing its work.
So let's see.
Searching for pizza.
Well, here we get some, (chuckles) not a number.
And of course, well,
here actually we already have something rendered.
And so that's undefined because right now
we are not returning anything from this function.
And so if we don't return anything
by default undefined is returned,
and so that's 10 basically rendered here to the page.
But anyway, what matters here
is that for some reason we got not a number.
So that means, ah!
And I know why here.
So here, this is the entire array, right.
So we are dividing this array by the number.
But of course what we want is the length of the array.
So let's try that again.
Yeah, now we get 5.9 pages.
And of course we now need to round that
to the next highest integer.
And so for that we simply use Math.ceil, remember.
And so this will now become six, okay.
So, let's do some check here.
So if we're currently on page one,
which is this.data.page equals one.
And so now it is very important
that we actually have the current page in this state, right.
So that's why we put it right here.
So whenever the getSearchResultsPage method
is being called with some value.
So just to illustrate this,
let's actually render the results at page two,
just to then test if what we have here works.
But anyway, here we are testing if we're on page one.
And so that's this.data.page equals one,
and there are other pages.
And so basically that means
that the current page needs to be less
than the number of pages.
And so that basically means
that number of pages here is greater than one.
So numPages greater than one.
So this is our first if check,
let's actually log that to the console page one
and others.
Or actually let's even return that string.
'Cause we already know that this will then get printed.
Even though of course,
this is not at all what we want.
Now this one here we will leave to the end.
So let's continue with this one.
So being on the last page basically means
that the current page
is the same as the number of pages, right.
So data.page is equal the number of pages.
So in the current example we have six pages.
And so if the current page is number six,
well, then that means we are on the last page.
So here let's return last page.
Then here some other page,
basically simply means that the current page
is less than the number of pages.
And if any of what I'm saying doesn't make sense,
then probably the best thing to do
is to pause the video,
and think about this on your own.
So that's always my advice.
So basically, reflecting on what I'm saying
is usually the best solution to really figuring out
what exactly it is what I'm doing here.
So of course I'm trying my best to explain everything,
but at a certain point,
you also have to take a look at the code
and think about it on your own a bit.
But anyway, this actually already covers
all of the scenarios.
So if none of these scenarios here is met,
then that basically means that we are on page one,
and there are no other pages.
And so in this case we can return only one page.
And actually that is not really true
because let's say that we only had one,
so that numPage would be one,
then actually this here would also be true.
So here we have to add the same as before,
so numPages needs to be also greater than one.
So then we are really on the last page
and need to render some other buttons.
So, but to test this,
let's keep in mind that we are currently on page two.
And so therefore,
we want to now see other page rendered.
So let's see.
And indeed, there we have another page.
Now let's try page number six.
So here we see we have six pages,
and so now it says last page.
Great.
And now page number one.
And so one more time.
And so page one but there are other pages.
Great.
So this was I think the hardest part,
so figuring out these different scenarios.
And now all we need to do is to create the markup.
So let's come here to the HTML and grab this code.
So this is the code for going to the previous page.
So when do we want to go to the previous page?
Well, for example right here,
on the last page, right.
So here, in fact, we only want exactly that one button.
Now what is the page here that we want to go to?
So we need this number here.
Well, that page is basically the current one minus one.
Right.
So, we are using this this.data.page a lot of times,
so let's store that in a variable.
So, yeah, const current page is this.
And so now let's replace that everywhere here
with current page.
So here we will then want the current page minus one.
And here we need to replace the icons.
Okay.
Then here on page one,
we basically need the opposite.
So again, we only want just one button
but the one to go to the next page.
So let's grab it.
And actually it's almost the same markup.
So another nice template literal.
And now here it says page three.
And so of course,
here we will now want to go to the current page plus one.
Right.
And then here, again, we need to replace the icons.
Okay.
And then here for the other page,
we want basically both of them.
So let's return this,
or actually this one here.
So first the button to go back.
And then also add the button to go forward.
All right, and then when we're on page one,
then we simply return nothing,
because here we don't want to render any button.
So if there are no other pages
there's nowhere to go, of course.
All right.
So let's see what page we are currently on,
so page one.
And so that should render then this button here.
So saying, page two.
So let's see.
And indeed, page two.
Now of course if we click here
then nothing's going to happen,
but for now that is not the point.
Let's try page number four now.
So, go back to page three or forward to page five.
So beautiful.
So it looks like it's working,
let's just try the last page here now.
Oh, what's going on here?
And indeed, it says to go back to page five
but no page to go forward.
So that's awesome.
Now the next thing that we need to do here
is to then add an event listener to these buttons,
so that we can then actually go back
and display another page.
So right now, this is not useful at all
but at least we already have our buttons here
showing up right in the beginning
when the search results are loaded.
Okay.
Now, this code here could of course be refactored
because we are repeating a lot of code here.
So this button is the same as this next button
and the previous button is also the same here, okay.
So we could refactor that
but I will actually not do it here,
and instead leave it for you as a challenge.
So you might create a method called,
for example, generate markup button,
and then refactor that code in that method.
But here, I'm not gonna waste any more time,
and instead, I will go immediately
to adding the event listener.
Now just like before,
we need to use the publisher subscriber pattern,
just like we did, for example here,
in the recipe view, remember?
So, again that works by creating a publisher,
which is basically a function
which is the one listening for the event,
which receives a handler function,
which in our case, is going to be a controller
that lives here in the controller.
And so with this,
we will then be able to listen for the event
here in the view where it makes sense,
while at the same time,
being able to handle that event
from the controller, basically, okay.
So, let's add that method here.
So addHandler,
I'm just calling it click.
So for a click on one of the buttons.
Okay.
Now here we are going to use event delegation,
because there are going to be two buttons,
but of course,
we don't want to listen to each of them individually.
So instead, we will add the event listener
to the common parent element,
which is indeed this.parentElement.
So .addEventListener,
and this time, finally, listening for a click.
And then here we have a function,
because we cannot immediately call the handler
that comes in here,
because first, we will need to figure out
which button was actually clicked,
based on the event.
So that's event delegation
and I hope you remember how it works.
So in this case we can simply create a button element
and select the closest button element
to the clicked element.
So the clicked element is e.target,
and then closest,
which is really an amazing method.
And so here we can now,
basically look for this button.
So for the element with this class, all right.
So remember that the closest method
is a little bit like query selector,
but instead for searching down in the tree,
so for children,
it basically searches up in the tree.
So it looks for parents.
And this is important because here in the button,
we might actually click on this span element,
or on this SVG or this use,
instead of clicking on the button itself.
So we cannot simply set the button to e.target,
but we actually need to search
for the closest button element itself.
And just to see that this works,
let's actually now create our controller.
So here we will then log that button
to the console only for now, all right.
And so now let's go to the controller
and as always create a new controller here.
So basically this is the controller
that will be executed whenever a click
on one of the buttons happens.
So controlPagination is a function,
which for now doesn't really do anything,
let's just call it Pag controller.
So simply logging this to the console,
but what matters here is that now,
we call the paginationView and addhandlerClick.
And so this will then, of course,
call this function here,
which in turn will call addEventListener,
so that we can start listening for the click event
on this pagination element, okay.
Now here we also parse in the handler.
So that's controlPagination,
And so just, again to check if everything is working,
let's simply also call that handler right here.
So once we click on the button now,
we should see this button logged to the console,
and then also the handler function should run,
which is, of course, this one,
and so that should then log Pag controller.
So let's see.
So looks like we're still on the last page.
Let's click and yeah.
So everything works as expected,
we have the button correctly selected,
and it also says pagination controller.
Great.
So now we have the click on the button,
so it is correctly selected
but why did we actually select the button?
Well, we need a way of knowing
which is the page where we need to go now.
So it says here page five
but how will JavaScript know
that it should now actually display
the results of page number five here?
Well, this is where we, one more time,
need to basically establish a connection
between the dumb and our code.
And so remember that we can do that
using the custom data attributes.
So what we will do here
is to create a data attribute
on each of the buttons,
which will contain the page that we want to go to.
And so then in our code,
we can read that data and make JavaScript
or make our application go to that exact page.
And so this is basically the final piece
of the puzzle, okay.
So here in our button,
let's now add that data attribute,
so data, and I'm gonna call it goto.
And so the page to want to go to
is exactly the same as this one.
So basically exactly the same that it says
in the button text itself.
So now let's copy that everywhere.
Here it is page number, so minus one.
Here again minus one.
And then here on this it is page plus one, right.
And so then here,
we can actually get that number, of course,
from the button.
So let's call it goToPage
and it is btn.dataset.goto.
And then let's log that to the console.
All right.
And actually, let's go to some other page.
So basically start this here on page four,
just so we have two buttons.
Very weird what's happening here with the scroll.
But anyway, here we have now,
the buttons for page three and five.
So let's click.
And indeed, here we have the five,
and we also successfully read the five here.
Now you see that this is here,
like kind of in this white,
while this here is purple.
So remember that purple here means the number,
and so this is still a string,
and so we will need to convert that.
But another thing that I wanted to show you,
is that of course,
we can also click outside of one of the buttons, right.
And so then here the button is of course null,
and we get an error by trying to read the data set.
So, let's first convert
that value we get from the user interface,
but all of this should only happen
if there actually is a button.
So, yet another guard clause here,
saying if there is no button,
then return immediately, okay.
So let's try another one now, pasta,
and if we click now,
then here we get the number three,
and here, the number five, right.
So, what are we going to do now with this number?
Well, we can simply parse that number back
to the controller basically.
And then in the controller,
we can finally, finally use that number
to actually render the results
on the page that we want,
by using, again, this method here, all right.
So these pagination and results controllers
are kind of intertwined.
So I think that this is a little bit confusing
because of that,
but if you analyze all of this here, maybe afterwards,
you'll see that it makes sense.
So we already see that here in the controller
we are rendering the pagination in the beginning,
and so here in the pagination
we will later render the new results.
But let's not get ahead of ourselves.
So what I want to do now here
is to actually parse that page into the handler.
Okay.
And so of course our handler
now needs to accept this value.
So let's call it again, goToPage.
And just to see, again, if this works,
log that value to the console.
Okay.
And indeed, we get page number five,
which is the button that we clicked on,
coming from the controller this time.
And now, finally,
what should actually happen here
on the user interface, once we click this button?
Well, we should get the search results
for page number five,
and they should be rendered here, right.
And then also new buttons should be rendered too.
So this should then change to page four,
and this to page Six.
Because when we go forward,
then these two values here are, of course,
going to increase, okay.
And so let's simply do that.
And we already know how to do it,
because we already did it before.
So, remember I said we want to render the results
and we want to render the pagination buttons.
And so that is exactly what we already have here.
And so, let's simply copy and paste it right here.
So let me change this here to render new results.
And then here, render new pagination buttons.
And then here, of course, we want not number four
but we want goToPage.
And then here rendering the new pagination buttons
is actually going to stay exactly the same, right.
So, when we do this here,
so get search results with this new page,
let's say four,
then here in the model,
state.search.page gets updated
to that new value, right.
So the state.search object is then different,
and so that is the object that we then parse
into rendering the new pagination.
And so therefore,
it will then render new buttons
because the page before has been changed, okay.
And so this should now actually work.
Just keep in mind that we will, again,
start at page number four.
Well let's actually make that page three.
Oh, and before actually checking out the results,
I just wanted to say that this actually works
because render will basically overwrite
the markup that was there previously.
And the reason for that is
that we here have the clear method.
And so before any new HTML is inserted into the page,
the parentElement is first cleared,
so it's first emptied, right.
And so then that means that render here
basically overwrites everything that was there
and puts the new content in the same place.
But anyway, let's make sure to reload, pizza,
and we are at page number three.
And now let's see the magic happen.
And I hope we don't have any bug here.
And it works!
So beautiful, beautiful.
And congratulations also for you
to following up until this point.
So this is, I think up until this point,
the most impressive work that we have done.
So you see everything updates here just as expected,
we get new content,
and now page six, you see here,
will be the last page, so let's see.
And yeah, so now we don't have the last button,
and we can only go back to page five.
So now let's go to the first page,
and indeed, we can only now move forward
and get the first 10 results here.
So, really great work here.
I'm really happy about this progress here.
Now just to make sure, one more thing,
let's try this here with the results per page
of a different value.
So let's say that we actually wanted
like only five results per page.
So then we should get a lot more pages.
And so it's important that we check
that that actually works.
So you see that it does actually work already.
So we only have five results here now,
and we can also already see
that we will have 12 pages.
Oh, and we are still on page number three.
But now we will be able to go back
or actually to go forward to page number 12.
And so each of these pages only has five recipes.
And now let's just try to search
for something that is not very common,
like dip for example.
And, wow, we still have a couple of pages here actually.
But before we go any further,
let's put this here actually to zero
or actually to one,
and remember that parsing nothing
is basically the same as setting it to one.
So let's try dip here again.
But it seems like we have still six pages.
So I just wanted to find some search term
that only has one page.
Let's try hummus maybe.
Ah, but we again have four pages.
Well we can fake that by, or here,
by having 50 results on one page.
And so with this,
we should already only have one page with pasta.
So pasta has 45 results,
but here we want 50 on one page.
So we only have one page you see,
and indeed, no buttons.
Great.
Let's put it back to a meaningful value, so 10.
And so with this, we are actually done.
Let's just clean up our code here a little bit more,
but I think that with this
we are now good to call this finished.
And actually we finished now the entire part one
of our flowchart, right.
So this was already done, this here as well.
And now, here what we did
was to listen to this event,
then render the pagination buttons,
and of course,
render the corresponding search results.
So all of this is now in place,
but I ask you to really analyze again
the code that we have here in control pagination,
as well as here in control search results.
Because again, these two controllers,
they're kind of intertwined.
So they both are doing similar things,
at least when it comes to displaying the pagination
and the results themselves.
So it's very important
that you understand the flow of this data here,
especially of this, goToPage, okay.
And then maybe also analyze again
how all of this here works,
but then you're good to go to the next video.
And so there we will discuss,
basically our next step, what we should do next.
So that next video is gonna be a lot shorter,
so that we can finally take a break
from this application.
So again, analyze this like I told you to,
and then after you're ready,
let's go move on to our next part of the flowchart.