The ipseitycloud Blog
Friday, 23 August, 2013

Una extension en chrome que me llamo mucho la atencion se llama, secure shell Secure Shell

Es un emulador de terminal y cliente de SSH, con el te puedes conectar a tus servidores remotamente desde el servidor, esta aun en beta pero a mi me ah funcionado bien.

Puedes cambiar algunas configuraciones desde la consola, yo lo primero que hice fue actualizarme los colores para q se vean mas como mi console en linux

Habran las web tools de chrome (ctrl + shift + j) or command en lugar de ctrl si tienen mac y pegen este pedazo de js en la terminal

// Disable bold. 
term_.prefs_.set('enable-bold', false) 

// Use this for Solarized Dark 
term_.prefs_.set('background-color', "#000");
term_.prefs_.set('foreground-color', "#1AAD53");

term_.prefs_.set('color-palette-overrides', [
  '#0E8FB0', 
  '#dc322f', 
  '#859900', 
  '#b58900', 
  '#268bd2', 
  '#D66433', 
  '#2aa198', 
  '#eee8d5', 
  '#0086A8', 
  '#cb4b16', 
  '#88AAB5', 
  '#8CACB8', 
  '#A9BFC2', 
  '#6c71c4', 
  '#93a1a1', 
  '#fdf6e3'
]);

Puedes jugar con los codigo de colores, que estan en RGB para ponerlo de la manera que mas te guste

Tuesday, 2 April, 2013

English version

Callbacks en javascript es algo complicado y mas para los que recien empiezan con el mundo de javascript, pero primero lo primero que es un callback?

Callback es la llamada que se hace de regreso cuando un proceso que empezamos anteriormente termina y necesitamos que ese proceso llame o haga algo en especifico. La forma mas comun de hacer esto es con una funcion que regresa algo de manera sincrona, por ejemplo

function prenderPc() {
    //mas procesos
    if (error) {
        return false;
    } else {
        return true;
    }
}

function doSomethingWithPc(isOn) {
    //hacer algo
}

var prendio = prenderPc();
doSomethingWithPc(prendio);
//otras funciones

En este caso, simplemente llamamos la funcion prender pc y esperamos a que la funcion prenderpc termine y regrese si la pc prendio correctamente o no, pero que pasa si tenemos una pc bastante lenta o anticuada, sentar a esperar sin hacer nada mas seria un poco efectivo

Programacion asincrona

La programcion asincrona se refiere a poder llamar una funcion sin tener que esperar a que termine... en javascript es muy comun usar programacion asincrona, para esto hay que mandar funciones a las funciones... hu??, facil in javascript las funciones se comportan un poco diferente a otros lenguajes, si necesitas mas info sobre funciones pregunta en los comentarios o bien revisa la documentacion en MDN, volviendo a nuestro ejemplo

function prenderPc(callback) {
    //mas procesos
    if (error) {
        callback(error, false);
    } else {
        callback(null, true);
    }
}

function doSomethingWithPc(error, isOn) {
    //hacer algo
}

prenderPc(doSomethingWithPc);
//otras funciones

Regularmente uno pasa variables como argumentos a las funciones, lo cual tambien se puede hacer, pero en este caso estamos mandando un funcion como argumento a "prenderPC" y al finalizar la funcion se llama de regreso a la funcion que mandamos con unos parametros "callback()", en este caso estamos usando un patron comun en node.js que es siempre regresar primero el error o null si no paso nada y despues los datos que esta funcion va a mandar a su callback

En este caso no necesariamente tenemos que esperar a que prenderpc termine podemos hacer otra cosa tomar un poco de agua o algo diferente, cuando prenderpc termina mandara a llamar a la funcion que le mandamos siguiendo despues con la funcionalidad

Callback hell

Es muy comun verse enredado en varias funciones que necesitan un callback, veamos nuestro ejemplo

function prenderPc(callback) {
    //mas procesos
    if (error) {
        callback(error, false);
    } else {
        callback(null, true);
    }
}
prenderPc(function (error, isOn) {
    if (!isOn) {
        tomarAgua(function () {
            //tomando agua
            hacerOtraCosa(function () {
                //...
            });
        });
    }
});

Una forma de no pasar por eso es generar funciones y no mandar una funcion anonima o bien usar "promises"

Donde los he visto??!!

El uso mas comun de callbacks en javascript es para las llamadas ajax o los eventos del dom, en nodejs usualmente se usan para cosas que son muy complejas, lentas o usan archivos/red, vamos viendo un ejemplo sencillo de callbacks con jquery

$('element').on('click', function(event) {
    //hacer algo con el elemento(this)/evento(event)
});

En este caso, estamos ligando la funcion al event clik del elemento "element", esto se podria leer como cuando se haga click en "element" corre esta funcion, no necesariamente estas en la pantalla sin hacer nada sino que solamente esperas a que el evento se de para correr la funcion

Otro ejemplo muy comun es ajax

$.ajax({
  type: "POST",
  url: "some.php",
  data: { name: "John", location: "Boston" }
}).done(function( msg ) {
  alert( "Data Saved: " + msg );
});

Aunque jquery ya usa promises principalmente, aun asi podemos tomarlo como ejemplo, aqui lo que estamos haciendo es cargar alguna pagina, y al terminar de cargar los datos manda a llamar la funcion dentro de "done"

Node.js usa callbacks para muchas cosas, por ejemplo al cargar un archivo podemos hacerlo de manera asincrona o sincrona, si lo hacemos sincrona el thread se detendra hasta que la carga del archivo sea completa lo cual no es la mejor forma de hacerlo, pero si lo hacemos de manera asincrona, pueden pasar otras cosas en el thread mientras el archivo terminar de cargarse

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

La funcion toma el path del archivo y un callback, al terminar de leer el archivo llama al callback

Que pasa con this

This en javascript es el contexto en el que se esta corriendo actualmente, en el navegador, this sin estar dentro de una funcion en especifico es "window" que seria como el objeto global, pero cuando estas dentro de un callback, this puede cambiar, por ejemplo cuando escuchas por un evento con jquery, this se convierte en el elemento en el cual estas escuchando el evento

//aqui this es window
var parentwindow = this;
$('element').on('click', function(event) {
    //hacer algo con el elemento(this)/evento(event)
    //aqui this es el elemento
    var element = this;
});

Se puede enviar otro contexto en el callback con apply or call, en nuestro ejemplo inicial, seria algo asi

function prenderPc(callback) {
    //mas procesos
    if (error) {
        callback.apply(othercontext, error, false);
    } else {
        callback.apply(othercontext, null, true);
    }
}

De esta forma el primer parametro sera this en el callback

Monday, 4 June, 2012

Ahora que tenemos nuestra aplicacion funcionando con node.js y jquery mobile vamos a pasarla a su version "nativa" con phonegap/apache cordova

Si quieren ver como se hizo la app pueden ver la parte mobil aqui

Parte 1

