Tutorial node.js + express + jquery (III): Usando jQuery

Actualización (octubre 2015): Este tutorial está actualizado a la versión de express 3.0.0rc3. Si intentas seguirlo con versiones anteriores o posteriores, puede que tengas que ajustar algunas cosas. Para tener información detallada sobre versiones actualizadas de node.js y express, te recomiendo que le eches un vistazo a Express in Action: Node applications with Express and its companion tools, donde se explica de una forma bastante amena cómo crear aplicaciones web usando Node.js y Express.

Este post forma parte de una serie de varios:

A estas alturas el tutorial ya tenemos la aplicación creada y hemos generado nuestra vista principal. En realidad, la vista que hemos generado no sólo es nuestra página principal, sino que también es nuestra única página. A la hora de desarrollar una aplicación web, cada vez es más frecuente que sólo exista una única página que se modifica dinámicamente usando javascript e intercambiando información con el servidor mediante AJAX. Aunque esta técnica tiene sus problemas, es el sistema que vamos a emplear en Mega Facts, nuestra aplicación de ejemplo, para demostrar cómo se puede usar jQuery para hacer todo eso. Esto además nos va a permitir ver algunos aspectos interesantes de express.

Os recuerdo como era nuestra pantalla principal:

Mockup de la página de Mega Facts

Las dos funcionalidades que vamos a implementar son:

  • Al pulsar sobre el nombre de un héroe, mostraremos sus facts en la parte central de la página.
  • Mientras vemos los facts de alguien, podemos añadir un nuevo fact escribiéndolo y pulsando Añadir.

Integrando jQuery en nuestra página

Antes de empezar, hemos de instalar jQuery. Para ello, podemos descargar la versión que deseemos de la web de jQuery y la copiaremos en la carpeta ./public/javascripts/. Recordad que esa carpeta es la que teníamos configurada como carpeta para servir contenido estático desde express. Como alternativa, podríamos usar jQuery desde cualquiera de las CDN que distribuyen jQuery.

En nuestra página index.jade vamos a añadir la referencia a jQuery y nuestro propio script. Para ello añadiremos al principio de nuestra página un par de líneas dejándola así:

extends layout

block content

  script(src="/javascripts/jquery-1.6.4.min.js")
  script(src="/javascripts/view.index.js")

  #left-column.column
    h2 Elige tu héroe
    ul#heroes
      each hero in heroes
        li(class='hero-name')
          a(href='#') #{hero}

  #right-column.column
    h2 Facts
    ul#facts
    label(for="new-fact") Añadir nuevo fact:
    textarea#new-fact
    a(id='add-new-fact', href='#') Añadir

La primera línea es para referenciar jQuery y la segunda para incluir nuestro propio código. Podríamos haber incluido el código inline en el propio fichero jade, y de hecho habría sido más eficiente porque ahorraríamos una petición al servidor y todo eso, pero me gusta más mantener las cosas separadas porque me parece un poco más claro. Además no tengo ningún editor que no se líe al mezclar jade con javascript y me gusta que me coloreen la sintaxis :-)

El fichero view.index.js es donde escribiremos todo el código de la parte cliente de nuestra aplicación. Deberemos crearlo en la carpeta ./public/javascripts para que express lo sirva como contenido estático e inicialmente su aspecto será algo así:

$(function() {
  // Aquí añadiremos nuestro código
});

Esta es la forma de ejecutar con jQuery una función cuando la página está lista, es decir, cuando ya se ha cargado todo el html y por tanto puedo acceder a cualquier elemento del DOM.

Dentro de esa función iremos añadiendo nuestro código para implementar las dos funcionalidades que enumeramos antes.

Sirviendo JSON con express

Lo primero que vamos a implementar es la descarga de los facts de alguien al pulsar sobre su nombre. Lo que vamos a hacer es que cuando se acceda a la url /hero/nombre/ express nos mande un objeto JSON con todos los facts asociados al héroe con ese nombre.

Empecemos por la parte cliente. Necesitamos:

  • Engacharnos al evento click de los enlaces de los héroes.
  • Cuando se dispare el evento, descargar los datos del servidor.
  • Cuando tengamos los datos, cambiar el contenido de la lista con id facts por los datos descargados.

Esto implica añadir el siguiente código dentro de la función de inicialización que veíamos un poco más arriba:

$('#right-column').hide();

$('li.hero-name a').click(function() {

  var name = $(this).text();
  
  $('#right-column h2').text(name);

  $('#facts li').remove();
  
  $.getJSON('/hero/' + name, function(data) {
    for (var i = 0; i < data.length; i++) {
      $('<li>').appendTo('#facts').text(data[i]);
    }
  });
  
  $('#right-column').show();
  
  return false;
});

Si nunca has visto nada de jQuery, existe multitud de tutoriales mucho mejores que éste para aprender, pero por recordar un poco… jQuery se basa mucho en los selectores css. Lo más habitual es usar la función jQuery, o su alias $, para seleccionar a un conjunto de elementos html a partir de su selector css y luego ejecutar algo sobre ellos.

