Ajax

PrimeFaces (JSF2). Cómo detectar el fin de carga de la página con JavaScript

Primefaces  es una librería que implementa JSF2, de modo que facilita la creación de aplicaciones Web en un entorno J2EE a través del uso de componentes prefabricados como menús, botones, tablas de datos, formularios, etcétera. Puedes encontrar demostraciones de su potencial y un catálogo de sus componentes en este enlace: http://www.primefaces.org/showcase/

Aunque se use Primefaces, el resultado que llega al navegador siempre es el mismo: un conjunto de etiquetas HTML junto con complejas implementaciones en JavaScript que se generan automáticamente a partir del código Java y de componentes en XHTML. Como todo sistema que abstrae al programador de los niveles inferiores, siempre es complicado realizar algunas operaciones específicas que se salen de lo establecido para la tecnología.

Tal es el caso de la detección de cuándo se ha terminado de cargar la página y el usuario tiene toda la información para operar. Habría que distinguir dos fases de forma genérica:

  • Carga de la estructura HTML de la página
  • Carga de las llamadas asíncronas (AJAX) para la obtención de datos.

En muchas ocasiones JSF2 (y por ende Primefaces) utilizan AJAX para obtener los datos del servidor, lo que implica que la página puede haber cargado su estructura y scripts, pero éstos continúan con su ejecución. El resultado es que el usuario no puede utilizar la página hasta un poco después de que el navegador le haya notificado que ha acabado: faltan tablas por completar, combos, datos dinámicos, etcétera.

Sería bueno por lo tanto contar con un mecanismo que nos permitiese realizar alguna acción cuando se ha terminado esa carga de datos. Se puede usar en algunas ocasiones cuando los que nos ofrece PrimeFaces no es suficiente: por ejemplo bloquear la pantalla con un fondo e icono de carga hasta que no esté plenamente operativa en la primera carga.

Como PrimeFaces utiliza jQuery para operar, simplemente aprovecharemos la posibilidad que nos brinda esta librería de Javascript: se enlazan un document.ready dentro de otro, de modo que el segundo se ejecutará cuando haya acabado el primero, que es el que contiene todas las operaciones de PrimeFaces

$(document).ready(function(){
    $(document).ready(function(){
        //Código Javascript que queramos ejecutar después de Primefaces
    });
});
Anuncios

Charla Polymer en 3er Betabeers Guadalajara

Recientemente tuve el privilegio de poder exponer una charla sobre Polymer en la tercera edición de Betabeers Guadalajara @betabeersGUADA, que es el capítulo alcarreño del conocido evento de desarrolladores, startupters y cervezas español.

La iniciativa de Guadalajara no podría llevarse a cabo sin la gente de Beperk (@beperk), a los que agradezco enormemente la organización de estos eventos. No es fácil en una ciudad pequeña y tan influenciada por Madrid reunir a los pocos e incomunicados desarrolladores que dormimos en las afueras de la Alcarria y encima que nos lo pasemos bien y nos vayamos conociendo. Seguro que con el tiempo sale algo grande de ahí, aunque ya de momento merece mucho la pena.

Algunas de las cosas que pasaron en la charla se pueden revisitar en este hastag:#bbGuada y en el twitter oficial de @betabeersGUADA

Un señor presentando Polymer

Gracias por la foto @SergioCC14

Volviendo al tema de Polymer…

En la charla pude desarrollar en vivo los conceptos que expuse en un post de este mismo Blog sobre la introducción a Polymer. Puedes encontrar el ejemplo práctico paso a paso en este enlace: Introducción a Polymer

