Putting it all together in Node.js - Node.js for .NET Developers (2015)

Node.js for .NET Developers (2015)

Chapter 9. Putting it all together in Node.js

You now have everything you need to create your Sports Survey site. Users can log in to access the survey, which will be populated by Question objects and contain Player objects as the answers. That structure will be displayed on a view, and the answers will be tracked in memory so that you can display results.

Many of these pieces are already in place. You have your login view redirecting to the survey view if the user successfully logs in. You have code to fetch a collection of Player objects. You just need to cache and not display the collection for the moment. You will wind up displaying only a small section of it as the possible answers to choose from for each survey question you ask.

You already have your objects ready:

function Question(sQuestion, oAllAnswers, oCorrectAnswer) {
this.questionText = sQuestion;
this.answersList = oAllAnswers;
this.correctAnswer = oCorrectAnswer;
}
function Player () {
this.protoype = Person;
this.sport = "Football";
this.displayName = function(){
return(this.lastName + ',' + this.firstName);
}
}

Now let’s start putting them together. From looking at the properties of the Question object, it appears as if the answersList would be an array, so let’s pop some players into it and see what happens. Your first question is, “Which of these players was on a baseball team?”

Make sure that one, and exactly one, player from your list is a baseball player. You don’t need to specify the player or the other questions in this case, because the question is very generic. To get this logic going, put some code in a function in your app.js that will load the list of players into cache memory when the application first starts running:

function fetchPlayers() {
try {
var arrPlayers = [];
var config = {
userName: 'fromCache',
password: ' fromCache ',
server: '123.45.67.89',
options:
{
database: "Dev"
}
};
var connection = new Connection(config);
connection.on('connect', function (err) {
var request = new Request("select * from tblPlayers", function (err, rowCount) {
if (err) {
console.log(err);
} else {
console.log(arrPlayers.length);
}
connection.close();
});
request.on('row', function (columns) {
var oPlayer = new Player();
columns.forEach(function (column) {
if (column.value === null) {
console.log('NULL');
} else {
// console.log(column.metadata.colName);
switch (column.metadata.colName) {
case "firstName":
oPlayer.firstName = column.value;
break;
case "lastName":
oPlayer.lastName = column.value;
break;
case "sport":
oPlayer.sport = column.value;
break;
case "id":
oPlayer.id = column.value;
break;
}
}
}); // columns for each
arrPlayers.push(oPlayer);
}); // rows for each

cache.put("PlayerList", arrPlayers);
request.on('done', function (rowCount, more) {
});
connection.execSql(request);
}); // connection on
}
catch (Error) {
console.log(Error);
}
}

Don’t forget to add references to tedious to allow SQL database access. Also, don’t forget to include your Player class at the top of the file.

Now you just need to run your fetch when the app starts:

server.listen(1234, function () {
fetchPlayers();
console.log("ready on 1234");
})

You are displaying the number of players to the console when the function is finished. Note how your console will log that it is ready before you get your results back from the database. That is asynchronous programming as it was intended to run—that the users do not wait to be able to do A, which in this case would be logging in, while background task B (getting data ready for the next page) is completing.

Now that you have your list in memory, you can use its members to populate the answers to your survey questions. In the case of the first question, you want one and exactly one answer to be a baseball player. Because each player has been given a sport property value, you can use that value to insert the exact items you want into your answer set:

var arrAnswers = [];
var arrBaseball = [];
var arrNonBaseball = [];
for (var i = 0; i < cache.get('PlayerList').length; i++) {
var oPlayer = cache.get('PlayerList')[i];
if (oPlayer.sport.toString().toUpperCase() == "BASEBALL") {
if (arrBaseball.length == 0)
arrBaseball.push(oPlayer);
}
else {
if (arrNonBaseball.length < 4)
arrNonBaseball.push(oPlayer);
}
}
for (var i = 0; i < arrBaseball.length; i++) {
arrAnswers.push(arrBaseball[i]);
}
for (var i = 0; i < arrNonBaseball.length; i++) {
arrAnswers.push(arrNonBaseball[i]);
}

And there you have it, an array of answers to your question. You inserted exactly one correct answer among the five choices. But you don’t know or care exactly which players met your criteria either way. In this way, not everyone who takes the survey will see the exact same questions in every single case. Originally, you created only five players in your data, but if your testing led you to do more than that, you will see a variety of players come up in the list as you regenerate it.

The preceding code would probably best go in your survey page. You’ll see what I mean if you include it inside of your get function for the page and then render the page as before using:

res.render('survey', {

players: arrAnswers

});

Granted, in this case, the correct answer for this question is always first in the list, but you can randomize this with some simple logic as you choose. Of course, there are lots of ways to manipulate your array as you like. The arr.pop(); function will remove the last items, whereasarr.shift(oPlayer); will dump the top item.

You already saw how push adds an item to the bottom of the array. To insert a value at the top, you need

arr.unshift(oPlayer);

To search the array for a value, use indexOf or lastIndexOf, depending on whether you want to start the search from the front end of the array or the back end. Keep in mind when you do so that the comparison uses the strictest operator (===), so it produces the following “not found” example for searching the array using numbers versus strings:

var arr = ["1", "2", "3"];
// Search the array of keys
console.log(arr.indexOf(2)); // returns -1

More likely, in our specific case, you will want to use the convenient filter function found in the JavaScript array type, say, to identify all players who are in the sport “Football,” like this:

arrPlayers = arrPlayers.filter(function(player){
return (player.sport.toString().toUpperCase() == "FOOTBALL");
});