Bueno en este post nos dedicaremos a tener todo listo, para empezar ya deben de tener su eclipse ide listo con el sdk de android, bajen eclipse de aqui e installen el sdk de android de aca ya que lo instalen, configurenlo y agreguen almenos un emulador, cualquier duda configurando esto no duden en preguntar

Tambien tenemos que bajar phonegap que es un zip con configuracion para varias plataformas, en nuestro caso nos interesa la de android

Ya con todo esto, abrimos nuestro eclipse y creamos un proyecto nuevo de android la version 4.0.3 del sdk, nombre que gusten, le ponen nombre a su paquete y listo, aceptan para que genere el proyecto, ahora vamos a ir agregando todo lo de phonegap

En el root del proyecto, creamos 2 directorios: /libs /assets/www

copiamos cordova-1.7.0.js del zip que descargamos a /assets/www, luego cordova-1.7.0.jar a /libs y al final copia el folder xml del zip de phonegap al folder /res, todo el folder

Ahora tenemos que agregar el jar a nuestro build path del proyecto, click derecho en el project, build paths -> configure build path... y en el tab de libraries agregamos cordova-1.7.0.jar

Hay que actualizar nuestro archivo java donde esta el main con lo siguiente Agregar import org.apache.cordova.; Cambiar a q la clase extienda de actividad a DroidGap Remplazar el setContentView() con super.loadUrl("file:///android_asset/www/index.html");

el codigo de java debe de quedar algo asi:

package com.ipcloud.nomination;

import org.apache.cordova.DroidGap;
import android.os.Bundle;

public class NominationActivity extends DroidGap {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        super.loadUrl("file:///android_asset/www/index.html");
    }
}

Ahora tenemos que agregar los permisos necesarios a nuestra app, para esto abrimos el archivo AndroidManifest.xml con el editor xml y agregamos los permisos entre los tags y .

<supports-screens 
    android:largeScreens="true" 
    android:normalScreens="true" 
    android:smallScreens="true" 
    android:resizeable="true" 
    android:anyDensity="true" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> 
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />

Y tambien agregamos que orientacion soportamos dentro del tag de .

android:configChanges="orientation|keyboardHidden"

El xml debe de quedar algo asi:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.ipcloud.nomination"
    android:versionCode="1"
    android:versionName="1.0" >

    <supports-screens
android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true"
android:resizeable="true"
android:anyDensity="true"
/>
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />

    <uses-sdk android:minSdkVersion="15" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:label="@string/app_name"
            android:name=".NominationActivity"
            android:configChanges="orientation|keyboardHidden" >
            <intent-filter >
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name="org.apache.cordova.DroidGap" android:label="@string/app_name" android:configChanges="orientation|keyboardHidden"> <intent-filter> </intent-filter> </activity>
    </application>

</manifest>

Creamos un archivo index.html dentro del folder assets/www que contenga nuestro index para moviles:

<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Nomination</title>
<meta name="description" content="Nomination">
<meta name="author" content="mrpix">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="images/favicon.ico">
<link rel="apple-touch-icon" href="images/apple-touch-icon.png">
<!-- CSS-->
<link rel="stylesheet" href="stylesheets/jqm.css" type="text/css">
<link rel="stylesheet" href="stylesheets/index.css" type="text/css">
<!-- js -->
<script src="javascripts/lib/jquery-1.7.1.min.js"></script>
<script src="javascripts/lib/jqm.js"></script>
<!-- cordova -->
<script src="cordova-1.7.0.js"></script>
</head>
<body>
    <div data-role="page" data-theme="d">
        <div data-role="header" data-theme="b">
            <h1>Nomination</h1>
        </div>
        <div data-role="content">
            <table class="att_table">
                <thead>
                    <tr>
                        <th>Nominate your friends in facebook</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td>Have fun</td>
                    </tr>                    
                </tbody>
            </table>
            <br />
            <br />
            <div style="text-align:center;">
                <a href="#" onclick="login()"><img src="images/cwf.png" /></a>
                <br />
                <br />
                <br />
                <img src="images/ipn.png" width="250px"/>
            </div>
            <br />
            <br />
        </div>
        <div data-role="footer" data-theme="b">
            <h4>&copy; Ipseitycloud</h4>
        </div>
    </div>
</body>
</html>

Y lo corren para verlo en el emulador, deberia de mostrar la pagina de inicio de nomination, en posts posteriores agregaremos el plugin de facebook para hacer login y mas funcionalidad

Saludos

Wednesday, 23 May, 2012

En esta ocasion vamos a ver como hacer el deploy de tu aplicacion node.js a cloudno.de, existen otros servicios mas comunes como heroku o nodester, que tambien son muy recomendables pero para esta ocasion usaremos este que en lo personal me ah servido mucho y su ayuda cada vez que me encuentro algun problema siempre es rapida y buena, ademas de que puedes poner bases de datos coudb y redis si asi lo requires, el servicio esta en fase beta y gratuito, muy recomendable

Para empezar como con todos los sistemas de este tipo hay que instalar su cli o command line interface que no es mas que un script con comandos listos para facilitarnos el deployment, actualizacion y demas de nuestra app

npm install cloudnode-cli -g

O sin la g si quieres una instalacion local y no global

Bueno ya con el cli instalado y tu cuenta lista hay que setear el usuario, esto solo hay que hacerlo una sola vez

$ cloudnode user setup <username> <password>

cloudnode info verifying credentials...

el usuario es el mismo que en la pagina y el password es el api key que se genera en la pagina en la seccion sobre tu cuenta lo encuentras

ahora para ver mas opciones escribe cloudnode en la terminal y tendras todos los comandos disponibles

Bien, vamos ahora a la pagina y creamos una aplicacion nueva donde nos dara la url como subdominio de la app y un repositorio github para subirla

Ya con nuestra app creada en la pagina, hay que generarla localmente para codificar, para esto hacemos un app init en mac o linux, si estas en windows hay que hacer los pasos manualmente

en mac o linux

$ cloudnode app init <app name>
cloudnode info initializing git repo for <app name> into folder <app name>
cloudnode info cloning the repo git clone cloudnode@git.cloudno.de:/git/hs/62-6fc0d44abf9974b91625cc10ff118871.git <app name>
cloudnode info clone complete

en windows

Que es lo mismo que haces en la mac o linux pero automatizado, esto lo que hace es clonar el repositorio a tu maquina para poder codificar en el y crea una app de entrada, o bien si estas en algun ide, solo es cuestion de clonar y empezar a hacer commits

Cada que haces un commit, el sistema de cloudnode reinicia la applicacion para que veas los nuevos cambios, no importa que puerto pongas en la applicacion cloudnode lo va a sobreescribir para usarlo en el subdominio

y listo!!, puedes ver tu app corriendo en cloudnode con la url .cloudno.de

Puedes en la pagina de administracion de applicaciones ver el log, pararla o reiniciarla

Saludos

Monday, 16 April, 2012

Estos seran una serie de fragmentos de codigo comun de node.js, codigo que se uso para nomination o en otras partes, solo para tener como referencia cosas importantes que podemos volver a usar despues

