Jonas: Well, welcome back.
And I hope that you're still having fun in this project.
Now, the next part of this project
is going to be implementing bookmarks.
And so that's what we will do
in this lecture and the next one.
Okay.
And let's start again with a look at our flow chart here.
And so in this video
what we will do is simply basically adding a handler
to the recipe so that then the user can bookmark the recipe
and then a that will render or update the recipe
with the bookmark button, then update it.
And for doing that there's actually a lot of stuff
that we need to implement.
So there's a lot of work for us to do, so let's get started.
And actually, before we get started
I think we should actually also see this in action.
So here in the demo version, so let's say that we are here
on this first recipe and when I click this recipe
I then want this, icon here to change right?
And so this is now bookmarked.
So when I then go to the next result
then of course this bookmark here is back to being empty.
But when I go back to that recipe that I had bookmarked,
then it is back to being filled.
And so this means
that the current recipe is indeed a bookmark.
Then in the next lecture, we will take care
of also displaying all the bookmarks up here.
All right, but that's something different.
And so for now, let's take care of the core functionality
of being able to bookmark a recipe.
Okay and finally there's one more thing
that we actually need to do before starting that.
So I discovered a small bug here.
Let's say that we searched for something.
And then we go to page two or page three, for example
and then let's say we search for something else.
So that gives us our results.
But as we go back here
notice how we are still on page number three.
And so that is a bug that we need to fix.
Now, can you figure out maybe on your own,
why does bug is happening?
So I think that this would be a nice challenge
for you to pause this video actually
right now and try to figure out why this bug is happening.
So hopefully you figured it out.
And the reason for it is that here in the model,
when we do the search so here,
when we load the search results
of course the state is set to one.
So the page is set to one here automatically.
However, as we move to another page
then this variable here gets updated right.
But then when we search for something else
we never reset this page to one right?
And so that's what we have to do right here
where we load the search results.
So after this here, we need to say state.search.page
and set it back to once.
And so now whenever we load some new search results
so basically whenever we do a new search
then the page will reset to one.
So let's try that again now.
So we are again at page three and you already see,
we have different results.
And indeed now we are at page number one.
Okay so that was a small bug that needed to be fixed.
But now let's go back to the actual topic of this video,
which is to implement bookmarking.
So bookmarks are all about data.
And so we will start by working again in the model
and we will add a new method here.
Well actually a new function
that we export that we will then use from the controller,
which is simply add a bookmark.
And so this will receive basically a recipe
and then it will set that recipe as a bookmark.
So a recipe and then in our state,
we already have an array for bookmarks
or actually we don't, but I think I had it
in the architecture diagram.
So anyway, we have an array of bookmarks,
which we start as an empty array,
but then basically adding a bookmark
simply means to push the recipe here into this array.
Okay so what I just said is this state.bookmarks.push
and then the recipe object that we just received here.
So let's add a comment here
add bookmark because besides this
we also want to mark the current recipe as being bookmarked.
So at least if the current recipe is the same recipe
as this one that we are adding here.
So mark current recipe as bookmark
because this will then allow us to actually display
this current recipe as bookmarked in the recipe view.
So we can say if recipe.id.
So the idea of the recipe that we passed in here
is equal to the ID of the current recipe.
So the one that is currently loaded in our application
which is state.recipe.id, while in this case
we want to say state.recipe.bookmarked should be true.
So basically setting a new property on this recipe object.
So for now that is the add bookmark function here.
And so now let's create a new controller.
So a controller for adding a new bookmark,
so control, add bookmark.
And so here let's not try to add a bookmark
with the current recipe.
So model.state.recipe
and then let's also immediately log that to the console
so that we can see that it is actually marked as a bookmark.
And now of course we need a way of calling this
a controller function.
So when will that happen?
Well, it should happen when we click on this button right?
So this bookmark button
and right now the icon here is actually wrong.
So let's start by fixing that here in the recipe view.
So where is that,
here.
So here it should not be fill for now,
it should simply be the empty icon like this.
All right.
Now the name of this button here is button round
but let's add a another class here.
So let's say button
bookmark, and so now we want to listen for clicks
on this element.
However, to keep this view simple
I will not select this element, so at least not directly.
And instead I will once again, do event delegation.
So here let's add another function called addHandler
and hear add bookmark.
Which has always will receive a handler function
which in this case is going to be the controller,
we just created a minute ago.
So here this.parentelement.event listener click,
and then, or a function here.
And so in fact this is really necessary
because the element that we are trying to select.
So this button with this class
of a button bookmark does actually not exist
by the time the application is loaded, right.
So basically by the time that the page starts
this element does not exist.
And so it's impossible to add an event listener
to an element that doesn't exist.
And so that is yet another great use case
for event delegation.
So we do exactly what we do here
which is to simply listen for the event on a parent element
and then try to figure out if the click actually happened
on the element that we are looking for.
And so once again, we will do a button element here
and then e.target.closest, because one more time
the user might actually click on the icon element.
So on the SVG data is in there.
Okay and if there is a button
or let's actually say, if there is no button
then return so the same guard clause as always
and otherwise call or handler.
So this was pretty straight forward I believe
because we have done this many times before.
Okay and so now
all we have to do is in the controller module
to call this method here right.
So recipe, view.addhandler add bookmark
and then we pass in control add bookmark.
And so then when we click the element that we just selected
then we will add the current recipe as a bookmark
and prologue also that current recipe to the console
so that we can see that it was actually marked
as bookmarked.
Okay so lets the reload just to make sure
let's click the button.
And did anything happen?
Oh yeah, I think it just
yeah locked this object here.
So let's see.
Ah, and here we have it.
So bookmarked is now set to true.
Great.
So an important piece of the work here is already done
but now we then want to render here this icon as filled.
And so let's go back to our view and basically say
if bookmarked is true, then a render the filled icon.
And if not, then simply render this one.
So that should be easy enough.
So not here in the view, of course
but in the recipe view where regenerate a markup.
So yeah, this is the button here, and this is the icon
that I'm talking about.
So here, when the recipe is bookmarked
we want to add like this fill part.
And so we can simply say this.data
which is the recipe.bookmarked.
So if so then here create a fill and otherwise nothing.
Okay and now finally, to actually make this work
of course, we will also have to update the entire recipe.
So recipe view and so this is why we created
that update method before.
And so now again, this method is coming in very useful
because otherwise we would have to render the entire page
or actually the entire view
which would make a lot of sense
because with this we will only update the element
which has actually changed.
And so here then again we need to pass in the data
that we want to update.
And so that's model.state.recipe once again.
Okay and so now as we click here
let's see, and indeed the button was updated.
Now we get all of these logs here.
Let's take them out that is in the view.
So that's this one here.
Great.
So here, let's now search for something.
So let's mark this one here as a bookmark,
then this one here
but now as we come back here, let's see what happens.
And you see that the bookmark is actually gone,
right?
So now here you no longer see bookmarked set to true.
So why is that?
Well, as we load each new recipe
it will always be loaded basically from scratch.
So it will be loaded from the API, right?
We are not loading this recipe from the bookmarks.
And actually we won't do that.
But what we will do is to now use the data that we store
in the bookmarks array and the state
to basically Mark any recipe that we load as bookmarked,
if it is already in the bookmarks array.
So that probably sounded more complicated
than it actually is.
Okay so let's go here back to the model
and then to our function, which loads the recipe.
So that is this one.
So here the as we got all this data
and store it in the state.
Then what we can do is to check if there is already a recipe
with the same ID in the bookmarks state.
And if it is then we will mark the current recipe
that we just loaded from the API as bookmarked set to true.
So let's use
one of the nice array methods that we learned about,
which is the sum method.
And remember that the sum method returns true or false.
And so that's great for doing an if check like this.
So here we can check if state.bookmarks.some
which basically means any,
so at least that's how I like to think about this method.
So this method will loop over an array
and then return true if any of them actually has to
for the condition that we specify here.
So if bookmark.id is the same
as the ID that we just received here
okay, let's call this here bookmark actually.
And so basically what this means is that
if there is any bookmark, which has the bookmark ID
equal to the ID that we just received
well, then we want the current recipe
which is state.recipe to be bookmarked.
So bookmark equal to true.
Okay and otherwise we wanted a set to false.
And so with this, all the recipes that we now load
we'll always have bookmarked set to either true or false.
Okay great.
And so now we actually used this state.bookmarks
for anything meaningful.
So up until this point, we had stored the recipe
in the bookmarks array, but it wasn't really useful.
So there was really no point up until now,
but now here use basically use this array
in order to store all the bookmarks
so that when we come back to one of them,
we can then mark the recipe as being bookmarked.
So let's try this now again.
So bookmarking this, then I come to this
and yeah so that worked and you see that it is bookmarked
set to true and the previous one.
So the double crust stuffed pizza
is of course bookmarked set to false.
And so when we click that here,
it doesn't have the bookmark.
Now we bookmarked it.
And when we come back to it, then there it is.
Okay and now just as the final piece here,
when we click on this button again
we then want to unbookmark basically this recipe.
So we want to remove it from the bookmarks
but right now, of course, that doesn't happen.
And so what we need now is basically yet another method
or function here in our model,
which will simply remove a bookmark.
So doing the opposite of add bookmark.
So const delete bookmark
and this one will simply receive an ID
because that is actually simpler.
And this is actually a common pattern
that you will see in programming when we add something
we get the entire data.
And when we want to delete something, we only get to the ID.
So this is a pretty common pattern again.
So what we want to do here is to basically delete the recipe
which has this ID from the bookmarks array right?
So state.bookmarks and then to delete something,
we use the splice method.
Remember, and then here we need the index
where the element is actually located
that we want to delete.
So we will calculate the index in a second.
And then how many items we want to delete
which has only one.
And so this index here, we can calculate it by using
our old friend defined index method.
So state.bookmarks.
And I think we already did something very similar to this
in the Bankist application.
So here we are looking for the element
which has the ID equal to the ID that was passed in.
So there's going to be one bookmark
for which this condition here is true.
So word current bookmark.id is equal to this ID.
And so for this element where this condition is true
the index will be returned.
And then we can take that index and deleted from the array.
And then finally, let's also mark the current recipe
as not a bookmark anymore.
So as not bookmarked and here as well,
and here let's say delete bookmark, okay.
And now we need to of course use this function somewhere.
And that's always, that can only be in the controller.
Okay, so this control add bookmark function will be executed
whenever we click on that bookmark button.
And so here we now need to control
what exactly should happen in that case.
So when do we actually want to add a bookmark?
Well, actually only when the recipe is not yet bookmarked.
So let's write that.
So if model.state.recipe.bookmarked
so if that's false,
then do this.
So add to bookmark then but if not,
and so let's now do this instead of an else
because this is more readable
then we want to delete bookmark.
And again, here we pass in actually the ID like this
and that's actually it, and this is possible.
Remember so it's possible that we read the bookmark property
right at the beginning
because we add that to all the recipes
that are ever loaded right at the beginning right here.
Okay and so now this should not work.
Let's try avocado now for a change.
So some sushi here that looks delicious
and we get ourselves a bug.
So let's go to line number eight, 98 actually.
So 98.
Oh and here we are using recipe.id
because we simply copied it from here.
So you see that simply copying some code many times can
make us run into bugs.
So here, of course we are already receiving the ID.
So that's only ID let's go back
and let's search for avocado again.
So here we have that let's bookmark it.
Well, that didn't work.
Let's see why, so probably something wrong
in our controller.
So let's lock this year to the console,
just to see if this is having any effect
let's clear this here, and it is false.
And so,
therefore this should have worked
but somehow nothing worked.
So this code here did not get executed for some reason.
Oh and actually I see what is happening.
So this code actually did get executed.
So this code here, will mark the recipe as bookmarked,
but then in the next line of code right here,
then the recipe is already bookmarked.
And so then it will delete that bookmark.
And so here, actually we need an else.
Okay so that's the kind of sometimes difficult
bugs to find, but I'm sure that with time,
you will also be able to find all of these bug.
So let's click on some recipe here, mark it as a bookmark.
Then we go here to some other one mark that.
Then here, that is still working and now let's click
and see if it disappears.
And yes, it does.
Now do the same here, come back.
And it is still unbookmarked.
Well, all right.
So with this, we have two core functionality
of bookmarking actually already implemented.
So great job once again.
And now all that is missing
is that we display all our bookmarks up here
in this bookmarks panel.
And that is a little bit more work than it might sound.
And so that's leave that for our next video.