Wednesday, 30 May 2018

Practical exercises: the to do list

I think that it’s high time we moved on to some practical exercises. We’ve done the rounds of a large number of basic Node.js features and we’ve learnt how to use the Express micro-framework.
But you only get a true feeling of understanding once you’ve practiced and completed your first real app. This is what I’m going to suggest you do in this practical exercise. :)
We’re going to create a to do list. The visitor will simply be able to add and remove tasks.
So we’ll start with something simple. Then you’ll be able to improve it in any way you want to!
Before going any further, I’m going to show you the page that we’re going to create (next figure).
The beautiful app we’re going to create :)
The beautiful app we’re going to create :)
  • We can add elements to the to do list via the form.
  • We can delete items by clicking on the crosses in the list.
  • The list is stored in the visitor’s session. If someone else connects to the site, they will have their own list.
The instructions are simple and the final code won’t be very long. However, if it’s your first more complex app using Node.js, you’ll probably fumble around a bit and move forward slowly. Don’t worry, it’s perfectly normal! Keep at it and if you really need to, read the "A bit of help" section before comparing your work with mine. ;)
You’ve got all the information, now it’s over to you!

Need help?

So you need a bit of help? You're lost and don't know where to start? :p
Remember how we want our final application to look like. I showed you a screenshot of it earlier (see next figure).
The app we’re going to create. Still beautiful!
The app we’re going to create. Still beautiful!

Modules and package.json

Let’s take a deep breath and look at things in the right order. What do we need?
  • Express: without the Express framework the job will be very difficult. Now that you know how to use this framework, it would be a shame to leave it out.
  • EJS (or another template system): this will allow us to easily construct the HTML page that displays the to do list and the form.
In theory, these modules are enough. A good first exercise would be to create apackage.jsonfile in our project’s folder:
{
"name": "my-todolist",
"version": "0.1.0",
"dependencies": {
"express": "~4.11.0",
"ejs": "~2.1.4",
"cookie-session": "~1.1.0",
"body-parser": "~1.10.1"
},
"author": "Mateo21 <mateo21@email.com>",
"description": "A very basic to do list manager"
}
Obviously, you can obviously replace the name, the author and the description with whatever you like. ;)
For the Express and EJS version numbers, I based myself on the versions available when this course was written. As Node.js advances very quickly, you will most likely have more recent versions. The tilde ( ~ ) allows for future patches on these modules, but not for new minor or major versions, which guarantees that their API won’t change and that our code will continue to work even with these updates.
Now that you have yourpackage.json, install the dependencies using a simple:
npm install
We’re ready to start working!

Routes

I said earlier that the definition of your routes was important when constructing a web app. If you’re hesitating and don’t know where to start, I would suggest you list the routes of your app. What should it do?
  • List the tasks.
  • Add a task.
  • Delete a task.
In theory, we can associate a route to each of these features:
  • /todo: list of tasks.
  • /todo/add: add a task.
  • /todo/delete/:id: delete task n°id.
So you’re going to write the routes in your app.js file like this:
.get('/todo', function(req, res) {
});
// ...
However, it would be a good idea to take a quick look at the form. Generally, forms send data using the POST method and not the GET method. So adding tasks is going to be done on the /todo/add route but with the POST method. That’s why instead of calling for.get(), we’ll call for.post()for this route:
.post('/todo/add/', function(req, res) {
})

The right way to chain calls to middleware

We know that we need a session system. The documentation on the cookie-session piece of middleware I mentioned to you earlier tells us how to use the sessions
Another thing: we would need to retrieve the data from the form in/todo/add. We’ve learnt how to retrieve the settings from the URL but not from forms. It’s actually really easy. You just need to include the middleware body-parser. You’ll then have access toreq.body.nameOfField.
Therefore, the start of our JavaScript code should look something like this:
var express = require('express');
var session = require('cookie-session'); // Loads the piece of middleware for sessions
var bodyParser = require('body-parser'); // Loads the piece of middleware for managing the settings
var urlencodedParser = bodyParser.urlencoded({ extended: false });
var app = express();
/* Using sessions */
app.use(session({secret: 'todotopsecret'}))
/* Route management below
.... */
Thesecretsetting sent to the session module is compulsory: it allows the session cookies to be secured. Send the value of your choice. Note that you can send other options, such as the lifespan of your session cookie (by default, the session will last as long as the browser is open).
OK, I’ve already said too much, it’s up to you to get coding! :)

Correction

OK, it’s time for correction. Don’t read this section unless you’ve successfully completed the task or all your attempts have failed and you’re desperate. :p
The code isn’t that hard to read in the end. It’s not very long either, but you had to think carefully to find exactly what needed writing.

The answer

