Categories
web development

Talk @ MalagaMakers about deployment

These are the slides for a talk I gave at MalagaMakers Geekbeers on 2015-01-15. The talk was about deployment, glossing about preparing your assets using Grunt for single page web applications, and Capistrano for deployment of Ruby on Rails apps.

Categories
programming web development

Time zones, Rails and PostgreSQL: Setting things clear

After an amazing weekend battling with Rails, PostgreSQL and time zones, visiting a lot of references around the interwebs and trying different database and application configurations I finally came to a working setup, so I wanted to set things clear once and for all, and use it as a reference for my future self and any other that might find this information useful.

The problem

Say you need to store timestamps in your database and you are using Ruby on Rails as your development framework and PostgreSQL as the database.

In PostgreSQL, as it is known by everyone, exist two types for storing timestamps: with time zone and without. The difference, you may ask? Well, the latter stores just a date and a time with no reference to anything else, and the former add to the mix a reference to a geographical zone (the time zone), storing the value with reference to the zero time zone (UTC, GMT, +00 whatever).

It is best shown with an example. Say you have the timestamp “2014-10-06 12:00:00+02” which refers to today noon at time zone UTC+02 or Central Europe Summer Time. If you store this value inside your PostgreSQL database in a timestamp without time zone column, what you will have is just “2014-10-06 12:00:00” with the time zone information ignored. Everything is right, as long as your application is always used in the same place(or time zone).

Have a look now at the date when we store it in a timestamp with time zone column. What PostgreSQL really inserts is this value: “2014-10-06 10:00:00“, that is, the date shifted to UTC. Later, when you retrieve this value from the database, the value is presented in your timezone, which is defined by one configuration parameter in the database driver the client is using to connect.

You might ask, but why messing with time zones if I just want a date and time? Easy, say you are building a blog, and posts have a publication date and time. If you use a timestamp without time zone you would always get the same value from the database, wether you are seeing it from Hawaii or Japan, and it would be slightly strange to see a future publication date if you are in the former location (when it is 12:00:00 in central Europe in the summer time it is twelve hours less in Honolulu, so it’s 00:00:00).

Instead, it you used a timestamp with time zone, the date the user would see would be set to a concrete time zone, and if the user knows what that means (many times it is asking too much), she would be able to translate this time to her own time zone.

So it makes sense to use a type with time zone information, at least to not worry users with bizarre time travel issues.

The solution

Now that we have framed the problem we can observe we have two variables: the time zone on the application server and the time zone of the user connecting to the application. I don’t take into account the time zone of the server’s operating system because PostgresSQL doesn’t care at all about that, the only time zone it cares about is that of the client connection.

So, for Rails to know where it is in respect to time zones there is a configuration parameter in the file “config/application.rb called “config.time_zone” that you can set to your own time zone, or that of the server physical location. You can run “rake -D time” and get the list of available time zones for this parameter. If this parameter were not set, Rails would use UTC as the default time zone, and it is not a problem, unless you need to input dates and times from the user and store them into the database, in which case they would all be shifted to UTC when used in the application, whatever the user time zone is.

With this settings, all the timestamps would be set to the parameter set in the configuration, but the best for a user facing application is to show dates in the user time zone. So if you have registered users, you can have an additional parameter that specifies the user time zone, and you could use that parameter when working with dates and times. The way to use it is setting the user time zone in an around filter and then always manipulating dates and times with “Time.zone“, that gives all the usual methods of the class Time but translated to the user time zone.

around_filter :set_user_time_zone, :if => :the_user
def set_user_time_zone(&block) 
  Time.use_zone(the_user.time_zone, &block)
end

Then, using “Time.zone.now” would give the right time in the user time zone, and everything would be pink unicorns and rainbows.

The warning

ActiveRecord has another parameter for working with dates, times and time zones, and you should never set it if you are working inside a Ruby on Rails application and have “config.time_zone defined, because it would set an additional shift to your dates when they travel to the database, and you will lose several hours trying to know why all your dates are being stored in the wrong time zone (believe me, I’ve been there).

The parameter is “config.active_record.default_time_zone“, the default value is UTC, and it is intended for ActiveRecord only, so if you use Ruby on Rails, you should set it at the default value.

Categories
español programacion web development

Crear una aplicación sencilla (otra vez) con AngularJS

Hola a todos, ha pasado mucho tiempo desde el último post.

