Haz un juego de votación de imágenes con Node.js (Parte 2)

En la primera parte de este tutorial, sentamos las bases de nuestra aplicación web node.js. Aprendió sobre cómo ejecutar e instalar node, sobre npm y la biblioteca nedb, e incluso escribimos nuestro primer módulo. Esta semana continuaremos con las rutas y vistas de nuestra app de votación por imágenes.

Las rutas y los archivos de configuración

La semana pasada creamos un módulo que maneja la inicialización de dos conjuntos de datos:usuarios y fotos . Estos conjuntos de datos fueron exportados por el módulo, lo que nos permite solicitarlos y acceder a ellos en nuestros otros archivos js. Haremos esto en nuestro routes.js que contiene todas las rutas a las que responderá la aplicación.

rutas.js

/**
 * This file defines the routes used in your application
 * It requires the database module that we wrote previously.
 */ 

var db = require('./database'),
    photos = db.photos,
    users = db.users;

module.exports = function(app){

    // Homepage
    app.get('/', function(req, res){

        // Find all photos
        photos.find({}, function(err, all_photos){

            // Find the current user
            users.find({ip: req.ip}, function(err, u){

                var voted_on = [];

                if(u.length == 1){
                    voted_on = u[0].votes;
                }

                // Find which photos the user hasn't still voted on

                var not_voted_on = all_photos.filter(function(photo){
                    return voted_on.indexOf(photo._id) == -1;
                });

                var image_to_show = null;

                if(not_voted_on.length > 0){
                    // Choose a random image from the array
                    image_to_show = not_voted_on[Math.floor(Math.random()*not_voted_on.length)];
                }

                res.render('home', { photo: image_to_show });

            });

        });

    });

    app.get('/standings', function(req, res){

        photos.find({}, function(err, all_photos){

            // Sort the photos 

            all_photos.sort(function(p1, p2){
                return (p2.likes - p2.dislikes) - (p1.likes - p1.dislikes);
            });

            // Render the standings template and pass the photos
            res.render('standings', { standings: all_photos });

        });

    });

    // This is executed before the next two post requests
    app.post('*', function(req, res, next){

        // Register the user in the database by ip address

        users.insert({
            ip: req.ip,
            votes: []
        }, function(){
            // Continue with the other routes
            next();
        });

    });

    app.post('/notcute', vote);
    app.post('/cute', vote);

    function vote(req, res){

        // Which field to increment, depending on the path

        var what = {
            '/notcute': {dislikes:1},
            '/cute': {likes:1}
        };

        // Find the photo, increment the vote counter and mark that the user has voted on it.

        photos.find({ name: req.body.photo }, function(err, found){

            if(found.length == 1){

                photos.update(found[0], {$inc : what[req.path]});

                users.update({ip: req.ip}, { $addToSet: { votes: found[0]._id}}, function(){
                    res.redirect('../');
                });

            }
            else{
                res.redirect('../');
            }

        });
    }
};

Aquí app es una instancia de una aplicación web Express.js que crearemos en nuestro index.js expediente. Estamos exportando una función que toma la aplicación como argumento, lo que nos permite inyectarla como dependencia más adelante.

El siguiente archivo que escribiremos es un archivo de configuración que establece algunas configuraciones para nuestra aplicación:

config.js

/**
 * This file runs some configuration settings on your express application.
 */ 

// Include the handlebars templating library
var handlebars = require('express3-handlebars'),
    express = require('express');

// Require()-ing this module will return a function
// that the index.js file will use to configure the
// express application

module.exports = function(app){

    // Register and configure the handlebars templating engine
    app.engine('html', handlebars({ 
        defaultLayout: 'main',
        extname: ".html",
        layoutsDir: __dirname + '/views/layouts'
    }));

    // Set .html as the default template extension 
    app.set('view engine', 'html');

    // Tell express where it can find the templates
    app.set('views', __dirname + '/views');

    // Make the files in the public folder available to the world
    app.use(express.static(__dirname + '/public'));

    // Parse POST request data. It will be available in the req.body object
    app.use(express.urlencoded());

};

Usamos el motor de plantillas handlebars para nuestras vistas (con la ayuda de esta biblioteca de adaptadores), porque es fácil de escribir y admite vistas de diseño. Un diseño nos permitirá compartir un diseño común para todas nuestras páginas, lo cual es un gran ahorro de tiempo. El código anterior también usa el middleware de conexión estática para servir los archivos en /public carpeta. Esta es la mejor manera de hacer que todos los activos del sitio sean accesibles desde un navegador web.

