Now outputting orders is not everything
that I added though.
For one, I fixed a tiny issue
in the card controller JS file.
I added a + here in front of rec.body.quantity
to ensure that the quantity we update an item with
is always a number.
Otherwise it would be treated as a string
and this could lead to incorrectly output values here.
For example, on the cart badge,
if you update your card items,
because a string would be concatenated with a number
and you would get a string as a result instead of a number,
we need a number here though.
And therefore always forcing a conversion to a number
before we update an item with a given quantity
is a tiny fix I wanted to implement here,
but that's not the main thing I added.
The main thing is a different thing.
Assume that you are a user logged in or not, doesn't matter
and you add items to your cart.
Now we have the keyboard in the card here.
Now, what if that price of that keyboard changes
whilst we have the item in the cart, but before we check out
or what if the product is removed altogether,
then we could theoretically buy a product
at a incorrect price, or we buy a product
that's actually not in the store anymore.
Both scenarios are scenarios I'd like to avoid here.
And for this I added a brand new method to the cart model,
and that is the update prices method here.
Now it is a rather large method,
but it's not a difficult one.
I start by finding my product IDs.
So an array of the IDs of products that I have
in this cart.
For this I go through all the items
and then with that map method, which we saw before
in the course already,
we pass a function to map,
which is executed for every item and which has to return
the trends from the value that you want to use instead
in the newly generated array.
And for did we get that item
as a parameter value automatically,
and then I simply return the ID of that item product.
So off the product that belongs to the card item.
And this gives me an array of product IDs and yet
then I code the find multiple method
on the product model to which I pass my product IDs.
This is also a new method, which I added
in the new product model JS file,
which you of course find attached just as you find
the cart model file and other files attached.
And in the product model JS file find multiple,
again, transforms all these IDs to convert them to
mongodb.objectid instances.
So I transformed this array of string IDs into
an array of MongoDB.objectid objects.
And then I run a query and I use the special dollar sign
in query operator that is supported by mongodb.
This is official mongodb query syntax
and what I'm saying here in this line is
I want to find all the products where the product ID
is one of the IDs specified in a given array.
Productids is an array of objectids of productids
converted to objectids in the end.
So I looked through that array and all product ids
that are part of this array will be fetched
or all the products that belong
to those productids to be precise.
And then I just convert my array of product documents
into an array of product objects
based on my product blueprint, that's all I'm doing here.
So that's what find multiple does.
And in cart model, I get a list of products
with help of find multiple.
Then I create a deletableCartItemProductIds helper array
which will become important in a second.
And then I loop through all the cart items here.
I go through all the cart items and for every card item,
I then find the matching product that I just fetched
from the database again.
So here I find a product in this product array,
which we fetched here by using the built in find method,
which works a bit like map,
but which is not there to transform values,
but to find a value in an array.
And find takes a function which will be executed for us
and it will be executed for every product
in this products array.
Hence we get the product as a parameter value
automatically by JavaScript in that function,
because JavaScript will call that function for us,
for every product in the array.
And then we have to return true or false to tell JavaScript
if this is one of the items we wanted to find,
and I return through here, if the ID of the product
at which I'm currently looking at in my products array here
is equal off the ID of the product in my cart item
at which I'm currently looking.
So I'm finding the updated product data
for the card item I'm currently looking at.
Now, of course the product data is stored in my card item,
but the idea here is that I want to get the latest data.
Hence I simply find the latest product data
for the product that belongs to this card item
from my products collection in the end.
Now, once I identified the product
that belongs to this card item, I continue.
If I did not find a product, that means that
I have a product in the card item,
but I was not able to find it
in the products collection anymore.
It was probably deleted in that case
and hence, I want to schedule it for removal from the cart.
That's why I created
this deletableCartItemProductIds helper array here,
because I then push this productid
that doesn't exist anymore in the collection
into this array and then I use the continue keyword,
which I also introduced briefly earlier in the course,
which simply ensures that the next iteration
of this four loop starts without going through
the remaining code in that for loop first.
So I skipped dead remaining code in that for loop
if I scheduled a product for deletion.
Now, we'll see what happens with these scheduled products
in a second, for the moment,
let's continue with the next lines of code
and dose only run if I did find a product.
So if the product that's stored
or that belongs to a card item still exists
in the product selection.
If that's the case, I simply update the product data
stored in the card item
with that latest product data from the database.
For that, I override the product stored
in the cart item with the fetch product.
And I update the total price of the cart item
by taking the quantity of the product, which hasn't changed
and multiplying it with the latest price
which I got from the database.
Keep in mind that cartItem.product was updated
in the line before so when I referred to it here,
I am referring to the latest data.
With that, the for loop is done so I did that
for all the cart items.
Now, then we move on to the next chunk of code here,
where I use this deletableCartItemProductIds array
to now delete items from the cart for which
the products were deleted from the database.
This code only runs if I have at least one item
scheduled for deletion in this helper array.
So if it has a length of zero this code doesn't run
but if there is at least one item in it, this code runs.
And then here I use the filter method on the cart items,
which has a number built in array method in JavaScript,
just like map and find
and just like map and find filter takes a function.
For example, this anonymous function as a parameter value,
and this function will be executed automatically
by Javascript for every item in that array
on which you call filter,
you will then get that item automatically passed
into this function
once the function is executed by JavaScript,
and then in this function, you have to return true
if you want to keep an item,
if it should not be filtered out
or false if you want to drop an item.
Filter will then return a new array
and I set this new array as my new items array.
And since I dropped all the items
for which I returned false, this will then be a new array
with less items than before, at least if I found items
that have to be deleted.
Now, what's my logic for determining
whether items should be dropped or not?
I have a look at this deletableCartItemProductIds array,
which contains at least one item now, as we know,
and the indexof method is another built in method
in JavaScript that you can execute on erase,
which allows you to search for a specific value in an array
and it will then return to index off that value.
And you learned indexes start at zero for erase
and therefore then I look for a given product ID
in this array of productids that should be deleted.
And if I find the index here,
so if I get a value of zero or higher,
then I know that this productid is part of this
deletableCartItemProductIds array,
because I found the index.
Now indexof will return minus one as a result,
if it could not find a value in an array.
And that's why I have this condition
because then filter returns true
if I could not find an item, a productid
in this deletableCartItemProductIds array
and that means this is not a product that was deleted
so the cart items should be kept.
That's why I then return true and on the other hand,
this will return false this condition
and hence the card item will be dropped.
If I do find a productid
in the deletableCartItemProductIds array,
that's the logic here.
Well then, after, since I updated card item data,
or I may be even removed card items.
I recalculate the cart totals by resetting
the cart totalQuantity and price to zero
and then by recalculating it by again,
well, deriving the new totalQuantity
by going through all the items here
and increasing the totalQuantity
by the item.quantities step-by-step.
And I do pretty much the same for the totalPrice,
updating the carts total price with the item.totalPrices.
That's the idea here.
Now that updatePrices method should therefore
help us keep the prices in the cart updated
and to execute this, I added a new middleware,
the update-cart-prices middleware
in the middleware's function,
where I simply get my cart from res.locals.cart.
I place it there in my other cart middleware
where I get it from the session.
So here I get it from res.locals.cart.
I call updatePrices and I awaited this
because it's a async function.
Hence this is also async.
And then I call next thereafter
and then I used this middleware in app.js.
Here I imported right where I also import
all the other middleware functions
and then use it right after the cart middleware
here I use the updateCartPrices middleware,
and that means for every request I update cart prices.
You don't have to do it like this.
You could also decide that you just want to do that
whenever the cart is loaded or an order is placed,
and you can definitely also play around with
the logic for updating the prices.
I just use a relatively simple straight forward approach
so that this is hopefully quite understandable.
Now with all of that we can of course test
wherever that works.
For this, I'll keep that window open
and I opened a brand new incognito window
where I'm therefore not logged in.
And I will now log in
with my administration account, like this.
Now keep in mind, and the other account
I have to keyboard in the cart
and the current price is $99.99.
In my administration account I'll now manage
this keyboard and change the price to $109.99,
and then saved this.
So now here, the price was saved.
Of course, here, I still see the old price
because we have no a feature that would reload the page
to user is currently on.
But if I navigate around and come back to the cart,
indeed, I now have the latest price here
and the total price also was updated, as you can tell.
So that worked.
Now what if I delete the keyboard altogether?
If I do that here in the admin page
and I then reload the cart, my cart is now empty
because that item was deleted.
So this is now working.
This should do the trick here,
and this should ensure that we always see
the latest data in our cart and also in the end
use the latest cart when we then place an order.