En el post anterior os mostraba cómo hacer una aplicación sencilla usando Backbone.js para crear una single page application. Después de un año y muchas cosas de por medio, en mi toolkit de tecnologías web para el frontend he cambiado Backbone.js por AngularJS.

¿Y por qué ese cambio?

Todo empezó por un interés en conocer este framework Javascript, que si bien ya llevaba existiendo un tiempo, comenzaba a hacerse más popular según iba subiendo de versión, y ya se podía considerar algo más estable. Después de hacer las típicas aplicaciones “Hello World”, seguí adentrándome más en su funcionamiento, los componentes y extensiones, y desde hace un tiempo puedo decir que es mi framework de referencia para afrontar cualquier desarrollo de aplicación web (aplicación en sentido moderno, tipo single page app, con todas las modernidades posibles).

Una de las cosas que me disgustaban de Backbone.js era la cantidad de código javascript que había que meter para poder tener un esqueleto de aplicación, y a partir de ahí, continúa escribiendo el código de negocio de tu aplicación. Es cierto que hay componentes para Backbone.js que agilizan determinadas partes del entorno, pero no me encontraba del todo cómodo dependiendo de tantos añadidos externos, que además había veces que me presentaban un conflicto de dependencias, o no estaban tan depurados como hubiera deseado.

Así que, después de varias semanas probando el entorno decidí dar el salto y centrarme en AngularJS, y desde mi opinión personal, encuentro este framework mucho más ágil, con una mejor separación entre la capa de presentación y la de negocio.

No está en mis objetivos dar una introducción a AngularJS en esta entrada, para eso ya existen muy buenos recursos en la parte de documentación de su sitio web. Lo que yo quiero mostrar al lector es cómo empezar una aplicación sencilla que le permita avanzar al siguiente escalón por su cuenta.

Entrando en materia

Así que para mostrar las similitudes y diferencias entre AngularJS y Backbone.js, he pensado en reescribir el programa del post anterior con esta nueva tecnología y que sea el lector/programador el que saque sus propias conclusiones.

El objetivo del programa ya es conocido: se trata de un buscador de vídeos de Youtube en el cual un usuario introduce una cadena de búsqueda y le aparecen los vídeos relacionados con ese texto. Esta aplicación utiliza la API de búsqueda de Youtube, recibe los resultados en formato JSON (que ya comenté en el post anterior), y extrae los datos necesarios para mostrar la información al usuario.

Para realizar esta aplicación deberemos descargar AngularJS desde el sitio web, o bien enlazarlo desde la CDN de Google de librerías Javascript. Actualmente se encuentra en la versión 1.2.5 en su rama estable, así que haremos uso de esta versión, y para no tener que bajar ningún archivo, enlazaremos directamente al código de la CDN.

AngularJS se basa en el modelo MV*, por lo que en una aplicación que utilice AngularJS se encontrarán controladores y vistas, además de otros componentes como servicios, factorías, directivas (ligadas a la parte de presentación), recursos, etc.

En este programa hay dos archivos, uno que define el aspecto de la aplicación y es el que contiene el código HTML y otro que define la lógica de la aplicación y que está escrito en Javascript. A continuación os voy a mostrar el código de los dos archivos y lo voy a comentar línea a línea, explicando cada parte.

El código para la parte de HTML es el siguiente:

<!DOCTYPE html>
<html ng-app="videos">
	<head>
		<meta http-equiv="content-type" content="text/html;charset=utf-8"/>
		<title>Aplicación de ejemplo en AngularJS</title>
		<style type="text/css">
			* {
				font-family: sans-serif;
			}
			.video {
				width: 350px;
				display: inline-block;
				margin: 15px;
				vertical-align: top;
			}
		</style>
	</head>
	<body ng-controller="AppCtrl">
		<input type="text" ng-model="query">
		<button type="button" ng-click="buscar()">Buscar</button>
		<h1>Vídeos de Youtube con AngularJS</h1>
		<div ng-repeat="video in videos" class="video">
			<h2>'{{video.title}}' por {{video.uploader}}</h2>
			<div>
				<iframe width="320" height="240" ng-src="{{videoEmbedURL(video.id)}}" frameborder="0" allowfullscreen></iframe>
			</div>
			<h3>Descripción</h3>
			<div>{{video.description}}</div>
		</div>
		<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.5/angular.min.js"></script>
		<script type="text/javascript" src="application.js"></script>
	</body>
</html>