Of course, the same can be done to filter down to the correct answer (for example, by name) so that you can both insert that specific player into the array and record that same player as the correctAnswer. For anyone who has done LINQ filtering on object collections in .NET using delegates, the preceding code looks very familiar indeed. In fact, if you replace filter with FindAll and the word function with the word delegate, you have C#/.NET syntax exactly for filtering a List<T>.

You also have the forEach syntax, which is useful for walking through the properties of objects as you might want to for Player objects:

var arrKeys = Object.keys(arrPlayers);
arrKeys.forEach(function(Player) {
var arr = Object.keys(arrPlayers[Player]);
arr.forEach(function(prop) {
var value = arrPlayers [Player][prop];
console.log(Player +': '+ prop +' = '+ value);
});
});

The preceding code is not unique to Node.js and represents only a quick overview of arrays in JavaScript. Arrays are very useful and, when combined with object-oriented programming and caching, they form the backbone of many applications. In Node.js, they are indispensable.

You’ll also notice, both here and in the production code examples, the use of toUpperCase for string comparisons, where it is clearly intended that you want the values to match. For example, “FoOTball” should be a match even if it has minor issues. Obviously, there are places where this practice should be avoided—such as for logins and passwords, where values are intended to really not match unless they really do match in every conceivable way. However, the approach shown tends to be the rule rather than the exception, and years of hair-pulling from chasing data that “matched but didn’t match” leads me to use some discipline in these areas.

So you have your answers filtered to your question content. Let’s put them all together this way using our question “constructor” function as shown next. This function expects a text question, an array of answers, and the correct answer (which in this case we supplied as the first answer in the collection):

var sText = "Which of these players was on a baseball team?";
var oQuestion = new Question(sText, arrAnswers, arrAnswers[0]);

Now you simply have to render the page using the Question object’s data. That requires some minor changes to your render and also to the view itself. Let’s start with this:

res.render('survey', {
Question: oQuestion
});

Then the page itself will need to match the parent/child structure of the Question object to display its data properly. Because you expect to have to track answers provided by users, you’ll supply a form to submit and a post action that uses the same page:

<form action="/survey" method="post">
<%=Question.questionText %>
<ul>
<% for(var i=0; i< Question.answersList.length; i++) { %>
<li><a href='details/<%= Question.answersList[i].id%>/
<%= Question.answersList[i].lastName%>'>
<%= Question.answersList[i].displayName() %>
</a></li>
<% } %>
</ul>
<input type="submit" />
</form>

Now browse to your survey page and you will see the fruits of your labors. Add some Bootstrap styling, and you are well on your way to a commercial Node.js application.

At this point, you have several options for recording user input. The most obvious way to accomplish this is to pull the selected answer from a form post operation, which is what you set up. Identify everything properly in the view, and then use your body-parser to pull the user selection from the list of answers.

Assuming you put the Question object in the cache, by using whatever management and tracking process you decide to use in memory, with this or that combination of custom objects as you like, you have a quick and easy comparison of the user answer and the correctAnswer contained in the cached Question.

Of course, this is all just a demonstration app, and you can take it as far as you like. What matters is that you now possess all the tools needed to make any Node.js application into whatever you desire.

Sockets

There is one final feature of Node.js that can be very useful in developing web applications. It isn’t directly connected to all the work you did earlier, but I wanted to make sure I covered the topic so that you truly have all Node.js capabilities at hand. That feature is sockets.

Sockets allow for real-time, two way communication between client and server. This feature is essential for applications such as chatrooms, gaming with multiple users, and so on. The fastest and easiest way to enable sockets in your Node.js web application is by employing the Socket.ionpm package in the standard way after download:

var socketio = require('socket.io');

At this point, your path will diverge from using Express. Sockets require a completely different server backbone. Note that the npm packages you have connected to your Express app, such as EJS or MySQL, will not be connected to your Socket.io app until you connect them again manually.

To enable your socket server, add this code to your existing app.js file:

var server2 = http.createServer(function (req, res) {
res.writeHead(200, { 'Content-type': 'text/html' });
res.end(fs.readFileSync(__dirname + '/views/chat.htm'));
}).listen(1235, function () {

console.log('Listening at: http://localhost:1235');

});
socketio.listen(server2).on('connection', function (socket) {
socket.on('message', function (msg) {
console.log('Message Received: ', msg);
socket.broadcast.emit('message', msg);
});
});

There are several things to notice here. You are using the http module to create your server instead of the Express module. Within the function that creates the server is a reference to chat.htm, the page you want to render when a socket message comes in. And you are also listening on port 1235 instead of on your Express port at 1234. In effect, you have created an entirely new Node.js application.

Seeing this in action is as simple as constructing your own chat.htm page. Capture the incoming message in the socket.on function into an array of messages, and the just render that array to the page using any technique you choose. You can do it via EJS as you did before or by using any of the other data-rendering engines available to you, such as Angular.js. Again, because the sockets application, in effect, is living in its own world, you have many choices for precisely how you choose to implement any aspect of the application without regard for how you did so earlier or on another port.

Conclusion

You have seen the Node.js callback asynchronous structure, how to use Node.js techniques for routing, how to render content, and how to combine all that with Object-Oriented JavaScript to optimize code management and performance. You’ve captured user input; pulled data from form posts, query strings, and cache; connected successfully to external databases; and accessed information using files and streams. And on top of all of it, you’ve learned how to add images, styling, and even authentication to a range of services.

As you can see, the world of Node.js appears at first to be a very different one for doing web development than the world of .NET. You definitely need a combination of web skills and experience in the realm of web development to master it. However, Node.js has enough common elements that, once you get past the new style, you will find that many of your standard .NET best practices still apply.

So take your tools with you with confidence, and enjoy your brave new world!