4.24.2012

node.js fragments - part 1


This are gonna be some fragments of common code of node.js, some were used for nomination, hopefully you will find them useful and comeback for reference

Version en español

The always needed first program

Loading modules

First part on the file is loading a module, the modules are loaded with require, to understand more about require check this post, plus read the documentation about it

Besides just loading them, you can pass them parameters like

require('./routes/index')(app, log);

In this case we are passing some vars to the require module

Console

Also in the first program, we can see the console.log to print out information in the console, this is useful at debugging time or to show information to the user, check the documentation for more information about using the console, there are also different levels to show the information besides there are a lot of modules that extend the functionality, some print out in colors, prettify the information, etc.

Create a server

Besides creating a serve with the http module and tell the port and the ip to listen, we can create servers with other modules, one of the most common is express, to create it with express its simple:

var app = module.exports = express.createServer();


And then we can tell it in which port to listen

app.listen(3000);


You can pass the ip also, by default it takes the '0.0.0.0' which means everywhere

There are a lot of other modules to create servers, do a quick search in github or npm to find the one that suit you better, most of them follows the same pattern

Read and write files

For this we have a nice module called "fs" or filesystem, this module have everything needed for read, write, etc in files, links or directories

FS have the option to do the operations in async or sync way, as a good practice try to always do it async

Read

fs.readFile('data.txt', function (err, data) {
  if (err)
    throw err;
  if (data)
    console.log(data.toString('utf8'));
});

fs.readdir('.', function (err, files) {
 if (err)
    throw err;
 for (var index in files) {
    console.log(files[index]);
 }
});


Write

fs.writeFile('data.txt', 'Hello, World!', function (err) {
     if (err)
       throw err;
});


Check attributes