This is the JavaScript code that I created in the main app.js file. Remember that this is just my version and that your code doesn’t have to be identical to mine!
var express = require('express');
var session = require('cookie-session'); // Loads the piece of middleware for sessions
var bodyParser = require('body-parser'); // Loads the piece of middleware for managing the settings
var urlencodedParser = bodyParser.urlencoded({ extended: false });
var app = express();
/* Using sessions */
app.use(session({secret: 'todotopsecret'}))
/* If there is no to do list in the session,
we create an empty one in the form of an array before continuing */
.use(function(req, res, next){
if (typeof(req.session.todolist) == 'undefined') {
req.session.todolist = [];
}
next();
})
/* The to do list and the form are displayed */
.get('/todo', function(req, res) {
res.render('todo.ejs', {todolist: req.session.todolist});
})
/* Adding an item to the to do list */
.post('/todo/add/', urlencodedParser, function(req, res) {
if (req.body.newtodo != '') {
req.session.todolist.push(req.body.newtodo);
}
res.redirect('/todo');
})
/* Deletes an item from the to do list */
.get('/todo/delete/:id', function(req, res) {
if (req.params.id != '') {
req.session.todolist.splice(req.params.id, 1);
}
res.redirect('/todo');
})
/* Redirects to the to do list if the page requested is not found */
.use(function(req, res, next){
res.redirect('/todo');
})
.listen(8080);
Nice middleware chaining, isn’t it? :-°
There’s also a template file that goes with it. Thetodo.ejsfile:
<!DOCTYPE html>
<html>
<head>
<title>My todolist</title>
<style>
a {text-decoration: none; color: black;}
</style>
</head>
<body>
<h1>My todolist</h1>
<ul>
<% todolist.forEach(function(todo, index) { %>
<li><a href="/todo/delete/<%= index %>"></a> <%= todo %></li>
<% }); %>
</ul>
<form action="/todo/add/" method="post">
<p>
<label for="newtodo">What should I do?</label>
<input type="text" name="newtodo" id="newtodo" autofocus />
<input type="submit" />
</p>
</form>
</body>
</html>
And with that, the essential package.json that allows the dependencies to be installed with a simplenpm.install:
{
"name": "my-todolist",
"version": "0.1.0",
"dependencies": {
"express": "~4.11.0",
"ejs": "~2.1.4",
"cookie-session": "~1.1.0",
"body-parser": "~1.10.1"
},
"author": "Mateo21 <mateo21@email.com>",
"description": "A very basic to do list manager"
}

The explanations

My code is already fairly well explained, which should allow you to navigate it easily.
You’ll see that I allowed myself to redirect the visitor to the list (/todo) after items were added or deleted, usingres.redirect(‘/todo’).
The task list is stocked in an array. It is also this which causes the slight subtlety of this program: as JavaScript doesn’t like going through arrays that don’t exist, I created a piece of middleware which creates an empty array if the visitor doesn’t have a to do list (because they just started a session, for example). That’s the role of this code:
.use(function(req, res, next){
if (typeof(req.session.todolist) == 'undefined') {
req.session.todolist = [];
}
next();
})
This middleware function receives the request, the response and the next function to run. So I wrote a piece of middleware myself along the lines ofcookie-parser or cookie-session . Its role is very simple: it checks to see if there’s a to do list in the session and, if this isn’t the case, it creates an empty array (which is why we have the[]). This prevents a lot of errors later on.
Why create the middleware? Because it’s the only way able to run the features before any pages load. So that the middleware ‘passes the hot potato to its neighbor’, I must finish by anext()call (for the following function). In this case,next()refers to.get(‘/todo’, function (){}).
This aside, there isn’t really anything special. I add items at the end of the array using.push()and I remove them with.splice(), but that’s basic JavaScript. ;)
What’s so special about the template? Not a lot, other than I browse my to do list with a forEach (again, it’s JavaScript):
<% todolist.forEach(function(todo, index) { %>
<li><a href="/todo/delete/<%= index %>"></a> <%= todo %></li>
<% }); %>

Downloading the project

I’ve given you all the code for the project, but if you must download it all in a .zip file, go to the following link below:
Download the Node.js project: my-todolist.zip.
Don’t forget to do annpm installto install the dependencies before running it!

Taking it further!

My little to do list is very basic. You can add numerous features to it:
  • Modifying task names.
  • Rearranging tasks amongst themselves.
  • Exporting a CSV.
  • Attributing priority and deadlines.
  • Preserving the to do list (storing it in a database or an NoSQL base).
  • Sharing a list amongst several people.
  • Synchronizing the to do list in real time between people without needing to reload the page.
Some of these features are easier to do than others. If you want to create new ones, you’ll have to find and use new modules.
You’ve got enough to keep yourselves amused for a good while now, good luck!

No comments:

Post a Comment