So I haven't forgotten
about the image upload preview image,
but I wanna actually start by outputting products here
in a grid on this overview page
before we finalize the Add new Product form
and we add this image preview.
And, therefore, let's add such a grid.
And I'm saying grid
because I don't just wanna have a list of products
where we have one product below the other one,
but instead I wanna have a grid with columns and rows.
And with the CSS grid,
it's really easy to create such grid layouts,
which is why I wanna include it here.
For this, it's time to go to the all-products.ejs file
where I have this section for outputting a list of products.
Here, I'm not really outputting a list of products yet,
but that should now change.
For this, I'll add a unordered list in there,
could also be a ordered list,
but the order isn't too important,
and then in there, I wanna have list items
with my product data.
Now, the product data can be more or less sophisticated.
In the end here,
I just wanna have, let's say, an article
for semantic reasons.
We could also do it without that,
but it is considered a good idea to wrap standalone items
that could work on their own with such a article,
and a single product is a standalone item in that list
even though it's part of that list.
So I'll use a article to wrap my product data.
And then in this article,
I, in the end, wanna have h2 tag
with the title of the product,
and then let's say above that, the image of the product,
and then below that, I want to have two buttons
that allow me to interact with that product.
And for this, I'll actually then just add a div here
which allows me to style these two buttons
to sit side by side.
And the first button will actually be an anchor tag
which loads the detail page or the added page
for an existing product which doesn't exist yet,
but which we'll add soon.
Here, I'll say View & Edit.
And then I also wanna have, let's say, a button below that
which says Delete.
And we'll make sure
that these buttons and links work correctly later.
For the moment, I'll just add them.
And I'll give the anchor tag a class of btn btn-alt,
and the button will get the same classes here
to have the proper styling.
But speaking of styling,
we, of course, need to work on the overall list
and the articles in there.
And, actually, I will outsource this article into an include
so that here in admin/products, I'll add an includes folder,
and in that includes folder,
I'll add a product-item.ejs file,
and in there I have this article,
and I'll add a class of product-item on the article
so that we can use this class for styling later.
And then in all-products, in this list item,
I just wanna include this list item EJS file,
with include,
and then it's in the includes folder
which sits side by side to the all-products.ejs file here,
and then product-item, like this.
Now, of course, I don't just wanna have
one list item here, though,
but instead I wanna output multiple list items,
and therefore we wanna loop
through a couple of list items here
with a for-of loop.
We, of course, have to add some logic here,
but that's the idea later, like this,
and also close it like that.
That's what we did before in the course already.
But now, of course, we need the data
through which we can loop here.
And that is something we have to work on on the backend,
in our model, to be precise.
There, we have to add logic
for fetching data from the database.
For this, we can go back to our model to the product.model.
And there, below the constructor, above the save method,
we could add a findAll method which has to go,
as the name suggests, of finding all the product documents.
Now, I will add async in front of that
because we will use async/await,
and also static
because this should be a so-called static method
which I also introduced before in the course.
The special thing about static methods
is that you don't need to instantiate the class first.
You don't need to create an object based on the class
in order to use static methods.
Instead, you can call them on the class itself.
And it's very useful for cases like this
where we can't create a product based on this class yet
because we have no product data.
We're looking for product data instead.
And then we can just use this class
as a logical grouping unit, so to say,
to which we add utility methods like findAll
that find us all the product documents in the database.
And then in findAll, we can therefore use db.getDb
and use the products collection as before,
and then there, we can call find,
like this, to find all the documents.
If you call find like that in MongoDB
with no parameter values,
it will give you all the documents.
That's what we also did before here in the Mongo Shell.
Now, find returns a so-called cursor object
which you could use for moving through greater datasets.
Here, I instead wanna add toArray at the end
to enforce the returnal of an array
so that we get back an array of product objects in the end.
Now, this is something we can and should await
to get our products in the end, like that,
and then here, I wanna return the products I got.
And, actually, I don't wanna return them like this
because that would be the pure documents
which I have in the database.
Instead, I wanna transform them.
And that is something we can do
with help of a utility method
that you can execute on any array.
No matter if it's an array
full of products or full of strings,
it doesn't matter.
On any array in JavaScript, you can call a map method.
And keep in mind that products is an array
because we converted to an array with toArray.
That's a utility method offered by MongoDB,
and it ensures that we get back an array.
On such arrays, as products is one, we can call map,
and map takes a function, an anonymous function,
for example, as a parameter value.
Now, this function will be executed by JavaScript
for every entry in that array.
And we'll get that entry, that item,
or in our case that product as we know,
as a parameter in this anonymous function.
And in this anonymous function,
we can then return a new value which should be used instead,
and this allows us to transform all the entries in an array
so that I can transform an array full of products,
full of product documents, to be precise,
into an array full of product objects
that look like this blueprint
or that are based on this blueprint.
So here I then return,
inside of this anonymous function,
that's where I'm returning,
a new product now for every product document
that I have here in the end.
So maybe I name it such that it's clearer
that this is a product document
as it's coming from the database.
And now I'm returning a new product instance instead,
and to that constructor function here
I pass productDocument.
Now, that is a JavaScript object where we will find
all the fields I'm looking for in the constructor,
but I now convert it to a object based on this class.
And why am I doing that?
Well, simply so that I rebuild
the imagePath and imageUrl fields
because remember that those were not stored in the database
for reasons mentioned in the previous lectures,
but I do need them when working with the products
in my code and in my templates.
Therefore, I wanna transform
all the documents fetched from the database
into objects based on that blueprint
so that I do have imagePath and imageUrl
for all my products again.
We would not have to do that
if that data would have been stored in the database,
but I deliberately didn't do that.
Now that we are doing this kind of transformation here,
I will also, by the way, add an id field here
to this to-be created object
which is based on productData._id,
._id because that is the id field
that's automatically created in the database by MongoDB.
Here we can also see
that it's using this special ObjectId format,
and I wanna instead work with strings only
so we can call toString on that
because basically all values in JavaScript,
including this special ObjectId data type,
do have a toString method
to force a conversion into a pure string.
In case of ObjectId,
that means that we get this string id
instead of this special object
which has more under-the-hood capabilities
which we don't need here.
So that's why I'm calling this built-in toString method
to convert this id to a string
which is stored in this id field in the product object.
Of course, when a new product is created,
this might be undefined, though,
and calling toString would fail in that case,
and therefore I will actually wrap this into a if check
where I check if productData._id is actually truthy,
which undefined would not be, for example,
and I only extract and convert and store the id
if I have one.
Otherwise, I don't do anything.
This ensures that if we create
a product without any id data,
we don't get an error.
Okay, so with that, we got that.
I'm transforming everything here.
Now, with all that done, back in the admin-controller,
we can get all the products
for the getProducts controller action.
Here, we can now also add async
because we will use this newly added findAll method
which returns a promise since we use async/await,
hence I wanna use async/await
here in the controller action as well.
And I can get my products by awaiting Product.findAll
using this static method.
Now, just as before with saving, this could fail,
so I will actually try-catch this
to avoid any unwanted errors,
and I'll accept that this third-parameter value next here
to call next error
and then return if something does go wrong
about fetching the products.
However, if everything succeeds,
if we stay in the try block, if we got no error,
then I got my products,
and those products can now be handed here to the template.
So now I'll hand them under a key named products,
though that key name here is up to you,
into that template.
And, therefore, now with all that done,
in all-products, in this for-of loop which was not finished,
we can now loop through all the products,
that's the key name I just chose,
and get our individual product items.
And for every item, we build such a list item
and repeat that included product item.
And in that product-item.ejs file,
we can therefore then output that title
by referring to product.title here.
And here, I can then also output product.imageUrl
as a source for the image,
imageUrl because in the product.model we worked on
a couple of seconds ago,
I am building this imageUrl property.
I'm deriving it dynamically.
That's why this transformation was so important
so that this imageUrl property exists.
And the alt text here could then be the title again
as a fallback text.
I also already wanna set the link here for editing.
That could be /admin/products/
and then the product.id,
though that, of course, should not be product.id like this.
Instead, here we need to inject this dynamically like this.
Now we got the proper product.id here.
And it's product.id instead of product._id
because in that model we worked on,
I added this .id like this, not this ._id.
Now we should also make sure
that product is available here in product-item.
And to ensure that this is the case,
we can go to all-products.ejs
and add a second-parameter value here
to the include function
where we now also provide an object of key-value pairs
that will be made available to the included snippet.
And here I wanna make a product key available
inside of product-item
since I'm referring to it here in all these places.
And then the value for that product key
is the product I have here in the loop.
So now just as we saw it before in the course,
we got product here.
That's why we need it here to use its value.
Product here on the right side,
on the left side of the colon, on the other hand,
is up to you.
That should just be the key which you then use
inside of that snippet that's being included.
With all of that done, if you reload,
you should see some data here.
You won't see the image yet, though,
and the reason for that
is that we're not serving it statically yet.
If we inspect this here,
we see that there is the path to it,
so it tries to fetch it,
but we're not serving the uploaded images statically yet.
And that, of course, is something we'll have to change
before we then in the next step ensure
that we get a proper grid styling here.
Now, how can we serve our images statically?
Well, app.js is the place to go.
In app.js, we are also ensuring
that the public folder is served statically.
Here, I now wanna serve another folder statically.
And we can do that with express.static, as you learned,
but now it's the product-data folder.
I added that product-data folder before.
And in there, I have the images folder with the images,
but I actually wanna serve
the entire product-data folder statically
so that if we had other folders or files in there,
they would be served statically as well.
And therefore, of course, the images folder
will also be served statically.
However, please note that in the product-model,
the imageUrl which is being created
and which is therefore being used for requesting images,
looks like this.
It doesn't look like product-data/images.
Instead, it's /products/assets/images.
Now, going for images and then some image name
will work if we look in the product-data folder,
but we're not serving /products/assets statically right now.
Well, we can configure the static middleware
to look for this kind of request
to then serve the content of product-data.
All we have to do is take advantage
of this filtering mechanism I mentioned before.
For app.use, you can define a first-parameter value,
which is a path that acts as a filter.
In here, we can look for /products/assets
since that is the part with which my URL starts here.
Now, just as we did this before
for serving the admin routes,
only requests that start with /products/assets
will be handled by this middleware then,
by this static-serving middleware in this case.
In addition, just as with admin down here,
this beginning of the path
will be removed for this request now, so to say,
and Express will look at the rest of this path
for these middleware functions that handle that request.
So in my case,
that would be /products/assets, which is removed,
and Express would then just continue
with looking for /images and then the name of the image.
Now, since the middleware
which I connect to /products/assets
tells Express that it should look for this requested data
in the product-data folder,
it will go into that product-data folder
and look for images folder and then that image name,
and this will now succeed.
So that is how we can serve uploaded images statically
even if we wanna go for a specific URL that should be used
for requesting that data or these files
that might not be the same
as the folder structure we have on the server.
And the advantage of this could be
that now with the URL which is seen in the browser
and which can be viewed by the visitors,
we don't give any hints
about the folder structure we have on the server.
It wouldn't be horrible if we gave some hints,
but I wanted to show you this alternative.
So, therefore, now if I reload,
here's my image.
Styling is off, no question about that,
but the image is there.
It's being served, and now we can focus on the styling.