Jonas: In this lecture,
we will develop an algorithm which will update the DOM
only in places where it actually changed.
And remember that that is necessary
because right now, as we update the servings here,
then that will always basically re-render
the entire recipe view here.
And so we can see some flickering around the page.
Right now, it's not really happening
for some reason.
But you saw that in the previous video
where every time that I would update the servings,
it would then basically reload this image,
which would then cause a short flicker.
And having to re-render the entire view,
so all of these HTML elements
is actually a bit overkill
and it will put too much strain on the browser.
So it will create unnecessary work.
And so therefore, let's now create an update method
that we can use in this situation.
All right?
So again, here instead of calling the render method,
we actually want to be able to call an update method.
So let's duplicate this line here
or comment it.
And so here we want to call update.
And of course, the update will still need all the data,
just like the render method
but again, the difference between update and render
is that the update method
will basically only update text
and attributes in the DOM.
So without having to re-render the entire view.
And so let's now go to the view here
because we will want this update method
to be available on all the views
because actually, we will use this
in multiple situations in this project.
So actually, not just here
for updating the servings.
But anyway, this is called update
and it receives also some data.
Here then, let's get this same check here.
And also, we will want to do these two parts.
And that is because well, once we update the data,
we then want the view's data
to become that new data.
And we also want to generate some new Markup.
So let's actually call this here the newMarkup.
And this will basically be the entire Markup
as if we wanted to render a new view.
But again, here we are just going to update the Markup,
but for doing that, we still need the entire Markup
so that we can then compare it
to the old Markup basically.
So again, what we will do here
in this method is to create newMarkup
but not render it.
So instead, all that we're gonna do
is to generate this Markup
and then compare that new HTML
to the current HTML.
And then only change text
and attributes that actually have changed
from the old version to the new version, okay?
So here we now have the Markup
but that is just a string.
And so that is gonna be very difficult to compare
to the DOM elements that we currently have
on the page.
And so to fix that problem,
we can actually use a nice trick,
which is to basically convert this Markup string
to a DOM object
that's living in the memory
and that we can then use
to compare with the actual DOM that's on the page.
So let's do that and create newDOM.
And so document.
And then we need to call a function called createRange.
Okay?
So this will create something called a range,
and on the range, we can then call yet another method,
which is called createContextualFragment.
And so this is where we then pass in the string
of Markup, so like a string of HTML.
And so as I said before,
this method will then convert that string
into real DOM Node objects.
So basically, newDOM here
will become like a big object,
which is like a virtual DOM.
So a DOM that is not really living on the page
but which lives in our memory.
And so we can now use that DOM
as if it was the real DOM on our page.
And so we can do something like this.
We can say newElements.
And then we can take that newDOM,
and then on that, we can call querySelectorAll,
and select all the elements in there.
And so if we now log this to the console,
then we will basically see all the elements
that will be contained inside of this newDOM element
that we basically created from generating the new Markup
from the updated data, okay?
So that sounds confusing I'm sure.
But we will see it in practice really soon.
So we are calling update here.
And so that will then trigger all of this
and so let's now see in the console
what happens.
So just to make sure everything works here,
so as we're clicking here,
it will, of course, now no longer update the values here
in the DOM because we are now using the update method,
which for now doesn't do that.
Okay, but what we did got
was the entire list of all the elements
in the new DOM.
So in this so to say virtual DOM, okay?
So if we check now this element,
so this is the element that contains the number,
that should now contain five.
So let's see the textContent property
or innerHTML is the same
and so indeed, here we have five now.
And so again, the reason for that
is that this here
is now basically the DOM that would be rendered
on the page if we simply used the render method from before.
Okay?
And so now with this,
we can compare this DOM
that we have here, which is a little bit
like a virtual DOM.
So to the actual DOM that is really on the page.
So for example, again, this one here
has now changed to five
and this one is still at four.
And so that is one of the elements,
which is different and so that is then one
of the things that we will change.
But everything else is the same
or at least, most of the things.
For example, here the minutes,
this value here, this is, of course, still 45.
So you see, of course, it's still 45
and so this element will not change,
and the same, for example, for the image
and for this icon
and for lots of things here.
And so with this, we will be able
to only change what happened
from the new DOM,
which is this one here to the actual current DOM,
which is basically what we have here on the page.
So in order to being able
to actually do that comparison,
we now also need to get all the actual elements
that are currently on the page.
So get them into our code.
And so therefore, let's also select them.
So let's call that the current elements,
which are at this._parentElement
and then querySelectorAll.
And then again, selecting all of them.
And maybe you saw previously
that this here returned a Node list
and so let's right away convert this and this one
to a real array.
So remember, we can do that with Array.from.
All right?
And so let's actually now do that comparison.
Well, I mean, only visually in the console for now.
So let's log the current elements
and let's also again log the newElements.
So let's click here now.
And you see that now it is actually an array.
I'll go OK.
So let's look again for the servings
and that's actually not the one,
this is the one.
So let's see the textContent or the innerHTML.
And so indeed, it is still four, right?
And again, because that is the current DOM.
But if we now come to our new DOM,
then well, it's gotta be down here.
So then if we open up this one,
then again we should be already at five.
Right?
So here we have five
and so now let's actually compare these two,
basically one by one.
Okay?
So I'm going to loop over the newElements array now,
and here in the callback function,
then the current element
is the newEl let's say,
and then we also need access to the current index
so that we can then also grab the current element
from this array.
So basically, we need to loop over the two arrays
at the same time
and I think we've done that before.
So for that, we also need the index
so that now we can say that the current element
is equal current elements at position i.
Okay, and now, how will we actually compare
these two elements?
So current element and newElement?
Well, there's actually a very handy method
that is available on all nodes,
which is isEqualNode.
So let's log that here to the console.
So in each iteration,
we will log whether the new element
isEqualNode to the current element.
And then let's disable these two console.logs here.
And let's also log the current element
so that we actually know
what element we are comparing here, all right?
So again, this method here
will basically compare the content
of this one and of this one.
So it doesn't have to be the exact same node.
All that matters is that the content
in these nodes is the same.
Right?
And if you're curious about the difference
between elements and nodes,
then please go watch the beginning
of the advanced DOM section
because there's a very nice lecture about that.
Okay?
But anyway, let's now check this.
So clearing this.
And so a lot of things happened here.
And so you see that most of these nodes
are actually the same.
Let's go all the way to the top here.
So these here are all the same,
so you see true.
But here you also see false.
So basically, this entire container
is not the same as before
because something inside of there changed.
But let's actually go to that detail.
So here it is.
So here this is now false
because this current element here
is now different from the new element,
which will have five servings.
And the same here for these two buttons.
So this button and also this button
because this data attribute in there
will also be different.
So if we are on page five,
then this here will update to six
and this one to four.
Okay?
So again, we have this to false
because the current element is, at this point,
different from the new element.
But the same also happens
on all the parent elements of that.
So it happens here in recipe__info
and then as we saw previously,
also in this whole container
simply because the four in there changed.
And so that means that this entire container is,
of course, different to what it was before.
But nevermind about that for now.
That's the detail we will tackle later.
For now, let's use this node here for our advantage
and do something in this situation.
So basically, if the newElement isEqual
to the current element,
so if that is not the case,
so if they are different,
then we want to change the text content
of the current element to the text content
of the new element.
Right?
And so this essentially is updating your DOM
only in places where it has changed.
Or where it was about to change.
So currentElement.textContent,
and remember that this current element
is the actual element that is currently on the page.
And so that is the one that we want to update.
So to mutate.
And a textContent should be the same
as the newElement.textContent.
However, now we will get a problem as you will see.
So let's click here
and indeed, some replacement was done.
So you see, we now have five servings,
but again, the problem here
is that we now basically replaced the entire container here
because here there was already some change.
And then right away,
the entire text content from this parent element
was taken and replaced.
And so, of course, this doesn't work.
So instead of what we're doing right now,
we now need to find a way of determining
if the element only contains text
because that is the only thing that we want to replace.
So again, what we want to replace
is only this one here
because this here contains only text, okay?
So how can we do that?
Well, there is yet another very nice method
or actually property
that is available on all nodes.
And let me actually show that to you
on the MDN documentation,
and it is called nodeValue.
So this is the one.
And so the value of nodeValue
will be null if the node is an element
or really most of these things
but if it is text,
then we actually get the content
of the text node.
And so we can now use this to our advantage.
So besides checking for this,
we can now also do another check.
So, of course, the elements still need
to be different but now,
we only want elements that are actually text.
So this is how we can do it.
So new element,
and now we need to select the child node.
So let's take the first child,
which remember, will return a node
as we learned also in that advanced DOM section.
And we need to select the child
because the child node
is actually what contains the text, right?
So the element is really just an element.
It is an element node
and not a text node.
And so what is, in fact, the text
is the first child node.
And again, we talked about that in the advanced DOM section,
which I hope you remember.
And so now we can take the nodeValue here
and so if this is text,
this will then be some kind of text here.
And so this text should not be empty.
So different from an empty string.
And actually, let's also trim any white space here.
Okay.
And so with this, it should work.
But I'm sure this is again a little bit confusing,
so let's actually log this value here
to the console.
Okay?
Just so you see what this will actually be.
And let's log this together with some emoji
just so we can see it in the console different
from all the other logs.
So by now, this should actually already been working.
But I want to focus for now
only on the log.
So you see it already did work here.
But let's now go to our logs.
And all the way to the top.
So this here has the explosion,
so it's the log we are interested in.
And so indeed, it just got the text here
out of this element, right?
So this was the current element in the loop
and so then at this point,
the first child.node value is five.
And again, in all other elements,
which do not contain text directly,
this nodeValue will be null.
And so therefore, then this whole expression here
will be false,
and so then this replacement here
does not take place.
Now, here, let's just add some optional chaining
because the first child might not always exist.
So with this, we make 100% sure
that this code is going to work.
So let's check it out again here.
And so if we click on minus now,
you see that only this value is replaced
and also, these values here
but you don't see any of the flickering
or any other weird effects here
because there is nothing else
that is being replaced.
It's only really this text.
Everything else is staying the same.
And so that is exactly what we wanted to achieve
with this algorithm.
Now, as you noticed, as I click here,
the only options I have is to go to three servings
and to five servings.
And the reason for that is that right now,
we are not replacing yet the attributes here
on these buttons.
So they are still at three and five.
Let's check that here.
So indeed, we still get three and five
because all we did yet was to update the text
but not attributes.
So whenever an element changes,
we also want to change the attributes.
So let's quickly do that here as well.
And this, we can actually nod to here
in this if block
because the condition that we created here
makes it so that this code
is only executed on elements
that contain text directly.
So that was the whole point
of this line of code
that I explained like over the last five minutes or so.
And so instead, we only want
to change the attributes
when the new element is different
from the old one or the current one.
Okay?
So let's just comment this here.
Updates changed TEXT.
And then here we want to update
changed ATTRIBUTES.
Now, here there is something cool that we can use,
so yet another new thing,
which is the attributes property on an element.
So let's start by logging that
to the console this time.
So let's say newElement.attributes.
And let's get rid of this console.log here now
and so for now,
what this will do is to log this attributes property
of all the elements that have changed.
So let's see.
Well, we still get a lot of different logs here
but that doesn't matter.
So here we have, for example,
this ingredient here
and so this has changed
and so here we then get a list
of all the attributes.
So we have the class, we have the length.
So let's see here
what that actually is.
But this one doesn't really matter.
The one I'm interested in
is actually the one on the buttons.
So let's go up a little bit further.
And this is the one.
Right?
So here we have the actual attributes,
so data-update-to,
and probably they should have the values
also somewhere in there.
So this is a lot of stuff we don't need
to take a look at this.
So this is very confusing
but I just wanted to show
that basically, this here will return something
like an object of all the attributes that have changed.
So let's now convert this object here
to an array, and then we can loop over this array
and basically copy all the attributes
from one element to the other element.
So from the new element basically to the current one.
So basically, doing something similar
as we did here with the text.
So we want to loop over newElement.attributes,
and again, we need to convert that to an array like this.
And then for each of them,
so each is an attribute.
And then on the current element,
we want to set the attribute.
So remember this method from way back.
setAttribute, which again, we learned about
in the advanced DOM section.
And so here we want to take the name
of the attribute
and the value.
Okay?
And maybe just to make this a bit more clear,
so right now,
this kind of comes out of nowhere,
so let's also log this to the console
so we can see what we're actually looping over here.
So let's go back.
Ah, and so here is that array.
And so here we have the class and data-update-to.
And so now let me actually show you the name.
So that is the name.
And let's do it for data-update-to,
which is the one we are actually interested in.
So we will take that name
and set it to the value,
which is four.
So we will get that value
and set it into the current element.
Okay?
So we don't need these curly braces anymore.
And so again, basically, what we are doing here
is to simply replace all the attributes
in the current element
by the attributes coming from the new element.
And so this will then replace the five
that is currently here with six.
And so now we are able to go as far as we want here.
All right?
And so we successfully implemented a method
for updating the DOM only in places where the text
or the attributes actually changed.
Now, this algorithm here
is probably not the most robust one
and so maybe it might not be the best algorithm
to really use in the real world
unless you have like a kind
of small application, like this one.
But for a really huge application,
probably this algorithm is not performant enough
and might not be good enough.
But here in this project,
I think that this was an excellent exercise
to get even more familiar
with some of the lower-level details
of the DOM, and also to think logically a little bit
about what we wanted to achieve here.
And so again, I hope that you liked this kind
of small exercise,
which was a little bit different
from the rest of the project.
And this was actually one
of the more difficult parts
for me to also figure out
when I was building this project,
and so I hope that you can go over this code
one more time by yourself
and really figure out exactly
what is happening here.
So let's check out our flow chart here on page two
or actually, part two.
And so we successfully just did this part here.
Now, I also want to use this algorithm
that we just developed to quickly tackle this part here
of the application,
which is to mark the selected result
in the search results.
So let me show that to you in practice.
So here in the demo,
whenever I select one of the recipes,
it will then get this selected class here.
Right?
And so then when I move out the mouse,
it will stay selected.
And so that's a nice touch
that we are currently still missing here, right?
So of course, it doesn't stay selected,
and so we can now use this update algorithm
in order to update this entire search results view
whenever we click on one of these results.
And so let's go implement that.
And we actually need to start with the view here.
So searchView or actually, resultsView
because this is where we need the logic
of marking the current recipe as selected here.
So the link that is selected will get a special class here.
Let me show that to you here in the results.
And so that is this one here.
So preview__link--active.
And so here we want to give that class
to this anchor element here
if this result.id
is basically the same as the current ID in the URL.
Okay?
So let's start by determining that ID.
And so remember that is at window.location.hash.
And then we want to take everything
except the first element.
So basically, starting to read from the first element.
Okay?
And now here we want to conditionally add that class.
So we can write a simple template literal here.
And I'm right away putting the class here.
And so here we want to say if the result.id
is the same as the current id,
then the result of this expression
should become this class name.
And otherwise, then simply an empty string.
All right?
And so now here in the controller,
so the controller for the results,
so SearchResults or actually,
in this one here, so the one controlling the recipes,
so here we now want to update the resultsView.
And let's do that right at the beginning right here.
Let's say zero.
Results view to mark selected search result.
And so that's gonna be resultsView.update,
and then here, we want to pass in the current page.
And so remember that is model.getSeachResultsPage.
Okay?
So basically, we are doing here exactly the same
as this here.
But instead of using render, we use update.
Okay?
Now, it would also work with render,
and let's first see that.
So let's
search for something here.
So pizza.
And now I will click on this one here
and you see that it stays selected.
And that happened because as the hash here changed,
remember, then this recipe got loaded
and also as we just did,
this entire search results view got rendered again.
And then this time, the ID of this result
is the same as this one,
and so therefore, it got that active class.
Right?
But watch what happens to all these images
when I click, for example, this recipe here.
So you see, like before,
there is this flickering
because everything is re-rendered.
And so these images are basically loading again.
And that is the whole reason
why we have now the update method.
And so with this, we will now fix that problem.
So pizza again, and you see that already
it did select the recipe that is currently active.
Okay?
And now you see that there is no flickering
because now only the element
that actually changed was updated.
Great, now I just noticed
that when we reload the page,
then we get no recipes found right here.
And the reason for that is that our application
is immediately trying to update this view here.
So when the page loads and there is an ID,
then it will, of course, immediately load that recipe.
Right?
Even if there was no search before.
And so then here,
as we are trying to update, of course,
there are no search results.
And so this here will then return an empty array.
And so let's go to our update method here.
And here we can then remove this check.
So again, right now, we are calling update
with an empty array.
So data is this empty array
and so therefore, this part will then be true
and then this error gets rendered.
And this doesn't make sense so let's remove that,
and so now that error is gone.
And now immediately this one here gets selected
and so everything keeps working the same.
All right, so we just did a big step
towards improving our application even more
and making it even more user friendly.
And it was a lot of work
but I really hope that it was worth it for you.
And with that being said,
let's now move on to the next feature,
which will be to implement bookmarks.