El siempre obligado primer programa

Cargar modulos

Aqui podemos ver, cosas importantes como por ejemplo, como cargar los modulos con require, para entender mas sobre require vean este post (ingles), ademas de que lean la documentacion

Tambien se le pueden pasar parametros al cargar los modulos por ejemplo

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

En este caso estamos pasandole un par de variables al modulo index

Consola

Tambien esta la parte de console.log para imprimir informacion en consola, nos sirve mucho para debuguear o dar informacion, para mas informacion del uso de la consola revisen la documentacion, hay diferentes niveles para mostrar la informacion ademas de modulos que extienden, colorean entre otras cosas el output de la consola

Crear un server

Ademas de poder crear un server con el modulo de http y decirle el puerto y la ip a escuchar, tambien podemos crear o iniciar servicores con mucho modulos uno de los mas comunes es express, para crearlo con express es sencillo

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

y luego le decimos en cual puerto escuchar

app.listen(3000);

Tambien se le puede pasar la ip, por default toma la '0.0.0.0' q es de donde sea

Hay muchos otros modulos para crear servidores, date una vuelta por github o npm search para que encuentres el que mas te guste, aunq la mayoria segun el mismo patron

crear y leer archivos

Para esto se usa el modulo "fs" o filesystem, este modulo tiene todo lo necesario para leer, escribir, etc en archivos, links y directorios

FS tiene la opcion de leer archivos de maner syncrona o asyncrona, como buena practica siempre es mejor leerlos asyncronamente

Leer

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]);
 }
 });

Escribir

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

Ver atributos

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!');
  }
}

Para mas opciones, revisen la documentacion de fs

Tambien puede hacer uso de async.js para hacer mas facil la manipulacion asyncrona de los archivos

logging

Para el caso que tengas que quieras loguear informacion en un archivo para futuras referencias, puedes hacer uso de algunos de los muchos modulos, en el caso de nomination usamos log.js que es bastante sencillo de usar

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

Para log, tenemos que cargar el modulo y crear un objeto que es el que vamos a usar para loguear, ademas tenemos que cargar fs, porq necesitamos crear el archivo donde log va a escribir

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

Despues le decimos que nivel de logs queremos en el archivo, esto se puede cambiar dependiendo de la configuracion que vayamos a usar para tener mas o menos logs dependiendo si estamos en dev o prod

Creamos el archivo y se lo adjuntamos como stream y solo nos queda empezar a loggear con los diferentes niveles

Revisen la documentacion de log.js ademas de que hay muchos otros modulos para hacer logueo, hay desde cosas manuales como este o unos helpers o middleware para express/connect entre otras cosas, ver mas modulos

callbacks

Dada la naturaleza de los programas que vamos a terminar construyendo con node.js se hace mucho el uso de callbacks, los callbacks no es nada menos que a donde va a regresar una funcion una vez que termine, la convencion es siempre regresar como primer parametro el error, exista o no y despues los datos que queramos

por ejemplo

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

Y de donde la llamamos

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

De esta forma siempre sabes si existe un error o no y es mas facil controlar tu codigo

Errores

Se recomienda user el "new Error()" para marcar tus errores en lugar de solo una cadena de string, porque el new Error trae informacion sobre el error, donde se produjo, etc muy necesaria para debuguear y darnos una mejor idea de que esta pasando, si solo regresamos el string pues nos perderiamos mucha de esa informacion, ver el tema de callbacks anterior para ver como manejar propiamente los errores

Puedes leer mas sobre esto en este post (ingles)

Tambien es recomendable usar un error listener, para atrapar aun mas errores que se nos escapen como describen aqui

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)
    }
}

Esto es todo para la primera parte de nuestros fragmentos de node.js

Si buscan alguna forma en especial de hacer algo no duden en preguntar para poner informacion al respecto

Saludos

Friday, 13 April, 2012

Ver como se creo la aplicacion:

Parte 1

y en su version movil

Parte 1

Para esta ocasion vamos a crear un servicio que corra diariamente a cierta hora para que termine las nominaciones que ya pasaron, para esto necesitamos instalar un nuevo modulo que nos ayuda a agendar tareas en nuestra app, vamos actualizando nuestro package.json

, "node-schedule" : "0.1.5"

y corremos nuestro comando npm install -d para que se instale la nueva dependencia

Antes que nada hay que actualizar nuestro contoller para hacer busquedas de las nominaciones mas viejas que la fecha actual, para esto agregamos la siguiente funcion al controller nominator

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

Estamos buscando las nominaciones con fecha final mas baja que la fecha actual y regresamos los datos en el callback.

Bien ahora en nuestro archivo server.js vamos a agregar las siguientes variables

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

la primera es nuestro nuevo modulo, despues cargamos el modulo de facebook, la url de nomination para los mensajes en facebook y nuestro nominator que es nuestro controller para los datos.

Iniciamos nuestro 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;

Con esto le estamos diciendo que corra diariamente a la 1 con 0 minutos, y luego le decimos que funcion queremos que corra

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

Aqui estamos buscando todas las nominaciones viejas y las que encuentre vamos a ir terminandolas con la funcion endNomination que es asi:

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 ' );
    });
}

Aqui decidimos el ganador, y hacemos llamadas a la api de facebook para notificar a los usuarios involucrados, puede que no llegue bien la llamada por que el que creo la nominacion no tiene forma de escribir en el muro de las otras personas pero bueno eso ya es parte de la seguridad de facebook

Esto es todo, pueden revisar mas opciones para agendar las tareas en la pagina github del modulo

Fork the code

https://github.com/nodejs-mexico/nomi-nation

Saludos

Wednesday, 4 April, 2012

Este post es parte de una serie sobre como crear una aplicacion con node.js y express para facebook en su version movil

Parte 1 Parte 2 Parte 3 Parte 4

Ve como se creo la version de escritorio que consta de 10 partes

Parte 1

Ultimo post, vamonos de lleno

Primero la parte de borrarme, para aquellos que no les gusto salir en una nominacion, si se borran no se pueden volver agregar a esa nominacion, para esto actualizamos mscript.js y agregamos esta funcionalidad

$('#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'); 
    }); 
});

y tambien actualizamos la pagina para guardar el id en el boton:

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

Sencillo tomamos el id del usuario que guardamos previamente en el boton de borrar, nos traemos la pagina de detalles y buscamos el id de la nominacion le mandamos los datos al servidor y si no hay errores borramos esa fila

OK, ahora para actualizar, vamos a hacer lo siguiente, agregamos la clase refresh a todos los botones de refresh

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

y agregamos la funcionalidad al boton

//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);
});

Tomamos el id de la pagina actual, le sacamos el tipo de nominaciones que estan viendo, sacamos el divider, borramos la lista actual, agregamos el divider de nuevo y recargamos las nominaciones

