Monday, April 18, 2011

Express Pagination Middleware Simplicity

Inspired by watching TJ Hollawaychuk's supremely awesome screencast about using route-specific middleware at http://expressjs.com/screencasts.html, I threw together a simple use case for myself: pagination. While pagination might not be an obvious choice for middleware, it works here because it doesn't need to know anything about the route that's using it, though in this case it may take advantage of some passed in variables if they exist!

// articles per page
var limit = 10;
// pagination middleware function sets some
// local view variables that any view can use
function pagination(req, res, next) {
var page = parseInt(req.params.page) || 1,
num = page * limit;
db.articles.count(function(err, total) {
res.local("total", total);
res.local("pages", Math.ceil(total / limit));
res.local("page", page);
if (num < total) res.local("prev", true);
if (num > limit) res.local("next", true);
next();
});
}
// a few sample routes using the pagination middleware
// both of them use the same view, which is enhanced
// by the local variables setup in the pagination function
app.get('/', pagination, function(req, res) {
db.logs.update({}, {$inc:{count:1}});
db.articles.find({}, {'snippet':1,'images':1,'date':1,'title':1}, {limit:limit, sort: [['date','desc']]}, function(err, cursor) {
cursor.toArray(function(err, articles) {
res.render('home.html', {
articles: articles
});
});
});
});
// this route helps us view paginated pages,
// but thanks to the middleware, doesn't have
// to do anything fancy before rendering its view
app.get('/pages/:page', pagination, function(req, res) {
var page = req.params.page || 1
db.articles.find({}, {'snippet':1,'images':1,'date':1,'title':1}, {skip: (page - 1) * limit, limit: limit, sort: [['date','desc']]}, function(err, cursor) {
cursor.toArray(function(err, articles) {
res.render('home.html', {
articles: articles
});
});
});
});


Here is a partial using EJS syntax that will display some previous / next arrows wherever you choose to place them. Notice I'm using locals.prev and locals.next here, which works, becuse I'm not sure those variables will even exist. In these cases you access them as properties off of the locals object in the view, which means you don't have to do this if (typeof prev !== "undefined" && prev), which is a little more verbose if you ask me:

<nav class="pagination">
<% if (locals.prev) { %>
<a href="/pages/<%= (page + 1) %>">Previous</a>
<% } %>
<% if (locals.next) { %>
<a href="/pages/<%= (page - 1) %>">Next</a>
<% } %>
</nav>
view raw pagination.html hosted with ❤ by GitHub


I liked how easy the middleware approach was over having specific pagination logic in each route that used pagination. Express is fantastic for letting me send in multiple functions that I can chain together by calling next(). In this example, without using the middleware I would have had to wrap each paginated function in the count() database call, which would give me an un-neccessarily long callback chain. I prefer the simple middleware approach allowed by express.

Thursday, April 14, 2011

JS Codings Standards

Here are some JavaScript coding standards/conventions I've been thinking about lately. I put this together pretty quickly, so it may contain some errors. What you think about these? Are there other conventions you support? Please post your feedback and/or corrections and suggestions!

////////////
// jQuery //
////////////
// 1. Name jQuery objects so that we know what they are
var $container = $("div");
///////////////
// Normal JS //
///////////////
// 1. 1 var per function
// 2. function not too long
// 3. define the length once, it's optimized
// 4. initalize vars with relevant type?
// 5. for loop over foreach. it's faster and better supported. don't care on server-side
// 6. Var stuff is all indented
// 7. Function declarations better than function expressions because of hoisting
// 8. Variables that don't get initialized (just declared) usually hang out at the bottom of the var call.
// 9. Semi-colons make it easier for me to read the flow
// 10. So does consistent tabbing
function bla(someArray) {
var hi = "",
k = [],
len = someArray.length;
i = 0,
k;
for (i = 0; i < len; i++) {
doSomethingWithI(i);
}
}
function doSomethingWithI(i) {
var x = 2;
return x * i;
}
/////////////////
// Server-side //
/////////////////
// 1. Take advantage of native-stuff wherever possible
// 2. console.log is easier than most things and takes multiple args
// 3. Don't leave in extra requires statements
Date.now(); // faster than new Date().getTime()
Array.isArray(); // better than underscore
Object.keys(); // returns the keys, yep
var arr = [1,2,3];
arr.forEach(function(val) {
console.log("Val", val);
});
////////////////
// Good Links //
////////////////
// 1. http://kangax.github.com/es5-compat-table/
// 2. https://developer.mozilla.org/en/JavaScript/Reference/
view raw standards.js hosted with ❤ by GitHub

