Desarrollo móvil multiplataforma
 

Tutorial de Backbone.js – V. Routers

Tutorial de Backbone.js – V. Routers

Introducción

En la programación web tradicional es normal hacer routing en el servidor y según la URL solicitada ofrecer un contenido u otro. Por ejemplo, una URL como blog.com/post/5 podría significar que debemos mostrar una determinada entrada de un blog.

Las aplicaciones JavaScript basadas en una única página no tienen esta separación de contenidos ya que toda la aplicación se genera en la misma página, por lo que sería necesario disponer de algún mecanismo que nos permitiese emular dicho comportamiento y así poder enlazar secciones o contenidos específicos de nuestra aplicacion.

El mecanismo que se utiliza en estos casos es el uso de los fragmentos de página utilizando la almuadilla (#) como parte de la URL. Siguiendo el ejemplo anterior podríamos generar la siguiente URL: blog.com/#/post/5. En Backbone serán los routers los encargados de ofrecernos esta funcionalidad, llamados controladores en versiones previas a la 0.5 de la librería.

Definición de un Router

Podemos definir un router a través del método extend del objeto Backbone.Router. En él especificaremos el objeto hash routes que contendrá todas las rutas que se atenderán, debiendo contener al menos una. En este objeto hash introduciremos la ruta como key y una función asociada como valor que será la que se ejecutará cuando estemos en dicha ruta.

Enrutador = Backbone.Router.extend({
    routes:{                                     // Ejemplos de coincidencias:
        ""                  : "index"
        "help"              : "ayuda",           // #help
        "tag/:tagid"        : "muestraEtiqueta"  // #tag/perro
        "tag/:tagid/p:page" : "muestraEtiqueta"  // #tag/perro/p5
        "download/*file"     : "descargar"        // #download/path/to/file.txt
 },
    index: function() { /* ... */ },
    ayuda: function(){ /* ... */ },
    muestraEtiqueta: function(tagid, page){ /* ... */ },
    descargar: function(file){ /* ... */ }
 });

Si nos fijamos en el ejemplo anterior, podremos observar que hemos definido algunos parámetros dinámicos dentro de las rutas. Backbone ofrece dos tipos de variables para especificar variables dentro de una URL. Por una parte estarían las variables de tipo “:param” que captura todo el contenido de la URL desde su definición hasta la próxima barra (/) de la URL; por otra parte estarían las variables de tipo “*param” que capturan todo el contenido de la URL a partir de su definición, por lo que se deben utilizar siempre al final de la definición de la ruta. Las secciones capturadas por las variables se pasan como parámetros de la función asociada a la ruta.

Este mecanismo de definición de variables es bastante útil por su sencillez, pero si necesitamos más potencia a la hora de especificar una ruta podemos hacer uso de las expresiones regulares. Para definir una ruta mediante una expresión regular podemos utilizar el método route:

Enrutador = Backbone.Router.extend({
    initialize: function(){
        this.route(/post\/(\d+)/, 'id', function(pageId){/* ... */});
    }
});
var miRouter = new Enrutador();

Esta función permitirá definir una ruta tradicional o una expresión regular en su primer parámetro, asociándosela a la función definida como tercer parámetro. El segundo parámetro de esta función nos permite asociar un nombre de evento a la ruta, de forma que se lanzará el evento route:nombre cada vez que se solicite la ruta especificada.

Especificación Ajax Crawling

Antes de continuar me gustaría incidir en un problema tradicional existente en las aplicaciones JavaScript. Cuando queremos que nuestra aplicación sea interpretable por motores de búsqueda como Google nos encontramos con el problema de que éstos no son capaces de interpretar el contenido dinámico generado por nuestra aplicación, ya que los buscadores no son capaces de ejecutar javascript. Además, cuando utilizamos los fragmentos hash en nuestras URLs de aplicación ninguna de las rutas serán indexadas pues para el buscador se trata siempre de la misma URL.

Para solucionar este problema nació la especificación Ajax Crawling, que nos permite detectar cuando un contenido específico está siendo solicitado por un buscador y así tener la oportunidad de generar el correspondiente contenido estático desde el servidor.

Esta especificación exige que antepongamos el signo ! tras la amuadilla (#) y delante de cualquier ruta hash dinámica. Un ejemplo claro sería twitter:

http://twitter.com/#!/almarinero

Esta exclamación avisará al motor de búsqueda que nuestra aplicación está preparada para la especificación Ajax Crawling, y traducirá la solicitud a la siguiente URL:

http://twitter.com/?_escaped_fragment_=/maccan

De esta forma, desde el servidor podremos capturar la ruta que nos solicita el buscador examinando la variable GET _escaped_fragment_ y poder generar directamente la versión estática de dicha ruta.

Si decidimos seguir esta especificación, tendremos que tener en cuenta el signo ! en nuestras rutas a la hora de definir nuestro router.

Enrutador = Backbone.Router.extend({
    routes: {
        "!/post/:title": "post" // #!/post/tutorial-backbone-js
    }
    // ...
});

Backbone.history

Una vez definidos todos nuestros routers con todas sus rutas asociadas, es necesario que alguna entidad se encargue de monitorizar las solicitudes realizadas dentro de la aplicación para capturar los eventos hashchange (actualización de fragmento hash) del navegador y así poder aplicar la ruta oportuna y ejecutar las funciones asociadas. Esta entidad es Backbone.Hostory, y debemos crearla tras definir todos nuestros routers invocando al método start([options]):
Una vez que hayamos definido todas las rutas de nuestra aplicación en uno o más routers, debemos iniciar

Backbone.history.start();

Si nuestra aplicación no empieza desde el la URL raíz /, podemos indicárselo a través de la propiedad root:

Backbone.history.start({root: '/app/home'});

Ojo, para poder utilizar Backbone.history deberás haber creado al menos una instancia de los routers que hayas definido.

Resumen

En esta lección hemos aprendido a utilizar los routers para segmentar nuestra aplicación y poder asociar funcionalidades específicas a cada URL almuadilla, simulando el funcionamiento de una aplicación web tradicional y facilitando la navegación y el anclaje de contenidos. También hemos visto la especificación Ajax Crawling que nos indica cómo hacer hacer aplicaciones javascript accesibles por buscadores.

En el siguiente y último tema veremos cómo comunicarnos con el servidor y poder sincronizar nuestros modelos.

  1. Rafa February 6, 2012

    Hola, muchas gracias por este tutorial y se agradece mucho en el idioma en que esta escriot.

    Estoy probando de aplicar Backbone en una aplicacion Rails.

    Cuando que inicia
    i=Backbone.history.start({pushState: true});

    con el siguiente router:

    var Test_Route = Backbone.Router.extend({
    routes: {
    “/user/users/:userid/mails/new”: “mailnew”,
    “/”: “home”
    },

    mailnew: function( userid ){
    var S3i_mailnewview = new MailNewView;
    },

    home: function(){
    $(“#main”).empty();
    }

    });

    var test_app = new Test_Route;

    Preguntas:

    .- Backbone esta esperando que las rutas en los link este el caracter “#” o lo inserta backbone al ejecutar history.start()?

    Muchas gracias.
    Rafa.

    • Alfonso Marín February 12, 2012

      Hola Rafa.

      Lo primero, aclararte que Backbone.history.start debes invocarlo despues de definir y crear todos los routers, es decir, en el ejemplo que me has puesto deberías ponerlo justo al final de todo.

      Respecto a lo que me comentas del caracter “#”, tus enlaces deberán llevar la almohadilla siempre que no definas {pushState: true} en Backbone.history.start(). Es decir, si no utilizamos la funcionalidad pushState de HTML5, todos nuestros enlaces deben ir con con # en los href de nuestros enlaces y backbone los interpretará correctamente.

      Por otra parte, si deseamos utilizar la funcionalidad pushState de HTML5, no tendremos que incluir el # en nuestros enlaces, PERO, tendremos que capturar los eventos click() para invocar a la función ‘navigate’ de nuestro router, ya que de otra forma el navegador recargaría la página completa al tratarse de un enlace normal. Por ejemplo, una aproximación muy simplificada sería:

      $('a').live('click', function(e){
         test_router.navigate($(this).attr('href'), {trigger:true});
          return false;
      });

      La opción trigger la especificamos para que se ejecute la función del router que hayamos definido.

      Espero que te haya quedado claro.

      Un saludo

  2. sebastian March 27, 2012

    Hola , esto es ie7 +??

  3. Diego February 27, 2013

    Hola Alfonso!

    Muchas gracias por el tutorial, es un excelente material de apoyo para los que estamos incursionando en el mundo Backbone, se nota que estás bien familiarizado con el framework!

    Estoy haciendo un sitio donde cargo cada página de forma dinámica a través de ajax y coloco su contenido en un único div. Ya tengo resuelto el tema de los routers y la navegación me está funcionando bien. El problema que tengo es que en cada página utilizo distintos plugins de jQuery (como galerías de imágenes por ejemplo) y cada vez que cargo una página, los plugins no funcionan.

    En definitiva lo que necesito es “montar” y “desmontar” los scripts correspondientes a cada página que cargo por ajax. ¿Tienes alguna idea de cómo lograr esto?

    Por otra parte, las URLs las manejo todas con pushState, sin embargo en ie (como no lo soporta), por defecto coloca #, por ejemplo:
    http://misitio.com/#nivel1/nivel2/nivel3.

    En lugar de eso me gustaría que, para aquellos navegadores que no soporten pushState coloque !#/ en lugar de solo #, quedando de esta manera:
    http://misitio.com/!#/nivel1/nivel2/nivel3

    ¿Conoces alguna forma de setear eso?

    Saludos y desde ya muchas gracias por tu ayuda!

  4. Gaspar Fernández March 6, 2013

    Alfonso, otro pequeño error tipográfico. La palabra almohadilla.

  5. Armando Garcia June 16, 2014

    isn’t :
    var miRouter = new Enrutador();