Let's now render the search results
by creating a new view.
So we already have the searchView
but now we also want to create like a resultsView.
So a resultsView, which will contain a render
so that we can then call that render here
with these search results.
So similar to what we have here,
so to the recipeView,
which also has a render method,
which takes in some data
and so now, again we want the same
for a results view.
So we also want it to have a render method,
which takes in data and which is then rendered
to our user interface.
So let's create the resultsView.js.
So ResultsView
and so now this view
will actually be pretty similar to the recipeView.
So we don't need that one anymore.
But now, again this one is going
to be very similar.
So we will have a parentElement,
data, these messages.
We will basically also need the same render method,
the same renderSpinner.
So we will need to display the spinner
while the data is being fetched,
so in this case, the search results.
We might need to also render an error
and so now it's time to basically refactor this code
and create a parent class.
So I had mentioned that before
but now it's actually time to do it
because by creating a parent class,
we will then be able to reuse all of these methods
on all the views.
So basically, on all the objects
that are created through all the view classes.
So let's create yet another file here
and I'm calling it View.js.
And so let's create a new class here called View
and so we will then export immediately this class.
And actually, let's make it a default export.
So at this time, we are actually exporting the class itself
because of course, we are not going to create any instance
of this view.
We will only use it as a parent class
of these other child views, all right?
Here in the recipeView,
let's now import that parent class.
So import View as,
or actually, not as but from View
and actually, we don't even need the js
but I like to keep it there for consistency.
And so now, all we need to do
is to basically copy everything
that should be common to all the views
to that parent class.
However, there is one thing
that we still need to change here.
And that is that right now,
with Parcel and Babel,
inheritance between these truly private fields
and methods doesn't really work yet.
So here it was nice to actually use the native way
of JavaScript of protected methods and properties
but now we cannot really use it anymore.
So maybe that might be possible at some point in the future
but for now, we will have to go back to protected fields
and protected methods.
So let's basically select all of these hash symbols
and actually, I will select them like this.
So with the dot because otherwise,
that will also select these ones.
So hitting Command + D here a couple of times.
And then replace it with ._
and now we just have to change these method names.
So changing from truly private
to only protected using the underscore convention.
And then the same right here.
Okay?
So now our code should still be working.
It says here connection lost
but indeed, our code keeps working here.
Okay?
Let's just check out the search view
and so let's use the same convention here as well.
Okay.
Just here as well.
And now here we also converted everything successfully.
But anyway, let's now basically extract the View class
from this RecipeView.
So what we need in all the views
is this render method,
then this one as well, this one,
and basically everything except for generating the markup
because that, of course,
is gonna be unique to every single view, right?
And also, adding the handler.
But everything else we want to be the same on all the views.
For example, rendering a spinner
will be really easy.
So all the views will simply have this method
and we will then be able to call it on that method
and thanks to the parentElement property here,
it will then know exactly
to which element it should attach the spinner
because again, the parentElement,
of course, will be unique to each single view.
So the parentElement will be unique,
the message, so these two messages
but this data can actually also be on the parent.
So let's copy all of this here or actually cut.
So everything except for adding the handlers
and generating the markup.
So let's put that here.
And here we, of course, then need to extend this one.
So extends View.
Right?
And so let's see if we have everything still in place.
Yeah, so I'm confident that this will work now.
So let's see.
Oh, and icons is not defined.
So let's go back here to our view
where we will have to copy this one.
Because here, of course, we are also using the icons.
Okay.
But here it is.
All right?
So we successfully extracted the View class
from the recipeView.
And now all that's left here
is really what is unique to this view.
And so this makes this even easier to work with
in my opinion because now let's see what we can do here.
So even without any code yet in the results view,
we will already be able to add a spinner
to the search results.
All right?
Well, we cannot really leave it empty.
We still need actually the parent element here.
All right?
And we also need to import the View.
So import View
and then this is just another child class of View.
Then here, we need to say that parentElement
should be well, something.
And let's check out what it actually should be.
So here in index,
let's see.
So it's this here.
So this results element, so this unordered list.
All right, and then basically,
each of the search-results will be one
of these preview elements.
But more about that later.
So for now, let's just set the parentElement
to this results element here in resultView.
So document.querySelector results.
And then we have to export
default just like before
and then a new instance of this view.
And so this way they can only be one ResultsView
and then here in controller,
we can immediately import that
without having to create the instance here manually.
So that's resultsView like this.
And so now here we should already be able
to load the spinner
or actually to display the spinner.
So let's do that at the very beginning.
So resultsView.renderSpinner.
And let's see what happens.
So pizza and here is a small error
and so let's once again do some debugging here
and I hope that you can appreciate
that I'm actually not cutting out
all these like small debugging sessions
so that you can actually see
what I'm doing here.
But anyway, apparently the problem appears here in View.js
at line 14.
So right here.
So it seems like it cannot find the parentElement.
So let's simply log that to the console.
So this here is the result of doing that for the view.
And now let's see.
So now that is undefined.
So to me it seems
like here we selected the wrong element here maybe.
So results.
So that should actually
be correct.
And so should the name.
So I think parentElement is correctly spelled.
Oh, but it's actually called parentElement all written out.
Okay.
So you see, it's actually called parentElement like this.
For some reason, I thought it was called
just like the abbreviation.
Ah.
And you see, here is the nice loading spinner,
spinning away here in our search results.
Right?
And again, all without writing any new code
for displaying that spinner here in the ResultsView
because it is now simply inheriting that method
so that renderSpinner method from its parent class.
So let's get rid of this
and just for the sake of curiosity,
let's actually take a look
at this resultsView,
so just so we can see the whole prototype chain here
in the console.
So for that, we need that controller to run
and so here it is.
So you see that we have still some undefined data.
Then we have the parentElement.
So the one we selected.
And then we get,
well, right now, we don't have any methods
in the class already
but we do have methods in a parent class.
And so these are the ones.
Right, so the render one that we're gonna use next,
then renderError and Spinner and Message.
So it's always good to remember these things
so that we still know how everything still actually works
behind the scenes.
But now it's actually time
to create basically this generateMarkup method also
in the resultsView, okay?
So all the child views should have this method here
because in the view, the render method then actually relies
on that, okay?
So this.generateMarkup needs to exist in order
for the render method then being able
to create the markup and render it
or to insert that HTML into the DOM.
And so let's take that, just that name
and create that right here.
So as always, a template literal
and then now it's time to actually get this code here.
Okay?
And this here is called a preview
because we will actually use the same thing later here
to render the bookmarks.
So that's gonna be a nice trick
but we will save that for the future.
Okay?
Some more indentation here on all of this
to make it look great.
And so now we need to take our data
and actually put that right here.
Now, where actually is that search results data?
So with all of the structure,
it might be a little bit hard to follow
where that data is.
So let me actually start
by calling the render method here.
So here, besides of simply logging the search results,
we now also want resultsView.render
because of course, just like renderSpinner,
the resultsView also inherits render
and then into that, we pass whatever we want to be rendered.
And so that is all of this.
Okay?
And then in the render,
here we see that it takes this data
and puts it into this._data.
And so here, if we then take a look at the resultsView,
let's simply log that then to the console.
So this._data
and so this_data here
should basically be exactly this search.results.
So let's see.
And it is.
So you see that twice we now get this array
and you also see that it already printed something here
to the results.
So that is that pre-formatted HTML
that we already created here.
So it's already basically this HTML, right?
Because this method here is already returning that
and so then the view takes that data
and renders it on the user interface.
Okay?
And so let's now actually create this method.
Now, this is actually an array now
and so here we need to loop over that array.
So in fact, we don't want to return simply this
but instead, we want to return a whole string,
which contains like one of these elements
for each of the search results in the array.
So we will return basically this._data
and then we map over it.
Remember, so we did that before with the ingredients
and then at the end,
we join everything together
and so then that will be the string
that we return here.
Now, we could now go ahead
and put this code here into the map function
but instead, I will create just another method down here
to make this a little bit cleaner.
Let me copy this name.
Preview.
And I'm simply calling it this
because that's also the class name.
So let's put this one here.
And so then here we can call the method.
Oh, what was that?
So we can call it right here then.
And so now this should like print 50 or 60 of the same.
So just to see if it works.
And indeed, here it is.
So 59 results
and so it prints this 59 times right here now.
Right?
Now, all we need to do is to fill it
with the real data.
So with the id, image, publisher and title.
Okay?
So to start, we need the ID here
in order to build the link
and so then we click the link,
that will change the hash in the URL,
which in turn, will trigger a loading a recipe.
So that's what we did by the beginning of this section.
Now, here we need to pass in the result then.
And so here that is result.id.
Then here it's the image,
so .image.
Here it's the title.
And then here the publisher.
Okay.
Here we also need to replace the icons.
So we will need again the icons right here.
We will probably need them in all of them.
But that should be it.
And it works.
Beautiful.
So we get all of these search results here.
We just have to fix some small details
but you see that it's already working
and now let's click on one of them and it works.
Minimizing this a little bit
and so you see, now we get exactly the recipe
that we are clicking on here.
So watch what happens with the ID
when I click here on Pizza Casserole
or Casserole or whatever.
So it changed the ID up there
and changed the recipe here also in the view.
Great.
And now let's just fix this icon here
and actually, also here.
And then also all of these should not be highlighted.
We will take care of that a little bit later.
So we need to take away this class here of active.
And here we also need to take away this.
Okay?
And we will come back to this later again
when we actually start
to implement uploading our own recipes.
And here in the alt text,
we should just put that title as well.
Okay?
Now you see that actually the states
that we had here before in the page is gone
and so let's activate that hot module reloading
that I mentioned in the last section.
So remember that with parcel, we can do this.
So if module.hot,
then module.hot.accept.
All right?
And again, this is not real JavaScript,
at least this module.hot.
This is simply coming from parcel.
All right?
And now finally, let's also delete these fakes links
that we had before.
Let's reload.
And that looks a lot better.
Okay?
So let me now change something in the code.
For example, getting rid of this console.log here.
And so previously, that would then reload the entire page
but now, as we go back,
you see that the state here actually remains.
All right?
All it says is the console was cleared but that's it.
So the rest remains the same
and we can now also delete this user icon
from the recipeView.
So let's see where that is.
It is simply this div here.
And so once again, it will now not re-render everything.
Well, maybe we should re-render.
And yeah, now it's gone.
Well, it also messed up our button here for some reason.
So maybe that was not intended.
Maybe I should just remove this here.
So basically keeping an empty div.
Okay, so for the time being,
let's just leave it like this.
Okay.
Now, let's just see what happens when we search something
that for sure does not exist.
So you see that here we still get a success.
You see.
So we still get that data but the only difference
is that the recipes array is now empty
and here we also see that the array is empty coming
from the state.
So in this case, we actually want
to render like an error message here saying
that there was no result for that query.
So let's start by creating also
the errorMessage property here.
So let's copy these two actually to our resultsView.
And here let's say no recipes found for your query!
Please try again.
But now, how exactly
will we actually display this errorMessage
on the parentElement?
So we could do that right here in generateMarkup
and basically check if the array is empty
and if it is so,
then render the errorMessage.
So that would work.
However, we can also do that directly
in the render method.
So automatically, right as we receive the data.
And I believe that is what makes more sense.
So right in the beginning
when the render method is first called
and receives the data for the first time,
we can immediately check if that data actually exists.
So right now, we only simply assumed
that there always is data
and that we can then generate some markup with that data.
But as always, it's a good idea
to check if the data actually exists.
And that is true no matter if we receive a simple object
or if we receive an entire array of objects,
which is the case of the results.
So let's add a guard clause here.
So if there is no data,
then we want to return immediately
and we also want to render the error.
So we can do that all in one line
and this will then still work.
So this.renderError
and we actually don't even have to pass in any message
because we already automatically get the message
from this._errorMessage.
And so in this case, that is going to be this one
that we just specified before.
Right?
Now, however, in this case,
this check is actually not enough
because in fact, we do get data.
It's just an empty array
and so in this case,
we also want to treat the empty array
as though we had no data.
So this here basically only works for undefined
or for null.
But now we also want to check if the received data
is an array and if it is empty.
So we can do that by saying
so if there is no data or
and then basically, if the data is an array
and if it's empty.
And we can do that
by using a helper function
that is on the Array constructor,
which is isArray.
So we can check that.
And so if it's an array
and the length of the array is zero basically,
data.length zero,
well, then in this case,
exit this function immediately
and also render the error, okay?
So again, if there is no data
or if there is data,
but that data is an array,
and it is empty.
So in both these cases, we want the error to be shown.
So let's check that.
So with pizza, it all works the same
because there are results.
But now with this, ha, there are no results
and then we get our error message.
So great.
That works.
And so I think we are done actually with this part.
So just one more look at our flowchart.
So we implemented the event
that the user searches.
We asynchronously loaded the search results
and then we also rendered them to our user interface.
And so with that, we are done.
Right?
Just a final look here at the controller
that everything looks good.
So in the next lecture, we will actually take care
of the problem that we have these 59 results,
all one after another here.
So that's not good
and so we will implement pagination
in the next two lectures.