viernes, 26 de abril de 2013

Ejemplo lista de tareas con Backbone, RESTEasy y Tapestry

Backbone.js
Normalmente escribo artículos con ejemplos que tratan sobre una única tecnología, al tratar cada tecnología por separado entender que puede ofrecer es más sencillo pero en una aplicación real normalmente se usan varias tecnologías combinadas y al mismo tiempo. Esta entrada aunque sigue siendo un ejemplo es mucho más parecido a lo que podría ser una aplicación real que lo que he explicado durante las últimas entradas. En esta serie sobre javascript he explicado un montón de cosas por separado entre ellas RequireJSMustacheBackbone, servicios rest con Tapestry y RESTEasy. En esta entrada haré un ejemplo haciendo uso de todas las anteriores y alguna cosa más adicional como twitter bootstrap.

El ejemplo consiste en una lista de tareas, pudiéndose introducir nuevas tareas y marcarlas como realizadas. También se podrá eliminar de la lista las tareas completadas y ver un resumen con el número de tareas completadas y de tareas totales.

En la parte cliente de la aplicación se hace uso de RequireJS para manejar la carga de los archivos javascript necesarios, de backbone para gestionar la lista de tareas y realizar la comunicación con el servicio REST del servidor y de Mustache como motor de plantillas para generar la vista (el html). En la parte del servidor usa Tapestry como framework web y RESTEasy como librería para implementar un servicio REST que proporcionará la persistencia en memoria de la lista de tareas.

Backbone permite organizar el código aplicando el patrón de diseño MVC (modelo-vista-controlador) ampliamente extendido en los frameworks web. Este patrón de diseño tiene la ventaja que separa la aplicación en tres partes diferenciadas:
  • El modelo: que contiene los datos del dominio que maneja la aplicación que cuando es modificado lanza eventos para que el controlador y la vista actúen en consecuencia.
  • El controlador: que reacciona ante los eventos que se produzcan en el modelo o por el usuario y modifica adecuadamente el modelo o la vista según el evento.
  • La vista: que a partir del modelo produce la interfaz que se ofrece al usuario para que pueda manipularlos, los eventos producidos en la vista son manejados por el controlador.
Como se ve las tres partes se relacionan entre si pero están separadas de forma que cada una se encargue de una tarea. El código queda bien organizado y se evita el código espagueti que podríamos obtener de otra forma.

En una aplicación con la combinación de Backbone, Mustache y un servicio REST el servidor devuelve el html de la página inicial, las plantillas y los datos del servidor y se delega en el navegador del usuario la tarea de renderizar el html final. Esto tiene la ventaja de que entre el navegador y el servidor viajan menos datos (los datos y las plantillas tendrán menos tamaño que los datos formateados a html) y la parte del servidor se simplifica (no es necesaria la lógica para formatear a html los datos que dependiendo del framework hacen los jsp, tml de tapestry o gsp de grails). Aunque el código de la parte cliente crecerá, el código de la parte del servidor se hará más simple. Una vez cargada la página inicial entre el servidor y el cliente solo viajan los datos en formato json.

Veamos el ejemplo a continuación empezando por el modelo. El modelo con Backbone se define con Backbone.Model.extend, donde podremos indicar las propiedades de los objetos, también podemos definir funciones de utilidad. En este caso el modelo estará formado por los objetos Tarea que tendrán las siguientes propiedades:
  • Un id que identificará la tarea.
  • La descripción de la misma.
  • Y un indicador de si está completada.
Pero en la lista de tareas no solo tendremos un objeto Tarea, tendremos una colección de ellas. Esta colección también forma parte del modelo y se define con Backbone.Collection.extend, los elementos de esta colección serán objetos del modelo Tarea y lo indicamos en la propiedad model, también podemos definir funciones de utilidad como una función para obtener un array con las tareas completadas y otra para eliminar las tareas completadas que nos interesarán para ofrecer la funcionalidad de la lista de tareas. Las propiedades urlRoot y url son importantes para que backbone sepa localizar el servicio REST que proporcionará la persistencia.

La interfaz del servicio web REST es:

En Backbone la vista y el controlador de define en una misma entidad con Backbone.View extend. Aunque se definen ambas cosas a la vez la vista principalmente está formada por el método render, el resto será parte del controlador. Otra sección importante de la vista es la propiedad events donde indicaremos que eventos manejará el controlador y sobre que elementos de la vista, básicamente se define como clave el evento (click, change, ...) y un selector similar a los usados en jquery y como valor el nombre de la función que manejará el evento. En la vista también podremos definir métodos de utilidad. Tendremos dos vistas: una para para una tarea y otra para la aplicación de la lista de tareas.