Las primeras líneas (1-17) no tienen nada de especial, lo único destacable es el atributo ng-app=”videos” que hay en el elemento html. Este atributo se utiliza para indicarle a AngularJS que el contenido de esta página es una aplicación que debe de gestionar, teniendo el control de todos los elementos extra que define el framework. Por lo demás, está la definición de la cabecera, los estilos y el título.

En la linea 18 se encuentra el elemento body, que igualmente tiene otro atributo de AngularJS, en concreto ng-controller=”AppCtrl”. Este atributo le indica a AngularJS que el contenido de ese elemento estará gestionado por el controlador AppCtrl. Este controlador define una serie de funciones y tiene una serie de propiedades que haran que la aplicación de ejemplo funcione correctamente. Más adelante mostraré el contenido del archivo Javascript que es donde está definido, pero por ahora sigamos examinando el HTML.

En las líneas 19-20 tenemos el elemento input que tiene un atributo nuevo llamado ng-model, el cual vincula el contenido de este campo de entrada con una variable del controlador, llamada query, que veremos más adelante, y después el elemento button, que también tiene una propiedad nueva, ng-click, la cual ejecuta dicha función del controlador cuando el botón es pulsado. El código de de esta función lo veremos después, cuando muestre el contenido del archivo Javascript.

Entre las líneas 22-29 tenemos un elemento div con una particularidad, el atributo ng-repeat, el cual se utiliza para repetir el contenido según la iteración descrita: video in videos. Esto instanciará una variable video que iterará sobre cada elemento del array videos definido en el controlador, exponiendo este objeto y sus propiedades al contenido de este div. Por lo tanto, cuando en las líneas posteriores se hace referencia a {{video.title}}{{video.uploader}}, o {{video.description}}, lo que se está haciendo es mostrar el valor de esas propiedades dentro del texto HTML, como reconocerá cualquiera que haya usado una librería de plantillas HTML como handlebars o moustache.

Lo que requiere mayor explicación es la línea 25. El atributo ng-src del elemento iframe se utiliza para proporcionar un atributo src con valores interpolados desde variables de AngularJS. Como el identificador del vídeo lo tenemos en el objeto video, no podemos hacer referencia a esta propiedad directamente en el atributo src, y por eso es por lo que necesitamos el otro atributo. Además se utiliza la función videoEmbedURL() para generar la URL que porporciona el fuente para este iframe a partir del identificador del vídeo, que veremos más adelante en el archivo Javascript.

Y por último quedan los elementos de las líneas 30-31 que cargan, por un lado, el fuente de AngularJS desde la CDN de Google de librerías Javascript y, por otro lado, el fuente Javascript de la aplicación, que vamos a ver seguidamente.

angular.module('videos', []).
	config(['$sceDelegateProvider', function($sceDelegateProvider) {
		$sceDelegateProvider.resourceUrlWhitelist(['http://www.youtube.com/embed/*']);
	}]).
	controller('AppCtrl', ['$scope', '$http', '$log', function($scope, $http, $log) {
		$scope.query = '';
		$scope.buscar = function() {
			$log.log('Iniciando búsqueda de "' + $scope.query + '"');
			$http.get('http://gdata.youtube.com/feeds/api/videos?v=2&alt=jsonc&max-results=9', {params:{q:$scope.query}}).
				success($scope.fetch_ok).
				error($scope.fetch_failed);
		};
		$scope.fetch_ok = function(result) {
			if (result && result.data && result.data.items) {
				$log.log('Resultados recibidos');
				var videos = [];
				angular.forEach(result.data.items, function(item) {
					videos.push({
						id: item.id,
						title: item.title,
						uploader: item.uploader,
						url: item.content['5'],
						description: item.description
					});
				});
				$scope.videos = videos;
			} else {
				$log.log('Los resultados no son válidos');
			}
		};
		$scope.fetch_failed = function() {
			$log.log('La búsqueda ha fallado');
			alert("¡La búsqueda en Youtube ha fallado!");
		};
		$scope.videoEmbedURL = function(videoId) {
			return 'http://www.youtube.com/embed/' + videoId;
		};
	}]);;

Este es el contenido del archivo Javascript y en él se puede observar cómo está estructurado el código de esta aplicación.

En general, una aplicación web basada en AngularJS se compone de uno o varios módulos, los cuales pueden contener controladores, directivas, factorías, servicios o bloques de configuración. Cada uno de estos componentes tiene su funcionalidad, y no está dentro del objetivo del post explicar cada uno de ellos, sólo me voy a centrar en los que utilizo en esta aplicación.

