viernes, 30 de agosto de 2013

Ejemplo lista de tareas con Marionette

Marionette
Como comentaba en el ejemplo de una aplicación de una lista de tareas en una aplicación javascript Backbone es una herramienta que nos puede ayudar mucho a evitar que el código se nos convierta en difícil de manejar facilitándonos como estructurarlo con los modelos, colecciones y vistas y permitiéndonos separar el modelo de la vista actualizando estas últimas a través de los eventos que produce el modelo y escuchados en las vistas.

Backbone es una herramienta que nos da bastante libertad en cuanto a como queremos hacer las cosas y de lo que ofrece podemos usar solo lo que queramos. En algunos casos podemos considerar que Backbone ya es de por si una solución suficiente pero pero en otros podemos necesitar algo que nos facilite la tarea un poco más, de hecho hay muchas herramientas de terceros que proporciona multitud de funcionalidades adicionales sobre Backbone.

Usando Backbone por si solo a medida que vayamos haciendo varias aplicaciones nos daremos cuenta que debemos escribir código que empezaremos a considerar repetitivo, tampoco nos ayudará en desasociar los modelos de las vistas cuando eliminemos estas de la aplicación y si no lo hacemos correctamente tendremos fugas de memoria o vistas zombies que siguen escuchando eventos de los modelos cuando esperamos que se hubiesen destruido, también debemos encargarnos de gestionar donde deben visualizarse las vistas. Marionette es una de esas herramientas que nos puede ayudar a facilitarnos las tareas anteriores que en Backbone debemos hacer manualmente, además de ofrecernos algunas otras funcionalidades adicionales, pero también sin obligarnos a usar lo que no queramos siguiendo la misma filosofía de Backbone.

En esta entrada implementaré el mismo ejemplo de la lista de tareas que hice con Backbone pero esta vez implementado con Marionette aunque revisándolo incluiré un par de funcionalidades adicionales como externalizar del html las plantillas de las vistas y como internacionalizar los textos que aparecen en ellas si necesitamos soportar múltiples idiomas. Para ello utilizaré una de las versiones «bundled» que ofrece Marionette y un par de dependencias que necesita (wreq y babysiter).

El modelo y la colección de tareas es muy parecido aunque se han simplificado un poco al moverse el moverse el código que convertía el modelo a json a la vista, donde Marionette ofrece y llama al método serializeData cuando necesita y posteriormente el método render con los datos obtenidos, en el método render en definitiva se usa Mustache para producir el html de la vista. En realidad, el convertir el modelo al json que necesita a la vista está mejor colocado en la vista, ya que la vista es la que conoce los datos que necesita mostrar del modelo. Otra cosa muy útil que nos ofrece Marionette es la sección con los elementos ui de las vistas, esto evitará que tengamos los sectores de jquery repartidos por varios sitios de la vista y hará el código más simple y legible. Las secciones de las vistas modelEvents y collectionEvents sirven para los mismo que hacíamos en el método initialize con .on o .listenTo de Backbone.

Los objetos Marionette.ItemView sirven para visuaslizar un modelo (una tarea) y Marionette.CollectionView sirve para mostrar una colección de elementos (una lista de tareas). El objeto Marionette.Layout se utiliza para componer una vista con varias secciones (tareas y estado) donde podremos colocar las vistas de la aplicación, aparte de estas secciones un Layout es como una vista Marionette.ItemView con su plantilla. Con Marionette.Controller construiremos una clase que podemos utilizar para ofrecer la interfaz de la aplicación al exterior del módulo con únicamente los métodos necesarios (sin initialize, render y los métodos onXxx del mismo ejemplo con Backbone). Y hasta aquí, son los cambios más importantes en cuanto al cambio de Backbone a Marionette.

A continuación el código del módulo tareas que contiene la mayor parte del código javascript de la aplicación y el módulo main que no cambia en nada.

Para externalizar las plantillas del html podemos usar el plugin de RequireJS text que permite cargar el contenido de archivos como dependencias de un módulo tal como sucede como los archivos javascript. Y para internacionalizar las cadenas de las plantillas el plugin i18n. Los literales de cada idioma se definen en un archivo distinto, en el caso de esta aplicación solo he proporcionado uno, para el idioma inglés en la carperta src/main/webapp/js/i18n/nls/en, cada uno de estos archivos contiene una clave/valor, algunos de los cuales son una plantilla de Mustache para sustituir las variables como en «{{completadas}} tareas de {{total}} completadas». A continuación el código de un plantilla de Mustache que se carga de forma externalizada y el archivo con los literales que se utilizarán en la localización por defecto. Estos dos plugins de RequireJS probablemente nos sean necesarios y útiles en un ejemplo real.

El idioma de la aplicación se especifica en el archivo que genera el html de la página.

La aplicación tiene el siguiente aspecto:


Para terminar y como prólogo de la siguiente entrada un detalle que comentaré es la cantidad de archivos que se cargan para este ejemplo y eso que no es excesivamente complejo, en total al cargar el ejemplo se hacen 31 peticiones, unas cuantas son de los estilos, fuentes e imágenes pero la mitad son de archivos js y plantillas mustache. En la siguiente entrada explicaré como optimizarlo para conseguir reducir esas 31 peticiones a 13, con una diferencia importante de tiempo de carga y esto en local (probablemente puesto el ejemplo en internet la latencia y la diferencia sería mayor), también conseguiremos reducir algunos kilobytes de peso a la página. La optimización será similar a lo que explique en la Optimización de módulos RequireJS y archivos javascript del ejemplo más sencillo de la Introducción a Backbone pero aplicado a este ejemplo de Marionette que es bastante más complejo. En el código fuente puede verse también como ejecutar pruebas unitarias usando Grunt, Jasmine, Sinon y como integrarlo con Gradle pero la expicación de esto será tema para otra entrada.

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.

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
Introducción y ejemplo de Backbone.js
Ejemplo de pruebas unitarias en javascript con Jasmine y Sinon
Backbone
Marionette