En el método render se hace uso de Mustache para generar la vista a partir de una plantilla que contiene el código html, la plantilla combinado con los datos del modelo genera un resultado que será incluido en el html de la página. En las funciones initialize se inicializa la vista principalmente para definir que eventos que produzca el modelo serán tratados por el controlador, estos son las funciones on sobre el modelo, por ejemplo, nos interesará saber que se añade, modifica o elimina una tarea de la colección de tareas para actualizar la vista adecuadamente.

En el caso de TareasApp en el método initialize además se inserta en el elemento «el» que se le pasa en el constructor la vista inicial de la aplicación. De esta forma podríamos crear varias instancias de TareasApp.

Las plantillas de Mustache las he definido entre elementos <![CDATA[ ... ]]> ya que las plantillas de Tapestry han de ser xhtml válido, algunas expansiones de Mustache como {{attrs.checked}} de la plantilla tarea-template Tapestry no las entiende como válidas ya que realmente la plantilla Mustache en este caso no es html válido.

Una vez desarrollada la parte del servicio REST en el servidor con RESTEasy integrarlo con Backbone es muy sencillo, únicamente deberemos llamar en los puntos adecuados a las funciones save y destroy de los modelos. Al método save se le llama cuando se añade una nueva tarea en TareasApp.addTarea y cuando se modifica en TareaView.onChangeCompletada. Cuando se hace clic en el botón para limpiar las tareas completadas se llama a la función destroy de cada uno de los modelos. Estos métodos lanzarán las peticiones AJAX al servicio REST de forma automática variando su método y con su correspondiente url según hemos definido con las anotaciones del servicio REST: POST (crear), PUT (actualizar) y DELETE (eliminar).

Petición al crear una nueva tarea
Petición al marcar como completada una tarea
Petición al limpiar tareas completadas
La carga de inicial de la lista de tareas puede hacerse de dos formas: devolviendo los datos de las tareas cuando se pide la página o una vez cargada la página mediante una petición AJAX adicional. En Backbone recomiendan la primera ya que así se evitan peticiones al servidor innecesarias. Además aunque hagamos que las peticiones AJAX sean rápidas probablemente sean lo suficientemente lentas para que el usuario vea como se va rellenando el contenido de la página y dependiendo del html verá como los elementos van cambiando de posición lo cual puede ser molesto. Aunque hay que decir que incluir los datos en la propia página impediría cachear la página. Al final de la página Index.tml, está la sección para cargar la lista inicial de tareas que se pasará a TareasApp después de su constructor en el método resetTareas.

Esta es una captura del ejemplo:

Después de haber usado Backbone en este ejemplo simplemente tengo que decir que es una gran herramienta y que facilita y ayuda a organizar el código javascript en gran medida. Permite separar los datos de la aplicación que forman el modelo del controlador y la vista que reaccionan mediante los eventos producidos en el modelo. También permite desarrollar aplicaciones con un gran peso de javascript en la parte cliente sin que el código se convierta posteriormente en un infierno de mantenimiento aún con la ayuda de jQuery. Es una ayuda tan grande para hacer algunas cosas de la interfaz del cliente como lo es jQuery para manipular los elementos html.

Algunas partes no las he explicado como las plantillas de Mustache, el uso de RequireJS o la parte del servidor del servicio RESTEasy ya que ya lo que he hecho en entradas anteriores que puedes visitarlas mediante sus enlaces.

Como en el resto de entradas el código fuente completo lo puedes encontrar en mi repositorio de GitHub. Si quieres probarlo en tu equipo lo puedes hacer de forma muy sencilla con los siguientes comandos y sin instalar nada. Si no dispones de git para clonar mi repositorio de GitHub puedes obtener el código fuente del repositorio en un archivo zip con el anterior enlace.

En la siguiente entrada puedes ver el mismo ejemplo pero implementado con Marionette que simplifica algunas cosas respecto a Backbone. Además en el ejemplo de Marionette puedes ver como conseguir internacionalización y como externalizar las plantillas de Mustache del html.

Referencia:
Introducción y ejemplo de RequireJS
Introducción y ejemplo de Mustache
Logging en Javascript con log4javascript
Capturar errores de Javascript
Optimizar módulos de RequireJS y archivos Javascript
Patrón de diseño MVC del lado cliente con Backbone.js