In our previous post, we looked at how to setup a Node.js development environment on a Mac. In this post, we’ll write some minimal code to get a functional web-site going, and get ready to style it up and push it out to someplace useful.
index.js
It seems many Node.js projects have a main file named “index.js”, so I chose that convention for mine as well. My index.js file is below. Read through the code first. We’ll discuss it in detail below.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var app = require("express").createServer(); | |
var io = require("socket.io").listen(app); | |
var tp = require("./targetprocess"); | |
app.listen(8080); | |
//routing | |
app.get("/", function(req, res) { | |
res.sendfile(__dirname + "/index.html"); | |
}); | |
app.get("/index.css", function(req, res) { | |
res.sendfile(__dirname + "/index.css"); | |
}); | |
//users currently connected | |
var _usernames = {}; | |
//Global TP options | |
tp.api({ | |
username: "myusername", | |
password: "mypassword", | |
url: "https://mycompanyname.tpondemand.com/api/v1/" | |
}); | |
io.sockets.on("connection", function(socket){ | |
//get completed user stories and bugs for the current iteraion | |
tp.api("getEntitiesForActiveIteration", function(entities) { | |
//console.log("returned from TP call"); | |
socket.emit("entitiesretrieved", entities); | |
}); | |
//when the client emits "adduser", this listens and executes | |
socket.on("adduser", function(username) { | |
//store username in the socket session for this client, and in global list | |
socket.username = username; | |
_usernames[username] = username; | |
//tell the client that he or she has been connected | |
socket.emit("updateaudit", "SERVER", "you have connected"); | |
//tell everyone else that he or she has been connected | |
socket.broadcast.emit("updateaudit", "SERVER", username + " has connected"); | |
//update the list of users on the client side | |
io.sockets.emit("updateusers", _usernames); | |
}); | |
//user changes active item | |
socket.on("changeactiveitem", function(activeItemId, activeItemName) { | |
socket.emit("updateaudit", "SERVER", "you changed the active item to '" + activeItemName + "'"); | |
socket.broadcast.emit("updateaudit", "SERVER", socket.username + " changed the active item to '" + activeItemName + "'"); | |
io.sockets.emit("activeitemchanged", activeItemId); | |
}); | |
//handle client disconnects | |
socket.on("disconnect", function() { | |
//remove the username from the global list | |
delete _usernames[socket.username]; | |
//update the list of users on the client side | |
io.sockets.emit("updateusers", _usernames); | |
//tell everyone that the user left | |
socket.broadcast.emit("updateaudit", "SERVER", socket.username + " has disconnected."); | |
}); | |
}); |
The first things to notice are the “var xxx = requre(“something”);” lines at the top. Node.js uses something called a module loader to load in modules (which in Node.js are just other *.js files). It uses this mechanism to decide what depends on what, in a fashion (to me, at least) that seems reminiscent of the AMD API in RequireJS or even the IoC pattern in type-safe languages.
For us, we require express and socket.io, which we talked about in the previous post. The third requirement, something called “targetprocess”, is just another js file that I wrote that encapsulates access to the Target Process REST API.
Second is a line that tells the express application variable to listen on port 8080. This is a good practice for dev macines where something else, such as a web server, might normally be listening on the usual port 80. After that are a couple of routing calls. These calls tell the routing functionality in express to route all requests for the root web site to a page called “index.html”, and all requests to the root index.css file to a similarly named file in the directory requested.
Next is a variable to hold the names of all users currently connected to the system, and a call to the targetprocess.js component to set the username/password combination for the Target Process (it uses basic authentication) and the URL to its API.
Then come the Socket.IO calls. Socket.IO contains libraries for both server and client-side functionality. Without going through every line in detail, here is a brief summary of the methods that I use…
- socket.emit – sends a “message” (I would think of it as creating an “event” to which the client html page can respond) to the client represented by “socket”
- io.sockets.emit – sends a message to all clients connected to the application (including the one represented by “socket”)
- socket.broadcast.emit – exactly like io.sockets.emit, except that the client represented by “socket” does not receive the message. In other words, this sends a message to everyone except “yourself”.
Documentation is pretty limited on the main Socket.IO site, but check out the links provided in the answer here for more information.
Here in index.js, we are handling various messages. Again, without going line-by-line, below is a list of each with a rough description of purpose…
- connection – This is a built-in message that triggers whenever a client connects. All further code goes in the handler for this message, and the “socket” argument is used to send messages back to that client.
- adduser – Triggers when a client sends its username to the server, shortly after connecting
- updateaudit – A message sent from the server to the client whenever the server has some interesting information that should be displayed in an “Audit” or “Recent Activity” area of the client web page
- updateusers – A message sent from the server to the client when the user list has been modified
- changeactiveitem – Triggers when a client clicks on a particular item (user story or bug) on the web page to indicate that a new item is currently being demonstrated.
- activeitemchanged – A message sent from the server to all clients to indicate that the currently active item has changed
- disconnect – Another built-in message that triggers when the client represented by “socket” disconnects
index.html
Perhaps the next most interesting page is the client page, index.html. This page contains client-side JavaScript to handle the messages being passed by the server, and the HTML to render to the browser. Let’s look at the code…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script src="/socket.io/socket.io.js"></script> | |
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> | |
<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.3.3/underscore-min.js"></script> | |
<link rel="stylesheet" type="text/css" href="index.css" /> | |
<script> | |
var socket = io.connect("http://myservername:8080"); | |
// on connection to server, ask for username | |
socket.on("connect", function() { | |
socket.emit("adduser", prompt("Enter username:")); | |
}); | |
// update the audit log when appropriate | |
socket.on("updateaudit", function(username, data){ | |
var auditEntry = "<li>[<%= timeStamp %>]<b><%= name %>:</b><%= info %></li>"; | |
$("#audit > ul").prepend(_.template(auditEntry, {timeStamp: (new Date()).toString(), name: username, info: data})); | |
}); | |
// update users list when needed | |
socket.on("updateusers", function(data){ | |
var userList = "<% _.each(usernames, function(username) { %> <li><%= username %></li> <% }); %>"; | |
$("#users > ul").html(_.template(userList, {usernames: data})); | |
}); | |
socket.on("entitiesretrieved", function(queryResults) { | |
var entitiesList = "<% _.each(items, function(item) { %> <li id='<%= item.Id %>'><div class='<%= item.EntityType.Name %>-icon'></div><%= item.Name %></li> <% }); %>"; | |
var html = _.template(entitiesList, { items : queryResults.Items }); | |
$("#items > ul").html(html); | |
$("#items > ul > li").on("click", function(event) { | |
socket.emit("changeactiveitem", this.id, this.innerText); //highlight the currently selected item when clicked | |
}); | |
}); | |
socket.on("activeitemchanged", function(itemId) { | |
$("#items > ul > li.highlight").removeClass("highlight"); | |
$("#items > ul > li[id='" + itemId + "']").addClass("highlight"); | |
}); | |
</script> | |
<header> | |
<h1>you-я-here</h1> | |
<h2>Your Friendly Agile Demo Helper<h2> | |
</header> | |
<section id="users"> | |
<h3>Users</h3> | |
<ul></ul> | |
</section> | |
<section id="items"> | |
<h3>Items</h3> | |
<ul></ul> | |
</section> | |
<section id="audit"> | |
<h3>History</h3> | |
<ul></ul> | |
</section> |
You’ll notice several external script references at the top of the page. The first is Socket.IO, required to handle the client-side messaging functionality. After that are jQuery and Underscore, which I am currently only using for its JavaScript templating functionality. Both are loaded from content delivery networks to try and minimize the number of files I have to host, and to speed up delivery of the libraries.
Next we see the initial connection being made to the root of the web site, followed by a bunch of message handlers. Here is a rough list of descriptions of each…
- connect – Built-in message for initial connection. The handler here sends back the “adduser” message along with the user’s name
- updateaudit – Listens for audit messages from the server, and lists them out on the web page, in reverse chronological order
- updateusers – Listens for messages from the server indicating that the user list has changed, and re-renders it to the web page
- entitiesretrieved – Listens for the message from the server with teh same name, and renders the list to the web page. Also wires up a handler that will send a message to the server to change the active item in the list whenever the user clicks on one in the web page.
- activeitemchanged – Responds to the message from the server by adding a highlight to the currently active item, and removing the highlight from all other items.
Oh, the Humanity!: An “Issue” With Underscore
One thing to note here is that as I was working with Underscore, I kept getting an error related to an equals sign being invalid. After much fighting, it turns out that whenever you reference a variable or object property inside your template, you ABSOLUTELY MUST put a space between the equals sign and the variable or object property.
i.e. “<%= name %>” works, “<% =name %>” does not
targetprocess.js
The targetprocess.js file, as discussed above, is simply a wrapper for calling into the Target Process REST API. Let’s look at the code…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var rest = require("restler"); //https://github.com/danwrong/restler | |
//var moment = require("moment"); //http://momentjs.com | |
var self = this; | |
var methods = { | |
globalOptions : {}, | |
init : function(options) { | |
self.globalOptions = options; | |
self.globalOptions.format = self.globalOptions.format || "json"; | |
console.log("init"); | |
}, | |
getEntitiesForActiveIteration : function(callback, options) { | |
//todo: extend globalOptions with options to create localOptions variable | |
options = options || {}; | |
var getOptions = { | |
username: options.username || self.globalOptions.username, | |
password: options.password || self.globalOptions.password, | |
parser: rest.parsers.json | |
}; | |
var url = []; | |
url.push(options.url || self.globalOptions.url); | |
url.push("Assignables?format="); | |
url.push(options.format || self.globalOptions.format); | |
url.push("&where="); | |
url.push(encodeURIComponent("(EntityType.Name in ('UserStory','Bug'))")); | |
url.push(encodeURIComponent(" and (Iteration.EndDate eq '")); | |
//url.push(moment().format("YYYY-MM-DD")); //todo: use moment.js to make this work for any day (finished in current iteration) | |
url.push("2012-06-13"); | |
url.push(encodeURIComponent("')")); | |
url.push(encodeURIComponent(" and (EntityState.Name in ('To Verify','Done','Fixed','Closed'))&take=1&include=[Id,Name,EntityType]")); //todo: make this end states configurable | |
url = url.join(""); | |
console.log("url is: " + url); | |
rest.get(url, getOptions).on("complete", function(result) { | |
if (result instanceof Error) { | |
console.log("ERROR: " + result.message); | |
} else { | |
callback(result); | |
} | |
}); | |
} | |
}; | |
api = function(methodName, callback, options) { | |
console.log("Calling " + methodName); | |
if (methods[methodName]) { | |
return methods[methodName].apply(this, Array.prototype.splice.call(arguments, 1)); | |
} else if (typeof methodName === "object" || !methodName) { | |
return methods.init.apply(this, arguments); | |
} else { | |
throw new Error("Method " + method + " does not exist in tp.api"); | |
} | |
} | |
exports.api = api; |
The module requires the restler module which, as discussed in previous blog posts, provides a simple way to interact with REST-based APIs, not unlike the httparty Ruby gem.
In this module, I used a convention often used in jQuery plugins. The module has but one method, called “api”. If the user uses this method in the “normal” way, it takes a string argument indicating the name of the action to perform, followed by a JavaScript callback function that will be called when the requested information is returned from Target Process. If, on the other hand, the user wants to set global options, he or she can call the method with only one object as an argument, and the username, password, and url will be extracted from that object. This convention can be found in the “api” method toward the bottom of the code file.
Other than that, the only action method available is “getEntitiesForActiveIteration”. This method retrieves all UserStory and Bug objects from Target Process that were completed in an iteration ending on a specific date. Right now, this specific date is hard-coded, but I hope to get that calculated or passed in later, which should be easy enough. For more details on how to form these HTTPGet queries to retrieve Target Process objects, see the Target Process REST API.
One final thing to note is the line at the very bottom of the code file. Whenever a Node.js module is written, you must declare which methods, if any, will be exposed publicly to the modules that use your module. In our case, only the “api” method is exposed publicly.
Oh, the Humanity!: An “Issue” With The Target Process REST API
On my first few attempts, I kept getting a message back from Restler saying that it couldn’t parse the JSON coming back from Target Process because the less-than sign (<) was an invalid character. At first, I thought that this was caused by the fact that we sometimes put HTML into the comments or descriptions of our Bug and UserStory objects. But I narrowed the query to only select one item, and the issue persisted. As it turns out, I had a bad query that I hadn’t tested in the browser, and Target Process was giving an error message back in the form of an HTML response. Apparently when errors occur, they appear in the form of HTML-formatted responses, even if you ask for JSON in the request URL.
index.css
Last (and probably least) is index.css. As of now I have only some minimal formatting, just to make sure that the highlight of the active item shows up, and that my lists aren’t blindingly ugly. Code is as follows…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
html | |
{ | |
font-family: 'Segoe UI', Tahoma, Arial, Helvetica, Sans-Serif; | |
margin: 12px; | |
} | |
ul | |
{ | |
list-style-type: none; | |
margin: 0; | |
padding: 0; | |
} | |
.highlight | |
{ | |
background-color: yellow; | |
} |
Testing the App
To test the app, fire up bash (Terminal Window on a Mac), navigate to the path where index.js exists, and type “node index.js”.
Then fire up a couple of instances of your favorite web browser (they need not be on the same computer, just able to see the web site – I used my Windows computer), type your username on each…
…and then start clicking on items in the list.
Clicking on an item in one browser will make that item highlight in every browser that is connected.
Next Time
Next time, we’ll add some styling to improve the look of the application, and (hopefully) get it running in a real website somewhere (like hieroku).