El código se engancha al evento click de los enlaces usando:

$('li.hero-name a').click(function() {
  ...
}

A continuación descarga los datos del servidor:

$.getJSON('/hero/' + name, function(data) {
    ...
  });

Y por último los añade a la lista de facts:

for (var i = 0; i < data.length; i++) {
  $('<li>').appendTo('#facts').text(data[i]);
}

Ahora llega la parte interesante, ¿cómo hacemos para que express responda a la petición a /hero/name? Pues para eso tenemos que empezar definiendo una nueva ruta en el fichero app.js:

app.get('/hero/:name', routes.hero);

La manera de definir rutas en express es muy sencilla. Usando el método get() indicamos que queremos atender peticiones HTTP GET en la ruta que pasamos como primer parámetro, y que para atenderlas usaremos la función pasada como segundo parámetro.

express permite usar marcadores al definir la ruta a los que luego podremos acceder a través del objeto request.params. Esos marcadores se definen colocando dos puntos (:) delante del nombre del marcador. En este caso estamos definiendo el marcado :name donde irá el nombre del héroe del que queremos obtener los facts.

La función hero la vamos a definir dentro del módulo routes, que como recordaréis está implementado en el fichero routes/index.js. Es una función muy simple cuyo código es así:

exports.hero = function(req, res) {
  var facts = _(heroes).detect(function (p) { 
    return p.name == req.params.name;
  }).facts;
  res.json(facts);
}

Algunos apuntes sobre esta función (parte ya lo vimos en el capítulo anterior del tutorial, pero no está de más recordarlo):

  • Al añadirla al objeto exports estamos añadiéndola a las funciones exportadas por el módulo.
  • En req.params.name accedemos al parámetro que hemos definido antes con un marcador en la ruta.
  • Estamos usando la libería underscore para buscar por nombre del array de héroes. detect() es equivalente al Select() de linq.
  • Para devolver JSON, lo único que tenemos que hacer es usar el método json() del objeto response.

Con esto, nuestra aplicación ya debería ser capaz de mostrar los facts cuando vamos pulsando en los distintos héroes. Ya tenemos hecha la mitad de lo que queríamos implementar.

Recibiendo JSON en express

Vamos con la segunda parte. Queremos añadir nuevos facts escribiéndolos en el textarea que aparece a la izquierda y pulsando en Añadir.

Nuevamente vamos a empezar por la parte cliente. Añadimos el siguiente código dentro de la función de inicialización:

$('#add-new-fact').click(function() {
  
  var name = $('#right-column h2').text();
  var fact = $('#new-fact').val();

  $.ajax({
    type: "POST",
    url: "/hero/add-fact",
    data: JSON.stringify({ name: name, fact: fact }),
    contentType: "application/json; charset=utf-8",
    dataType: "json",
    success: function(data) {
      $('<li>').appendTo('#facts').text(fact);
      $('#new-fact').val('');
    },
    error: function(err) {
      var msg = 'Status: ' + err.status + ': ' + err.responseText;
      alert(msg);
    }
  });

  return false;
});

Estamos enganchándonos al click del enlace con id add-new-fact y obteniendo, por una parte el nombre del héroe y el nuevo fact a añadir:

var name = $('#right-column h2').text();
var fact = $('#new-fact').val();

Esos datos los enviamos a la URL /hero/add-fact serializados en JSON mediante un POST:

$.ajax({
  type: "POST",
  url: "/hero/add-fact",
  data: JSON.stringify({ name: name, fact: fact }),
  contentType: "application/json; charset=utf-8",
  dataType: "json",
  ...
});

Como veis, estamos enviando un objeto con dos propiedades, name, que contiene el nombre del héroe al que vamos a añadir un fact, y fact, que contiene el fact propiamente dicho. Es importante usar el método JSON.stringify en lugar de construir el string JSON a mano, porque ese método se encargar de lidiar con caracteres de escape que pudieran ser necesarios si, por ejemplo, fact contuviera una comilla simple (‘) o doble («).

Si el envío funciona correctamente, añadimos el nuevo fact a la lista de facts y limpiamos el textarea. Si por el contrario hay algún problema, mostramos un mensaje de error. Podríamos mostrar el error con jQuery UI, pero por mantener el ejemplo simple vamos a usar un triste alert:

success: function(data) {
  $('<li>').appendTo('#facts').text(fact);
  $('#new-fact').val('');
},
error: function(err) {
  var msg = 'Status: ' + err.status + ': ' + err.responseText;
  alert(msg);
}

¿Cómo recibimos los datos en la parte del servidor? Fácil. Añadimos una nueva ruta en el fichero app.js:

app.post('/hero/add-fact', routes.addFact);

En este caso la ruta la añadimos con el método post() porque queremos atender peticiones HTTP POST. Además, como se trata de un POST, vamos a recibir todos los parámetros a través del cuerpo de la petición, por lo que no tenemos ningún marcador como los que teníamos antes.

En el fichero ./routes/index.js debemos añadir la función para manejar esta ruta:

exports.addFact = function(req, res) {
  var hero = _(heroes).detect(function(p) {
    return p.name == req.body.name;
  });
  
  hero.facts.push(req.body.fact);  
  res.json({status: 'ok' });
}

La única cosa interesante que tenemos en esta función con respecto a la función hero() que definimos hace un poco es que ahora en lugar de acceder a los parámetros mediante la URL, con req.params, tenemos que acceder a los parámetros que nos envían en el cuerpo del POST.

Gracias al middleware express.bodyParser() que dejamos configurado en Express, la propiedad req.body ya contiene el resultado de parsear el JSON que hemos enviado desde jQuery, por lo que podemos acceder directamente a las dos propiedades del objeto: name, con el nombre del héroe, y fact, con el fact a añadir.

Conclusiones

Con esto ya tenemos toda la aplicación implementada. El código completo lo puedes encontrar en mi cuenta de github.

Prácticamente hemos terminado este tutorial. El próximo post será el último y veremos un pequeño resumen de las partes que hemos tocado y lo que hemos dejado en el aire.

Último capítulo: próximos pasos.

9 comentarios en “Tutorial node.js + express + jquery (III): Usando jQuery

  1. Pingback: Tutorial node.js + express + jquery (I): Creando la aplicación « Koalite's blog

  2. Me ha gustado tu introducción a node.js + express. Para mí, que estoy empezando, me ha servido de mucho. Muchas gracias.
    Tan solo he tenido un pequeño problema al seguirlo relacionado con la importación del módulo «underscore». No he visto que mencionaras en ningún sitio que hay que poner un «require(‘underscore’)» y lo he necesitado para que me funcionara. En mi caso lo he puesto en routes/index.js pero no se si se debe poner en algún otro sitio.
    Sin más.
    Un saludo y gracias

  3. ismael, gracias por el comentario.

    Sobre el require(‘underscore’), efectivamente hace falta ponerlo en el fichero routes/index.js. De todas formas es posible que se me haya pasado algún paso más, pero puedes ver el código completo (y funcionando) en el repositorio de github.

  4. Hey!! muchisimas gracias por el tutorial, la verdad había buscado en varias partes y el tuyo fue el mas completo y mejor explicado.

  5. Hola, tengo problemas cuando añado las lineas:
    script(src="/javascripts/jquery-1.6.4.min.js")
    script(src="/javascripts/view.index.js")

    Cuando las quito la pagina me carga normal y cuando las pongo garga solo la pagina en blanco, no carga nada del «block context»
    Sin embargo descargué el proyecto que hiciste y funciona perfectamente.
    Me pueden ayudar. Gracias

  6. rafael garcia dijo:

    que tal amigo felicidades, la verdad es lo que estaba buscando, solo una cosa que tal quiero hacer una api un poco mas grande con unos 3 modulos, cliente , usuario , productos.
    y quiero tener una carpeta por modulos , y dentro de cada carpeta sus metodos y todo el código que necesita cada modulo
    cliente/
    –clientes.js(aqui tengo los metodos guardar, modificar y eliminar cliente)
    producto/
    –produtos.js(aqui tengo lo metodos guardar,modificar y eliminar producto)
    usuario/
    –usuario.js(aqui tengo lo metodos guardar,modificar y eliminar usuario)
    aparte tener una carpeta para funciones generales
    js/
    –funciones.js
    –bootstrap.js
    –etc.

    y solo con html.

    hay alguna manera de que puedas explicar como acceder a todas esas rutas ?

    de antemano gracias por tu aporte, espero me puedas responder

    saludos

  7. Hola Rafael,

    Yo también prefiero organizar el código por funcionalidades (https://blog.koalite.com/2014/10/formas-de-organizar-el-codigo/), para hacerlo así con express, la manera más sencilla es basarse en los módulos de node (https://nodejs.org/api/modules.html) y usar require entre ellos:

    Fichero cliente/clientes.js:

    module.exports = {
      guardar: function() {...},
      borrar: function() {...}
    }
    

    Fichero donde configuras las rutas (por ejemplo, app.js):

    var clientes = require('./cliente/clientes.js');
    
    app.get('/clientes/guardar', clientes.guardar);
    app.get('/clientes/borrar', clientes.borrar);
    

    Un saludo,

    Juanma.

  8. Buenas tardes, está genial tu página ! muchas gracias por todo el conocimiento compartido.

    Tengo una consulta, con un colega estamos haciendo una api rest con Nodejs + express + mongodb.
    Necesitamos que usuarios suban una imagen al server pero que puedan realizar un crop de la imagen en el front end ,
    como podria ser?

Comentarios cerrados.