fs.stat('data.txt', function (err, stats) {
  if (err)
     throw err;
  if (stats.isFile()) {
      console.log('It\'s a file!');
  }
  if (stats.isDirectory()) {
    console.log('It\'s a directory!');
  }
}


For more information check the documentation

Also you can use async.js to do it even more easy the async manipulation of files

Logging

In case you want to log information to a file for future references, you can use some modules for this, in the case of nomination we use "log.js" which is quite simple to use:

Log = require('log'),
log = new Log(),
fs = require('fs');


First we have to load the module and create an object which is the one that we are going to use in the app to log information, then we have to load fs, because we need to create the file were we are going to write

log.level = Log.DEBUG;
log.stream = fs.createWriteStream('logs/dev.log', { flags: 'w' });
log.notice('logging today:' + new Date());


After we tell the level of logs the file will have, this should be changed depending on the configuration that we are going to use, we may want to have less level of log traces for production for example

After create the file lets add it to the stream and we just need to start logging information with different levels

Check the documentation for log.js, and also there are alot of modules to do logging, from manual stuff like the one we use here to others that are helpers or middleware for some frameworks like express/connect, etc, check more modules

Callbacks

Since we use alot of async in our node.js programs we end up using a lot of callbacks, callbacks are nothing more than where our function will return after doing its process, the convention for calling the callback its always return an error even if there isn't any and then the data

E.g.

function hello (user, callback) {
    if (!user){
        callback(new Error('no user :('));
        return;
    }
    var msg = 'hello' + user;
    callback(null, msg);
}


And from where we call it

hello('newuser', function (error, msg) {
    if (error) { console.log(error.msg); return; }
    console.log(msg);
});


In this case you always check if there happened any error or not and its more easy to control the flow

Errors

We recommend to use the "new Error()" to send your errors instead of only one string, because new Error have information about the error, where did it happened, etc, this information its very important to debug the problems and have a better idea of what its going on in the code, with the string only we could lose some of that information, check the callback information above for more information about sending errors.

Also you can see more information about this in this post

Also is recommended to use an error listener, to catch even more errors that we may be missing as described here

var events = require('events')
emitter = new events.EventEmitter()

function y(arg, callback) {
    if (arg === 1) {
    x()
 callback()
    } else {
 function onTick() {
     try {
  x()
     } catch(err) {
  emitter.emit('myerror', err)
  return
     }
     callback()
 }

 process.nextTick(onTick)
    }
}


Well nice first fragments, i will continue to update with more posts about this in node.js

If you are looking for some special information please ask for it in the comments i will try to post something about it

Greetings

4.13.2012

Nomination - recurrent service



This post its a part of a series about creating an app with node.js and express for Facebook... this time we will create a service

See how we created the app

Desktop version:


mobile browsers


This time we will create a service that runs daily at some time to end the old nominations that are older than the actual date, for that we will need to add a new module, lets update our "package.json"

, "node-schedule" : "0.1.5"

And we run our command "npm install -d" to install the new dependency

Now lets first update our nominator.js controller to find old nominations

/**
/**
 * find old nominations
 * @callback function
 * 
*/
NOMINATOR.findOldNomination = function(callback) {
    Nomination.find({"endDate": {"$lt": new Date()}}, callback);
};

We are looking for nominations where the enddate is lower tan the actual date, then we send the results to the callback

To put this to work, lets update our file server.js, first our needed variables

schedule = require('node-schedule'),
fb = require('facebook-js'),
url = 'http://nomination.cloudno.de/',
nominator = require('./controllers/nominator.js');

Firs its our new module, then load our facebook module, the nomination url to add it to wall messages and our nominator controller

Then lets start the scheduler

//add process to kill old nomination
var rule = new schedule.RecurrenceRule();
rule.dayOfWeek = [0, new schedule.Range(1, 6)];
rule.hour = 1;
rule.minute = 1;

We are telling the scheduler to run daily at 1 with 1 minutes and then we have to tell which function to do at that time

schedule.scheduleJob(rule, function(){
    nominator.findOldNomination(function(err, doc){        
        for (var i=0; i<doc.length;i++){
            endNomination(doc._id, doc);
        }
    });
});

The function just look for old nominations and those are we going to end them with endNomination funciton  

function endNomination(id, doc){
    if (doc.ownerdata){
        var users = doc.users;
        var usersl = doc.users.length;
        var voters = doc.voters;
        var votersl = doc.voters.length;
        if (usersl > 0){
            var winner = users[0];
            for (var j=1; j<usersl;j++){
                if (winner.votes < users[j].votes){
                    winner = users[j];
                }
            }
            var onerror = function (error) {
                            if (error) { log.debug('error posting on voted user'); return; }
                        };
            fb.apiCall(
                'POST',
                '/'+doc.owner+'/feed',
                {
                    access_token: doc.ownerdata,
                    message: app._locals.t('dashboard.won', { wname: winner.name, nname: doc.name }),
                    name: app._locals.t('dashboard.create'),
                    link: url
                },
                onerror
            );
            for (var i=0;i<usersl;i++){
                if (users[i]._id == doc.owner){ continue; }
                fb.apiCall(
                    'POST',
                    '/'+users[i]._id+'/feed',
                    {
                        access_token: doc.ownerdata,
                        message: app._locals.t('dashboard.won', { wname: winner.name, nname: doc.name }),
                        name: app._locals.t('dashboard.create'),
                        link: url
                    },
                    onerror
                );
            }
            for (i=0;i<votersl;i++){
                if (voters[i]._id == doc.owner){ continue; }
                fb.apiCall(
                    'POST',
                    '/'+voters[i]._id+'/feed',
                    {
                        access_token: doc.ownerdata,
                        message: app._locals.t('dashboard.won', { wname: winner.name, nname: doc.name }),
                        name: app._locals.t('dashboard.create'),
                        link: url
                    },
                    onerror
                );
            }
        }
    }
    nominator.eraseNomination(id, function(err){
        if (err) { log.debug('error erasing nomination'); return; }
        log.notice('nomination '+ id +' erased by: cron ' );
    });
}

We decide the winner, make the facebook api call to update the walls of involved users, it may not reach all the walls but that its by security issues of facebook not really with our method

And we are done, check more option to schedule tasks in the github page of the module

Fork the code


Greetings

4.11.2012

Nomination mobile - part 5




This post its a part of a series about creating an app with node.js and express for Facebook... this time for mobile browsers



Desktop version:


*NOTE: jqm = jquery mobile

Last post for this mobile series

Firs the erase part, lets update the "mscript.js" with this

$('#remove').click(function(ev){
    $.mobile.showPageLoadingMsg();
    ev.preventDefault();
    var uid = $(this).attr('uid');
    var details = $('#details');
    var nid = details.find('#attd').attr('nid');
    $.post("/nominations/eraseuser", { id: nid, user: 'eraseme' },
        function(data) {
            if (data){                    
                //get the row of the user and erase it
                $('.users').find('#'+uid).remove();
                var usersl = details.find('.users');
                usersl.listview('refresh');
            }else{
                showMsg('dashboard.error', 'dashboard.error_removing');
            }
            $.mobile.hidePageLoadingMsg();
        }
    ).error(function() { 
        $.mobile.hidePageLoadingMsg(); 
        showMsg('dashboard.error', 'dashboard.error_removing'); 
    }); 
});

And also the page to store the user id in the button:

a#remove(href="#", data-icon="minus", uid="#{user.id}") #{t('dashboard.remove_me')}

Simple, we just get the id of the user, we ge the details page and we search for the id of the nomination, send the data to the server and  if we don't have errors erase that row

God, now to refresh, lets do the following, first lets add the class "refresh" to all refresh buttons:

a.refresh(href="#", data-role="button", data-theme="b", data-icon="refresh") #{t('dashboard.refresh')}

And then add the functionality in the script

//refresh the list of nominations
$('.refresh').click(function (ev) {
    $.mobile.showPageLoadingMsg();
    ev.preventDefault();
    //get the actual page id
    var pageid = $.mobile.activePage.attr('id').split('-')[1];
    //erase the actual list
    var divider = $('#'+pageid).find('li')[0];
    $('#'+pageid).find('li').remove();
    $('#'+pageid).listview('refresh');
    //add the divider
    $('#'+pageid).append(divider);
    //reload the nominations
    loadNominations(pageid);
});

Get the id of the actual page, and then get the type on nomination the user is looking, then get the divider, erase the actual list, add again the divider, refresh and reload the nominations of that type

Good, easy, now lets update the routes to handle the request from small devices, lets update the file "routes/dashboard.js"

bt = new bowser(req);
if (bt.isMobile() || bt.isTablet()){
    res.redirect('/dashboardm');
}else{
    res.render('dashboard', 
        { 
            user: req.session.user, 
            error : req.param('error'), 
            type: 'dashboard', 
            invited: invited                
        });
}

We are checking for tablet or mobile and if so lets redirect them to the mobile version, with this any user can see the mobile version by updating the url

We are just missing to update the strings in the jade files or js to i18 strings, lets give that task as a homework for all, any doubt on doing that let me know

Thats it for the mobile verions, we have a nice version now quite interesting, lets announce it everywhere now, if you can please help me with some bugs or new stuff for the application, i may be missing a nice feature.

Next time we will try to do a job to run it daily and close the nominations already passed and send notifications in facebook to the users.

After that we will move this to phonegap to make it native for different platforms :)

Fork the code


Greetings