Ahora vamos a actualizar nuestras ruta, vamos a actualizar la de dashboard, asi para cuando llegue con un movil los redirigimos a la version movil y ademas si alguien quiere probar la version movil o usarla sin estar en un dispositivo movil lo puede hacer cambiando la url,el cambio es bastante sencillo en la ruta dashboard de nuestra archivo 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                
        });
}

Aqui solo revisamos que esten en una plataforma movil, si asi es los enviamos a la version mobil

Solo falta actualizar los strings que tengamos en las plantillas o en el js a strings localizables, eso ya se queda de tarea para uds, cualquier duda pasenla como comentario

Esto es todo para la version movil, tenemos una version funcional muy interesante, ahora falta anunciarla por todos lados, ayudenme con bugs o cosas nuevas para la aplicacion

Para el proximo post intentare hacer algun job para que corra diariamente y cierre las nominaciones que ya estan pasadas y avise a los q pueda en facebook

Despues de eso nos iremos con algo de phonegap para hacerla nativa para diferentes plataformas

Fork the code

https://github.com/nodejs-mexico/nomi-nation

Saludos

Wednesday, 28 March, 2012

Este post es parte de una serie sobre como crear una aplicacion con node.js y express para facebook en su version movil

Parte 1 Parte 2 Parte 3 Parte 5

Ve como se creo la version de escritorio que consta de 10 partes

Parte 1

Ahora vamos a votar, borrar e invitar amigos a la aplicacion.

Para empezar vamos a agregar una funcionalidad a la lista de amigos, que al hacer swipe aparezcan los botones de borrar y votar, para esto actualizamos mscrip.js despues de agregar a los usuarios en los detalles, agregamos esto (alrededor de la linea 150)

$('.users li').bind('swiperight swipeleft', swipe);

Esto buscara la funcion de swipe cada vez q el usuario aga swipe a la izquierda o derecha en un elemento de la lista, hay que agregar esta funcion en el mismo archivo

function swipe(){
    // reference the just swiped list item
    var $li = $(this);
    // remove all buttons first
    $('.aDeleteBtn').remove();
    $('.aVoteBtn').remove();
    // create buttons and div container
    var $deleteBtn = $('<a>Delete</a>').attr({
            'class': 'aDeleteBtn ui-btn-up-r',
            'href': '#'
        });
    var $voteBtn = $('<a>Vote</a>').attr({
            'class': 'aVoteBtn ui-btn-up-bl',
            'href': '#'
        });
    // insert swipe div into list item
    $li.prepend($deleteBtn);
    $li.prepend($voteBtn);
    $deleteBtn.slideToggle();
    $voteBtn.slideToggle();
}

Aqui estamos tomando el li como referencia, borramos los botones si es q ya existen para no terminar con duplicados, creamos 2 botons los cuales tienen 2 clases una sirve para referenciarlos luego y la otra para darles estilo y posicionarlos en la pantalla, agregaremos esto a nuestro css