Monday, April 11, 2011

MongoHQ and Node

THIS IS OUT OF DATE CHECK OUT THIS NEW ARTICLE INSTEAD

MongoHQ

Free MongoDB hosting for playing around with Mongo. Free for 16mb of storage. $5/month for 256mb. It's good for small stuff and getting started with Mongo.

Difficulties

One of the problems I found while connecting to MongoHQ from node was the authentication. Most of the MongoDB libraries for node are very simple to use, but cut some of the features from the mongodb native library like authentication (or at least make it difficult to access), so I ended up rolling my own wrapper to make the authentication step as easy as possible.

Custom Wrapper

I wrote a simple wrapper to the Node MongoDB native driver that made it easier to hook into MongoHQ, which requires authentication. It may be useful to other people as well:
////////////////////////////////////////
// Simple MongoDB Wrapper //
// Written to use with MongoHQ.com //
// By Jamund Ferguson //
// April 9, 2011
////////////////////////////////////////
var mongodb = require('mongodb'),
db;
// inititalize the db
exports.init = function(options) {
db = new mongodb.Db(options.name, new mongodb.Server(options.host, options.port, {auto_reconnect:true}), {});
db.open(function(err, p_client) {
if (typeof options.user !== 'string' || typeof options.pass !== 'string') return;
db.authenticate(options.user, options.pass, function(err) {
if (err) console.log(err);
});
});
return this;
}
// register a collection for use
exports.collection = function(collection) {
db.collection(collection, function(err, col) {
exports[collection] = col;
});
}
// convenient access to make ObjectIDs
exports.ObjectID = mongodb.BSONPure.ObjectID;
// convenient access to the native driver
exports.db = db;
view raw db.js hosted with ❤ by GitHub


Connecting the pieces

To get this custom driver up an running it's very simple.
  • Initialize it.
  • Register your collections.
  • User it

Docs and Help

Locals in ExpressJS

Using Local Variables in Express

ExpressJS allows you to define "locals" which are variables available to the view in multiple places and in multiple ways. Here are a few examples.

// using the res.local syntax
app.all('*', function(req, res, next) {
db.logs.findOne(function(err, logs) {
if (err) return console.log("Error: ", err);
res.local('count', logs.count);
res.local('edit', false);
next();
});
});
// alternatively the res.locals syntax
app.all('*', function(req, res, next) {
db.logs.findOne(function(err, logs) {
if (err) return console.log("Error: ", err);
res.locals({
'count': logs.count,
'edit': false
});
next();
});
});
// finally, as the second argument to a render call
app.get('/reviews/add', function(req, res) {
res.render('view.html', {
review: {
rating: 0,
title: "This is the place.",
article: "This is your review.",
images: [],
slug: "your-url"
}
});
});
// in the first two cases you can confirm which variables
// have been set simply by inspect the _locals variable
console.log("Locals: ", res._locals);

Incrementing a Count Variable With Mongo

The main line that you need to look for is the following one:
db.logs.update({}, {$inc:{count:1}});

~ $ mongo -u USERNAME -p PASSWORD Flame.mongohq.com:27094/wereviewutah
MongoDB shell version: 1.6.5
connecting to: Flame.mongohq.com:27094/wereviewutah
> db.logs.find()
{ "_id" : ObjectId("4da0d8a5a130714ae1000023"), "count" : 1 }
> db.logs.findOne()
{ "_id" : ObjectId("4da0d8a5a130714ae1000023"), "count" : 1 }
> db.logs.update({}, {$inc:{count:1}})
> db.logs.findOne()
{ "_id" : ObjectId("4da0d8a5a130714ae1000023"), "count" : 2 }