Yeoman

Creando una sencilla aplicación con AngularJS y Yeoman

(Acceso directo a la demo: http://rusizate.com/angular )

Las páginas Webs ya no son lo que eran. Estamos pasando del modelo XHTML+CSS en donde el servidor consideraba que el cliente era un poco “tonto” y le enviaba el código fuente para representarlo sin que tuviera mucho que pensar (salvo unos pequeños destellos de Javascript), a un modelo donde gran parte de la aplicación reside en cliente (en s unavegador, mejor dicho), dejando al servidor como poco más que un interfaz para el acceso a base de datos. Esto es lo que nos trae HTML5 y la gran evolución de Javascript: clientes potentes en Javascript que se comunicación con el servidor a través de AJAX.

Este fenómeno se ha llamado las Webapps Single-page application. Desde el punto de vista tradicional del HTML, el cliente accede a una sola página (o unas pocas), done se descargan las librerías y código Javascript una sola vez. A partir de ese momento, todos los cambios se realizan mediante manipulación del DOM de esa única página, sin tener que navegar por otras páginas. La comunicación con el servidor se hace por llamadas AJAX en el protocolo preferido.

Es fácil suponer que, al pasar la responsabilidad desde el servidor al cliente, la complejidad y tamaño del código Javascript aumenta de forma exponencial. Como sabemos, es mucho más fácil hacer un buen código estructurado en Java (sobre todo gracias a sus múltiples frameworks – struts, spring, google juice…) que hacerlo en Javascript. Surge por lo tanto la necesidad de tener algún tipo de framework que ayude a estructurar las aplicaciones en Javascript que corren en el cliente. Afortunadamente muchos proyectos open source se han puesto las pilas como por ejemplo EmberJS, Backbone, Knockout o AngularJS como los más destacados.

Estos frameworks implementan están basados en implementar el Modelo Vista Controlador (MVC) y se encargan de gestionar de forma automática las dependencias entre los componentes, ahorrando mucho trabajo al programador (y muchos errores).

En este post nos vamos a centrar en el que está pegando con más fuerza actualmente, que no es otro que AngularJS, desarrollado por Google. Vamos a ver los pasos necesarios para crear una aplicación desde cero. La gran actividad de desarrollo de Angular incluye la adaptación de herramientas para la generación de las aplicaciones Web para el soporte de este framework. La herramienta que vamos a usar en esta ocasión es Yeoman , que nos permitirá partir de una aplicación básica con Angular para desarrollar la nuestra, así como a través de Grunt, poder disponer de un servidor con recarga automática y poder generar una versión e distribución.

Comencemos…

Para que funcione Yeoman en nuestro entorno de desarrollo necesitamos instalar NodeJS y NPM (Node Package Manager). Tal y como aparece en las instrucciones de la página oficial de Yeoman, tenemos que instalarlo de modo global:

npm install -g yo

(-g indica que es un paquete global)

Yeoman utiliza generadores para crear las aplicaciones web, que no es otra cosa que plantillas para generar aplicaciones con unos componentes determinados. Se podría decir que es como los archetypes de Maven. Existen muchos de ellos, por ejemplo el de una aplicación Web simple con una página de demo es “npm install -g generator-webapp”. Para angular vamos a usar:

npm install -g generator-angular


Para que todo funcione correctamente debemos tener instalado
GIT
 

Una vez tenemos el generador de angular cargado en NPM, podemos usar yeoman para generar la aplicación: creamos un directorio en nuestro disco, en donde residirán todos los ficheros y directorios, y accedemos a él. Una vez dentro ejecutamos:

yo angular

Nos pedirá configurar algunas dependencias, como por ejemplo el uso de Twitter Bootstrap (que sí usaremos).

Esperamos pacientemente a que se descarguen todos los ficheros (puede llevar un buen rato). Al final tendremos un árbol de directorio con ficheros de configuración en el raíz. Destacan especialmente la configuración de Bower (bower.json) y de Grunt (Gruntfile.js). Servirán para introducir nuevas dependencias y configurar las tareas del servidor, tales como mostrar el servidor, crear la versión de distribución, etcétera. Serán de gran utilidad para el desarrollo.

Una vez está todo perfectamente descargado iniciamos el servidor de desarrollo utilizando Grunt. Este servidor tiene la utilidad de que está al corriente de los cambios en los ficheros, de modo que cuando editando cualquier js, css o html y lo salvamos, el servidor es capaz de refrescar de forma automática el navegador para que se vean reflejados los cambios (podemos ver el parámetro watch del Gruntfile.js). Lo lanzamos en modo servidor, en el directorio raíz de la aplicación:

grunt serve

Si todo ha ido bien (a veces hay que usar el parámetro grunt serve –force), se abrirá nuestro navegador predeterminado con la página de inicio del proyecto que se ha generado al hacer uso de Yeoman. Es hora de comenzar la edición.

Dentro del directorio creado, podemos localizar el directorio “apps”, dentro de el cual está la aplicación propiamente dicha. El resto pertenecen a ficheros de configuración o dependencias de Bower y otras tareas. Los ficheros que vamos a tocar serán los siguientes:

  • index.html que contiene la plantilla donde se cargarán las vistas de la Webapp. Esta plantilla contiene un tag ng-view donde en su interior se insertarán las vistas.
  • scripts/app.js que es donde se define el módulo principal de la aplicación, y donde además se establece la definición del enrutamiento, asignando a cada patrón de URL del navegador, una vista que mostrar y un controlador para esa vista. Hay que tener en cuenta que se trata de una aplicación web en una sola página, por lo que los cambios de vista son realmente cambios de capas y no de páginas.
  • scripts/controllers/main.js que es el fichero donde vamos a definir el controlador principal, y por tanto, en esta pequeña demo, vamos a establecer el modelo (los datos) y las funciones básicas.
  • views/dibujo.html que es la vista que será cargada dentro de la plantilla (index.html) y será cargada porque se ha asignado en scripts/apps.js como ruta básica con el controlador determinado.

Con estos 4 elementos hemos definido la vista (la plantilla y la vista); el modelo (dentro del código del controlador); y el controlador (que define las acciones sobre el modelo). Todo ello relacionado con el sistema de enrutamiento.

Vamos a ver la aplicación en detalle:

Se trata de una sencilla aplicación que define una malla o grid de elementos (una tabla…) en los que al pasar el ratón por cada una de las celdas toma un color cada vez más oscuro. En realidad, cada una de las celdas está asignada a un atributo de un objeto (que imita una malla para guardar los datos). Existe un binding o relación entre cada celda y la propiedad indicada, de modo que al pasar el ratón por encima de la celda, el atributo del modelo correspondiente a esa celda aumenta su valor en una unidad (es un número). Al mismo tiempo, cada celda refleja con su color el valor que tiene el atributo relacionado. Cuanto mayor es el número más oscura será la celda.

Supone un ejemplo muy visual, sobre todo si se examina el código, de cómo AngularJS es capaz de relacionar la vista y el modelo y cómo reacciona en tiempo real a los cambios del modelo. Si paso el ratón por encima cambia el modelo, y al cambiar el modelo se cambia la vista de forma instantánea y casi sin hacer nada en el código. ¡Es algo mágico!

¿Qué se ha cambiado respecto de la aplicación base?

En primer lugar vamos a ir con el fichero “index.html”. Este fichero es la plantilla básica, que si hemos seguido las instrucciones propuestas nos debería valer. Tiene todo lo necesario para que funcione AngluarJS y además tiene Twitter Bootstrap 3. Lo que hay que destacar de este fichero es el siguiente tag:

<div class="container" ng-view=""></div>

Dentro de este tag, al tener el atributo ng-view, se van a cargar las vistas que definamos en la aplicación. Una vista es un fragmento de código HTML con notación de AngularJS que va a ir dentro del tag con ng-view

El siguiente elemento que vamos a definir es la aplicación, en el fichero scripts/app.js

'use strict';

'use strict';

angular.module('angularJs2BbApp', [
'ngCookies',
'ngResource',
     'ngSanitize',
     'ngRoute'
 ])
.config(function ($routeProvider) {
     $routeProvider
         .when('/', {
             templateUrl: 'views/dibujo.html',
             controller: 'ctrlDibujo'
 })
 .otherwise({
     redirectTo: '/'
 });
});

Aquí se pueden ver dos elementos importantes.

En primero lugar tenemos la declaración del módulo ‘angularJs2BbApp’ que es el nombre que le hedado a la aplicación. Como parámetros tenemos un array con las dependencias qur utiliza, entre ellas ngRoute para el enrutamiento. Cuando definamos controladores, servicios u otros elementos, veremos que se relacionan con el módulo. Es muy importante por lo tanto el nombre del módulo, ya que haremos constantemente referencia a él para decorarlo con nuevos elementos.

Por otra parte tenemos la configuración de un routerProvider en la aplicación. Es una función anónima que recibe la referencia al routeProvider general (comienza por $), y establece las URL a las cuáles responde la aplicación. Es un sistema que imita al funcionamiento habitual de una página web: cuando cambia la URL se carga otra página. Aquí, al tratarse de una WebApp solamente se utiliza una página (la del index.html), pero se van cambiando los contenidos de las vistas. El usuario creerá que está cambiando de página, pero realmente solo cambian los contenidos de algunas capas. Por ejemplo, una ruta puede ser el raíz “/” o mejor dicho “#/” y otra ruta puede ser “#/dibujo”. En el ejemplo se puede ver cómo para la ruta raíz, se mostrará la plantilla “dibujo.html” y usará el controlador llamado “ctrlDibujo”.

En el enrutamiento se indica tanto el templateURL o vista (view), que como vemos corresponde a un fichero html (que dentro tendrá sintaxis de AngularJS), y un controlador, que está definido en el fichero main.js. Es una buena forma de separar la funcionalidad y las vistas de nuestra aplicación Web de una sola página pero muchas pantallas.

El siguiente elemento será el Controlador, que está situado en el fichero main.js:

'use strict';

angular.module('angularJs2BbApp').controller("ctrlDibujo", function($scope)
 {
     $scope.alto = ['a','b','c','d','e','f','g','h', 'i', 'j', 'k', 'l', 'm', 'n'];
     $scope.ancho = [1,2,3,4,5,6,7,8,9,10,11,12];
     $scope.actual = "nulo";
     $scope.elementos = {};

$scope.activaElemento = function(indice1, indice2)
     {
         var cadena = indice1.toString()+indice2.toString();
         $scope.actual = cadena;

if($scope.elementos[cadena]==null)
             $scope.elementos[cadena]=1;
         else
             $scope.elementos[cadena]++;
     }

});

Volvemos a ver de nuevo la invocación al módulo, que es llamado con el mismo nombre. Esto lo que hace es recuperar la referencia al módulo para decorarlo con nuevas características. En este caso va a introducir un controlador llamado “ctrlDibujo” (recordar que en el fichero app.js hemos definido que es el controlador para la ruta y la vista dibujo.html).

El controlador, como todos sabemos (o deberíamos), se encarga de manipular el modelo, de manera que está formado principalmente por funciones. En este caso está definido como una función anónima que recibe el $scope, que no es otra cosa que la referencia al $scope del módulo. El $scope del módulo nos da acceso a toda la información del modelo. Si se quiere acceder a un atributo o función que tiene que estar disponible para el modelo se hace a través de $scope.elemento.

Si vemos el código podemos observar la definción de 2 vectores (alto y ancho); una cadena de texto (actual) y un objeto vacío llamado elementos. Estas tres variables están perfectamente disponibles para cualquier elemento del módulo, así como para la vista y otros elementos más avanzados que no vamos a definir en este post. Obviamente estos 4 elementos pueden ser considerados como el Modelo de la aplicación, puesto que almacenan la información del estado y lo que queremos mostrar por pantalla y ser manipulado. Este modelo ha sido definido dentro del controlador por cuestiones de simplicidad ya que este post es una pequeña introducción. En otras condiciones se utilizan “servicios” para una comunicación con un servidor a través de REST, por ejemplo.

Además de estas cuatro variables existe una función que constituye el verdadero controlador de la aplicación. Esta función se encara de dos aspectos. Por una parte establece el valor actual como el nombre del cuadrado sobre el que está pasando el ratón. Cada cuadrado estará designado por el varlo correspondiente de alto y ancho, es decir, sus coordenadas que son sacadas de la combinación de las variables alto y ancho. Un ejemplo sería el primer elemento “a1” o “c6” por ejemplo. Este valor se usará para mostrar por pantalla cuál es el elemento actual. Por otra parte lo que hace es tomar el objeto elemento y añadirle como propiedad la coordenada, además del número de veces que se ha pasado el ratón por encima. La primera vez, será 1 y se creará en ese momento el miembro del objeto. Esta función será invocada en un evento mouse over como veremos en la vista.

Con esto hemos creado el controlador, además de una parte del modelo. Ya solamente nos queda por definir la vista, que está en el fichero “dibujo.html”

<div class="row">
     <div class="col-xs-12 text-center">
         <h1>Draw!</h1>
     </div>
</div>
<div class="row">
     <div class="col-xs-12 text-center">
             Last selected: {{actual}}<br />
         </div>
</div>
<div class="row">
     <div class="col-xs-12" id="areaDibujo">
         <div class="row" ng-repeat="al in alto">
             <div class="col-xs-1 cursorPointer cuadrado text-center" ng-repeat="an in ancho" ng-mouseover="activaElemento(al,an)" ng-style="{'background-color':'rgba(0,0,0,'+(elementos[al+an]/20)}">
                     {{elementos[al+an]}}
             </div>
</div>
     </div>
</div>

Si no estás familiarizado con Twitter Bootstrap puedes imaginar que las clases “row” o “col-xs-…” son para maquetar como si fueran tablas (dicho muy genéricamente). Por lo demás parece un código HTML bastante estándar. Acuérdate que esta vista se carga dentro de aquel div con el atributo ng-view que estaba en index.html. Por eso no tiene ni body ni head ni nada de lo demás…

En una revisión descendente podemos ver que el primer elemento que llama la atención es {{actual}} en medio del texto (sí, con llaves dobles). Esto indica que se pinte ahí el valor de “actual”. Si recuerdas, en el controlador se ha definido la variable $scope.actual. Es evidente que es la misma variable. Además si recueras, la función “activaElemento” hacía que esta variable cambiase.

Es aquí donde comienza la magia de AngularJS frente a métodos tradicionales como por ejemplo jQuery. AngularJS está vigilando todo el rato los cambios en el modelo (la variable), de modo que ante cualquer cambio de la variable $scope.actual, actualiza el valor {{actual}} por el nuevo valor, sin que tengamos que hacer nada. Esto equivaldría a hacer un jQuery(“#elemento”).html() cada vez que un controlador alterase el valor de $scope.actual. El funcionamiento es casi mágico. ¿Y cuando cambia el valor de “actual”? Pues cambia cuando álguien invoque la función “activaElemento”.

El siguiente paso si seguimos para abajo es el de ng-repeat=”al in alto”. Si recuerdas $scope.alto es un vector con elementos literales de la a a la letra n. Mediante ng-repeat=”al in alto” lo que hace angular es repetir el tag que lo contiene tantas veces como elementos tenga alto, y además, a cada vez le asigna ese elemnto a la variable “al”. Como vamos a hacer un tablero (2 dimensiones), repetimos instrucción en el siguiente tag, con ng-repeat=”an in ancho”. Así tendremos alto*ancho elementos y encima podremos usar las variables “al” y “an” para etiquetar cada elemento. Como sucedía con “actual”, vamos a encerrar el valor de elementos[al+an] dentro de unas llaves dobles para que se muestren por pantallas. Con esto hacemos que cada “div” tenga en su interior un texto que corresponde al número de veces que ese elemento ha sido atravesado por el cursos. Es fácil de entender: tenemos el objeto “elementos” vacío. Si pasamos el ratón, la función toma los valores de al (“a”) y an (3) y los une haciendo un literal (“a3”), que se asigna como miembro del objeto elementos elementos.a3=1 ó elementos.a3++

Vamos ahora con las fuciones. Tenemos dos atributos, uno es ng-mouseover, que llama a una función cuando se pasa el ratón por encima del elemento. En este caso tenemos la función controladora que no es otra que “activaElemento”. Recibe por parámetro las coordenadas del elemento, y como se puede ver en el texto, asigna ese valor a “actual” (lo que provoca el cambio automático gracias a la magia de AngularJS en el texto que acomapaña a Last selected), y por otra parte añade o aumenta en una unidad el valor del miembro elementos.al+an. Entonces el controlador va cambiando los valores de elementos, y por tanto va mutando todo el ratola vista gracias al doble “binding” entre vista y modelo.

Finalmente hay un último atributo, llamado ng-style que modifica el color de fondo de la capa, estableciendo que sea un color negro (0,0,0) con una transparencia que dependen del valor de veces que haya pasado ratón. Si ha pasado una vez será 0.05. y si ha pasado 10 veces será 0.5 y 20 veces 1 (es decir, opacidad total). Se ha tomado 20 como un valor arbitrario, por eso se divide por 20. Como se puede suponer, también hay un binding aquí con el valor de elementos[al+an] (ojo! recuerda que elementos[al+an] es lo mismo que decir elementos.al+an, aunque esta última es un error de sintaxis, por eso se usa la notación corchetes al tener una suma). Vamos, que si cambia elementos[al+an] porque se activa el controlador porque álguien pasa con el ratón por encima, se actualiza la vista de forma mágica.

En resumen, hemos visto cómo AngularJS nos brinda un framework para el desarrollo de aplicaciones Javascript en cliente muy potentes de forma rápida y estructurada. Se encarga de implementar un verdadero patrón MVC de forma muy eficiente para el programador, con pocas líneas de código. Existen muchas otras estructuras y elementos para hacer aplicaciones mucho más complejas y más útiles.

Si queréis probarlo por vuestra cuenta he dejado la demo funcionando en http://rusizate.com/angular . Podéis encontrar el repositorio con el código para que podáis extenderlo en: https://github.com/4lberto/AngularJSDemo1