En la línea 1 se puede observar cómo se define un módulo de AngularJS. En este caso recibe el nombre de videos, y el segundo argumento de la función especifica las dependencias que tiene de otros módulos. Como en este caso es una aplicación muy sencilla, no hay dependencias, pero en otros casos pueden existir varias, ¡o un montón!

Entre las líneas 2-4 se define un bloque de configuración, esto es, dentro de este bloque se configuran componentes de AngualrJS, o propios, de alguna forma concreta que se ajuste a las necesidades de la aplicación. En nuestro caso, y debido a una funcionalidad que incluye AngualrJS 1.2, debemos configurar uno de estos componentes, en concreto el componente de Script Contextual Escaping$sce, el cual se utiliza para limpiar el código HTML que se genera y controlar posibles fallos que introduzcan vulnerabilidades en la aplicación. Lo que se está haciendo en la línea 3 es indicar a AngularJS que las URLs que comienzan por http://www.youtube.com/embed/ las introduzca en una lista blanca de orígenes permitidos, y ya veremos más adelante por qué es necesaria esta configuración.

En las líneas 5-38 se define el código del controlador. Lo primero que se hace es darle un nombre, en este caso AppCtrl, y es este nombre el mismo que hay que indicar en el atributo ng-controller de la parte de HTML, como ya hemos visto antes. Lo que viene a continuación son las dependencias de este controlador, que son objetos definidos dentro de AngularJS con una funcionalidad concreta:

  • $scope es el ámbito que hace de enlace entre el código HTML y el código Javascript. Todo lo que esté definido dentro del objeto $scope estará disponible en el HTML, ya sean propiedades o funciones.
  • $http es el objeto que nos permitirá hacer llamadas a servicios web externos, del mismo modo que la función $.ajax() de jQuery.
  • Y el objeto $log nos permitirá interactuar con la consola del navegador, que como es una aplicación de ejemplo, será utilizada para indicar el transcurso de la aplicación.

Estos objetos son inyectados en el controlador mediante los parámetros de la función y podrán ser utilizados dentro del mismo. Una observación que se puede hacer en la definición es por qué están definidos como cadenas de texto dentro de un array, teniendo la función del controlador como último elemento del array. El controlador se puede definir simplemente como la función que recibe los parámetros directamente, pero esto se hace así para poder sobrevivir a una minificación del código fuente, pues este proceso cambia los nombre de las variables y AngularJS es sensible al nombre de los argumentos de las funciones. Al indicar en los elementos del array el nombre que estos deben tener, los objetos pueden ser inyectados en los parámetros independientemente del nombre que tengan los argumentos.

La línea 6 define la variable que se utiliza en el elemento input y que contendrá la cadena de búsqueda que se va a enviar a Youtube.

Las líneas 7-12 definen la función que realiza la búsqueda. Es invocada cuando el usuario pulsa el botón “Buscar” gracias al atributo ng-click que tiene añadido el elemento button, y muestra por consola la cadena de búsqueda a enviar, y hace una llamada GET a un servicio de Youtube, indicando los callbacks para éxito o error.

Las líneas 13-30 definen el callback para el éxito en la búsqueda de Youtube. El argumento de esta función es el objeto que se obtiene al deserializar el JSON recibido en la respuesta. Este objeto se recorre y se extraen determinados valores que son los que se van a mostrar en la parte de presentación de la aplicación, como el título, la persona que ha subido el vídeo, la descripción o el identificador del vídeo. Los elementos extraídos se añaden a un array y este se asigna a la propiedad $scope.videos, que como recordaréis, es el array que se recorre en la parte de presentación, en el elemento div de la línea 22.

En las líneas 31-34 se define el callback para el caso de error de la consulta al servicio web de Youtube, el cual lo único que hace es mostrar por consola y en una alerta, un mensaje de error.

Y por último en las líneas 35-37 se define la función que generará la URL para el orígen del iframe que contendrá el vídeo. Esta función lo único que hace es devolver la concatenación de la base de la URL de Youtube junto con el identificador del vídeo que le llega como argumento. El motivo de hacer esto así es por las medidas de seguridad introducidas en AngularJS a partir de la versión 1.2, que impiden hacer concatenaciones en los atributos src o ng-src que puedan resultar inseguras.

¿Esto es todo sobre AngularJS?