También (y sobre todo) recomiendo la página oficial del proyecto Polymer (https://www.polymer-project.org/, que es tremendamente didáctica. Además de la documentación clásica, se incluyen ejemplos paso a paso muy sencillos de seguir. En la sección de Vídeos puedes ver las presentaciones de Google I/O 2013 y 2014, que son maravillosas y lo explican todo muy fácil (además en un inglés muy clarito). Finalmente también recomiendo la página oficial de WebComponents (http://webcomponents.org/) donde se puede abstraer un nivel más arriba de Polymer y consultar las novedades y artículos sobre este conjunto de estándares para HTML5. Las secciones Articles y Presentations son imprescindibles.

Las transparencias de la presentación y comentarios (escritos con el tono coloquial de presentación y no de redacción -perdón por las expresiones-) aparecen a continuación:

Polymer - Betabeers1

Hola a todos, gracias por venir y gracias a los organizadores de Beperk que hace esta charla posible.

Mi nombre es Alberto Moratilla y os voy a hablar de una tecnología que probablemente sea protagonista en los pŕoximos años en el desarrollo Web: Polymer y los WebComponents.

Esta charla surgió por una entrada que escribí en mi blog, que utilizo para explorar tecnologías que no tengo la oportunidad de explorar en mi vida profesional y conviene conocer para no quedarse atrás. No siempre uno puede utilizar las tecnologías que más le gustan sobre todo si son demasiado novedosas como es el caso de Polymer.

Por tanto no soy ningún experto en Polymer, y tampoco creo que abunden porque está todavía en fase expertimental (aún va por la versión 0.5.0 si no recuerdo mal), asi que espero no meter mucho la pata.

Polymer - Betabeers2

En los últimos tiempos se está produciendo un trasvase de funcionalidad desde el back-end al front-end. Hasta hace poco tiempo se procuraba que la lógica de la aplicación residiera en el servidor, utilizando el cliente para la visualización de la información e interacción básica que desencadenaba acciones que se llevaban a cabo en el servidor. En frameworks como JSF, Struts o Spring MVC el programador acaba creando páginas en servidor que son procesadas para ser enviadas al cliente sobre las que no se suele ejercer un control final.

Con la aparición de HTML5 con sus API y la mejora de JavaScript (ECMAScript), y de los intérpretes de los navegadores, en especial gracias al motor V8 de Google, se está trasladando el protagonismo de la aplicación hacia el navegador: aplicaciones en navegador potentes que se comunican con el servidor para el intercambio de datos (servicios REST) usando AJAX y JSON. Es lo que se conoce como Single Page Application o Web Application: una página Web HTML que contiene bibliotecas JavaScript que le permiten capacidades de comunicación con el servidor y de manipulación del DOM para crear una experiencia de usuario superior. De este modo el servidor queda para implementar la lógica de negocio y proveer de unos puntos de acceso a información utilizados por la aplicación Web.

Polymer - Betabeers3

Como la complejidad en el lado del cliente iba creciendo, el código cada vez era más complejo y se debían encontrar forma de estructurarlo al menos como se hace en el back-end. Empezaron a surgir frameworks para JavaScript (y sus evoluciones en forma de ECMAScript).

Algunos ejemplos podemos verlos en este gráfico de demanda de empleos en tecnología, como son EmberJS o BackboneJS. Su objetivo es facilitar el desarrollo de aplicaciones más serias que los fragmentos de script típicos que se suelen incluir en las páginas Web clásicas. Cualquiera que hayáis tenido la oportunidad de hacer alguna página con efectos de JQuery, validación, envío por AJAX de forma más o menos manual pero con JQuery detrás habréis tenido la sensación de que al final se os va un poco de las manos.

Cabe destacar en este gráfico el triunfo de AngularJS sobre los demás. Es un framework para SPA creado por un grupo de desarrollo de Google y que está apostando bastantes recursos para que triunfe. Sobre todo gracias a su relación con Google Chrome. Ahora muchas empresas se están planteando dar el salto de soluciones típicas de JSF o incluso GWT a este framework. Si no lo conocéis o si lo conocéis y os gusta el mundo del desarrollo de Front-end merece mucho la pena que profundicen porque es una apuesta segura.

Polymer - Betabeers4

Una vez descrito este “estado del arte” vamos con Polymer, que se situaría en un nivel por encima de los frameworks vistos anteriormente.

Polymer no es un framework o una librería más que hace uso de HTML5 que pueda tener cierto éxito dependiendo de que se haya puesto de moda en ciertas comunidades o esté siendo utilizado por alguna compañía cool del momento. Polymer es la implementación que Google ha hecho de los WebComponents. Hay otra implementación destacada, que es X-Tag de mozilla.

Por lo tanto la parte valiosa de esta presentación son los Web Components y cómo son implementados por Polymer. Veréis que además de una parte técnica de implementación, lo que es realmente importante es ver cómo cambia la filosofía del desarrollo de aplicaciones web complejas en el navegador.

Como digo no creo que sea una moda pasajera o algo que no llegue a implementarse. Me extrañaría mucho viendo las características que tiene, y el esfuerzo que están haciendo en Google para su desarrollo y utilización (hay productos internos que los utilizan). O el hecho de que W3C esté desarrolando los estándares en los que se basan los Web Components.

Polymer - Betabeers5

Pues como su nombre indica, son componentes que conforman una página Web. A mí me gusta mucho el ejemplo que suelen poner de un combobox de HTML normal y corriente (un select – option a nivel de tag).

Un título H1 o un enlace en HTML tienen una representación sencilla y no parecen hacer mucho, pero cuando escribimos un select con options, el navegador renderiza algo más complejo. Además es algo que tiene un estado que podemos consultar o manipular con Javascript. Por tanto parece un componente con cierta complejidad que usamos en formularios normalmente. Además dispone de un tag específico en HTML lo que facilita enormemente su reutilización.

Polymer - Betabeers6

Pero desafortunadamente este caso es prácticamente un caso aislado dentro de HTML. Además, HTML no nos provee de los suficientes elementos que necesitamos. Primero porque cada uno solemos llegar a un extremo en el diseño en el que tenemos que emplear controles o partes personalizadas (desde controles hasta menús y otros elementos). Y segundo porque los diseños van cambiando.

Es necesario por lo tanto tener un sistema en el que podamos crear estos componentes por nosotros mismos, facilitando la reutilización y la interoperabilidad.

La alternativa actual suele estar relacionada con JQuery UI o tecnologías similares, que arrastran una serie de dependencias. Por ejemplo aquí vemos un control de tipo Slider con jQuery UI.

Podemos ver cómo se hace uso de tag divs con los ID y de funciones de Javascriptm para dotarlo de funcionalidad. Es una opción válida y fantástica en su momento pero cuando las cosas se empiezan a complicar es bastante engorroso. ¿Podemos ver el contenido de una página Web sabiendo sólo su código? ¿Viendo que sólo hay divs con ciertos ID o clases?

Polymer - Betabeers7

Lo que vemos ahora en pantalla es un control equivalente, de tipo slider usando polymer.

Lo primero que vemos es que tenemos un tag especial que no está definido en HTML5, sino que tiene pinta de que álguien particular lo ha elegido. Además los atributos también son específicos del tag.

Debajo vemos la representación del slider. Quizá os suene un poco si habéis visto alguna pantalla hecha con Material Design de Google. Una pena que sólo haya puesto una imagen porque se puede ver que tiene animaciones bastante curiosas.

Debajo podemos ver lo que realmente conlleva una vez se está utilizando, pero ojo! no es el código que nosotros manejamos. A menos que queramos modificar o extender el componente. Nosotros a la hora de trabajar únicamente utilizamos el código superior.

Como podemos ver se parece bastante al ejemplo del combobox con select-option de HTML que hemos visto antes. Una solución elegante y encapsulada.

Por tanto los webComponents son componentes encapsulados y reutilizables que están compuestos por código HTML5, una parte de Javascript y CSS, pero todo ello encapsulado detrás de un tag con un nombre específico.

Ya podéis imaginar que estos elementos los podemos esciribir nosotros po podemos utilizar elementos de terceros. Un ejemplo claro es el que nos provee Google con sus Paper Elements u otras páginas en las que desarrolladores ofrecen los suyos. También podéis imaginar que la cantidad de componentes web será casi infinita con tanta gente colaborando, y que cubrirán casi todas nuestras necesidades. Y sino es muy fácil hacer los nuestros.

Polymer - Betabeers8

Para soportar los WebComponents se han creado 4 estándares de W3C que deberían implementar los navegadores (tal y como hace Chrome) o al menos se deberían soportar a base de Polyfills (que es un fragmento de código Javascript que suple las carencias del navegador (ejemplo cĺásico es el dotar a bordes redondeados a Internet Explorer 8 mediante un script CSS por ejemplo).

Son cuatro tecnologías:

  • Custom Elements: para poder definir nuevos tags en el código, tal y como hemos visto. Esto tags están en minúsculas y tienen que tener obligatoriamente un guión. Así los podemos distinguir de los tags normales.
  • HTML imports: nos permiten incrustar el código de un fichero HTML dentro de otro con un tag de tipo link: . Igual que hacmos un link de css o un tag script de Javavascript, que se pega el contenido tal cual y el navegador lo procesa como un todo.
  • Templates: la capacidad para definir plantillas de código HTML + Javascript + CSS que se aplican a nuestro elementos. Lo hemos visto en el código que realmente se está utilizando en un slider pero que nosotros no vemos como desarrolladores hasta que lo inspeccionamos con la herramienta de depuración.
  • Shadow DOM. Independizar el template o el código que genera del resto de la página, y además está preparado para que un nodo del árbol DOM pueda albergar otros subárboles DOM (expansión). De este modo se evitan los conflictos con otros elementos. ¿Cuántas veces usando JQuery no se ha armado un Belén al coincidir los atributos class, id; las CSS, etcétera. Elimina los efectos laterales. El código de los componentes está aislado completamente. Se podría decir que se establecen unos límites en el arbol DOM.

Más info en: http://webcomponents.org/

Polymer - Betabeers9

Creo que con esto más o menos nos hemos hecho una idea de lo que son los componentes Web. Ahora cuando veamos un ejemplo usando la implementación de Polymer nos quedará más claro. Pero básicamente se podría resumir en:

  • Encapsulación: se tiene un componente con HTML + Javascript + CSS que queda completamente sellado y listo para utilizar. Como una pieza dentro de un engranaje. Ella sola se basta para funcionar (aunque claro, dependiendo del tipo de componente igual no tiene sentido por sí sola, como por ejemplo unos Tabs que no tienen contenido).
  • Interacción: tienen una interfaz para comunicarse y configurarse. A través de javascript podemos manipular su contenido, leerlo, conectarlo con otros elementos…
  • Reutilización: pues derivado de lo anterir, están listo para ser utilizados. Incluso los que no hemos creado nosotros son muy sencillos de reutilizar. Gracias a los templates y al shadow DOM no se “pegan” con otras partes de la Web. Seguro que cuando habéis pegado un componente complejo de Javavascript en vuestro diseño tenéis que pasar un tiempo adaptando.
  • Catálogo de componentes reutilizables como el caso de los Paper Elements de Polymer o los de componente.kitchen. La gente publica sus componentes y podemos reutilizarlos por nuestra parte.

Polymer - Betabeers10

Vamos ahora con Polymer, que como habéis visto ya es la implementación que Google propone para los Web Components. Junto con X-Tags (pero éstos en menor medida), son los Web Componentes más interesantes.

Como características más destacadas:

  • Para nuevos navegadores. Es Chrome el que tira del carro de estas novedades. Google se lo está trabajando mucho, de modo que hay una comunicación continua entre los equipos de desarrollo de los nuevos frameworks como AngularJS o Polymer con el equipo de desarrollo de Chroime. Lo podéis ver por ejemplo en el desarrollo de las herramientas devtools de Chrome, que están todo el día metiendo novedades.
  • Es algo reciente (2013). En Youtube tenéis los vídeos de presentación de Google I/O 2013 y muchos otros comentando polymer que seguro que lo hacen mejor que yo.
  • Existe cierta compatibilidad hacia atrás a través de código Javascript gracias a los Polyfills. Aún así esta tecnología tenéis que tener cuidado a la hora de utilizarla porque no está madura (versión 0.5) y compatibilidad no muy alta. Quizá sea algo demasiado experimental, aunque hay algunas páginas que empiezan a usarlo (como GitHub).
  • Dispone de un catálogo de elementos basados en material design (Android 5) pero para la Web.

Polymer - Betabeers11

Polymer cuenta actualmente con dos bibliotecas de elementos. Por una parte los “Core elements” y por otra los “Paper elements”. Si váis a la página del Proyecto Polymer podréis usarlos.

Tenemos:

  • Core elements: para la funcionalidad básica que se espera en una página web de forma independiente a su apariencia. Se trata de componentes básicos como llamadas Ajax, y efectos de animaciones como Collapse, iconos, barras… estructuración de la página, etc…
  • Paper elements, que es la implementación del Material Design usando Web Components que es la apariencia que Google ha adoptado a partir de Android 5.0

Como digo en la página de Polymer hay unas demos del funcionamiento de cada uno de ellos así como la documentación para utilizarlo, que ya sabéis que es del tipo: pongo el tag, añado unos atributos y listo.

Polymer - Betabeers12

Como ya había dicho antes, todavía es un proyecto que está en desarrollo y que tiene que mejorar sobre todo en compatibilidad con los navegadores que lo soportan. Esto es un trabajo más de los navegadores que de polymer.

Hay dos versiones: una es la versión nativa que cuenta con la compatibilidad sobre todo de Google Chrome (webkit) en sus diferentes versiones y alguno más como firefox por ejemplo. Esta información está sacada de la página de Polymer en la vesión 0.5.0. Es posible que otros navegadores también soporten Web Components de forma específica. sin que aparezcan. Esto hay que verlo en su momento.

Polymer - Betabeers13

Voy a intentar hacer una pequeña demo de Polymer si los medios y el directo acompaña.

Voy a utilizar Yeoman + Grunt + Bower, aunque en la página Web de Polymer os podéis bajar un ZIP que ya lleva todo lo necesario para empezar.

Estos tres elementos me van a permitir:

  • YO (Yeoman): descargar un esqueleto de la aplicación con todo montado.
  • Grunt: correr un servidor para que podamos ver la demo (no funciona en el sistema de ficheros), y si quisiera generar una versión para distribuir con las hojas de estilo optimizadas, código Javascript ofuscado, corriendo test, etcétera.
  • Bower: controlar las dependencias: si quiero añadir más librerías por ejemplo.

Vamos con ello.

* En este punto se puede continuar con el ejemplo descrito paso a paso en el post: Introducción a Polymer. En él se puede ver cómo se instala Yeoman en Ubuntu 14.04 y cómo se desarrolla el ejemplo del componente contador que realicé en la presentación de Betabeers Guadalajara

Polymer - Betabeers14

Clon de Spritz en AngularJS: Fast Reader

Durante estas semanas ha salido a la luz un método de lectura en el ordenador y dispositivos móviles que parece estar llamado a revolucionar la forma de leer textos: presentan el texto palabra por palabra en un cuadro, centrando cada palabra en una posición. De este modo se evita perder tiempo en enfocar la vista en la siguiente palabra. Se puede encontrar más información en muchas noticias, como por ejemplo aquí, o en su sitio oficial: http://www.spritzinc.com/

La parte mala es que todavía no está disponible, más allá de una simple demo. Así que como me gustaría utilizarlo ya mismo, y no voy a apuntarme a su cartera de beta-testers, he decidido usarlo como excusa para hacer una webapp usando AngularJS  y Bootstrap.

Si queréis probar mi implementación, lo podéis hacer en http://rusizate.com/reader/ y como siempre el código fuente en el respositorio de GitHub: https://github.com/4lberto/FastReader

lectura

En cuanto a la parte técnica es muy sencilla. Sigue estos pasos:

  1. Lee el texto del textarea, que se guarda en una variable.
  2. Lo transforma a un array de palabras
  3. Va mostrando cada cierto tiempo las palabras en pantalla, calculando dos cosas:
    1. Qué letra se va a iluminar en rojo: mediante un switch case, dependiendo de la longitud de la palabra.
    2. Cuántos espacios se va a insertar delante de la palabra para que quede centrada respecto de la letra iluminada.

Otros dos aspectos destacables:

  • Control del tiempo en AngularJS: mediante el componente $timeout, que es como el window.setTimeout de Javascript. Es decir, espera una serie de milisegundos para llamar a una función. Metido en un bucle y mientras haya palabras por mostrar se va ejecutando. es muy importante usar $timeout y no window.setTimeout en AngularJS. El método tradicional no funciona.
$timeout(loop, Math.floor(((palabra.length+2)/4)*$scope.speed));
  • Necesitamos mostrar una palabra que tiene código HTML dentro. Para pintar de rojo la letra, debemos rodearla con un span. Para ello , en angular se puede usar $sce.trustAsHtml(palabra) y luego en el atributo de la vista podemos usar: ng-bind-html=”palabraActual”
<h3 ng-bind-html="palabraActual">{{palabraActual}}</h3>

 

$scope.palabraActual=$sce.trustAsHtml('pal<span class="rojo">a</rojo>bra');

En cambos casos tenemos que inyectar $timeout y $sce en el controlador de AngularJS


function TodoCtrl($scope, $timeout, $sce) {

El controlador al final queda de este modo:

</pre>
function TodoCtrl($scope, $timeout, $sce) {

var textoSplit;
 $scope.palabraActual=$sce.trustAsHtml("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;a-word");
 $scope.indicePalabra=0;
 $scope.textoArea = "There is something at the bottom of every new human thought, every thought of genius, or even every earnest thought that springs up in any brain, which can never be communicated to others, even if one were to write volumes about it and were explaining one's idea for thirty-five years; there's something left which cannot be induced to emerge from your brain, and remains with you forever; and with it you will die, without communicating to anyone perhaps the most important of your ideas. - Fyodor Dostoyevsky, The Idiot"
 $scope.speed = 120;
 $scope.speedFactor = 10;
 $scope.leyendo = false;

$scope.startReading = function()
 {
 $scope.leyendo = true;

if(!((textoSplit)&&(textoSplit.length>0)))
 {
 $scope.indicePalabra=0;
 textoSplit = $scope.textoArea.split(" "); //Separa el texto
 }

loop();

}

$scope.stopReading = function()
 {
 $scope.leyendo = false;
 }

var loop = function()
 {
 console.log("Han llamado a loop:" + textoSplit.length + " - indicePalabra" + $scope.indicePalabra);

if(textoSplit.length>$scope.indicePalabra)
 {

var palabra = textoSplit[$scope.indicePalabra];
 var puntoCentral = calculaPuntoCentral(palabra);

$scope.palabraActual = $sce.trustAsHtml(generaEspacios(puntoCentral)+colorea(palabra, puntoCentral));

$scope.indicePalabra++;
 if($scope.leyendo)
 $timeout(loop, Math.floor(((palabra.length+2)/4)*$scope.speed));
 }
 else
 {
 console.log("Fin lectura...");
 }
 }
 $scope.speedUp = function()
 {
 $scope.speed += $scope.speedFactor;
 }

$scope.speedDown = function()
 {

$scope.speed = Math.max(0,$scope.speed-$scope.speedFactor);
 }

$scope.clearTextArea = function()
 {
 $scope.textoArea="";
 textoSplit = [];
 $scope.indicePalabra=0;
 $scope.palabraActual=$sce.trustAsHtml("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;a-word");
 $scope.leyendo = false;
 }
 var calculaPuntoCentral = function(palabra)
 {

var puntoCentral = 1;

switch ( palabra.length) {
 case 1:
 puntoCentral = 1;
 break;
 case 2:
 case 3:
 case 4:
 case 5:
 puntoCentral = 1;
 break;
 case 6:
 case 7:
 case 8:
 case 9:
 puntoCentral = 2;
 break;
 case 10:
 case 11:
 case 12:
 case 13:
 puntoCentral = 3;
 break;
 default:
 puntoCentral = 4;
 };

return puntoCentral;
 }
 var generaEspacios = function(puntoCentral)
 {
 var espacios = 10;
 espacios = espacios - puntoCentral;
 var esp = "";
 for(var i = 0;i<espacios;i++)
 {
 esp+="&nbsp;"
 }

return esp;
 }
 var colorea = function(palabra, indice)
 {
 if (palabra.length <2)
 return '<span class="rojo">' + palabra + '</span>'
 else
 return palabra.substr(0,indice-1) + '<span class="rojo">' + palabra.substr(indice-1,1) + '</span>' + palabra.substr(indice,palabra.length);

}

}
<pre>

El código fuente completo está en el respositorio de GitHub: https://github.com/4lberto/FastReader y la demo en http://rusizate.com/reader/

$resource para comunicación RESTful en AngularJS

En el post anterior hemos visto cómo se podía hacer una comunicación con un servidor RESTful mediante el objeto $http de AngularJS. Este objeto proporcionaba una interfaz para las llamadas AJAX parecida a la que se puede hacer con JQuery o Prototype.

En realidad referirse a la anterior como comunicación como RESTful era un tanto específica, puesto que se trataba de un caso particular. En realidad eran llamadas AJAX y no tenían por qué hacerse a un servidor RESTful específicamente: cualquiera que admitiera una llamada AJAX valdría.

Por recordar, dentro del controlador teníamos algunas funciones como estas.

$http.get("http://127.0.0.1:5984/dibujo/"+id).success(function(datos)
{
//Operaciones con el objeto JSON datos obtenido de la llamada
});

$http.post("http://127.0.0.1:5984/dibujo", {"e":$scope.elementos,"t":new Date().getTime()}).success(function(datos)
 {
//Operaciones una vez se ha enviado el objeto en formato JSON
 });
<span style="line-height: 1.5em;">

Es decir, llamadas GET y POST indicando la URL directamente, e incluso, si hacía falta, componiendo la URL final con el id a base de concatenación de cadenas de texto. También existían otro tipo de llamadas como DELETE, HEAD o PUT (http://docs.angularjs.org/api/ng.$http).

AngularJS es un framework centrado en facilitar la vida a los programadores, con un nivel de abstracción bastante elevado y la comunicación con un servidor puramente RESTful es una buena candidata a automatizar tareas.

Mediante $resource, AngularJS pone a nuestra disposición objetos para interactuar con un servidor que soporte el protocolo RESTful. Este objeto encapsula los mecanismos de comunicación RESTful, evitando tener que bajar al nivel de hacer llamadas mediante $http. Para que funcione es necesario utilizar el módulo ngResource (ver cómo instalarlo en http://docs.angularjs.org/api/ngResource).

Así pues, usaremos el objeto proporcionado por $resource para representar la comunicación con el servidor para un modelo de datos. En realidad encapsula los datos y le añade además una serie de propiedades o funciones para controlar la comunicación con el servidor.

Si continuamos el ejemplo que vengo usando en este blog de la página de dibujo al pasar el ratón sobre un grid, tenemos que, básicamente, el objeto consistía en dos campos: “e” para mantener un objeto que contiene como métodos las coordeadas de los elementos sobre los que había pasado el ratón y el número de veces que lo había hecho; y “t” para indicar la fecha, en milisegundos en la que se habían salvado los datos.

Analizando la siguiente imagen de DevTools de Chrome, se puede ver que “element” es un objeto devuelto por $resource y que encapsula los datos descritos para el dibujo. Podemos ver:

  •  “e” y “t” como se han descrito. Que son los datos que nos devuelve el servidor y que nos interesa.
  •  “_id” y “_rev” que son el id y la versión del documento tal y como lo proporciona CouchDB (revisar post anteriores para más información).
  •  __proto__ que son las operaciones que se añaden y que permiten la comunicación con el servidor RESTful. Como se puede ver, coinciden con los operadores por defecto.

resource1

 

Por lo tanto, el objeto “element” encapsula por una parte la información obtenida de la llamada, y por otra parte añade las operaciones para la comunicación RESTful. Además añade otros elementos como por ejemplo un $promise para implementar la carga asíncrona de información por la llamada AJAX.

Vamos a ver cómo podemos implementarlo.

En primer lugar hay que inyectar el recurso en el controlador de la aplicación:

app.controller("ctrlDibujo", ["$scope","$http", "$resource", ,function($scope, $http,$resource)

De este modo podremos usar la factoría $resource dentro de nuestro código.

La primera forma es imitando a una llamada XHR normal y corriente. No hace uso de la potencia de $resource pero nos da una idea de su uso. Como se puede ver se indica la dirección del servidor así como la URL completa para cargar un objeto determinado:

var Resultado = $resource('http://127.0.0.1:5984/dibujo/9181ffd10aa47c52e1cee65ce0000195').get();

Esto no es realmente útil. Lo ideal sería poder parametrizar la llamada y poder referirnos con el identificador RESTful. Sería algo así:

var Resultado = $resource('http://127.0.0.1:5984/dibujo/:id', {id:"9181ffd10aa47c52e1cee65ce0000195"}).get();

En este caso se puede ver cómo “:id” será sustituído por el valor de “id” del objeto pasado por parámetro. Para el ejemplo se ha completado de forma manual, pero seguro que sabes cómo asignarle una variable.

En ambos casos el resultado es un objeto que contiene la información devuelta por el servidor más las operaciones que le permiten una sencilla comunicación con el servidor.

Una cosa que llama la atención es la asignación a la variable Resultado. Al tratarse de una llamada asíncrona, AngularJS devuelve un promise cuando se realiza la operación GET de modo que la carga de la información a la variable se realizará cuando sea posible. Por tanto no podemos utilizar posteriormente estos datos en la lógica de negocio. No obstante si necesitamos realizar alguna operación, siempre tenemos la opción de usar las funciones de CallBack:

var Resultado = $resource('http://127.0.0.1:5984/dibujo/:id', {id:"9181ffd10aa47c52e1cee65ce0000195"}).get(function(elemento){//...});

Otra de las formas de utilizar $resource es usando un servicio en la aplicación para luego inyectarlo dentro de nuestro controlador. Por ejemplo:

app.factory('ElementoResource', ['$resource', function($resource){
return $resource('http://127.0.0.1:5984/dibujo/:id',{id:"@_id"});
}]);

No debemos olvidar usar el servicio dentro de nuestro controlador:

app.controller("ctrlDibujo", ["$scope","$http", "$resource", "ElementoResource",function($scope, $http,$resource, ElementoResource)

Dentro del $resource del servicio se ha espeficado que el parámetro “id” se lee del campo “_id” del objeto al que arropa. Esto vendrá perfecto para la comunicación con el servidor CouchDB que usa este valor.

Usarlo es sencillo:

 $scope.objetoResource = ElementoResource.get({id:"9181ffd10aa47c52e1cee65ce0000195"}, function(element, $scope)
 {
element.t++;
element.$save();
 });

En este ejemplo se ha pasado el objeto {id:”9181ffd10aa47c52e1cee65ce0000195″} para indicar qué elemento recuperar; se ha aumentado en 1 el valor de su parámetro “t” y se ha vuelto a grabar. Como se puede ver es algo realmente sencillo de utilizar.

Nótese cómo se ha utilizado el Callback para manipiular el objeto.

También es posible crear un nuevo objeto y comunicarse con el servidor:

var nuevoElemento = new ElementoResource();

nuevoElemento.e = {"a1":12};
nuevoElemento.t = new Date().getTime();

nuevoElemento.$save();

nuevoElemento.t = 100;
nuevoElemento.$save();

En este caso se puede ver cómo se ha creado un nuevo objeto usando el servicio, se le han asignado unas propiedades “e” y “t” y luego se ha salvado con el servidor RESTful. Posteriormente se ha modificado la propiedad “t” asignándole 100 de valor y se ha vuelto a salvar.

Sin usar servicio de por medio también es muy directo su uso, y nos evita mucho trabajo respecto a si usáramos $http

var Elemento = $resource('http://127.0.0.1:5984/dibujo/:id');

var nuevoElemento = Elemento();
nuevoElemento.t=23;
nuevoElemento.$save();
...

En resumen, $resource de AngularJS permite comunicar nuestra aplicación con un servicio RESTful de una manera muy sencilla y eficiente.

CouchDB como base de datos para AngularJS

Vamos a ampliar el ejemplo anterior hecho con AngularJS (https://mysticalpotato.wordpress.com/2014/01/31/creando-una-sencilla-aplicacion-con-angularjs-y-yeoman/) con una interesante funcionalidad que nos permitirá guardar el estado del dibujo que hayamos hecho en un momento determinado y claro está, volver a recuperarlo más adelante.

Como se vio en el post anterior, se utiliza una estructura de objetos en Java (por tanto convertible a formato JSON) para almacenar el número de veces que se había visitado cada una de las celdas del ejemplo. De este modo, si almacenamos este objeto, podremos reproducir fielmente la posición en un momento determinado.

Existen diferentes opciones para guardar los diferentes estados para nuestra aplicación. Por ejemplo podemos utilizar el localstorage de HTML5 o también podemos usar una base de datos. En este último caso lo mejor es tratar con una base de datos que pueda manejar la información en formato JSON. Existen múltiples alternativas, pero en este caso nos vamos a decantar por CouchDB.

Usar CouchDB para este ejemplo nos ofrece varias ventajas. La primera es que es una base de datos NoSQL que maneja documentos JSON de forma nativa. Otra ventaja es que para interaccionar con ella, monta un servidor RESTful a través de HTTP. Esto facilita enormemente las llamadas AJAX que tenemos que hacer en AngularJS.

Vamos pues en primer lugar a ver cómo se instala y se crea la base de datos para salvar el estado del dibujo en cada momento con CouchDB. Más adelante veremos lo fácil que es modificar utilizar AngularJS para soportar esta nueva funcionalidad.

Seguimos los siguientes pasos:

  1. Descargar la última versión de CouchDB (al momento de escribir este artículo era la 1.5.0). Accedemos a su sitio web y descargamos el fichero. Están en http://couchdb.apache.org/
  2. Instalamos CouchDB. Muy fácil, sólo hay que indicar el lugar del disco duro donde lo pondremos. A vuestro gusto. Una cosa que no he hecho es hacer que se arranque como servicio.
  3. Accedemos al directorio de instalación/bin y ejecutamos CouchDB.bat . Así se arranca el servidor, que responde en http://127.0.0.1:5984/

Si queremos podemos probar a llamarlo desde un navegador y hacer llamadas. También podemos usar cURL para invocar las acciones de la API. Tienes una información detallada en la documentación oficial de esta base de datos: http://docs.couchdb.org/en/latest/intro/api.html

Por simplificar el proceso, CouchDB incluye un gestor Web integrado en su servidor. Su acceso es muy fácil, http://127.0.0.1:5984/_utils/ . Este manager se llama “Futon”. Como dato de la versatilidad de CouchDB, esta base de datos permite almacenar documentos y servirlos como si de un servidor web se tratase. Futon es una aplicación Web que sirve la base de datos, e incluso nosotros podremos incluir nuestra aplicación (HTML+CSS+JS) y hacer que CouchDB la sirva a los navegadores. Son los llamados Design Documents (http://guide.couchdb.org/draft/design.html).

Veamos cómo crear la base de datos dentro de CouchDB.

En primer lugar creamos una base de datos para almacenar la información. Entramos en Futon (http://127.0.0.1:5984/_utils/) Pulsamos sobre “Create Database…” y le damos el nombre “dibujo”. Aquí se almacenarán los objetos Javascript que determinan el estado de cada celda del dibujo, esto es, el número de veces que se ha pasado el ratón por encima.

couch1

Si abrimos el navegador e introducimos la dirección http://127.0.0.1:5984/dibujo podremos ver información acerca de la base de datos recién creada. Después de varios usos podremos obenter algo así:

{"db_name":"dibujo","doc_count":28,"doc_del_count":0,"update_seq":30,"purge_seq":0,"compact_running":false,"disk_size":90216,"data_size":13301,"instance_start_time":"1391813178196821","disk_format_version":6,"committed_update_seq":30}

Sin entrar mucho en detalle, CouchDB almacena por cada uno de los registros un identificador (_id) y un número de revisión (_rev). El Id se asigna autmáticamente si no indicamos nada, lo mismo que el número de revisión (_rev). Sí, aquí se almacenan diferentes versiones de un mismo registro, de modo que hay un histórico de datos. Estos dos valores son básicos, y se les tienen que unir el resto de datos que vamos incluyendo. Al final podríamos obtener algo así:

{
 "_id": "9cd4cfb4841782207f7f3460f1002285",
 "_rev": "3-313a13f005e452222cbc1e4aca28121a",
 "e": {
 "a2": 2,
 "b2": 2,
 "c2": 2,
 "d6": 1,
 "c6": 1,
 "b6": 2,
 "b7": 3,
 "a7": 2,
 "c3": 1,
 "d3": 1,
 "d4": 1,
 "c4": 1,
 "c5": 1,
 "b5": 1,
 "a8": 1,
 "a9": 1,
 "c7": 1,
 "d7": 1,
 "d9": 1,
 "b9": 1
 },
 "t": 1391809957089
}

Donde:

  • “e” es el atributo que representa cada uno de las celdas sobre las que ha pasado el ratón.
  • “t” es la fecha en la que se ha insertado el registro (un simple new Date().getTime() en Javascript).

Cabe suponer por lo tanto, que usando la característica RESTful de CouchDB, podamos sacar un registro concreto de la base de datos mediante la llamda a esta URL: http://127.0.0.1:5984/dibujo/9181ffd10aa47c52e1cee65ce0006ecd . Esto es, usamos el campo _id para identificar el elemento, asique lo tenemos fácil a la hora de recuperar la información de la base de datos.

Por otra parte también parece interesante recuperar un listado de los elementos que hay en la base de datos, para poder cargar la información. En una base de datos tradicional NoSQL haríamos algo así como  SELECT * FROM dibujo. Pero esto es una base de datos NoSQL y se emplea otro paradigma: MapReduce . A grandes rasgos es una combinación de dos funciones, Map y Reduce, que se aplican de forma secuencial, una detrás de otra y que construyen a partir de un conjunto de datos un conjunto de resultados:

  • Map se encarga de recorrer todos los elementos de la base de datos y extraer como resultados los que cumplen el criterio indicado. Es decir, para un conjunto de datos almacenados en la base de datos extrae otro conjunto de resultados.
  • Reduce se aplica sobre los elementos resultados obtenidos de Map para obtener un valor reducido, como por ejemplo la suma de un determinado campo de los resultados. Gracias a la arquitectura interna de CouchDB basado en árboles-B (B-tree) se consiguen unos rendimientos espectaculares.

Vamos a crear una función Map que nos devuelva los elementos que en la base de datos usando Futon (http://127.0.0.1:5984/_utils/).

Acedemos a nuestra base de datos (dibujo) y a la derecha arriba seleccionamos en el desplegable “View” la opción “Temporary view…”. Aparecerá una tabla con dos grandes celdas: la de la izquierda para escribir la función de Map y la de la derecha para Reduce. Vamos a ir solamente con la de Map.

couch1

Si vemos la función por defecto es la siguiente:

function(doc) {
 emit(null, doc);
}

couch1

Esta función Javascript se aplica a todos y cada uno de los documentos de la base de datos. Por ello tiene un parámetro llamado “doc”, que representa cada documento. Sí, se llama una vez por cada documentos que exista. El siguiente elemento que destaca es la función “emit”, que se encarga de incluir un resultado. Este resultado se emite en forma de dupla: [key, value]. Los resultados se ordenan respecto a la key (recordad la estructura de árbol-B). Además debemos saber que esta función se aplica cuando hay un cambio en la base de datos, y no cada vez que se llama la vista. Esto es una gran ventaja de cara al rendimiento de la base de datos.

Nuestra función Map será la siguiente:

function(doc) {
if(doc.e && doc.t)
 emit(doc.t, doc.e);
}

Que no es otra cosa que emite un resultado cuando “e” (los elementos del grid y las veces que se ha pasado el ratón sobre ellos) y “t” (la fecha) no sean nulos. Además emite como resultado la dupla (t,e) , es decir, el tiempo o fecha en la que se ha creado el registro y el contenido. Pulsamos sobre el botón “Run” y vemos los resultados (es posible que no veas nada porque aún no tienes datos… en este caso crea varios documentos nuevos con New Document). Si los resultados son los esperados, salvamos pulsando el botón Save As. De nombre elegiremos “_design/dibujo” y de nombre de la vista será “todos”.

Si hemos hecho todo bien veremos cómo podemos acceder a esta vista mediante la URL: http://127.0.0.1:5984/dibujo/_design/dibujo/_view/todos

Es el turno ahora de modificar el ejemplo de AngularJS para poder escribir y leer los registros de la base de datos.

En primer lugar debemos saber que AngularJS dispone de una interfazz para hacer llamadas AJAX contra el servidor fácilmente, al estilo JQuery. Esta interfaz es $http (http://docs.angularjs.org/api/ng.$http) y está especialmente diseñada para soportar el protocolo REST.

Comenzaremos por modificar el controlador existente para añadir una nueva función que salve el estado actual del juego:

$scope.saveElementos = function()
{
    $http.post("http://127.0.0.1:5984/dibujo", {"e":$scope.elementos,"t":new Date().getTime()})
    .success(function(datos)
    {
       $scope.listadoElementos.push(datos);
       $scope.respuestaServidor = datos;
    });
}
 

Podemos ver cómo hacemos una petición de tipo POST a la URL de la base de datos, pasando por parámeotr un objeto que tiene dos atributos “e” para salvar los elementos y “t” para salvar la fecha y hora. Los elementos se pueden sacar de $scope.elementos, que como ya sabemos de artículos anteriores, está continuamente actualizado gracias al doble binding con la vista de angular. La fecha se saca simplemente los milisegundos con el new Date().

Como se trata de una función asíncrona, preparamos una función anónima con el resultado del servidor. Por una parte insertamos con push() el resultado (que es el elemento insertado) en un listado en el que tenemos todas las partidas guardadas. Por otra parte actualizamos una simple etiqueta que muestra por pantalla el resultado. No hemos implementado la función de error.

Así se fácil es. Se crea una función que usa $http y funciona correctamente. ¡Ah! Es buena idea incluir la inyección del elemento $http en el controlador que acabamos de modificar.

angular.module('angularJs2BbApp').controller("ctrlDibujo", ["$scope","$http",function($scope, $http, elementosProvider)

El último paso para salvar elementos es crear un botón o un enlace que llame a esta función. Esto se tiene que hacer en la vista:

<button ng-click="saveElementos()"class="btn btn-warning">Salva elementos...</button>

Cuando se haga click sobre este botón se llamará a la función y se creará un nuevo estado en la base de datos con los datos de las celdas del dibujo.

Problema CORS

Aún queda un problema por resolver… y es el CORS (Cross-Origin resource sharing). Que resuminéndolo en una frase sería una llamada AJAX hecha de un dominio a otro. Es decir, nuestra aplicación puede estar corriendo en 127.0.0.1:9000 si la ejecutamos con gruntJS, pero el servidor CouchDB y su interfaz RESTful responden en 127.0.0.1:5984. Aunque es la misma IP no nos debemos olvidar del puerto, que hace que sea otro dominio diferente, y por eso dé el error. ¿Cómo podemos resolver esto?

La solución pasa por hacer que el servidor CouchDB emita una cabecera HTTP en cada llamada, indicando que admite que le hagan peticiones desde determinados dominios. Esta cabecera es “Access-Control-Allow-Origin”. ¿Cómo configuramos CouchDB par que la emita?

Es muy sencillo, sólo dos pasos (asegurante de tener una versión que lo soporta, la 1.5.0 sí que lo soporta – desconozco a partir de qué versión funciona):

1. ir a /etc/local.ini
2. Introducir estos valores:

[httpd]
enable_cors = true
[cors]
origins = *

3. Reinicamos el servidor CouchDB

Con esto le indicamos al servidor de CouchDB que vamos a recibir peticiones AJAX desde otro dominio, y que las aceptamos. Nuestra aplicación Angular, al recibir la contestación, verá que en la cabecera se permite su dominio, esto es “Access-Control-Allow-Origin:http://127.0.0.1:9000&#8221;. Así debería funcionar todo correctamente.

Además de ir almacenando las partidas, parece interesante disponer de algún método para ver las partidas que ya hay almacenadas. Es en este momento cuando vamos a utilizar la vista creada unos párrafos atrás. Cada vez que se cargue la aplicación web mostraremos un listado con las partidas guardadas en la base de datos.

Para ello procedemos de modo parecido a antes, utilizando la interfaz que nos proporciona $http para hacer llamadas AJAX:

$scope.loadTodosElementos = function()
{
 $http.get("http://127.0.0.1:5984/dibujo/_design/dibujo/_view/todos").success(function(datos)
 {
 $scope.listadoElementos=datos.rows;
 });
}

La URL que invocamos es la de la vista creada, que se encuentra dentro del documento_design dibujo. Esta vista nos devuelve un objeto JSON con metainformación, y dentro de su campo “row” se encuentra el listado de resultados que necesitamos. Utilizamos la variable “listadoElementos” para guardar cada uno de los estados del juego.

Usamos AngularJS para mostrar todos los elementos con un simple ng-repeat

<ol>
    <li ng-repeat="lista in listadoElementos"><a ng-click="loadElementosById(lista.id)" >{{formateaFecha(lista.key)}}</a></li>
</ol>

Si te fijas, para mostrar la fecha hemos usando la función “fromateaFecha”, que también es una función que forma partel del controlador que hemos creado. Vamos a crearla:

$scope.formateaFecha = function(milis)
{
    console.log("fecha: " + milis);
    return moment(new Date(milis)).format('llll');
}

Para facilitar el trabajo se ha usado http://momentjs.com/ que es una biblioteca de Javascript para el manejor de fechas. La puedes bajar directamente e incluirla dentro del index.html o usar bower si has usado Yeoman:

bower install -S momentjs

En el listado de las partidas, si te fijas, he incluído un enlace con ng-click y la llamada a la carga de la función por Id. Puedes suponer que se trata de otra función que hace uso del API RESTful de CouchDB:

$scope.loadElementosById = function(id)
{
    $http.get("http://127.0.0.1:5984/dibujo/"+id).success(function(datos)
    {
        $scope.elementos = datos.e;
    });
}

Con la URL apuntando hacia el ID correspondientes descargaremos el estado del dibujo determinado y como estamos usando AngularJS, al cambiar el modelo (la variable $scope.elementos), se cambia rápidamente la vista (que es el grid de divs sobre los que se pasea el ratón). El resultado total sería algo como esto:

angular2

En resumen, hemos visto cómo usar $http para conectar con una base de datos CouchDB que pone a nuestra disposición una interfaz RESTful para el desarrollo de aplicaciones AngularJS de una forma rápida y escalable. 

PS: podéis acceder al GitHub de esta aplicación en: https://github.com/4lberto/AngularJS_CouchDB

imgExtract: Biblioteca en Javascript para extraer imágenes de una URL

Hace un tiempo escribí una entrada (https://mysticalpotato.wordpress.com/2012/10/27/obtener-imagenes-de-un-enlace-al-estilo-facebook-usando-yql-json-y-jquery/) sobre la carga de imágenes usando YQL de Yahoo para extraer las imágenes dada una URL. He tenido que volver a usar el código, así que me he decidido a encapsularlo y guardarlo en GitHub para que cualquiera pueda usarlo: https://github.com/4lberto/imgExtract

Tenéis un ejemplo de uso en http://rusizate.com/imgExtract/test.html

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

HTML5 localStorage y JSON para dotar de persistencia a una Extensión de Chrome

Uno de los problemas a la hora de trabajar con Javascript+HTML que se ha tenido siempre es cómo almacenar información persistente de la aplicación que hemos creado en el navegador. Javascript fue concebido principalmente para manejar el árbol DOM y hacer modificaciones en la interfaz de usuario para hacerla más atractiva, de donde surgió aquel término llamado DHTML (Dynamic HTML). La tendencia actual es a crear aplicaciones autónomas en el navegador, tan potentes como las tradicionales aplicaciones ejecutables en un sistema operativo pero dentro del navegador, gracias a la aparición de nuevas funcionalidades, como las llamadas asíncrontas y sobre todo a la optimización de los motores de ejecución de Javascript en los navegadores, que se convierten en máquinas virtuales potentes sobre las que ejecutar scripts, con los benficios de la interfaz de HTML.

Como decía, uno de los problemas es dotar de más autonomía a la aplicación escrita en Javascript+HTML, y esto suele pasar por disponer de algún mecanismo de persistencia que permita almacenar la información del usuario para que la próxima vez disponga de la misma configuración o datos históricos. Antes de HTML5 se empleaban algunas técnicas como el uso de cookies para estas labores; o el método más correcto de tener un servidor de aplicaciones con un perfil de usuario en el que se almacena la información en remoto, pero esto requiere de conexión a Internet y de una infraestructura de servidor que realice esta tarea, con el coste que ello tiene.

Sin embargo, desde la aparición de HTML5 y de los navegadores que implementan esta especificación, podemos usar una de sus muchas ventajas: el almacenamiento local o localStorage.

Mediante localStorage podremos almacenar en el espacio que nos proporciona el navegador pares de atributos “string”,”string” hasta un máximo de 5 megas de espacio (las limitaciones a cadenas de texto y almacenamiento corresponden a la implementación que hacen los navegadores actuales de HTML5). De este modo podremos almacenar y recuperar estos valores en cualquier momento en nuestro código Javascript.

Para almacenar una cadena de texto en una variable procederemos esta forma:

localStorage.setItem("mivariable","mivalor");
//o lo que es lo mismo:
localStorage.mivariable = "mivalor";

La lectura del valor sigue el procedimiento esperado:

var valor = localStorage.getItem("mivariable");
//o lo que es lo mismo:
var valor = localStorage.mivariable;

La información permanecerá en localStorage de manera persistente y fiable, de modo que podemos apoyarnos en ella para almacenar datos como la configuración de la aplicación o información histórica el usuario.

A continuación se va a emplear localStorage de Chrome para ampliar la extensión básica que se creó en el post https://mysticalpotato.wordpress.com/2011/01/15/extension-sencilla-para-google-chrome/, de modo que se almacena el histórico del tiempo de uso. El resultado de esta modificación se corresponde con la versión 0.3 de la extensión, que puede descargarse en https://chrome.google.com/extensions/detail/lkhahcnmgjhbbdkieijbndecclgcibjp?hl=en#

Para almacenar el histórico de tiempo necesitamos conocer qué valores tenemos que almacenar y cuando. Necesitamos conocer el tiempo de inicio y el de final en cada sesión, que se encuentran en formato de milisegundos en la página background.html, que si se revisa el post anterior de la extensión, se verá que se carga cuando la extensión comienza la ejecución. De este modo el par de datos (inicio, fin), nos dará el tiempo de inicio y el tiempo que ha durado la sesión (fin-inicio). Están en formato Date, que se transforma a milisegundos desde el 1 de enero de 1970, para poder localizarlo con precisión en el tiempo. El resultado del almacenamiento es por lo tanto un array de pares de tiempo.

En primer lugar debemos crear un objeto en Javascript que modele este tiempo:

//Time object
function tiempo (inicio, fin)
{
	this.inicio = inicio;
	this.fin = fin;
}

Lo que queremos almacenar por lo tanto será un array de objetos tiempo. El problema radica ahora en encontrar la forma de transformar el array de tiempo a una cadena de texto y viceversa. El mejor método es hacer uso de JSON. En post anteriores se ha podido ver cómo emplear JQuery y algunas librerías especiales para manejar este tipo de objetos. En esta ocasión usaremos métodos nativos del navegador Chrome. La explicación es que la extensión es específica de Chrome, y tenemos la certeza absoluta de que el usuario dispone de este requisito para correr la app.

Almacenaremos una String de este tipo, que contiene un array de objetos tiempo en formato JSON:

[{"inicio":1295726332072,"fin":1295726339310},{"inicio":1295726339311,"fin":1295726527498},{"inicio":1295726527501,"fin":1295727082467}]

Suponiendo que en tiempo_instancia tenemos un objeto “tiempo” correctamente formado, su transformación a JSON es sencilla:

JSON.stringify( tiempo_instancia );

El problema está en cuándo realizar el almacenamiento de la información: cuando se cierra el navegador o se desactiva la extensión. Estos dos eventos tienen en común que el cierre de background.html, de manera que se puede capturar el evento y llamar a una función:

 //Event when extension closes.
window.onunload =
	function () {
	guardaEstadisticas();
};

Efectivamente, cuando se cierra la extensión o se cierra el navegador se llama a la función guardaEstadisticas(). Ésta se encarga de almacenar el objeto tiempo de la sesión actual, lo que significa encontrarse frente a dos situaciones: que no exista ningún valor anterior o que ya haya un array con uno o varios valores previos. En el primer caso habrá que añadir los corchetes “[]” para indicar que es un array de un sólo elemento. En el segundo caso habrá que recuperar el array , transformándolo desde JSON a array de objetos de Javascript, hacer un push para añadir el último objeto al array y volver a pasarlo a cadena de texto en formato JSON:

function guardaEstadisticas()
{
	//When extension ends, adds the time it's been used
	var tiempo_instancia= new tiempo(tiempo_inicio.valueOf(), (new Date()).valueOf());
	if(!localStorage.COT_history) //first time 
	{
		localStorage.COT_history = "[" + JSON.stringify( tiempo_instancia ) + "]";
	}
	else  //there was some data before
	{
		var historia = JSON.parse(localStorage.COT_history);
		historia.push(tiempo_instancia);
		localStorage.COT_history = JSON.stringify(historia);
		console.log("Añadido:" + JSON.stringify(historia));
	}
}

Como se puede comprobar, pasar de String en formato JSON a array de objetos se puede hacer a través del método JSON.parse que provee Javascript de Chrome. La comprobación de si existe registro anterior es sencilla: if(!localStorage.COT_history) . Como se puede ver, se ha elegido la clave COT_history para llamar a esa variable en el almacenamiento del navegador.

Otro aspecto es cómo se muestran las estadísticas. En este caso he empleado una página nueva llamada history.html enlazara con un enlace normal desde popup.html. Se encarga de recuperar la información del localStorage, pasarla de JSON a array de objetos tiempo y mostrarla mediante un bucle en una tabla, con la particularidad que muestra los tiempos en modo descendiente hasta un máximo de 15 tiempos.

function pintaEstadisticas()
{
	var tiempos = JSON.parse(localStorage.COT_history);
	var salida = "<table><thead><tr><td>Start Time</td><td>Opened for</td></tr></thead><tbody>";
	var limite = Math.max (tiempos.length-15, 0 );
	for (i = tiempos.length-1;i>=limite;i--)
	{
		salida = salida + "<tr><td>";
		salida = salida + (new Date(Number(tiempos[i].inicio))).toString().substr(0,24);
		salida = salida +"</td><td>" + formatTime(Number(tiempos[i].fin)-Number(tiempos[i].inicio));
		salida = salida + "</td></tr>";
	}
	salida = salida + "</tbody></table>";

	document.write(salida);
}

El resultado es:

Finalmente existe la posibilidad de borrar las estadísticas, que no es otra cosa que borrar la información que corresponde a la clave local “COT_history”. Para ello se ha incluido un enlace en history.html que lanza una función que realiza el reset del almacenamiento:

function clear_stats()
{
	localStorage.COT_uses = 1;
	localStorage.removeItem("COT_history");
	window.location.reload()

	return null;
}

Para concluir, podemos ir a la ventana de extensiones pulsando en la llave de opciones de Chrome -> tools -> extensions y activar el “developer modo”, ir a la extensión y pulsar sobre el enlace background.html. Aparecerá la ventaja de inspección (en cualquier página pulsando Ctrl+Shift+j). Pulsando sobre el icono Storage (3º por la derecha) podremos ver los datos del localStorage.

El resultado de este post y otras modificaciones más dieron lugar a la version 0.3 de la aplicación, que puede verse aquí: https://chrome.google.com/extensions/detail/lkhahcnmgjhbbdkieijbndecclgcibjp?hl=en#

Adjunto el zip de la extensión. OJO: RENOMBRAR A .ZIP

JSONP. Salvando la limitación de dominio de XHR cuando se hace Cross-Scripting (XSS)

Existe una variación de JSON llamada JSONP (P de padding – con relleno-) que permite salvar uno de los problemas que existe en las llamadas AJAX a través del objeto XMLHttpRequest. Esta limitación consiste en que desde la página web no podemos escribir un Javascript que llame a otro dominio. Esto es así para evitar problemas de seguridad.

Por ejemplo, en uno de los post anteriores en el que se probaba la utilidad de JSON con Jquery se hacía una llamada asíncrona desde JQuery a un servlet en el mismo servidor que respondía con datos en formato JSON. Parte del código de la llamada era

$.post("../jqueryJSON.action", {parametroJSON:$.toJSON(person)} ,

El parámetro de la URL de llamada es ../jqueryJSON.action, que es una dirección relativa y está en el propio servidor, que en este caso es localhost.
Esta vez colocaré la dirección de forma explícita (haz acto de fe que es la buena):

<pre>$.post("http://localhost:8084/TwitterPrueba1/jqueryJSON.action", {parametroJSON:$.toJSON(person)}</pre>

La página sigue haciendo peticiones sin problemas porque su URL es: http://localhost:8084/TwitterPrueba1/jqueryExamples/jqueryJSON.html

Ahora imaginemos que el servidor está en otra URL. Como estoy trabajando en local, voy a hacer un truco, que es cambiar localhost por 127.0.0.1. Todos sabemos que son equivalentes, pero a nivel de dominio son diferentes. Entonces tengo ahora

$.post("http://localhost:8084/TwitterPrueba1/jqueryJSON.action", {parametroJSON:$.toJSON(person)}

Recargo la página y si utilizo una consola de Javascript como firebug de ffox o la de Chrome, tengo el siguiente error al hacer la petición.

XMLHttpRequest cannot load http://127.0.0.1:8084/TwitterPrueba1/jqueryJSON.action/jqueryJSON.action. Origin http://localhost:8084 is not allowed by Access-Control-Allow-Origin.

Como se puede comprobar, no es posible hacer una llamada asíncrona a una url fuera del dominio

Por aclarar, se está usando Jquery, que enmascara todo el proceso de uso del objeto XMLHttpRequest, pero internamente se usa.

¿Para qué queremos cargar información de otros servidores? Muchas de las compañías de servicios de Internet publican sus API a través de este formato (Twitter,Flicker, Google, Yahoo…), de modo que podemos utilizar la potencia de sus servicios fácilmente.

Para poder hacer llamadas a otros servidores y cargar la información de otros servicios, se puede utilizar JSONP, que realmente lo que hace es, en vez de hacer una llamada con el objeto XMLHttpRequest (XHR), imita la inserción de un script en nuestra página, que contendrá básicamente la información de vuelta enformato JSON y la llamada a un función de Javascript que hemos creado.

Por tanto lo que tiene que devolver el servidor es algo del tipo

funcion(Respuesta_formato_JSON)

Este texto como respuesta se meterá dentro de un tag script, de modo que se podrá ejecutar sin llamar al objeto XHR. Por tanto en el cliente, se incluye algo parecido a esto:

<script src="URL_que_devuelve_JSONP"></scrip>

Esa URL como digo devolverá texto en formato JSONP que no es otra cosa que JSON “envuelto” entre paréntesis como parámetro de una función.

Entonces, si vamos más allá y vemos cómo se comporta el escript con la URL, quedará algo así:

<script>funcion(datos_JSONP)</script>

Este código será ejecutado normalmente por el navegador como cualquier otro Javascript

¿Cómo se especifica el nombre de la función que “envuelve” el JSON de respuesta?

Sencillamente se añadirá un parámetro más a la llamada de la URL, por ejemplo “callback”. En este parámetro se le indicará al servlet cómo se llama la función del cliente.

Por usar un servicio conocido que emplee JSONP, el buscador Yahoo provee los resultados en este formato:

http://search.yahooapis.com/ImageSearchService/V1/imageSearch?appid=YahooDemo&output=json&query=pepe&callback=MIFUNCION

Como se puede comprobar, devuelve los respuesta a la búsqueda en Yahoo “pepe” en formato JSON, incluyendo además la función MIFUNCION envolviendo toda la información.

Por lo tanto, en el lado del servidor se debe preparar para devolver la respuesta incluyendo esta función, cuyo nombre proporciona el cliente en Javascript a través de un parámetro.

Modificando el ejemplo de Struts2 en el que se usa Gson, simplemente sería añadir un nuevo parámetro cuyo valor se inyecta, siempre que tengamos el setter preparado:

private String callback;   //Nuevo parámetro

public void setCallback(String callback) {
	this.callback = callback;
}

El otro cambio que hay que hacer es la salida, envolviendo la cadena de salida en formato JSON con el nombre de la función que ha indicado el cliente:

salidaFormatoJSON = callback + "(" + salidaFormatoJSON + ")";

El resultado final de la acción de Struts2 que sirve el código en formato JSONP usando Gson es:

imports...

public class jqueryJSONP extends ActionSupport implements
        ServletRequestAware, ServletResponseAware {

    private HttpServletRequest request;
    private HttpServletResponse response;

    private String parametroJSON;
    private String callback;   //Nuevo parámetro

    public jqueryJSONP() {
    }

    public String execute() throws Exception {

        System.out.println("Parámetro de llamada:" + parametroJSON);

        Gson gson = new Gson();
        ServletOutputStream sos= response.getOutputStream();

        Persona persona=gson.fromJson(parametroJSON, Persona.class);
        System.out.println("Persona Recibida. Nombre:" + persona.nombre + ";Edad:" + persona.edad);

        //1. Conversión de un ArrayList de Objetos a JSON
        ArrayList prueba1Array = new ArrayList();
        for (int i = 0; i &lt; persona.edad; i++) {
            prueba1Array.add(new Persona("Nombre_" + i, ((int)(Math.random()*100))));
        }
        String salidaFormatoJSON = gson.toJson(prueba1Array);

        salidaFormatoJSON = callback + "(" + salidaFormatoJSON + ")";
        sos.print(salidaFormatoJSON);
        System.out.println("Salida:" + salidaFormatoJSON);

        return null;

    }

    public void setServletRequest(HttpServletRequest hsr) {
        this.request = hsr;
    }

    public void setServletResponse(HttpServletResponse hsr) {
        this.response = hsr;
    }

    public String getParametroJSON() {
        return parametroJSON;
    }

    public void setParametroJSON(String parametroJSON) {
        this.parametroJSON = parametroJSON;
    }

    public void setCallback(String callback) {
        this.callback = callback;
    }

    //Inner class
    private static class Persona {

        String nombre;
        int edad;

        //Necesario para el método fromJson
        public Persona() {
        }

        public Persona(String n, int e) {
            nombre = n;
            edad = e;
        }
    }
}

De este modo hemos creado una acción en Struts2 que puede ser llamada desde cualquier dominio usando JSONP. Es importante tener en cuenta que cuando publicamos algo con JSONP es accesible por cualquier cliente, de modo que debe ser información NO SENSIBLE.

Ahora toca el trabajo en el lado del cliente. Vamos a utilizar el ejemplo modificado que se empleó en el post de explicación de Json desde Jquery un poco modificado.

La llamada al servidor deberá ser de tipo GET, ya que no es una llamadd XHR, sino que es una llamada para hacer un include. Más información en http://www.markhneedham.com/blog/2009/08/27/jquery-post-jsonp-and-cross-domain-requests/

Por tanto vamos utilizar la función $.get de jQuery para recoger la información en formato JSONP con el añadido de la llamada a la función. El procedimiento es muy parecido, sólo que en esta ocasión se construirá la URL incluyendo los parámetros, tal y como se hace con Get

En este caso el código Javascript envía al servidor un objeto persona, con nombre y edad. El servidor devolverá otros objetos persona, tantos como edad tenga el individuo indicado. Por ejemplo 10 años, devolverá como respuesta en JSONP un array con 10 individuos.

Los parámetros en la llamada de la función $.getJSON (o $.get, ya que sólo evita poner el tipo “json” al final), debe constuirse en la URL, ya que de otro modo no funciona.

Esto es incorrecto

$.getJSON("http://localhost:8084/TwitterPrueba1/jqueryJSONP.action", {parametroJSON:$.toJSON(person),callback:"?"} ,

La URL debe construirse de forma explícita, concatenando cadenas:

$.getJSON("http://localhost:8084/TwitterPrueba1/jqueryJSONP.action?parametroJSON="+ $.toJSON(person)+"&callback=?" ,

Si se han seguido las indicaciones anteriores, se esperará que el parámetro callback tenga el nombre de la función de vuelta que tiene que incluir el servidor en la respuesta JSONP. La explicación de que sea una interrogación “?” es que JQuery sustituye la interrogación por un valor determinado por él, y será la referencia a la función de resultado que tiene $getJSON.

A continuación se muestra un ejemplo de salida del servidor en formato JSONP. Obsérverse el nombre elegido por jQuery para la función de callback = jsonp1290095678230

jsonp1290095678230([{"nombre":"Nombre_0","edad":31},{"nombre":"Nombre_1","edad":52},{"nombre":"Nombre_2","edad":82},{"nombre":"Nombre_3","edad":19},{"nombre":"Nombre_4","edad":14},{"nombre":"Nombre_5","edad":33},{"nombre":"Nombre_6","edad":86},{"nombre":"Nombre_7","edad":70},{"nombre":"Nombre_8","edad":56},{"nombre":"Nombre_9","edad":37}])

El resto del proceso no difiere de una llamada JSON normal y corriente. Se ejecutará la función de respuesta, que es otro parámetro de getJSON. En este caso esta función rellena, para mostrar un ejemplo, un select con opciones basadas en las “personas” obtenidas, borrando las que hubiera con anterioridad.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
    <head>
        <title></title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <script language="javascript" src="jquery/jquery.js"></script>
        <script language="javascript" src="jquery/jquery.json-2.2.js"></script>

        <script language="javascript">
            $(document).ready(function() {

                //Petición AJAX, que envía dos parámetros y recibe la respuesta con JSON
                $("#envioPeticion").click(function(e){
                    e.preventDefault();

                    //Crea el objeto que se va a enviar
                    person = new persona($("#nombre").val(),$("#edad").val());

                    $.getJSON("http://localhost:8084/TwitterPrueba1/jqueryJSONP.action?parametroJSON="+ $.toJSON(person)+"&amp;callback=?" ,
                    function(response){

                        console.log("no hay response");

                        var i = 0;
                        var textoSalida = "";

                        console.log(response);
                        

                        $("#selector> option").remove();


                        for (i=0;i<response.length;i++)
                        {
                            textoSalida = textoSalida + response[i].nombre + " - " + response[i].edad;
                            textoSalida = textoSalida + "<br/>";
                            
                            //Añade además al selector los valores
                            $('<option/>').attr("value",response[i].edad).text(response[i].nombre).appendTo("#selector");
                        }
                        //$("#vueltaServidor").html(textoSalida);    //Pega lo recibido en el div
                    });
                });

             

                //Objeto persona
                function persona (nombre, edad)
                {
                    this.nombre = nombre;
                    this.edad = parseInt(edad);
                }

            });
        </script>
    </head>
    <body>

        <h1>Prueba de JSONP</h1>


        <form>
            <input type ="text" id ="nombre" value ="Luis" />
            <input type ="text" id ="edad" value ="10"/>
            <a href="#" id="envioPeticion">Envía la petición</a>
        </form>

        <hr />
        <h3>Esta es la vuelta del servidor</h3>
        <p>Se recibe un objeto JSON y se dibuja en la pantalla</p>

        <form>
            <select id="selector">
                <option value="pepe">pepe</option>
                <option value="luis">luis</option>
            </select>
        </form>

        <div id ="vueltaServidor" style=""></div>


    </body>
</html>

Mediante la utilización de JSONP se podrá acceder a la información que proveen otros servidores, no solamente el servidor en el que se encuentra la página. Existen multitud de servicios que proveen de datos mediante este sistema, como por ejemplo Yahoo o Twitter.

Algunos enlaces útiles:

JSON en Java utilizando la biblioteca com.google.gson

Como ya se trató en post anteriores, JSON no es más que un formato de transmisión de información que es de especial utilidad cuando interviene algún script escrito en Javascript.

En un principio para la comunicación entre sistemas de diferente naturaleza XML parecía ser un lenguaje adecuado, pero su excesivo formalismo y poca eficiencia en el proceso de parseado provocó que se empezara a utilizar JSON par transmitir los mismos datos. Se puede encontrar un ejemplo en el artículo correspondiente de la wikipedia

Al fin y al cabo, como digo, JSON no es más que una cadena de texto que contiene los valores de los atributos de un objeto. El formato que debe cumplir se puede encontrar en json.org.

En este post centraremos la atención de la transformación de texto en JSON a objeto de Java y viceversa. Esta es una tarea sencilla pero tediosa, ya que hay que escribir código para pasar a texto o parsear el texto, algo que nunca nos gusta a los programadores porque siempre se acaba poniendo condiciones poco elegantes. Aquí un ejemplo de un post anterior, en el que se genera una cadena de texto en formato JSON que contiene un array de objetos

String parametro;
StringBuilder salida = new StringBuilder();
int contador = 0;

while (enumeration.hasMoreElements()) {
	contador++;
	parametro = enumeration.nextElement();
	System.out.println("Parámetro " + parametro + ": " + request.getParameter(parametro).toString());

	//Escribe a la salida de forma directa
	salida.append("{\"Parametro\":\"" + parametro + "\",\"Valor\":\"" + request.getParameter(parametro).toString() + "\"},");
}

//Quita la coma final poruqe sobra al ser un array
salida.deleteCharAt(salida.length()-1);

if (contador &gt; 1) {
	salida.insert(0, "[");
	salida.append("]");
}

System.out.println("Salida:" + salida);

Afortundamente existen librerías en java para llevar a cabo esta labor con el esfuerzo mínimo. En la web json.org se pueden ver librerías para todos los lenguajes.

En este caso se va a utilizar la librería gson de Google, que como todos los proyectos tiene su documentación propia. En este ejemplo se da una muestra básica de su uso, para información más detallada conviene visitar la documentación del sitio oficual.

Aquí muestro un ejemplo sencillo de uso con una inner class llamada Persona, que está al final del código. Esta clase solamente tiene dos atributos, nombre y edad, que será la información que se incluye en el texto en formato JSON. Los pasos del código son los siguientes:

  1. Paso de Objeto Java a JSON
    1. Creación del objeto gson de la clase Gson, que contiene los métodos para realizar las conversiones.
    2. Creación de un ArrayList de 3 objetos Persona de prueba, que serán convertidos a JSON.
    3. Obtención de la equivalencia en JSON del array de personas mediante gson.toJson(prueba1Array);
  2. Paso de JSON a Objeto Java
    1. Definición de una cadena de texto que contiene los atributos de un objeto persona según la especificación JSON
    2. Obtención del objeto equivalente de Java mediante gson.fromJson(personaEnJson, Persona.class). Se debe indicar en el segundo parámetro la clase del Objeto, en este caso Persona.class, lo que obliga a disponer de un constructor sin parámetros para dicha clase

El código fuente es el siguiente:

import com.google.gson.Gson;
import java.util.ArrayList;

/**
 *
 * @author Alberto Moratilla alberto.moratilla@gmail.com
 */
public class pruebaGson {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {

        pruebaGson pgs = new pruebaGson();
        Gson gson = new Gson(); //Objeto que lleva a cabo las operaciones de transformación


        //1. Conversión de un ArrayList de Objetos a JSON
        ArrayList prueba1Array = new ArrayList();
        for (int i = 0; i &lt; 3; i++) {
            prueba1Array.add(new Persona(&quot;Nombre_&quot; + i, i));
        }

        String salidaFormatoJSON = gson.toJson(prueba1Array);
        System.out.println(&quot;Conversión a JSON:&quot; + salidaFormatoJSON);

        //2. Paso de JSON a objeto predefinido
        String personaEnJson = &quot;{\&quot;nombre\&quot;:\&quot;Manolo\&quot;,\&quot;edad\&quot;:32}&quot;;
        Persona personaFromJson = gson.fromJson(personaEnJson, Persona.class);

        System.out.println(&quot;Persona Cargada desde JSON. Nombre:&quot; + personaFromJson.nombre + &quot;Edad:&quot; + personaFromJson.edad);
    }

    //Inner class
    private static class Persona {

        String nombre;
        int edad;

        //Necesario para el método fromJson
        public Persona() {}

        public Persona(String n, int e) {
            nombre = n;
            edad = e;
        }
    }
}

La salida de dicho programa coincide con las conversiones esperadas:

Conversión a JSON:[{"nombre":"Nombre_0","edad":0},{"nombre":"Nombre_1","edad":1},{"nombre":"Nombre_2","edad":2}]
Persona Cargada desde JSON. Nombre:ManoloEdad:32