Instructor: This will be a longer video,
where we're gonna write a couple of functions
for the search view.
And so in this video, you're gonna learn
a couple of advanced DOM manipulation techniques.
We're gonna use ES6 template strings
to render entire HTML components.
You will learn how to create a loading spinner,
which is really cool, I think,
and a lot of other smaller things and techniques.
So let's get straight to it.
So we already have the file here, searchView.js.
Let's close up this one, this one here as well.
And so yeah, so this module will have a bunch
of functions regarding the search view.
And so we will export all of them using named exports.
Okay?
And so let's start by the one to read the input
from our input form, okay?
So remember the syntax.
We write export, and then when we're using
named export, we just use the name of the variable,
the function in this case, that we want to export.
So getInput, in this case.
So what do we want to do in this function?
Well, all we want to do is really to return
the input value of the field.
So we will select the DOM element,
get the value, and then simply return it.
Now, we could do documents.querySelector
and all that stuff here again,
but I think it's better to have one central variable
where we have all of these DOM elements
that we need in our app.
That's a bit similar, again, to the Budgety app,
where we also did something similar.
Because it's a lot easier to have everything
in one central place instead of having
these documents query selectors all over the place,
which can turn into a maintenance nightmare
if we ever were to change, like this entire
underlying markup here.
And with this, our views will get a little bit
more decoupled from our markup, okay?
So let's now create a new module for that
right here in the views folder, as well.
And I'm going to call this one base.js.
Okay?
And we can also go ahead, before we forget,
we can go ahead and import it here.
Or maybe not yet, okay?
Let's first export some stuff here.
So what we want to do here is to create
an object which will contain all of the elements
that we select from our DOM,
so all the elements that we need from our app.
We will then export that.
So export variable called element.
And so this will be an object again.
And I'm doing it as a named export,
because later on we will have some more stuff
here in this module.
And so let's go with the named exports here, okay?
So let's call this one searchInput.
And so what we want to do here
is document.querySelector.
And let's take a look in the markup again.
And so the input field that we're looking for
is called search_field.
So let's copy it, and here we go.
So now we have an element with a property
called search_input, which contains
this DOM element here, right?
Now, we also did some document.querySelector in here.
And so let's actually get rid of that here, as well.
Let me copy it,
and insert it here.
And of course, we're needing the name here.
So searchForm for this one.
And now lets give it a save.
So you see that there's an error here.
And so VS Code, this editor here,
marks, then, this file here as red
in the entire compile tree, okay?
Now, we need to of course import this element.
And so we use the curly braces, remember,
elements from, and this one is in views,
and it's called base, okay?
And actually, we can also already start
by importing this search view file, okay?
So let's do, and in this case,
we are simply going to import all of the functions,
because we already know that we're gonna need
all of the functions that are in view
in the controller, okay?
So we want everything as searchView, okay?
And remember that this will then be an object
in which all of the exported variables
from the search view module will be stored, right?
So I explained you that in one of your previous lectures.
I hope you remember that.
So views, and searchView.
All right, and so now we can say
elements, and let's take a look at what we called it.
So we called it searchForm.
Let's get that.
And so now we should be good to go.
And from now on, we're simply gonna put
all of our DOM elements right here into this object.
Okay, so let's do the same here again, actually.
So let's get
this one and import it here, also into the searchView.
So element.searchInput, right?
Let's double check, and yeah.
So it's called searchInput.
That is our search field.
And all we need to do now, really, is
to say that we want the value.
So remember that this is an arrow function here, of course.
And if you only have one line,
then this is an implicit return.
So we don't even need to write return.
So this will automatically return
this value that we specified here,
so elements.searchInput.value,
which will be whatever we input into the search field.
Okay, now let's give it a try here
and replace this pizza we had here
with what we really want.
So how do we specify this function?
Well, we simply use searchView,
which is, remember, what we imported
from the searchView module.
And then dot getInput.
And my text editor, VS Code, actually
already knows that getInput is one
of the functions in there.
So it's very smart, it's very handy.
And so again, it's really a great text editor.
Okay, just to make sure that everything works here,
let's log it to the console.
It should reload now.
And we have an error here.
So let's go back to searchView.js.
And yeah, of course.
So I tried to import the base element from the views,
but we are already inside of the views folder.
And so I don't need any of this.
So we're in the current folder,
and then just base, okay?
And so that should work now.
And yeah, it does.
So instead of writing pizza now,
let's write pasta.
And indeed, it writes pasta to the console,
gives me the recipes, which are now, hopefully,
about pasta.
Yeah, so pasta with pesto, then the best lasagna,
and you see that they're all about pasta now.
So great, that part is already working.
And so let's now do the most exciting part,
actually, which is printing these results
here to use the interface here,
into this left column of the app, right?
So let's think about how we are going to do that.
Well, here in the state.search.result,
so this one here, this is our array
with all 30 results, right?
So let's now write a function in the view
which can receive all of this result
and then print each of the elements,
so each of the recipes in that array,
to the user interface.
So let's come back to our searchViews here
and write another function.
And I'm going to call this one renderResults.
And what this is going to receive
is of course all of the recipes.
Okay, so we receive an array of 30 recipes here, remember.
And so now we need to loop through these
in order to then print each of these
to the user interface.
So let's say recipes.for each,
because remember that is the best way
to loop through an array.
So instead of having all the logic
of printing here in this function,
let's do a separate function that has only one purpose,
which is to print one single recipe, okay?
And I'm doing that because later,
we will add some more stuff here
when we implement the pagination.
And so it's better to have one function
for each separate task, okay?
So I'm actually separating different functionalities
into different functions.
So I'm going to have another function here,
which is gonna be called renderRecipe.
And this one will receive just one recipe.
Okay, and note that I didn't prefix it
with the export command because we don't need
to export this function to the outside.
We just don't need it outside of this module here.
And so it's just like a private function here, okay?
So we can now call this function here,
and we can do it in a more complicated way,
something like this.
So we had a callback function
where we specified the input,
and then we say renderRecipe
with the current recipe.
But that's actually not necessary,
cause all we need to do is really to write
renderRecipe like this, and it will then
automatically pass the current element
into this function, into this renderRecipe.
And so for now, all that the renderResults function
is going to do is to loop through
all of the 30 results and then call
the renderRecipe function for each of them.
And so this is where really the rendering
will actually happen, in this function here.
So let's now get the HTML for each of the results,
which is stored right here.
So I have it here in comments into my HTML file.
So what I have here is, let's do a quick overview
of the HTML.
So we have the header in the top.
So you see the header, which is this
entire element here.
So all of this here is the header.
Then on the left side here, we have the results element.
And then in there, we already have an ordered list
which is called results list.
So I decided to already put it in the HTML
and not have it dynamically generated by JavaScript.
So it's easier to already have this
and then just fill it up with each of these li elements
in JavaScript, which is exactly
what we're gonna do next.
So we have all of these results here.
Then down here, well, we have the buttons
for the pagination, but we're gonna take care of that later.
Then in the middle we have the recipe element,
so this big part here.
Then we have the shopping list on the right side,
as you see, and then, I guess,
oh, that's more in the top.
So here we have, then, this likes panel here.
So this here.
We're gonna hide that here later,
when there's no liked pages.
But for now, again, let's leave it as it is.
Okay, anyway, what we really want to grab now
is this code for one li element, okay?
So let's create a variable called markup.
And now remember back in the Budgety project,
which we still did with ES5,
and when we wanted to insert some HTML,
we really had to put it all in one line.
And it was just really bad looking and confusing syntax.
But right now, in ES6 with template strings,
we can do it in a very easy way.
So just make a new template string,
and then give it some space here.
And now we can simply write it like this,
as if we would be writing real HTML.
So this is really cool, I think,
and looks much better.
So this looks really like as if we're writing
in an HTML file.
It actually looks a bit like React here, also.
Okay, and let's now look at the data
and actually also at the final result
in order to see how this is supposed to look
once it's done.
So I have this here also open in another port.
So this here is the final working project, okay?
So let's write pizza.
It has this nice spinner that we're gonna implement next.
And then we have all the results.
So what we have here is this image,
then this title, and then down here the publisher.
Okay, let's see if we can find that data in our results.
So let's put pizza here as well.
And it still should, now, log it to the console
at some point.
And indeed, here we go.
So let's take a look at the data
for each of the recipes.
And so we have an image URL.
We have the publisher, and we have the title
and also the ID.
So this is all that we need in order
to print the results to the user interface.
Okay, let's go through the markup one by one
and replace the static data here
with our dynamic data coming from the API.
So we start here with this part here,
and this is actually the ID of the recipe.
And you're gonna find out a bit later
why this is important when we start loading recipes.
But for now, let's simply insert the ID in here.
So since we're into a template string,
we can now use this template string syntax.
So what we want here is, let's go back
to grab the name, the property,
and it is called recipe_id.
Let's actually copy it.
And so it's recipe.recipe_id, right?
So recipe is the variable that we put into the function,
coming, of course, from this function call down here.
And so then from the recipe, we read the recipe ID.
Okay, so going back here, and now we changed it.
And so yeah, let's get it back here.
And wait for it, and here we go.
So this object here is now our recipe.
And then we need to read recipe_id,
image_url, publisher, and title.
Okay, so next one is the image URL, I guess.
And so now it is here, so let's replace this as well.
Okay, now here we have the title
and finally the publisher.
Let's just copy this part.
And here it's not, it's title.
Okay, and let's actually put it here
in the alternative text for the image.
Let's put the title here, as well.
Okay, and I think that's it.
We just have to remove this class from here,
which is for the active class.
So that one doesn't really matter.
We just need the results link class.
Okay, and so this here generates a variable
containing the markup that we want to print.
But now we need to actually, of course,
render it to the DOM, okay?
So where do we want to render it?
Well, we want to put it here, into the results list.
So let's add it here to this elements object.
So search results list, let's call it like that,
document.querySelector,
and let's copy this one here now.
And so elements.searchResList,
now remember the insert adjacent HTML method
that we used before.
Remember that one?
Let's take a look at it, just to remember how it works.
Maybe it's been a long time since you've taken
the rest of the course.
So I always like to use the MDN library
in order to get help about the different methods.
So let's take a look at how it works.
And so basically we have to specify the position
and then the text, so the markup that we just defined.
And in here, we can do different keywords,
so we can put before begin, after begin,
before end, or after end.
And here is basically the position
of each of these four keywords.
So imagine that this here is our list element
that we just selected.
And so we want our elements to be put before end.
So here, okay?
So we always want a new element to be put
on the end of the list.
So first the first one, then the second,
then the third, all the way until 30.
If we did after begin, then we would put
the second one before the first,
then the third before the second,
and we would end up with the inverse order.
Okay, and so we're gonna use before end
and then the markup.
Okay, I hope that made sense.
So before end, and then of course the markup.
And that's it.
So all of this will be executed 30 times,
each time for each of the recipes.
So let's hope that this actually works.
And in order to see it, of course,
we need to import it here.
So searchView.renderResults,
and we need to pass in the recipes.
So that is state.search.results.
I guess.
Let's take a look again at the model, just to make sure.
And it is called, okay, so it's called result.
Okay, does it make sense?
Just to make sure that it actually does make sense,
we have this result stored inside
of our state element, right?
We also have our imported function from the searchView,
which we did up here.
And so now we're bringing everything together
here inside of this function.
So we read the input from the input field.
This one we don't need anymore.
And we create a new search object
which contains that query.
And we actually get our results from our API call,
wait until that is done, and finally
we render it to the user interface, so to the DOM,
by using this method that we did
in the searchView just before.
So let's now hope that it actually works.
Writing pizza, and we don't have the spinner yet,
of course, but let's wait, if it actually happens.
And now we have an error here.
But that's not a problem at all.
So let's try to debug it.
So what it says is that recipes.for each
is not a function.
So let's find out why by logging to the console
what recipes actually is.
So console.log recipes.
Okay, because it's an array,
and so it actually should have the for each method
attached to it.
Let's wait for it, pizza.
And we still get the same error.
And, oh, cool, yeah.
This is, of course, just a stupid mistake.
Okay, I'm sorry for that.
So this is a capital E, forEach.
This is how it works.
Okay, so let's reload it.
Write pizza again, and let's wait for the result here.
And whoa, here we go.
So it actually printed our results here
to the user interface, just like we wanted it to work.
So that is actually perfect.
Okay, it's not quite perfect, actually.
There are some things that we have to change here.
But at least it's now really putting our results
here to this page.
So that's already a huge step forward.
So that's really cool.
Next up, there's just some small corrections
that we need to do, like clearing this text here
from the field.
So let's take care of that now.
And write a function export clearInput,
and we're missing a const here, of course.
And so this is actually pretty similar
to the one before.
Elements.searchInput.value, and set it to nothing.
Okay, and now we're actually not returning anything here,
so it's cleaner to wrap this here
into these curly braces, because otherwise,
it has an implicit return after result of doing this,
and we don't want that.
Okay, also missing semicolons down here.
It's not a big deal, but let's keep our code clean here.
So clearInput is the name of this function.
And let's now use it here.
So we're gonna put it here in prepare UI for results.
So searchView.clearInput, and that's it.
Now, another thing that we want is,
well, let me actually show it to you,
why we need to do this.
So let's again search for pizza here
and wait for our results.
So we have our results here, of course.
And you see that actually it cleared our input.
And let's now search for pasta.
And what you will see happening now
is that it will add these new results
here on the end of these, okay?
And so what we need to do is to actually
clear the results before we can display them.
And so that's another thing that we can put here
in prepare UI for results.
So we can clear the input and then also
clear the results from the previous search.
So let's quickly do that, as well.
clearResults.
And we already have this list selected,
so it's just a searchResList here.
And how do we remove the entire HTML
inside of this element?
Well, it's actually very easy.
We can simply set the inner HTML of the element
to nothing.
And so the HTML inside it will be empty,
which means that it will simply delete
all of the HTMLs, all of these li elements,
that are currently in it, right?
So let's test that one out, as well.
So again, our first search is for pizza,
which should display in a second here.
Here we go.
So again, our 30 results.
And now pasta.
And before showing the new results,
it will actually immediately delete these results, right?
Well, not really.
And so probably our method is not really working.
So yeah, it did add these here to the end.
And of course it did, because we didn't
even call this method
here in the controller.
So clearResults here.
And now it has to work.
There's just no way around it.
So this time, pasta first.
Here we go, with all our 30 recipes.
And now pizza, and you see now it cleared the search list
and displayed the new recipes.
Now, the next thing that we're gonna do
is to shorten these titles.
So we want all of these titles to be just in one line.
And if they are too long, well,
we just want the first words and then three dots here,
indicating that the text can continue.
So we have some of these here,
longer than just one line.
And so we're gonna fix that in the next video,
cause this one, I think, is already getting long enough.