En absoluto. AngularJS es mucho más extenso, y hay otras formas de hacer lo que se ha expuesto en esta entrada, pero como una introducción es suficiente, no hay necesidad de complicar demasiado la aplicación.

Otros componentes de AngularJS de los que no se ha hablado son las factorías, los servicios, las directivas, pero estos son material para un futuro post, que espero que llegue antes de que vuelva a pasar un año ;-)

Y hasta aquí ha llegado el ejemplo, espero que os haya resultado provechoso, e igual que el anterior en Backbone.js, también está disponible en mi repositorio de GitHub

Categories
español programacion web development

Crear una aplicación sencilla con Backbone.js

Este artículo es la versión en post del taller que dí el 6 de septiembre de 2012 en la primera edición de betabeers en Málaga. Para los que no lo conozcáis, betabeers es un evento que se realiza en varias ciudades españolas y del mundo, donde se reúnen desarrolladores para hablar de sus cosas. En esas reuniones se realizan charlas técnicas en los que se explica alguna tecnología, se presentan startups y luego hay una sesión de networking con cervezas.

En el taller que impartí introduje Backbone.js, un framework javascript para el desarrollo de single page apps. Este tipo de aplicaciones está en alza pues permite ofrecer al usuario una experiencia similar a la que ofrecen las aplicaciones de escritorio, y es de sobra conocido por todos pues, quien más y quien menos, seguramente haya usado alguna de las aplicaciones web más famosas alguna vez: GMail, Twitter, GitHub, etc…

El objetivo era dar un vistazo sobre qué es Backbone.js, explicar los modelos, colecciones y vistas y hacer una aplicación de ejemplo usando la API de YouTube. Esta aplicación carga unos vídeos del feed de YouTube y los muestra en la pantalla, todo ello controlado mediante javascript y usando el framework de Backbone.js.

Se puede encontrar una demo de la aplicación en esta dirección, y el repositorio del código se encuentra en esta otra dirección.

Aquí os dejo la presentación que usé en el taller, y a continuación paso a explicarla.

¿Pero qué es Backbone.js?

Backbone.js es un framework javascript pseudo-MVC creado por DocumentCloud. Entre sus principales características está la facilidad que ofrecen para acabar con el spaghetti-code en javascript. ¿Quién no ha tenido complicaciones con los handlers de los eventos usando jQuery? Que si en una llamada a $.ajax pongo un callback para success y otro para error, y luego cuando entre en success tengo que actualizar los elementos de la página para mostrar los datos recibidos, y todo ello con funciones inline o sueltas en los archivos de código.

No es la mejor situación para tener una buena estructura de código que ayude a que este sea más fácil de mantener.

Backbone.js ofrece una serie de objetos para poder organizar mejor el código de la aplicación:

  • Model, que contiene la información de un objeto de datos (por ejemplo un registro de la base de datos)
  • Collection, que contiene un conjunto de modelos
  • View, que se encarga de gestionar los objetos del DOM y son la parte visible de la aplicación
  • Router, que se encarga de las transiciones entre las vistas (lo que venían a ser los “cambios de página” de antaño)
  • History, que guarda el historial de navegación del usuario dentro de la single page app.

Cada uno de esos objetos ofrece además un conjunto de funciones auxiliares y gestores de eventos, de forma que se pueda gestionar la creación y el guardado de los datos (modelos), los eventos entre los propios objetos y demás.

Requisitos

Backbone.js tiene una serie de requisitos para su funcionamiento:

  • Para la gestión de las vistas necesita una librería para la manipulación del DOM como jQuery o Zepto
  • Para el procesamiento de JSON necesita la librería JSON2
  • Y como funciones auxiliares, necesita la librería Underscore, también desarrollada por DocumentCloud

La estructura de datos de la aplicación

La aplicación, como ya he dicho antes, va a cargar el feed de vídeos de YouTube y va a mostrarlos en la página. Por lo tanto, lo primero que hay que hacer es ver en qué consiste el modelo de datos de la aplicación.

La API de YouTube que voy a usar se encuentra en la siguiente URL:

http://gdata.youtube.com/feeds/api/videos?v=2&alt=jsonc