.aDeleteBtn, .aVoteBtn {
    -moz-border-radius: 5px;
    -webkit-border-radius: 5px;
    float: right;
    height: 15px;
    line-height: 15px;
    margin: 10px 10px 0 0;
    padding: 0.6em;
    position: absolute;
    right: 0;
    top: 0;
    z-index: 10;
    display: none;
}
.aDeleteBtn{
    right: 60px;
}
/* red color buttons */
.ui-btn-up-r { border: 1px solid #953403; background: #2567ab; font-weight: bold; color: #fff; cursor: pointer;  text-shadow: 0 -1px 1px #953403; text-decoration: none; background-image: -moz-linear-gradient(top, #ec4a0b, #ad390c); background-image: -webkit-gradient(linear,left top,left bottom,color-stop(0, #ec4a0b),color-stop(1, #ad390c));   -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#ec4a0b', EndColorStr='#ad390c')"; }

.ui-btn-up-r a.ui-link-inherit { color: #fff; }

.ui-btn-hover-r { border: 1px solid #953403; background: #f15c22; font-weight: bold; color: #fff;  text-shadow: 0 -1px 1px #014D68; background-image: -moz-linear-gradient(top, #f15c22, #f15c22); text-decoration: none; background-image: -webkit-gradient(linear,left top,left bottom,color-stop(0, #f15c22),color-stop(1, #f15c22));   -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#f15c22', EndColorStr='#f15c22')";  }

.ui-btn-hover-r a.ui-link-inherit { color: #fff; }

.ui-btn-down-r { border: 1px solid #225377; background: #79ae21; font-weight: bold; color: #fff; text-shadow: 0 -1px 1px #225377; background-image: -moz-linear-gradient(top, #bc770f, #e6590c); background-image: -webkit-gradient(linear,left top,left bottom,color-stop(0, #bc770f),color-stop(1, #e6590c));   -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#bc770f', EndColorStr='#e6590c')"; }

.ui-btn-down-r a.ui-link-inherit { color: #fff; }

.ui-btn-up-r, .ui-btn-hover-r, .ui-btn-down-r { font-family: Helvetica, Arial, sans-serif; }
/* blue color buttons */
.ui-btn-up-bl { border: 1px solid #036596; background: #2567ab; font-weight: bold; color: #fff; cursor: pointer;  text-shadow: 0 -1px 1px #036596; text-decoration: none; background-image: -moz-linear-gradient(top, #2567ab, #036596); background-image: -webkit-gradient(linear,left top,left bottom,color-stop(0, #2567ab),color-stop(1, #036596));   -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#2567ab', EndColorStr='#036596')"; }

.ui-btn-up-bl a.ui-link-inherit { color: #fff; }

.ui-btn-hover-bl { border: 1px solid #036596; background: #2567ab; font-weight: bold; color: #fff;  text-shadow: 0 -1px 1px #014D68; background-image: -moz-linear-gradient(top, #2567ab, #2567ab); text-decoration: none; background-image: -webkit-gradient(linear,left top,left bottom,color-stop(0, #2567ab),color-stop(1, #2567ab));   -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#2567ab', EndColorStr='#2567ab')";  }

.ui-btn-hover-bl a.ui-link-inherit { color: #fff; }

.ui-btn-down-bl { border: 1px solid #225377; background: #79ae21; font-weight: bold; color: #fff; text-shadow: 0 -1px 1px #225377; background-image: -moz-linear-gradient(top, #2567ab, #2567ab); background-image: -webkit-gradient(linear,left top,left bottom,color-stop(0, #2567ab),color-stop(1, #2567ab));   -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#2567ab', EndColorStr='#2567ab')"; }

.ui-btn-down-bl a.ui-link-inherit { color: #fff; }

.ui-btn-up-bl, .ui-btn-hover-bl, .ui-btn-down-bl { font-family: Helvetica, Arial, sans-serif; }

Muy bien ya tenemos los botones, ahora a darles funcionalidad, empezamos con borrar, agregamos esta funcion a mscript.js

$('.aDeleteBtn').live('click', function(ev){
    ev.preventDefault();
    $.mobile.showPageLoadingMsg();
    var li = $(this).parents('li');
    var details = $('#details');
    var nid = details.find('#attd').attr('nid');
    var user = {
        _id : li.attr("id"),
        name : li.attr("name"),
        votes : li.find(".count").text()
    };
    $.post("/nominations/eraseuser", { id: nid, user: user },
        function(data) {
            if (data){
               li.remove();
               var usersl = details.find('.users');
               usersl.listview('refresh');
            }else{
                showMsg('dashboard.error', 'dashboard.error_erasing_user');
            }
            $.mobile.hidePageLoadingMsg();
        }
    ).error(function() { $.mobile.hidePageLoadingMsg();
        showMsg('dashboard.error', 'dashboard.error_erasing_user'); });
    $('.aVoteBtn').slideToggle();
    $('.aDeleteBtn').slideToggle();
});

Aqui estamos tomando el li de donde nos llamaron, la pantalla de detalles para sacar el id de la nominacion

Creamos un objeto usuario con los datos que ya tenemos y se lo mandamos al server para que borre el usuario y escondemos los botones

Ya q regrese de la llamada, borramos el usuario de lista si todo salio bien o mostramos el error si fue el caso

Ahora vamos a la funcion de votar

$('.aVoteBtn').live('click', function(ev){
    $.mobile.showPageLoadingMsg();
    ev.preventDefault();
    var li = $(this).parents('li');
    var id = li.attr('id');
    var details = $('#details');
    var nid = details.find('#attd').attr('nid');
    //var name = $('.details').find('legend').text();
    $.post("/nominations/vote", { id: nid, userid: id },
        function(data) {
            if (data){
                var votes = li.find('.count');
                votes.html(data);
                //updat voted list
            }else{
                showMsg('dashboard.error', 'dashboard.error_voting');
            }
            $.mobile.hidePageLoadingMsg();
        }
    ).error(function() {
        $.mobile.hidePageLoadingMsg();
        showMsg('dashboard.error', 'dashboard.error_voting'); 
    });
    $('.aVoteBtn').slideToggle();
    $('.aDeleteBtn').slideToggle();
});

De la misma forma sacamos los datos del li y de details, posteamos al server la informacion y escondemos los botones

Al regresar del server, actualizamos la cuenta de los botones o mostrar cualquier error que haya pasado

Nos falta la funcion para invitar amigos a la aplicacion, para esto vamos a reciclar nuestra pantalla de agregar amigos, primero vamos actualizando el template dashboardm.jade, agregamos una clase a nuestro boton de invite (tenemos uno en cada pagina principal) para que quede asi

a.doinvite(href="#", data-icon="plus", class="ui-btn-right") Invite

Despues en nuestra pagina con id #addf agregamos este boton al footer

a.invite(href="#", data-role="button", data-theme="b") Invite

Y en nuestro css lo iniciamos escondido

.invite{
    display: none;
}

Ok ahora al script, en #adduser click agregamos lo siguiente para asegurarnos que el boton este escondido al agregar amigos a una nominacion

$('.invite').hide();

Despues agregamos el listener para el boton de invitar

$('.doinvite').live('click', function(){
    $.mobile.showPageLoadingMsg();
    $('.invite').show();
    $.mobile.changePage( "#addf",
    {
        transition: "pop",
        reverse: false,
        changeHash: false
    });
});

Mostramos nuestro boton de invitar y mostramos la pantalla de agregar amigos, y para finalizar agregamos la funcionalidad del boton para invitar amigos

$('.invite').live('click', function(){
    $.mobile.showPageLoadingMsg();
    var users = [];
    var userp;
    $('#lof').find(':checked').each(function(){
        users.push({
            "_id" : $(this).attr('id'),
            "name" : $(this).attr('name'),
            "votes" : 0
        });
    });
    var ul = users.length;
    if (ul > 0 && ul <= 1){
        userp = users[0];
    }else{
        userp = users;
    }
    $.post("/invite", { users: userp },
        function(data) {
            if (data){
                history.back();
                //showMsg('dashboard.warning', 'dashboard.invited'); 
            }else{
                showMsg('dashboard.error', 'dashboard.warning_invited');
            }
            $.mobile.hidePageLoadingMsg();
        }
    ).error(function() { 
        $.mobile.hidePageLoadingMsg();
        showMsg('dashboard.warning', 'dashboard.warning_invited'); 
    });
});

Tomamos la lista de usuarios seleccionados y se los mandamos al server para invitar a los amigos.

Listo ya tenemos suficiente funcionalidad :)

En el proximo y ultimo post veremos borrarme, refrescar paginas, actualizacion de strings, checar por session activa y actualizar las rutas y listo nuestra version beta para movil

Ayudame con bugs que encuentres, tengo algunos en github o mete mas features

Fork the code

https://github.com/nodejs-mexico/nomi-nation

Saludos

Tuesday, 13 March, 2012

Este post es parte de una serie sobre como crear una aplicacion con node.js y express para facebook en su version movil

Parte 1 Parte 2 Parte 4 Parte 5

Ve como se creo la version de escritorio que consta de 10 partes

Parte 1

En esta ocasion vamos a ver como crear una nueva nominacion, como ver los detalles y como agregar amigos

Para empezar vamos actualizando nuestro dashboardm.jade

#newn(data-role="page")
    div(data-role="header", data-theme="e")
        h1 New nomination
    //header

    div(data-role="header", data-theme="e")
        #errornf
        form#newf.ui-body.ui-body-a.ui-corner-all
            fieldset
                div(data-role="fieldcontain")
                    label(for="name") Name:
                    input#name(type="text", cols="40", rows="8", name="name")
                    label(for="date") End Date:
                    input#date(type="date", cols="40", rows="8", name="date")
                    button#newnfs(data-theme="b") Submit
    //content

    div(data-role="footer")
        a( href="#", data-rel="back", data-icon="back") Back
    //footer

///page new nomination
#addf(data-role="page")
    div(data-role="header", data-theme="e")
        h1 Add friends
    //header

    div(data-role="header", data-theme="d")
        div(data-role="fieldcontain")
            fieldset#lof(data-role="controlgroup")
    //content

    div.ui-bar(data-role="footer", data-theme="b", data-position="fixed")
        a#bina(href="#", data-icon="back") Back
        a.add(href="#", data-role="button", data-theme="b") Add

///page add friends
#details(data-role="page")

    div(data-role="header", data-theme="b")
        a#cancel(href="#", data-icon="delete") Cancel
        h1 Details
        a#end(href="#", data-icon="check") End
    ///header

    div(data-role="content")
        #attd
        .name
        .endD
        ul.users(data-role="listview")
            li(data-role="list-divider") Swipe to Vote/Delete
    ///content

    .ui-bar(data-role="footer", data-theme="b", data-position="fixed")
        div(data-role="controlgroup", data-type="horizontal")
            a(href="#", data-rel="back", data-icon="back") Back
            a#adduser(href="#", data-icon="plus") Add
            a(href="#", data-icon="refresh") Refresh
            a#remove(href="#", data-icon="minus") Remove Me
    ///footer

///page details

Estamos agregando 3 paginas, una para cada cosa, todas dentro del mimsmo html

En new form agregamos una forma donde pedimos el nombre y la fecha de terminacion

En add friends, mostramos la lista de amigos para que el usuario los seleccione y los pueda agregar a la nominacion

Y por ultimo los detalles, esta plantilla servira para todos los tipos de nominaciones solo mostraremos algunos botones y otros no dependiendo del tipo pero esto lo haremos con jquery

Ok, ahora hay que actualizar nuestro script para todo esto mscript.js

Primero agregamos la parte de cargar a los amigos:

function loadUsers(next){
    $.getJSON(next || 'http://nomination.cloudno.de/friends', function(data) {
    if (data.data.length > 0){
        var list = $('#lof');
        $.each(data.data, function(key, value){
            list.append('<input type="checkbox" name="'+value.name+'" id="'+value.id+'" />');
            list.append('<label for="'+value.id+'">'+value.name+'</label>');
        });
        $('#lof').trigger( 'updatelayout' );
        loadUsers(data.paging.next);
    }else{
        return;
    }
    }).error(function() { showMsg('dashboard.error', 'dashboard.error_friends'); });
}

En este caso estamos cargando la lista de amigos de facebook y agregandola a la lista para usarla despues, esta lista la cargaremos en cuanto entren a la pagina para esto agregamos al page create una llamada a esta funcion

$('#dashboard-mine').live('pagecreate', function(){    
    loadNominations('mine');
    loadUsers(null);
});

Ahora que ya se estan cargando los amigos, vamos a crear una nominacion, recuerdan que pusimos un boton en todas las paginas de nominaciones que dice "create", bueno pues vamos a darle funcionalidad, primero agregamos a mscrip.js

$('.create').live('click', function(){
    $.mobile.changePage( "#newn", { transition: "pop"} );
});

Con esto le decimos a jqm que al darle click al boton de crear se cambie a la pagina de nueva nominacion

Ya ahi, esperamos input del usuario y agregamos funcionalidad al boton de enviar (submit)

$('#newnfs').live('click', function(ev){
    ev.preventDefault();
    $.mobile.showPageLoadingMsg();
    var name = $('#name').val();
    var date = $('#date').val();
    if (name!=='' && date !==''){
    $('#errornf').html('');
    $.post("http://nomination.cloudno.de/nominations/create", { name: name, datep: date },
        function(data) {
            var list = $('#mine');
            list.append('<li id="' + 
                data._id + '" type="mine"><a class="details" href="#">' + 
                data.name + '</a></li>');
            list.listview('refresh');
            $.mobile.hidePageLoadingMsg();
            $.mobile.changePage( "#dashboard-mine" );
            return false;
        }
    ).error(function() {
        $.mobile.hidePageLoadingMsg();
        $('#errornf').html('Error saving the nomination, try again later');
        return false;
    });
    return false;
    }else{
    $('#errornf').html('Name and date required');
    $.mobile.hidePageLoadingMsg();
    return false;
    }
});

Primero mostramos nuestro mensaje de que estamos trabajando, traemos los datos de la forma, revisamos que tengamos datos, si falta algo le mostramos un error al usuario

Despues hacemos un post a nuestro server con los datos suministrados, y si todo esta bien la agregamos a la lista de mine, refrescamos la lista para que tome los estilos, cerramos nuestro mensajito de que estamos trabajando y cambiamos a la lista de mios, si surgio un error lo reportamos y nos quedamos ahi

Ya tenemos una nominacion, vamos a ver los detalles, para esto le damos a todas las listas la funcionalidad, todos los elementos de las listas tienen la clase details entonces usaremos esa

$('.details').live('click', function(){
    $.mobile.showPageLoadingMsg();
    var li = $(this).parents('li');
    var id = li.attr('id');
    var type = li.attr('type');
    $('#details').find('#attd').attr('past',$.mobile.activePage.attr('id'));
    showNomination(id, type, false);
    $.mobile.changePage($("#details"));
});

Tomamos los datos de cual nominacion seleccionaron, agregamos algunos datos a la pagina de detalles, cargamos la nominacion con la funcion showNomination y cambiamos a la pagina detalles

La funcion showNomination quedara algo asi:

//cargar la nominacion y llenar details
function showNomination(id, type, refresh){
    $.mobile.showPageLoadingMsg();
    $.getJSON('http://nomination.cloudno.de/nominations/'+id, function(data) {
        if (!data){
            //alert('Du! nominacion ya no existe o termino :(');
            showMsg('dashboard.warning', 'dashboard.warning_erased');
        }
        var details = $('#details');
        details.find('#attd').attr('nid',id);
        details.find('#attd').attr('type',type);
        details.find('.name').html(data.name);
        var daten = new Date();
        daten.setISO8601(data.endDate);
        details.find('.endD').html( daten.getDate()+'/'+(daten.getMonth()+1)+'/'+daten.getUTCFullYear());
        var ntype = type;
        if (ntype === 'appear'){
            $('#end').hide();
            $('#cancel').hide();
            $('#remove').show();
        }else if (type === 'mine'){
            $('#end').show();
            $('#cancel').show();
            $('#remove').hide();
        }else{
            $('#end').hide();
            $('#cancel').hide();
            $('#remove').hide();
        }
        var usersl = details.find('.users');
        usersl.html('');
        var userl = data.users.length;
        usersl.hide();
        usersl.append('<li data-role="list-divider">Swipe to Vote/Delete</li>');
        for (var i=0; i<userl;i++){
            usersl.append('<li id="'+data.users[i]._id+'" type="'+type+'">'+
                '<img src="https://graph.facebook.com/'+data.users[i]._id+'/picture"/>'+
                data.users[i].name+
                '<span class="ui-li-count count">'+data.users[i].votes+'</span></li>');
        }
        usersl.listview('refresh');
        usersl.show();
        $.mobile.hidePageLoadingMsg();
    }).error(function() {
        $.mobile.hidePageLoadingMsg();
        showMsg('dashboard.error', 'dashboard.error_showing'); 
    });
}

Aqui traemos los datos de la nominacion y los vamos llenando en la pagina de detalles, depende del tipo son los botons que vamos a mostrar, agregamos la lista de amigos nominados y refrescamos la lista, si pasa algun error le informamos al usuario, en esta funcion tambien usamos un prototype para date llamado setISO8601 esto es mas que nada para entender el formato de fecha que nos manda facebook, la funcion es asi:

Date.prototype.setISO8601 = function (string) {
    var regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})" +
        "(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?" +
        "(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?";
    var d = string.match(new RegExp(regexp));

    var offset = 0;
    var date = new Date(d[1], 0, 1);

    if (d[3]) { date.setMonth(d[3] - 1); }
    if (d[5]) { date.setDate(d[5]); }
    if (d[7]) { date.setHours(d[7]); }
    if (d[8]) { date.setMinutes(d[8]); }
    if (d[10]) { date.setSeconds(d[10]); }
    if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); }
    if (d[14]) {
        offset = (Number(d[16]) * 60) + Number(d[17]);
        offset *= ((d[15] == '-') ? 1 : -1);
    }

    offset -= date.getTimezoneOffset();
    var time = (Number(date) + (offset * 60 * 1000));
    this.setTime(Number(time));
};

Bien ya tenemos la nominacion, ahora tenemos que agregar algun amigo, para esto vamos a darle la funcionalidad

$('#adduser').live('click', function(){
    $.mobile.changePage( "#addf",
    {
        transition: "pop",
        reverse: false,
        changeHash: false
    });
});

Esto solo cambia a la pagina de add friends

En add friends mostramos la lista de amigos y un boton de añadir que tiene esta funcionalidad

$('.add').live('click', function(){
    $.mobile.showPageLoadingMsg();
    var users = [];
    var userp;
    $('#lof').find(':checked').each(function(){
        users.push({
            "_id" : $(this).attr('id'),
            "name" : $(this).attr('name'),
            "votes" : 0
        });
    });
    var ul = users.length;
    if (ul > 0 && ul <= 1){
        userp = users[0];
    }else{
        userp = users;
    }
    var details = $('#details');
    var nid = details.find('#attd').attr('nid');
    var type = details.find('#attd').attr('type');
    $.post("http://nomination.cloudno.de/nominations/adduser", { id: nid, users: userp },
    function(data) {
        if (data){
            $.each(users,function(key, value){
                var usersl = details.find('.users');
                usersl.append('<li id="'+value._id+'" type="'+type+'">'+
                    '<img src="https://graph.facebook.com/'+value._id+'/picture"/>'+
                    value.name+
                    '<span class="ui-li-count count">0</span></li>');
                usersl.listview('refresh');
            });
            $.mobile.changePage( "#details" );
        }else{
            $.mobile.changePage( "#details" );
            showMsg('dashboard.error', 'dashboard.error_adduser');
        }
        $.mobile.hidePageLoadingMsg();
    }).error(function() { 
        $.mobile.hidePageLoadingMsg(); 
        showMsg('dashboard.error', 'dashboard.error_adduser'); 
    });
});

Tomamos lo susuarios seleccionados de la lista, los agregamos a la nominacion y si todo salio bien, los agregamos a la lista en la pagina para despues regresar a la pagina de los detalles

Esto es todo por ahora, para la proxima veremos como votar/borrar usuarios en una nominacion y como invitar a mas amigos a jugar, asi como otras funciones que nos faltan para sacar nuestro beta movil

Fork the code

https://github.com/nodejs-mexico/nomi-nation

Saludos

Thursday, 1 March, 2012

Este post es parte de una serie sobre como crear una aplicacion con node.js y express para facebook en su version movil

Parte 1 Parte 3 Parte 4 Parte 5

Ve como se creo la version de escritorio que consta de 10 partes

Parte 1

Ya que tenemos nuestro index, vamos creando la vista del dashboard, en este post crearemos todas las vistas de las nominaciones que tiene un usuario que son "Mias", "Vote", "Aparezco", la pantalla nos quedara mas o menos asi:

Dashboard

Bueno, hay que empezar con el layout de la pagina, para esto creamos un archivo llamado "dashboardm.jade"

- if (invited)
    .invited(invited="#{invited}")
#dashboard-mine(data-role="page", data-theme="d")
    div(data-role="header", data-theme="b", data-position="inline")
        h1= t('app.name')
        a(href="#", data-icon="plus", class="ui-btn-right") Invite
        div(data-role="navbar")
            ul
                li
                    a#minel.ui-btn-active(href="#dashboard-mine", data-icon="star") Mine
                li
                    a#votedl(href="#dashboard-voted", data-icon="check") Voted
                li
                    a#appearl(href="#dashboard-appear", data-icon="alert") Appear
        ///navbar
    ///header
    div(data-role="content")
        fieldset.ui-grid-b
            .ui-block-a
                a.create(href="#", data-role="button", data-theme="b") Create
            .ui-block-b
            .ui-block-c
                a(href="#", data-role="button", data-theme="b", data-icon="refresh") Refresh
        br
        ul#mine(data-role="listview")
            li(data-role="list-divider") Select to see the details
    ///content

    div(data-role="footer", data-theme="b", data-position="fixed")
        label(data-theme="c", for="search-basic") Search nomination:
        input#searc-basic(data-theme="c", type="search", name="search", value="")
    ///footer

///page mine
#dashboard-voted(data-role="page", data-theme="d")
    div(data-role="header", data-theme="b", data-position="inline")
        h1= t('app.name')
        a(href="#", data-icon="plus", class="ui-btn-right") Invite
        div(data-role="navbar")
            ul
                li
                    a#minel(href="#dashboard-mine", data-icon="star") Mine
                li
                    a#votedl.ui-btn-active(href="#dashboard-voted", data-icon="check") Voted
                li
                    a#appearl(href="#dashboard-appear", data-icon="alert") Appear
        ///navbar
    ///header

    div(data-role="content")
        fieldset.ui-grid-b
            .ui-block-a
                a.create(href="#", data-role="button", data-theme="b") Create
            .ui-block-b
            .ui-block-c
                a(href="#", data-role="button", data-theme="b", data-icon="refresh") Refresh
        br
        ul#voted(data-role="listview")
            li(data-role="list-divider") Select to see the details
    ///content

    div(data-role="footer", data-theme="b", data-position="fixed")
        label(data-theme="c", for="search-basic") Search nomination:
        input#searc-basic(data-theme="c", type="search", name="search", value="")
    ///footer

///page voted
#dashboard-appear(data-role="page", data-theme="d")

    div(data-role="header", data-theme="b", data-position="inline")
        h1= t('app.name')
        a(href="#", data-icon="plus", class="ui-btn-right") Invite
        div(data-role="navbar")
            ul
                li
                    a#minel(href="#dashboard-mine", data-icon="star") Mine
                li
                    a#votedl(href="#dashboard-voted", data-icon="check") Voted
                li
                    a#appearl.ui-btn-active(href="#dashboard-appear", data-icon="alert") Appear
        ///navbar
    ///header

    div(data-role="content")
        fieldset.ui-grid-b
            .ui-block-a
                a.create(href="#", data-role="button", data-theme="b") Create
            .ui-block-b
            .ui-block-c
                a(href="#", data-role="button", data-theme="b", data-icon="refresh") Refresh
        br
        ul#appear(data-role="listview")
            li(data-role="list-divider") Select to see the details
    ///content

    div(data-role="footer", data-theme="b", data-position="fixed")
        label(data-theme="c", for="search-basic") Search nomination:
        input#searc-basic(data-theme="c", type="search", name="search", value="")
    ///footer

///page appear
#popup(data-role="page")

    div(data-role="header", data-theme="e")
        h1 Message
    ///header

    div(data-role="content", data-theme="d")
        h2#title
        p#msg
        p
            a(href="#one", data-rel="back", data-role="button", data-inline="true", data-icon="back") Back
    ///content

    div(data-role="footer")
        h4= t('app.name')
    ///footer

///page message

Linea 1-2: revisamos si viene referenciado a alguna nominacion

3: Pagina para nominaciones mine, se especifica que este div conforma una pagina con data-role="page" y ademas le decimos que use el tema "d" con data-theme="d"

4: El header de la pagina se le indica con data-role="header" y usaremos el tema "b" y su posicion va a ser "in-line", jqm lo pondra como barra en la parte de arriba de la pagina

5: El header de la app, como esta dentro del header, quedara centrado en la barra del header

6: Link para invitar personas, es un a regular que jqm lo hace como un boton y ademas unas cosas extras como data-icon="plus" con esto le indicamos que queremos un icono de "+" y class="ui-btn-right" le decimos que pase este boton a la derecha, si no ponemos esta regla el primer "a" lo pondra a la izquierda y el segunda a la derecha, pero como solo tenemos uno y lo queremos a la derecha usamos esta clase

7: Nuestro navigation bar data-role="navbar", se conforma de una lista que jqm acomodara como una barra vertical con botones grandes con las opciones de la lista

8-14: Cada li es una opcion de nuestro navigation bar, justo abajo de la barra del header, cada "li" tiene un "a" que a su vez tiene un icono, ademas de que el "a" de la pagina actual le pondremos una clase para que se vea diferente que es .ui-btn-active

17: nuestro contenido de la pagina data-role="content"

18: en este caso primero usaremos un fieldset con la clase .ui-grid-b esto quiere decir que la pagina la dividira en 3 partes, las cuales llenaremos con div con una clase en especifico, hay diferentes fieldset que divide la pantalla en mas o menos partes

19-23: todas las partes del fieldset se llenan con estas divs con la clase .ui-block-a el ultimo caracter va cambiando a "b" y "c" para este caso que se divide en 3 partes para otros usaremos mas o menos caracteres, tambien estamos agregando en 2 divs unos "a" con data-role="button" para que jqm los muestro como botones, en este caso no lo hizo automatico porque los links no estan dentro de una navegacion o dentro del header/footer

25-26: una lista para ahi meter las nominaciones de tipo mine del usuario data-role="listview", estamos agregando un primer elemento a la lista pero de tipo data-role="list-divider" esto quiere decir que no es un elemento regular sino un divisor de lista osea como un pequeño titulo entre los elementos, en este caso hasta arriba por ser el primero

29: El footer data-role="footer" con tema "b" y con su posicion fija, esto quiere decir que si la lista de nominacion es muy larga, el footer aparecer sobre la lista y el usuario tendra que desplazarse hacia abajo para ver los demas elementos, mientras se desplaza el footer se ocultara, cuando termine de desplazarse volvera aparecer y siempre al fondo de la pagina

30-31: una forma para hacer busquedas, el label y su input, aparecera al fondo dentro del footer

35-101: la pagina mine, se repite varias veces pero para cada tipo de nominacion, los datos son similares, solo cambian algunas cosas como el titulo o el boton activo en la navegacion

Si se fijan todo esta en una sola pagina, la primera pagina que se mostrara es la primera que tengamos en el archivo, las demas no las procesara sino hasta que entremos a alguna, jqm las carga con ajax al intentar entrar en ellas

102-119; pagina que la usaremos de pop-up, por si sola es una pagina como cualquier otra, en este caso estamos poniendo algunos divs que llanremos al momento de cargarla o en este caso de mostrar un mensaje, se vera como pop-up ya que la cargaremos de esa forma, esto lo veremos en el codigo de js

Esto es todo para la parte de jade, veamos ahora nuestro js que agregaremos en "mscript.js"

$( document ).bind( "mobileinit", function() {
    // Make your jQuery Mobile framework configuration changes here!
    $.mobile.touchOverflowEnabled = true;
    $.mobile.allowCrossDomainPages = true;
});
function loadNominations(type){
    $.mobile.showPageLoadingMsg();
    $.getJSON('http://nomination.cloudno.de/nominations/'+type, function(data) {
        var list = $('#'+type);
        if (data.length < 1 ){
            //showMsg('dashboard.warning', 'dashboard.warning_zarro');
        }else{
            $.each(data, function(key, value){
                list.append('<li id="'+value._id+'" type="'+type+'"><a class="details" href="#">'+value.name+'</a></li>');
            });
            list.listview('refresh');
        }
        $.mobile.hidePageLoadingMsg();
    }).error(function() { $.mobile.hidePageLoadingMsg(); showMsg('dashboard.error', 'dashboard.warning_nominations'); });
}
$('#dashboard-mine').live('pagecreate', function(){    
    loadNominations('mine');
});
$('#dashboard-voted').live('pagecreate', function(){    
    loadNominations('voted');
});
$('#dashboard-appear').live('pagecreate', function(){    
    loadNominations('appear');
});
function showMsg(err, msg){
    var dialog = $("#popup");
    dialog.find("#title").html(err);
    dialog.find("#msg").html(msg);
    $.mobile.changePage( dialog );
}

1-5: inicializamos algunas cosas de jquery mobile

6-20: esta funcion nos sirve para cargar las nominaciones por tipo, primero mostramos un loading gif para indicar que estamos trabajando y luego con ajax traemos la lista en formato json del server dependiendo del tipo, si no hay errores y trae datos, las agregamos a la lista de nominaciones que el id tiene el mismo nombre que el tipo y al agregarlas todas hacemos un refresh al listview sino se veran sin formato, al final quitamos nuestro loading; si hay un error mostramos nuestra pagina de error

21-29: estas funciones solo le dice a jqm que cuando la pagina con ese id, la termine de cargar haga x funcion, seria el tipo "ready" de jquery pero como aca se cargan con ajax y ese "ready" no existe, usamos esta funcion, en todos los casos llamamos a la funcion "loadNominations" con el tipo de nominaciones que queremos cargar para esa pagina

30-35: la funcionalidad para mostrar mensajes en pantalla, en este caso cargamos el dialogo, lo llenamos con algunos datos y mostramos el dialog con changepage que seria como darle click a algun link que nos lleve ahi

Bueno para poder ver esta pagina hay que hacer temporalmente una ruta, ya que aun no tenemos mucha funcionalidad, para esto tenemos que actualizar "dashboard.js" alrededor de la linea 56 con esto:

/**
 * Dashboardm landing, TODO: erase this
*/
app.get('/dashboardm', function(req, res){
    res.render('dashboardm', 
        { 
            user: req.session.user, 
            error : req.param('error'), 
            type: 'dashboard', 
            invited: false,
            layout: 'layoutm'
        });
});

Creamos una nueva ruta donde mostraremos lo que acabamos de hacer, solamente estamos mostrando la pagina con el layout mobile que hicimos en el post anterior y algunas variables necesarias, para poder ver esta pagina entren regularmente a la aplicacion, ya que esten logueados cambien el final de la url por "dashboardm" y veran la parte movil

Fork the code

https://github.com/nodejs-mexico/nomi-nation

Saludos

Next Page