Let's now start implementing the next feature
of our application,
which is going to be the search functionality.
And let's start by taking a look and or flowchart here.
And so, again,
we already have this part here basically now implemented,
and now, we are going to work basically first on this part.
So handling the event of a user
searching for recipes,
then loading the search results and rendering them.
So that's going to be this lecture and the next one.
And then afterwards,
we are going to worry about the pagination.
So again, we will now focus on these three parts.
And so for that,
we need to now implement the model, the view
and the controller that binds everything together.
And usually, I find it easiest to start with the data.
So basically with the model,
which means in this case,
starting to make some API calls
in order to load some search results.
And then, once we have that part working,
we can then render them on the screen
and also care about the actual event.
Okay, so let's actually leave that open here at the end
and go to our model here,
and start implementing the search functionality.
So, just like before,
we are going to create a function
that will be exported
so that it can be used
by the controller.
And this one,
I'm gonna call it loadSearchResults.
And so, since this one will be performing AJAX calls
it's going to be an async function.
Now, again dysfunction here
is going to be called by the controller.
And so, it's going to be the controller
who will tell this function
what it would actually search for.
So basically, it will pass in a query
like string, which we can then
pluck into our API call, now, right?
And here we are going to need a try catch block,
and here in the catch block,
all we will do is to,
basically do the same as here.
So, all we're really interested in here
is to throw the error again
so that eventually,
we will be able to use it in the controller.
So just like before
in the last lecture,
where we threw with the error here again.
So that part is easy,
but now let's actually implement the functionality.
And to do that,
let's go back to our documentations page.
And so, this is how we get all recipes.
So making a get request to this path,
and then, with the search parameter,
and the way we add a parameter,
and I think I explained this before
is with the question mark,
and then search equal,
and then, pizza here in our case
is actually the search query.
And again, for now,
don't worry about the key.
And we can actually already see
what the results will look like here for pizza.
And so, basically,
this is the URL
that we need to call in our AJAX request.
So let me actually copy it
and let's go back.
And so now,
we can, just like before,
use the get JSON method.
And so, that will then fetch the data
and also convert it to JSON
and create an error if something went wrong.
So get JSON returns a promise.
And so we await that here
and store it into data,
just like always.
So get JSON
and then the URL,
and we need a template literal.
And so, yeah now we just have to replace everything
to make this work.
And the first thing
is to actually take this here again out,
and replace it with this API URL
that we already have in our configuration file, right?
So API URL,
and I think
that actually already includes the recipes.
Yup, so it's everything.
And usually, actually a URL ends with a slash,
so it should be like this.
And so here, let's take that out,
actually, and then here as well.
Then here, question mark search
and then this one is going to be our query.
Now, right?
And so, we can then
already log that data
to the console,
to take a look at it
before we then manipulate it a little bit.
And so right now,
this would already be working.
And so let's,
before we actually write any controller or any view,
or we can already call that function here.
So load search results,
and then let's try it,
has always with pizza
and see what we get.
So that's right here.
And indeed, this is the result that we get here.
So it's coming from model at line 35.
So that's where we were.
And so here,
is the data that we get back.
So we have an object
which has a data property,
which then has a recipe's property,
which is this array.
And then, each array contains
all of this data
about each of the recipes.
So, again, this object here is called data
then in there, there is data again,
and then in there, there is recipes,
which is then,
the array itself
where each of the elements
is an object
with only some of the data
about the recipe.
So you see, it's not the complete recipe object,
it's only some of the data
so that we can actually display it
then here in the search results.
So without the unnecessary data
like ingredients and all that.
All right, so let's now take this data
and store it into our state.
And also, we will want to change,
again, this one here,
so to image only.
And so, we will basically create some new objects
based on this data that we receive here.
All right.
So, we get data.data.recipes.
So this is the array.
So this is the array of all the objects.
And now we want to create a new array
which contains the new objects
where the property names are different.
So, let's map over this,
and each element
is going to be a recipe.
And then here, we will simply return a new object.
That's actually copied a code from here
because they are the same,
at least some of them.
So taking all this,
int here now, this is only called a rec.
Okay, and also we don't have the source URL,
we only have ID, title, publisher and image URL, okay.
So this will return a new array with the new objects.
And so now,
where should we store that?
Well, as you can guess,
we will put it also in our state.
So again, the state contains all the data that we need
in order to build our application.
So let's call this one search
and all the data
about the application
that I just mentioned
might as well
also include the search query itself.
So let's create a new object here
and then specify the query,
which we start as an empty string.
And then, the results, let's say,
results, and will start
as an empty array.
So, basically the state
should contain all the data
really about the application.
Now, in this case,
we might not even need the query for now,
but it's still a good idea
to already store it here in the state,
because maybe one day
we will need it.
And then the data's already there for us.
For example, we might want to add some analytics
in the future
to know which queries are made the most.
And so by then,
yeah, as I said,
that would become useful.
Okay.
But anyway, let's now store this data
in state.search.results, right.
And here at the beginning,
the very first thing
that we're going to do
is to state.search.query
equal to query.
All right.
And now, let's quickly
just to see if everything worked,
log data to the console.
So state.search.results.
And here we are.
So that is exactly the kind of array that we had before.
And now the image URL we had before
is indeed called image.
And so everything has the same format as before.
So, we needed to do that
because here the image is also called image.
And of course, we want everything to have the same names
throughout the entire application, all right.
So let's get rid of this
because of course,
we want to call this function in a controller.
And so let's go there
to the controller
and basically create a new function
which is going to be like the new controller.
So const controlSearchResults.
And so this one
we'll call the asynchronous function loadSearchResults.
And so this one, of course
then also need to be an asynchronous function itself.
And so, as always try catch,
and here for now, let's simply log the error
and we will then fix that later.
So, here is where we actually
will want to call the loadSearchResults that we just built.
So that is in model.loadSearchResults.
And here we then,
we'll need to pass in the query,
but for now, let's just do that again manually.
And of course,
we need to await this,
but we do not store any results anywhere
because, just like the loadRecipe function,
this one also doesn't return anything.
All it does
is to manipulate the state.
And so, then we should be able to do this console.log
in the controller
and still get access to the results, right?
So, let's see.
Now, of course, we first have to also call this function.
And again, later,
we will, of course hook this us up to an event list
and our material, let's just
call it right away.
And now we get status not defined, all right.
And that is because,
of course it needs to be model.state.search.results.
So you see that this is becoming a pretty long chain here
but nevermind, that's not a problem at all,
because we know exactly what all of this is.
And so, now indeed
we get back our 59 search results for pizza.
But now, instead of only searching for pizza,
we actually want to get the query right here
from this input field, right?
And we also want to start this whole process
of loading the search results.
So we only want that to happen
when this button here is clicked.
And so, now let's basically create a view
only for this search part here.
So right here,
we will have a view
for this year.
So for the search field,
and for the button,
and then a different view
which will actually render the results.
So, it's a good idea
to keep every view really focused.
And so, it's best
to just do one for this part,
and then one for displaying the results,
also because in the user interface,
they're actually in kind of different places also.
So it makes sense to have a different view.
Now this view here
is of course
not going to render anything,
but what it will do
is to basically give us the content
of this input field.
So getting that input data
is of course
something that has to do with a DOM.
And so, it should be in a view
and not in a controller, right?
So of course,
if we wanted to be lazy,
we could go ahead
and select the element that we want.
So it doesn't matter.
And then from there,
we would simply get the value
and then use that as our query.
So again, we could do that here,
but this is clearly about the DOM.
And so, in order to follow our architecture,
we will create a new view right now.
And this one is going to be called the searchView.js.
And just like before,
here, we will create a class called searchView
and we will then not export that class
but export an instance,
so an object that was created by this class.
So export default new SearchView,
and without passing in any data,
done just like before,
we will have a parent element.
So just like in the recipe view,
so a parent element,
and then we will set that to something,
query selector, and now let's see what we get
here in the DOM.
So in the HTML,
so the search,
now, it's this one.
So we have this entire form,
which is called search
and which has this input field here
and also the button.
And so I think that this is the element
that makes sense to select.
So the one with the class of search, so .search.
So this is our parent element here in this case.
Now, again, this class is not going to render anything.
All we want is to get the query
and eventually to also listen for the click event
on the button,
but let's start with the query here.
And so, let's create a method
that we will then call from the controller called getQuery.
And all this will do
is to return this.#parentElement.
And so again,
that's going to be the search.
And then from there,
we want to select this input field.
So this one with this class.
And so let's a select this child element
using a querySelector,
and then simply get the value of that.
Okay, and that's actually it.
And again, of course
we could have written this exact same code
in a controller,
but that wouldn't make any sense,
because this is not concerned about the DOM at all.
So we don't want to have any DOM elements in here.
We don't even want to know what the DOM looks like.
So actually here we have this,
but we don't want this here.
Let's get rid of all this clutter here actually.
And now, I want to
get the searchView here.
And then here,
we can get that query
by saying searchView.getQuery.
And of course,
there might be no query.
And so, let's add another guard clause here.
So if there is no query,
then return immediately.
And so here,
we will now no longer look for pizza,
but for query.
Now, if we run this,
then of course there will be no result
because, well, right now,
there is nothing here to be found.
So that controller function
runs right in the beginning
when the application actually loads.
And so by then of course,
there is a no value here.
And so, in order to actually make this work,
we now need to listen for the event
of basically clicking this button or submitting this form.
And then, only on that event,
we want to actually call,
this controller function here.
So not in the beginning when the script loads.
So, in order to do that,
we will once again,
use the publisher subscriber pattern.
So basically exactly what we did here, right?
So we will list in for the event
in the view right here,
and then pass the controller function.
So the handler function
into the method that we will build here.
So this addHandler, a search method here
is basically going
to be the publisher anti-control search results function
is going to be the subscriber.
Okay, so hopefully you really understood that pattern
that we used before.
And so this now,
it should be obvious.
So, let's take our parent element.
So this.#parentElement of course,
and then this is where
we will actually add the event listener.
So not to the button,
but really to the entire form
because there, we can listen for the submit event.
And so then, this will work
no matter if the user clicks the submit button
or if the user hits Enter while typing the query.
Now here, we cannot simply call the handler immediately,
because remember, as we did many times before
when we submit a form,
we need to first prevent the default action,
because otherwise the page is going to reload.
So, preventDefault.
And only after that,
we can call the handler function.
And, as you already know,
this handler function
should be the control searchResults function.
And so, all we have to do now
is to call this method
and pass in that function.
So, let's do that here right at the beginning.
So searchView.addHandlerSearch.
So you'll see that I'm always going to use
this name here of addHandler,
and then we pass in the controller function
and that's it.
So now it should actually already work.
So let's see.
And actually, let's clear up all this clutter
here a little bit.
Okay, so this console.log,
and there's also some here in the recipe view.
So that's this one.
Okay, so that's again, try pizza
and yeah, beautiful, it worked.
So we get
this data first here coming from the model,
and then also in a controller,
we also logged it.
And so here we have the exact same 59 data
that we had before.
Let's try something else now,
avocado, let's say,
so I hit Enter,
and, so this time we got 39 search results
and you see,
all of them basically have avocado
right there in the title,
it's really beautiful
that it already works this great.
Now just one small detail,
is that, after we search,
we should probably clear out this field.
So let's add another small method for that in the view.
So, and this one here,
so clearInput,
and once again,
having it nicely encapsulated inside of the searchView.
So again, this could simply be also in a controller,
because, it's really just one line of code,
but we want to keep everything separated.
This will make it so much easier to add features
in the future.
And it's really so much better.
So, all we do is to set this to empty and that's it.
All right, let's try it one more time.
Let's say pasta this time.
Ah, of course we never called DOM method here anywhere.
So we need to do that in the controller.
And let's actually add some more comments here.
So Get search query.
And the code itself is actually pretty self explanatory.
And so many developers will tell you
you should not comment it,
but I think
that this is actually very helpful when you read the code
in like one or two years from now.
So, I would advise you to always write comments like this
even if your code seems pretty obvious,
especially when it's like
a bigger list of instructions like this,
so Render results.
So right now our rendering is only a console.log.
But here, when we get a search query,
we can actually then
immediately also clear it afterwards.
Or in fact,
in order to keep this controller even simpler,
we can do that
right in the getQuery method.
So, let's go back here,
and now we can make this private,
so we're not going to use it outside.
And then let's actually call it here.
So this.#clearInput.
And then of course,
we first need to store this somewhere here.
So we get the query,
then we clear the field
and then we return the query.
All right, so that should now work.
So pasta and clears the field,
and gives us or 45 results down here about pasta.
Great, so that works just fine.
And so, all there's left to do
is to now implement actually the view.
So the second view basically.
So the one
that is responsible
for then rendering all the results
here in this sidebar.
And so that's going to be a little bit of work.
So let's leave that for the next video.