El siguiente archivo es index.js , que une todos estos módulos e inicializa una nueva aplicación Express.js para nosotros:

index.js

/**
 * This is the main file of the application. Run it with the
 * `node index.js` command from your terminal
 *
 * Remember to run `npm install` in the project folder, so 
 * all the required libraries are downloaded and installed.
 */ 

var express = require('express');

// Create a new express.js web app:

var app = express();

// Configure express with the settings found in
// our config.js file

require('./config')(app);

// Add the routes that the app will react to,
// as defined in our routes.js file

require('./routes')(app);

// This file has been called directly with 
// `node index.js`. Start the server!

app.listen(8080);
console.log('Your application is running on http://localhost:8080');

¡Excelente! ¡Nuestra aplicación va tomando forma! Para iniciarlo, ejecute el comando node index.js y el servidor comenzará a escuchar en puerto 8080 . Sin embargo, si intenta abrir http://localhost:8080 en su navegador en este punto, solo verá mensajes de error para los archivos de plantilla faltantes. Esto se debe a que aún no hemos escrito nuestras opiniones.

Las Vistas

La primera vista que crearemos es el diseño. Este archivo definirá el HTML común que comparten las otras páginas de nuestro sitio. Su aplicación puede tener más de un diseño (por ejemplo, si desea tener diseños separados para su página de inicio y para sus pantallas de administración), pero aquí solo tendremos uno.

vistas/diseños/principal.html

<!DOCTYPE html>
<html>

    <head>
        <meta charset="utf-8"/>
        <title>Node.js Picture Voting Game</title>

        <meta name="viewport" content="width=device-width, initial-scale=1" />

        <link href="http://fonts.googleapis.com/css?family=Open+Sans:300,700" rel="stylesheet" />
        <link href="css/styles.css" rel="stylesheet" />

    </head>

    <body>

        <header>
            <h1><span class="green">Cute</span> or <span class="red">NOT?</span></h1>
            <h2>A Node.js Voting Game</h2>
        </header>

        {{{body}}}

        <footer>
            <a class="tz" href="https://tutorialzine.com/2014/01/nodejs-picture-voting-game-part-1/">Tutorial: Node.js Picture Voting Game</a>

    </body>
</html>

El {{{body}}} la etiqueta se reemplaza automáticamente por el HTML de las otras vistas que usan este diseño. Aquí está el código HTML específico de la página de índice:

vistas/inicio.html

<nav>
    <a class="active" href="./">Game</a>
    <a href="./standings">Standings</a>
</nav>

{{#if photo}}

    <img src="photos/{{photo.name}}" width="530" height="420" alt="Cat Picture" />

    <div class="button-holder">
        <form action="./cute" method="post">
            <input type="hidden" name="photo" value="{{photo.name}}" />
            <input type="submit" value="Cute!" />
        </form>
        <form action="./notcute" method="post">
            <input type="hidden" name="photo" value="{{photo.name}}" />
            <input type="submit" value="Not Cute!" />
        </form>
    </div>

{{else}}

    <h3>No more photos to vote on! Check out the <a href="./standings">standings</a>.</h3>

{{/if}}

Las plantillas de manubrios pueden tener construcciones if/else, bucles y muchas otras características que le permiten escribir HTML limpio. Y aquí está la plantilla para la página de posiciones:

vistas/clasificaciones.html

<nav>
    <a href="./">Game</a>
    <a class="active" href="./standings">Standings</a>
</nav>

{{#if standings}}

    <ul>

        {{#each standings}}

        <li>
            <img src="photos/{{name}}" alt="Cat picture thumbnail" />
            <p class="up">{{this.likes}}</p>
            <p class="down">{{this.dislikes}}</p>
        </li>

        {{/each}}

    </ul>

{{/if}}

Mediante el uso de plantillas, podemos separar el código para presentar los datos de los datos mismos. Puede usar muchos motores de plantillas diferentes en su aplicación web express.

¡Hemos terminado!

¡Con esto, nuestro juego de votación de imágenes de Node.js está completo! Puede mejorarlo con algunos de los innumerables módulos y bibliotecas de node.js y modificarlo de la forma que desee. ¡Espero que hayas encontrado útil este tutorial! Si tiene alguna sugerencia, tráigala a la sección de comentarios a continuación.