So now the functionality for our ToDo list
version 2 is almost complete.
We're able to access our default list on the home page and we can add new items here
which gets added to our database.
We can delete them from our database and we can also create custom lists
for example one called shopping
right?
So we now have a new list with three default items and we can write something like "Buy milk" and it will get
added to that particular list and all of this gets persisted.
So whatever it is that we decide to save into the home list or into the worklist then they always stay
where they're meant to be associated with that particular list.
So the last thing that we need to do is we need to be able to delete these items in our custom lists
because at the moment when I check this off then it somehow redirects me back to my home page instead
of actually deleting the item that I wanted from that custom list.
You can see it's still there right?
So why is this?
Well, if you take a look inside our list.ejs at the moment the way that we're implementing the delete
functionality is through the use of this form. And this form makes a post request to the /delete
route and it passes over the item id that the user wanted to delete and the form gets triggered when
one of the checkboxes gets clicked.
So here's a problem. In that route
we only look for all of the items but we don't currently check which list the item is from in order
to delete it from the correct list.
So the first thing we need is that when we tap into this route we need two pieces of information: the
ID of the checked item but we also need to know which list that item came from. In this form
there isn't a submit button right? Where we can put in our list title and pass that over when the form
gets submitted.
There's no other input other than our checkbox which already has a value and we definitely need that
value of the item ID.
What can we do here?
Rather handily in the list of inputs that we get from HTML, there's one which has a type of
hidden.
And this allows us to include data that can't be seen or modified by users when a form is submitted.
For example the id of the content or in our case the list name. So inside here just before the form
ends,
I'm going to add a new input and this input is going to have a type of hidden. And it's also going to
have a name so that I can refer to it when I look at my req.body and the name for this is going
to be called just listName.
And finally it's going to have a value
and this is where the neat part comes in.
We're going to use that EJS tag again
and inside here we're going to insert that listTitle that we already have access to.
And now we can close off our inputs and make sure that we add a closing input tag because it's not
a self closing tag,
these inputs.
So now if we hit save and we head back to our app.js in our post to our delete route we can now
check to see what is the value of that listName. So we can create a new constant and we can call it
list name.
And we're going to set it to equal req.body.listName. And just check to make sure that is
the same as it got here.
So yep! Looks good.
So the next thing we need to do is we're going to again use an IF statement to check to see if we are
making a post request to delete an item from the default list where the listName is today or if we're
trying to delete an item from a custom list. We're saying if listName is equal to today then that means
we're on the default list and we can do everything as we did previously.
So we'll just cut and paste that into that if block, but what about the else block?
Well this is the case where the list name is not today
so that delete request is actually coming from a custom list.
In this case we need to be able to find the list document that has the current listName and then we
need to update that list to remove the checked item with that particular ID.
Now because inside our list document there's an array of item documents
then it's actually a little bit more complex because we basically have to find an item inside this array.
We have to basically trawl through this array and find an item with a particular ID and then remove
the entire item from the array.
There's a few ways of doing this.
You can of course use FOR loops and use the Javascript filter method,
but let's see if there's actually a simpler way.
If we head over to Google and we simply just search for "mongoose remove document from array". That seems
pretty much like what we've been trying to do
right?
Let's see what the first non-ad Stack Overflow post says.
So in this case they're saying you want to delete an array element in a document and then save.
So that sounds kind of similar to what we wanted to do.
If we look at the answer, the first answer says that you can do that directly in MongoDB. And you
can use one of the pull or pullAll operators to remove an item from the array.
Now we have a bit of a hint to go on.
So we need to use this thing pull or pullAll.
So let's search the MongoDB docs and we're going to look for this pull operator to find out a bit
more information about it.
So it tells you that the pull operator removes from an existing array all instances of a value or values
that match a specified condition. And they've shown you an example of how you can use this.
So we can use this along with the Mongoose method which is called findOneAndUpdate to combine these
two together in order to achieve what it is that we need because with Mongoose alone or with Javascript
alone we can actually get this really neat pull functionality.
We have to use something that's fundamental to MongoDB. And all the operators that's preceded by a
dollar sign comes from MongoDB. And you can see if you scroll down there's actually a whole bunch of
other operators you can use as well but this one is far more efficient than if we tried to loop through
all of the items in our array, reading each of them and then removing them through the filter method that
Javascript gives us.
This is just a slightly more efficient way of getting to the end result which we found through just
doing a quick Google search.
This is what the mongoose
findOne
AndUpdate syntax looks like. We first specify the model which corresponds to the collection that we want
to find one and update from,
and then we call findOneAndUpdate
and finally we provide three things:
one is the filter conditions,
what do we want to find right?
This is the query.
And then the second one is well, what do you want to update?
And the final thing is a callback where we get the results of what we found based on those conditions.
So in our case the conditions we've already seen before and the callback we've really seen before.
But in this part where we have to provide the updates, how do we actually do it?
Well let's break it down a little bit more.
We want to use the pull operator.
So inside there, we have to specify the $pull operator as a key and then the value has to be the field
that we want to pull from,
so this has to be an array of something.
In our case is an array of items. And then we have to provide a value for that field, which item in tha
array of items.
do we actually want to pull?
So the final syntax looks something like this.
And it's a little bit complex which is why I broke it down.
But essentially we're saying we want to pull from a particular array
and the way that we're going to find the item inside that array is through its ID or through its name
or whatever it is you want to choose.
And then we have to provide the value.
So let's understand this a little bit better by seeing it in action.
Inside our delete route and inside this ELSE block where we've checked that the list that the delete request
came from is not our default list,
we're going to tap into our list model and we're going to call findOneAndUpdate and just make sure
you don't have any typos in there.
And then we have to specify three things right?
The first is the condition,
so which list do you want to find?
And we're only going to get one back,
remember. This is how findOne works.
The second thing is what updates do we want to make?
And the last thing is simply just a callback.
Now in the first part this is quite easy.
The list that we want to find has to have a name that corresponds to this listName constant that we
got from our list.
ejs. Let's put that in there.
This is going to be the name of our custom list. Now that we found that document that corresponds to
the customer list,
how are we going to update it?
Well first we need to use the pull operator, and then we're going to specify something that we want to
pull from and here we have to provide the name of the array inside this list that we found and that
name is of course going to be items.
This is the only thing that's an array inside our list document.
So now we can say pull from the items array.
Now how do we know which item out of all of the items we want to pull?
Well here's another further set of curly braces where we provide the query for matching the item.
The query we're going to make is we're going to pull the item which has an ID that corresponds to the
checkedItemId.
So we're going to put that in there.
And now this three layered cake is finally complete and this is the entirety of our update statement
where we pull from our items array an item that has an ID that corresponds to our checkedItemId.
And finally we're ready to write our callback.
So again it's going to be a callback that gives you an error and a found list because in this case when we're
calling findOneAndUpdate the find one corresponds to finding a list.
Now that we've found the list and we've updated it then if there are no errors we can simply res.
redirect. And we're going to redirect again to that custom list path
so it's going to be "/" + listName.
We're finally ready to save and test our app.
So if we're on the work route, let's just refresh that page, and I decided to delete this item.
Hit this to delete an item, then I click on it it gets deleted I get redirected back to the same list.
Let's delete another one just to be sure.
Again I get redirected to the same list.
So now we're able to create custom lists and we get to add items
and we get to delete items from our custom list as well as adding items to our default list and deleting
them there as well.
Now there's just one last niggling thing which is when we create our route parameters as we saw previously
it actually differentiates between a lower case work and a upper case work.
So in this case I'm trying to access localhost:3000/home with a lowercase "h" and
you can see here I get this list that has a title of lowercase home.
So let's go ahead and add a new item, "Buy sofa" to our home list.
Now if I switch over to /Home, then it's a completely different list right?
And that's crazy because the user probably wanted the same list.
How can we solve this? What if we always wanted the capitalized version of the list in our title?
How can we change whatever inputs that the user makes into that custom listName and change that string
into a title case?
So always capitalize the first letter.
There's again lots of ways of doing this
but the easiest way is just to simply use trusty Lodash.
So let's go ahead and exit out of our server and let's npm install Lodash.
Now while that's doing its thing, let me just show you the documentation for Lodash.
If you scroll all the way down to the strings section you can see that we tried out kebab case, lower
case, lower case first,
but there's also this one called capitalize. And if you read through the docs you'll see that this one
converts the first character of a string to uppercase and the remaining to lowercase.
So that means even if you write all caps FRED or all lowercase fred, whatever it may be, it'll always just
capitalize that string and only change the first letter to uppercase and all the rest to lowercase. We
can add Lodash into our app with the Lodash convention which is a underscore and we're going to let it
equal require "lodash".
So now whenever we need some functionality we can just tap into it using that underscore. And the place
where we need it is inside this route.
So instead of setting the customListName to just simply whatever it is that the user typed in here,
we're going to modify it to make it capitalized. And we can call that method by simply saying _
.capitalize and then inside the parentheses we'll provide a string just as you can see in this example.
Here we're going to say customListName is equal to _.capitalize
and remember because we're working with stuff created by people in America we have to respect the spelling.
So it's a capitalize with a "z" instead of a "s".
So now if we hit save and we go back to our console and we restart our server with nodemon.
Now before we test our app we first have to drop our current collection because if you check out db
.lists.find you can see we currently have the uppercase home, the lowercase home and it's all
a mess.
And this is before we enabled that functionality using Lodash.
So let's go ahead and do db.lists.drop and hit enter.
Now that's been executed.
So if we do db.list.find you can see it's completely empty.
Now let's go ahead and try to navigate to localhost:3000
/work with the lowercase "w".
And remember that Chrome loves to auto suggest. So sometimes you might think you're going to the capital
one but it actually automatically redirected you to the lowercase one.
So just to be sure just click on the one that you see in here.
So here I'm going to /work and now I'm going to add a new item "Write emails" and
hit add. And then I'm going to change this to the capital version of work,
hit enter.
You can see I'm still on the same list. And it's the same thing if we change that to lowercase
home, it'll always redirect me to the list with a capitalized name.
Even if I decide to do something really crazy like "HOMe".
So something that just completely messes up the casing then I still get taken to the right page.
I hope you enjoyed building this along with me and learning how to add a database to our Node.js apps
that will make your apps infinitely more powerful and be able to permanently store your user data. In
the next lesson
it's your turn to add a database to the blog website that you created earlier on and it's finally time
to actually turn it into a real product.
So for all of that and more, I'll see you on the next lesson.