El parámetro más importante es el que indica el formato de salida que queremos para los datos, pues nuestra aplicación va a trabajar con JSON, que es el formato nativo para intercambiar datos en javascript. Si cargamos la dirección podremos ver la estructura de datos que devuelve:

 
{
  apiVersion: "2.1",
  data: {
    updated: "2012-09-05T18:07:05.928Z",
    totalItems: 1000000,
    startIndex: 1,
    itemsPerPage: 10,
    items: [
      {
        id: "trenrwaJNwM",
        uploaded: "2012-08-20T07:43:32.000Z",
        updated: "2012-08-27T17:45:45.000Z",
        uploader: "theusbrhighlights",
        category: "Entertainment",
        title: "SummerSlam 2012 - Brock Lesnar vs Triple H Highlights",
        description: "Song: Skillet - Awake and Alive All rights Reserved by WWE No copyright...",
        thumbnail: {
          sqDefault: "http://i.ytimg.com/vi/trenrwaJNwM/default.jpg",
          hqDefault: "http://i.ytimg.com/vi/trenrwaJNwM/hqdefault.jpg"
        },
        player: {
          default: "http://www.youtube.com/watch?v=trenrwaJNwM&feature=youtube_gdata_player"
        },
        content: {
          5: "http://www.youtube.com/v/trenrwaJNwM?version=3&f=videos&app=youtube_gdata"
        },
        duration: 199,
        aspectRatio: "widescreen",

Podemos ver que la consulta nos devuelve un objeto data con unas cuantas propiedades, y entre ellas un array con los vídeos. La estructura de los vídeos tiene un montón de propiedades, pero nos vamos a quedar con tres de ellas en la aplicación de ejemplo: id, que es el identificador del vídeo en YouTube;  uploader, que es el nombre del usuario que subió el vídeo; y description, que es la descripción del vídeo.

El modelo de Backbone.js

El objeto que va a representar a un vídeo dentro de la aplicación de ejemplo va a ser el siguiente, que hereda del objeto Model de Backbone.js:

 var Models = {
  Video: Backbone.Model.extend(),
};

Bastante simple, ¿verdad? Una de las ventajas de Backbone.js es que si no necesitas nada más que la funcionalidad por defecto, la librería se encarga de todo lo que hay por debajo. Por lo tanto, nuestro objeto para el vídeo es bastante sencillo.

La colección de vídeos

Puesto que necesitamos cargar un conjunto de vídeos, será necesario crear también un objeto que los agrupe, por lo que definiremos un objeto que herede de Collection:

var Collections = {
  Videos: Backbone.Collection.extend({
    model: Models.Video,
    url: 'http://gdata.youtube.com/feeds/api/videos?v=2&alt=jsonc&max-results=9',
    parse: function(resp) {
      console.log('VideosCollection: Received server reponse and parsing data');
      return resp.data.items;
    },
  }),
};

La propiedad model indica a Backbone.js qué tipo de contenido va a haber en la colección de modelos, y en nuestro caso hemos definido que va a contener objetos del tipo Video.

La siguiente propiedad, url, indica la dirección en la cual va a tener acceso a los objetos. En nuestro caso indicamos que será la dirección del feed de vídeos de YouTube, con los parámetros que especifican que queremos la salida en formato JSON. Además he añadido un parámetro adicional, max-results, para que devuelva sólamente nueve resultados.

Y ahora llega la función más extraña de la colección. Por defecto Backbone.js tiene una implementación de parse(response) que devuelve directamente el resultado obtenido de la URL, lo cual va bien si los datos vienen adecuadamente organizados. En nuestro caso, si recordáis la estructura de datos mostrada anteriormente, el array de vídeos se encuentra en una propiedad interior, concretamente en response.data.items, por lo que la implementación por defecto no nos sirve y tenemos que hacer que devuelva el elemento que contiene el array de vídeos.

Las vistas de la aplicación

La aplicación mostrará una lista de vídeos, que es una vista (hereda del objeto View de Backbone.js), y cada vídeo individual será otra vista. Además, añadiremos un pequeño formulario en otra vista más, para que el usuario pueda realizar una búsqueda sobre un tema concreto y se muestren los vídeos relacionados.

La vista de un vídeo individual

El código javascript que define la vista de un vídeo es el siguiente:

SingleVideo: Backbone.View.extend({
  className: 'video',
  initialize: function() {
    this.template = _.template($('#single-video-template').val());
  },
  render: function() {
    console.log('SingleVideo: entering render');
    this.$el.html(this.template({video:this.model.toJSON()}));
    console.log('SingleVideo: leaving render');
    return this;
  },
});

Y la plantilla HTML es:

<!-- Plantilla para un vídeo individual -->
<textarea id="single-video-template" style="display:none">
  <h2>'<%= video.title %>' por <%= video.uploader %></h2>
  <div class="video-player">
    <object width="320" height="240">
      <param name="movie" value="http://www.youtube.com/v/<%= video.id %>?version=3&amp;hl=es_ES"></param>
      <param name="allowFullScreen" value="true"></param>
      <param name="allowscriptaccess" value="always"></param>
      <embed src="http://www.youtube.com/v/<%= video.id %>?version=3&amp;hl=es_ES" type="application/x-shockwave-flash" width="320" height="240" allowscriptaccess="always" allowfullscreen="true"></embed>
    </object>
  </div>
  <h3>Descripción</h3>
  <div class="video-list-description"><%= video.description %></div>
</textarea>

La propiedad className indica el contenido que se le debe dar al atributo class del elemento HTML cuando la vista sea incluída en el DOM. En este caso hemos definido una clase video que veremos más adelante.

La función initialize() es el constructor del objeto. Dentro de esta función iniciamos todos los objetos auxiliares que vayamos a necesitar dentro de la vida de esta clase. En nuestro caso tenemos una propiedad template, que contendrá la plantilla HTML para esta vista que hemos mostrado anteriormente. La función _.template es una función auxiliar de Underscore que nos permite cargar un bloque de HTML con trozos de código al estilo ERB de Ruby para interpolar variables y funciones de javascript que se pasan como parámetro, y el contenido de esta plantilla viene del contenido del elemento textarea con id single-video-template de la página.

La función render() se encarga de añadir la vista al DOM. Para ello genera el código HTML usando la plantilla definida anteriormente con el objeto que debe usar para interpolar esas variables, y lo inserta dentro del elemento this.$el. Este elemento es una caché del elemento del DOM al que está asociada esta vista, que además está extendido con todas las funciones de jQuery (de ahí lo del símbolo $).

También se puede observar que la función devuelve una referencia al objeto this puesto que, por convención, las llamadas a las funciones de un objeto de Backbone.js deberían poder encadenarse una detrás de otra.

La vista de la caja de búsqueda

El código para esta vista es:

SearchBox: Backbone.View.extend({
  events: {
    'click #search-submit': 'performSearch',
  },
  initialize: function() {
    this.template = _.template($('#search-box-template').val())
  },
  render: function() {
    console.log('SearchBox: entering render');
    this.$el.html(this.template());
    console.log('SearchBox: leaving render');
    return this;
  },
  performSearch: function() {
    console.log('SearchBox: entering performSearch');
    queryString = this.$el.find('#search-query').val();
    this.trigger('searchRequest', {queryString:queryString});
    console.log('SearchBox: leaving performSearch');
  },
});

Y su plantilla HTML es:

<!-- Plantilla para la caja de búsqueda -->
<textarea id="search-box-template" style="display:none">
  <input type="text" id="search-query">
  <button type="button" id="search-submit">Buscar</button>
</textarea>

Esta vista, al igual que la anterior, tiene unas funciones initialize() y render() que, respectivamente, inician el objeto y lo añaden al DOM de la página. La novedad en este caso es la aparición del objeto events. Este objeto define los eventos a los que va a responder la vista, que en este caso es al click sobre el elemento con id search-submit. Una vez que se pulse sobre este elemento se deberá ejecutar la función performSearch().

Y dentro de la definición de la vista se encuentra esta función, la cual obtiene la cadena de búsqueda introducida en la caja de texto (el elemento con id search-query), y lanza el evento searchRequest mediante la función de Backbone.js trigger(), pasando como parámetro dicha cadena de búsqueda. Todos los objetos de Backbone.js implementan las funciones on(), off(), y trigger(), que, respectivamente, definen un manejador para un evento, eliminan un manejador de un evento y lanzan un evento sobre la vista.

La propagación de eventos es lo que permite articular una aplicación de Backbone.js. Las vistas interiores son responsables de sus datos y notifican cambios de estado mediantes eventos que serán capturados por las vistas que las contienen:

Eventos en vistas de Backbone.js

La vista de la lista de vídeos

El código correspondiente a esta vista es:

VideosApp: Backbone.View.extend({
  initialize: function() {
    _.bindAll(this);
    this.template = _.template($('#app-template').val());
    this.searchBox = new Views.SearchBox();
    this.searchBox.on('searchRequest', this.performSearch, this);
    this.collection = new Collections.Videos();
    this.collection.on('reset', this.showVideos, this);
    this.performSearch();
  },
  render: function() {
    console.log('VideosApp: entering render');
    this.$el.html(this.template());
    this.$el.find('#video-search-box').html(this.searchBox.render().el);
    this.showVideos();
    console.log('VideosApp: leaving render');
    return this;
  },
  showVideos: function() {
    this.$el.find('#video-list-container').empty();
    var v = null;
    this.collection.each(function(item, idx) {
      v = new Views.SingleVideo({model:item});
      this.$el.find('#video-list-container').append(v.render().el);
    }, this);
    return this;
  },
  performSearch: function(evdata) {
    evdata = evdata || {};
    console.log('VideosApp: entering performSearch - queryString: ' + evdata.queryString);
    this.collection.fetch({data:{q:evdata.queryString}});
    console.log('VideosApp: leaving performSearch');
  },
});

Y la plantilla HTML es la siguiente:

<!-- Plantilla para la aplicación -->
<textarea id="app-template" style="display:none">
  <div id="video-search-box"/>
  <h1>Vídeos de YouTube con Backbone</h1>
  <div id="video-list-container"/>
</textarea>

Esta tiene varias novedades frente a la vista anterior. Puesto que esta es la vista principal de la aplicación, se debe encargar de la carga de los datos y la gestión de los eventos.

La función initialize(), igual que en el caso anterior, crea la plantilla a partir del contenido de uno de los elementos de la página. Además, inicia la subvista de la caja de búsqueda y establece un manejador para cuando esta lance el evento searchRequest. También inicia el objeto de la colección de vídeos y establece un manejador para el evento reset de la colección. Este evento se lanza cuando el objeto reinicia el conjunto de modelos, bien sea porque se han cargado los datos del servidor, o porque se ha vaciado o se han asignado los modelos a mano mediante código. Además hace una primera llamada a la función performSearch(), que veremos más adelante, para iniciar la carga de vídeos.

En la función render(), se carga la plantilla de la vista y se añade el contenido de la subvista de la caja de búsqueda dentro de su elemento correspondiente. Después se hace una llamada a la función showVideos(), que veremos a continuación, para mostrar los vídeos que contiene la colección que, como la aplicación acaba de iniciarse, en este momento se encuentra vacía.

La función showVideos() vacía el elemento que contendrá los vídeos obtenidos de la API de YouTube para, a continuación, iterar sobre la colección de vídeos y, para cada uno, instanciar una vista de vídeo individual y añadirla al elemento contenedor de la página.

La función performSearch() se encarga de realizar la llamada a la API de YouTube. Esta función será invocada cuando se capture el evento searchRequest de la vista de la caja de búsqueda, y hará una llamada a la función fetch() de la colección, pasando como parámetro el texto de la cadena de búsqueda introducida por el usuario.

El inicio de la aplicación

Lo único que queda es añadir el siguiente bloque de código:

$(document).ready(function() {
  var vs = new Views.VideosApp();
  vs.setElement($('#container')).render();
});

Este se encarga de instanciar la vista de la aplicación y añadirla al DOM de la página, dentro del elemento con id container. Y puesto que la función initialize() de la vista VideosApp se encarga de lanzar la búsqueda, en cuanto se reciban los datos se mostrarán en la página. El aspecto final de la aplicación se puede ver en esta captura de pantalla:

Captura de pantalla de la aplicación de ejemplo

No es lo más bonito del mundo, hay que reconocerlo, pero es un ejemplo de una aplicación sencilla y básica usando Backbone.js

¿Qué ha faltado?

De la funcionalidad disponible en Backbone.js, ¿qué ha faltado por ver? Muchas cosas.

No hemos visto nada de la creación y modificación de modelos y su posterior envío al servidor para ser guardados, así como tampoco hemos usado vistas complejas ni los enrutadores para gestionar los cambios de página y los fragmentos de URL.

Todo esto podría ser contenido de un futuro taller de betabeers, si os parece bien, y si no  en un futuro escribiré la continuación de este post donde añadiré algunas mejoras a esta aplicación que hagan uso de esas funcionalidades.

Espero que hayáis aprendido algo de Backbone.js y os haya resultado útil. Para más referencias, el código de este ejemplo se encuentra en mi repositorio de GitHub en esta dirección.

Categories
web development

Web developers cheat sheet compilation

Click on the post title and you will get to a wonderful compilation of different cheat sheets covering CSS(3), HTML(5) and jQuery