Javascript

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
    });
});

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.

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

Bootstrap 3: ¿Qué tipo de grid usa cada dispositivo?

Una de las mejores novedades que trae Bootstrap 3 es la capacidad para adaptar su sistema de grid a los diferentes dispositivos y así facilitar los diseños “responsive” con muy poco coste en tiempo y esfuerzo. De este modo clasifican sus elementos de grid con un sufijo, dependiendo del dispositivo que se trate:

  • XS: Extra small devices Phones (<768px)
  • SM: Small devices Tablets (≥768px)
  • MD: Medium devices Desktops (≥992px)
  • LG: Large devices Desktops (≥1200px)

Puedes ver más detalles sobre cada uno y cómo utilizarlo en http://getbootstrap.com/css/#grid-options

El problema está en saber qué es un dispositivo small, large o medium, o qué resolución va a usar cada uno. El criterio más objetivo debería ser la resolución, pero tenemos tablets con resoluciones de 1024×768 y 10 pulgadas, y tenemos móviles de 4 pulgadas full HD (1920×1080). ¿En un móvil de 4 pulgadas se debería adaptar a los “Large devices Desktop (>1200px) mientras que la tablet sería un “Medioum Desktop Desktop >992px?

El resultado es que cuando hago un diseño y lo pruebo en mi Nexus 4 (1280px), usa el modo XS, pero cuando lo pruebo en mi Asus TF101 (1024px), usa MD, a pesar de tener menos resolución de pantalla. La razón es que dependiendo de cada dispositivo se utiliza un factor multiplicador de modo que compensa el aumento de resolución de pantalla con el tamaño real en centímetros. Así, un iPad normal (1024) y uno con Retina Display (2048) muestran del mismo tamaño los elementos, pero el segundo con el doble de resolución.

Como en realidad lo que queremos es que nuestro diseño se adapte perfectamente a los dispositivos, lo mejor es saber qué modo de Bootstrap 3 utiliza cada uno.

Utilizando Google Chrome Canary (una versión de Chrome para desarrolladores con herramientas especiales), se puede emular correctamente una buena cantidad de dispotivos móviles usando Google Chrome. Mi comprobación empírica es que es fiable totalmente. Para comprobarlo por ti mismo puedes seguir estas instrucciones: https://developers.google.com/chrome-developer-tools/docs/mobile-emulation

Probando los más significativos he dado con este resultado usando Chrome para estos dispositivos:

  • Amazon Kindle Fire HD 7″ – SM
  • Amazon Kindle Fire /Fire HD 8.9″ – LG
  • BB PlayBook – MD
  • BB Z10 / Z30 – XS
  • HTC Evo, Touch HD, Desire HD, Desire – XS
  • IPAD 1/2/iPad Mini – MD
  • IPAD 3/4 – MD (En prueba real he podido comprobar que en realidad es SM…)
  • IPhone 3GS/4/5 – XS
  • Nexus 10 – LG
  • Nexus 7 / 7.2 – SM
  • Nexus S/4/5 – XS
  • Samsung Galaxy Note / S3 / S4 – XS
  • Sony Xperia S / Z – XS

O por la otra dimensión:

  • XS: BB Z10 / Z30; HTC Evo, Touch HD, Desire HD, Desire; IPhone 3GS/4/5; Nexus S/4/5; Samsung Galaxy Note / S3 / S4; Sony Xperia S
  • SM: Amazon Kindle Fire HD 7; Nexus 7 / 7.2
  • MD: BB PlayBook; IPAD 1/2/iPad Mini; IPAD 3/4
  • LG: Amazon Kindle Fire /Fire HD 8.9″; Nexus 10

* Estos datos están sacados del emulador de dispositivos de Chrome Canary. Se deberían probar en los dispositivos reales para estar completamente seguros…

Testing end to end (e2e) con WebdriverJS + NodeJS + Mocha + should.js

El testing en el desarrollo de software es una de las corrientes que más éxito está teniendo en los últimos tiempos. Las nuevas metodologías de desarrollo basado en test (TDD) o en comportamiento (BDD) permite crear aplicaciones más robustas y centradas en los requisitos más que en el contenido propio del código.

Dentro del testing se podría hacer una clasificación simple entre tres tipologías de test:

  • El testing unitario o de componente, se ocupa de probar las partes individuales en las que se puede dividir un código: si una clase o métodos funcionan correctamente o una parte determinada del programa. Ayuda a desarrollar partes concretas del código pero que no tienen que ver de forma directa con lo que el usuario espera del producto final. Por ejemplo podemos hacer un test para comprobar que un generador de números aleatorios funciona correctamente, pero solamente se comprueba una parte del sistema de seguridad que lo utiliza y que es lo que finalmente le interesa al usuario.
  • El testing de integración, para comprobar que los componentes reaccionan de forma adecuada a la hora de unirse. Es decir, controlan las interfaces entre los componentes o subsistemas.
  • El testing de fin a fin (end to end o e2e) es el que se ocupa de probar la aplicación final “de principio a fin”. Conceptualmente tiene mucho más que ver con lo que percibe el usuario del producto, que es al fin y al cabo la misión de nuestros programas: satisfacer al usuario y sus requisitos. Aspectos como el funcionamiento, el rendimiento o en consumo de recursos pueden ser comprobados con este tipo de test. En una página Web es especialmente relevante ya que debe responder de forma adecuada a todos los eventos de navegación que se producen en ella.

Una cosa que no debemos olvidar es que el testing solamente prueba que un software falla bajo determinadas condiciones, pero no indica que es totalmente fiable. Que un software funcione un número consistente de veces bajo unas condiciones no quiere decir que exista una posibilidad, aunque remota, de que falle bajo esas mismas condiciones. Se trata de que el número de fallos sea el menor posible, pero que si no falla, puede que lo haga en el futuro, sobre todo porque quizá no hayamos conseguido las condiciones de test adecuadas sobre las que se produce el fallo.

Dentro de los test “end to end”  (e2e),  están los test de comportamiento, que se encargan de comprobar que el programa (o aplicación Web en nuestro caso) funciona de forma correcta en cuanto al comportamiento: hacen pruebas como si se tratara de un usuario real. Es evidente que los test de comportamiento son muy adecuados para las aplicaciones Web actuales en las que la interacción juega un papel muy importante.

Un ejemplo de test de comportamiento podría ser el siguiente:

  • Accede a la página http://www.rusizate.com
  • Comprueba que el título de la página contiene la cadena de texto “Rusizate” [test1]
  • Localiza el botón de menú que da acceso a los contenidos para turistas y que tiene la clase “boton-189”.
  • Pulsa sobre el botón seleccionado
  • Comprueba que el título de la página que aparece contiene el texto “turistas” [test2]
  • etcétera…

La descripción del test debería ser lo más exhaustiva posible. Cuantos más aspectos probemos, más seguros estaremos de que la aplicación cumple con los requisitos que se han establecido, y también podremos dirigir nuestros desarrollos de un modo más adecuado a las necesidades del usuario final.

Para hacer los test end to end de nuestras aplicaciones web basadas en comportamiento se utilizará Selenium – Webdriver, que prové de una interfaz para poder manejar los navegadores más populares. Cada navegador dispone de un “driver” para que puedan ser manipulados por código como si de un usuario final se tratase. De este modo Internet Explorer, Chrome, Firefox, Safari u Opera disponen de drivers para que puedan ser utilizados por Webdriver.

Existen diferentes lenguajes para usar Webdriver: Java, Python o Javascript. Bajo mi punto de vista lo mejor es utilizar un lenguaje interpretado que pueda ser editado fácilmente. Los lenguajes compilados requieren el paso intermedio de compilación a ejecutable (o bytecode en Java), lo que ralentiza excesivamente la creación y depuración de los test. Los test no suelen ser programas complejos, sino que son una enumeración de los puntos que se deben cumplir, por lo que no es necesario contar con la potencia y complejidad de lenguajes compilados.

Un lenguaje intepretado que se puede utilizar con Webdriver es Javascript, que gracias a NodeJS, basado en el motor V8 de Google, está ganando enteros dentro del mundo del desarrollo. Gracias a NodeJS es posible ejecutar Javascript en la parte del cliente con un buen rendimiento, así como aprovecharse de su sistema de librerías NPM (Node Package Manager), que facilita la instalación y utilización de nuevas características que extienden la potencia de este entorno.

Volviendo a Selenium-Webdriver, existe un paquete descargable a través de NPM que instala todo lo que necesitamos de selenium-webdriver. Además deberemos descargar el driver de Chrome, llamado Chromedriver.exe, que será la interfaz a partir de la cual se manejará Google Chrome para realizar nuestras pruebas e2e. También vamos a utilizar Mocha, que es un framework de testing en Javascript, que facilitará la realización de nuestras pruebas, a través de funciones y comprobaciones que lleva incorporadas. Para la comprobación de los asserts de los test se empleará should.js, también disponible a través del sistema NPM.

Preparando el entorno

Prerequisitos:

  • Tener instalada una versión reciente de nodejs
  • Acceso a una shell.

Creando el directorio e instalando librerías:


mkdir testing
cd testing

npm install -g mocha
npm install selenium-webdriver
npm install should

Instalando el driver para Google Chrome

1. Acceder a http://chromedriver.storage.googleapis.com/index.html y descargar la última versión correspondiente al sistema operativo.

2. Hacer que el chromedriver.exe esté en el path del sistema: en windows hay que ir a panel de control->sistema->variables de entorno

3. Reiniciar la shell para que admita el nuevo cambio.

Creando el fichero testRus.js que contiene el test

Descargar el fichero de GitHub clonando el respositorio :

git clone https://github.com/4lberto/test_e2e.git

O simplemente copiar y pegar el código que pongo más abajo.

Ejecución del test

mocha testRus.js

Si todo ha ido bien y la página que se va a testear no ha cambiado, aparecerá una ventana de Google Chrome en nuestro escritorio y se realizarán las comprobaciones, como si un vídeo de screencast estuviéramos viendo. Debería pasar los 3 test de los que se compone el código de testRus.js. Debería ser algo así:

 

 

Comentarios sobre el código y el funcionamiento de WebdriverJS

Promises

Una característica de webdriveJS que tiene Javascript y que no tienen otro tipo de lenguajes para manejar Webdriver, como por ejemplo con Java, es el manejo de la naturaleza asíncrona de un navegador Web y de Javascript. Cuando cargamos una página y hacemos click en un botón, puede suceder que pase un tiempo hasta que la consecuencia de la acción se ha producido o Webdriver ha sido capaz de resolver el resultado de la instrucción. Es decir, si pedimos el contenido HTML de un elemento que hemos localizado a través de su clase, puede que este contenido HTML no esté disponible al instante. Si este contenido es utilizado en la siguiente instrucción puede que no pueda usarse todavía y la aplicación falle.

Para tratar esta asincronía, los creadores de WebdriverJS han recurrido a uná técnica llamada “promises”. Básicamente consiste en que cuando se solicita una información que puede que no esté disponible al instante, se devueelve de forma inmediata un objeto “promise” de esa información, es decir, un objeto que representa el objeto prometido, pero que no tiene por qué tener esta información. Este objeto será completado con la información correspondiente en cuanto sea posible, y será en ese momento cuando lance una notificación a sus observadores. ¿Quién son sus observadores? Pues son dos funciones: una que se lanza si todo ha ido correcto (no ha habido problemas a la hora de cargar la información); y otra función que se lanzará su ha habido algún error o problema.

En código se ve mucho mejor el funcionamiento del objeto promise:

var prom = driver.findElement(webdriver.By.className('miClase')).getInnerHTML();
prom.then(function(){//función correcta}, function(){//función error} );

Como se puede ver, en la variable “prom” queda alojado el objeto de tipo promise que debería tener alojado el contenido HTML del tag que tiene como clase “miClase”. Este objeto, en la instrucción siguiente, se le añaden dos observadores, que son dos funciones anónimas. Cuando el objeto “prom” disponga de la información real y no haya habido problemas, hará una llamada a la primera función anónima. Es obvio que es el método “then” el que nos ayuda a establecer ambas funciones observadoras que serán notificadas de la carga correcta o incorrecta de la información.

Por ejemplo, si queremos mostrar por pantalla el título de una página que se acaba de cargar podemos hacer:

var titulo = driver.getTitle();
titulo.then(function(titulo)
{
    console.log("título de la página:" + titulo);
}, function(){
    console.log("error");
});

Nuevamente, la variable titulo actúa como promise, pero su valor cuando pasa por esa instrucción no tiene por qué ser conocido. Sin embargo en la siguiente instrucción, con la propiedad “then” del promise, se registran dos funciones que serán llamadas cuando el objeto promise reciba el valor adecuado.

Por tanto, los promises son una forma de tratar con la asincronía existente en Javascript de forma elegante y nos permitirá amoldarnos a los eventos de la página.

Función wait()

Otro método que tiene Webdriver para tratar con los problemas de sincronía es el método driver.wait(función, tiempo_time_out). Este método espera hasta el tiempo_time_out para continuar con la ejecución, o hasta que se cumpla una condición (sea verdadera).

Se entenderá mejor con un ejemplo: si tenemos un botón que hace una llamada AJAX para mostrar una información que se recibe desde el servidor, lo que incluye cierto retardo en responder (pongamos 1 segundo), y muestra el resultado en el contenido de una capa, no nos servirá de mucho el sistema de promises, puesto que el contenido de la capa puede estar perfectamente definido (y tratado) aún cuando aún no ha llegado la respuesta desde el servidor. Es decir, puedo tener una capa vacía en la que se carga el resultado de la llamada. Con el sistema de promises puedo recuperar que el contenido efectivamente es vacío pero no podía esperar a recibir el contenido AJAX.

Utilizando la función wait podemos hacer un código parecido al siguiente:

driver.wait(function(){
    return driver.findElement(webdriver.By.id('resultado')).getInnerHTML().then(function(html)
    {
        return html.length>0;
    }
}, 3000).then(
    function()
    {
        //test del contenido...
    },
    function()
    {
        //trata el timeout...
    }
);

Esta función lo que haces es entrar en un bucle de comprobación constante que ejecuta la función de clausura que está en su interior y que comprueba que hay algo dentro del contenido HTML (con length>0) de un elemento cuyo id es “resultado”. Esto es, comprueba constantemente a ver el resultado de esa comprobación. Mientras sea false sigue haciendo comprobación hasta que encuentre un true o por el contrario pasen los 3000 milisegundos:

  • Si ha encontrado una salida positiva a su función de comprobación, ejecutará la primera de las funciones.
  • Si ha agotado el tiempo de time-out, ejecuta la segunda función, que es al función de error y debería tratar el problema.

Una vez han sido establecidos los antecendentes, vamos a pasar a ver el código del test sobre la página rusizate.com. Se trata de un test sencillo y nada exhausitivo, pero que debería darnos una idea sobre cómo se hacen este tipo de test. Los pasos son los siguientes:

  1. Acceso a la página y que se pulse el botón de aceptar las condiciones del uso de cookies, comprobando que una vez pulsado la capa de aviso que aparece, ha desaparecido.
  2. Salta a la página “Para gente curiosa”, comprobando que el título de la página que sucede a este salto es el adecuado.
  3. Utiliza el buscador, introduciendo el término “hermitage” y comprobando que el número de resultados es mayor 0.

Como he mencionado antes, el framework que se va a utilizar para hacer el testing es Mocha y las comprobaciones se van a hacer utilizando should.js. Todas estas tecnologías emplearán el driver para Google Chrome de Webdriver y se ejecutarán en nodejs.

El código es el siguiente:


var assert = require('assert'),
should = require('should'),
fs = require('fs');

var webdriver = require('../node_modules/selenium-webdriver/'),
test = require('../node_modules/selenium-webdriver/testing');
test.describe('Rusizate', function() { // Descripción del test
var driver;

//Antes de la ejecución de la prueba
test.before(function() {
    driver = new webdriver.Builder().
    withCapabilities(webdriver.Capabilities.chrome()).
    build();
});

//Test 1: accede a la página y acepta las condiciones legales
test.it('Acceso y aceptación de condiciones', function() {
    driver.get('http://www.rusizate.com');
    driver.findElement(webdriver.By.id('ca_banner')).then(function(div_banner)
    {
        div_banner.findElement(webdriver.By.className('accept')).click();
        driver.wait(function()
        {
            return div_banner.getCssValue("display").then(function(valor){
            return valor.indexOf("none")>-1;
        });
     }, 3000).then(function()
     {
        div_banner.getCssValue("display").then(function(valor){
        valor.should.equal("none");
     });
    });
    });
   });

//Test 2: Salta a la página "Para la gente curiosa"
test.it('Salta a Gente Curiosa', function() {
    driver.findElement(webdriver.By.className('item-117')).click();
    driver.wait(function() {
        return driver.getTitle().then(function(title) {
            return title.indexOf("curiosa")>-1;
        });
    }, 3000).then(function()
    {
        driver.getTitle().then(function(titulo)
        {
            titulo.should.include('curiosa');
        });
    });
 });

//Test 3: Utiliza el buscador y comprueba que devuelve 2 resultados
test.it('Buscador - buscar la palabra Hermitage', function() {
    driver.findElement(webdriver.By.id('mod-search-searchword')).sendKeys('hermitage');
    driver.findElement(webdriver.By.className('form-inline')).submit();

    driver.wait(function() {
        return driver.getTitle().then(function(title) {
        return title.indexOf("Buscar")>-1;
    });
 }, 10000).then(function()
 {
     driver.findElements(webdriver.By.tagName('dt')).then(function(resultados)
     {
         resultados.length.should.be.above(0); //Encuentra más de 1 resultado.
     });
 });
 });

//Cierra la instancia al finalizar la prueba
test.after(function() {
    driver.quit();
 });
});

Como puntos a destacar:

  • Carga de los módulos necesarios de webdriver, que no están instalados de forma global sino en un directorio por debajo del que estamos trabajando.
  • La descripción del test y sus fragmentos que se producen antes del test (test.before…) y después del test (test.afeter…) que permiten inveocar al driver y cerrarlo respectivamente.
  • Cada una de las pruebas que se llevan a cabo a través de test.it(descripción, funcion).

Entrando en detalle sobre el primer test, por que el resto cson cosas estándar, tenemos el acceso a la página web y la búsqueda de la capa en la que se muestra el aviso de conformidad con las cookies. Una vez localizado el elemento utilizando el ID, hay que localizar el botón que está dentro. Como se ha comentado antes, es información que puede estar disponible de forma asíncrona, asi que hay que usar los promises y su propiedad “then”.

Una vez localizado el botón se hace click sobre el elemento. Como la acción que esto desencadena sobre la interfaz consiste en que la capa que muestra el mensaje desaparece poco a poco, no se puede comprobar el efecto de manera inmediata.

Para esperar el tiempo necesario se emplea el método “wait”, comprobando constantemente durante 3 segundos a ver si ha desaparecido o no la capa (con display none). La comprobación posterior es redundante y consiste en cuando todo ha ido bien, es decir, que ha desaparecido la capa antes de los 3 segundos del time out, realiza una acción. En este caso la acción consiste en volver a comprobar el display:none. Esto en un escenario real podíamos decir que está repetido, pero se ha dejado por motivos didácticos.


//Test 1: accede a la página y acepta las condiciones legales
test.it('Acceso y aceptación de condiciones', function() {
    driver.get('http://www.rusizate.com');
    driver.findElement(webdriver.By.id('ca_banner')).then(function(div_banner)
    {
        div_banner.findElement(webdriver.By.className('accept')).click();
        driver.wait(function()
        {
            return div_banner.getCssValue("-display").then(function(valor){
                return valor.indexOf("none")>-1;
            });
         }
         , 3000).then(function()
         {
             div_banner.getCssValue("display").then(function(valor){
                 valor.should.equal("none");
          });

      });
  });
});

En cuanto al segundo paso no puede ser más sencillo: se localiza en la página el botón que da acceso a la página para la gente curiosa (localizado con un class unívoco), y hacemos click.

Del mismo modo que antes, se tiene que esperar un tiempo hasta que se produzca el evento de carga, que bien puede ser que espere cargando la página hasta que aparezca la palabra “curiosa”. Como en el caso anterior, la comprobación del test vuelve a ser la misma que la condición de salida.

 //Test 2: Salta a la página "Para la gente curiosa"
test.it('Salta a Gente Curiosa', function() {
    driver.findElement(webdriver.By.className('item-117')).click();
    driver.wait(function() {
        return driver.getTitle().then(function(title) {
            return title.indexOf("curiosa")>-1;
        });
     }, 3000).then(function()
     {
         driver.getTitle().then(function(titulo)
         {
             titulo.should.include('curiosa');
         });
     });
});

Finalmente vamos a ver cómo se realiza una búsqueda. Es muy sencillo. Simplemente se localiza el campo de texto donde se introducen los valores y se invoca el método “sendKeys” que imita la introducción por teclado de una cadena de texto. A continuación se localiza el formulario y se invoca el método “submit”.

De nuevo queda esperar a la carga de resultados con la comprobación del título de la página durante 10 segundos. Una vez tenemos la página de resultados se buscan los elementos de resultado (esta vez en plural) a través del nombre del tag “dt” (previamente sabemos que estos tag sólo se usan por los resultados). Usando should.js hacemos la comprobación de que hay más de un resultado.


//Test 3: Utiliza el buscador y comprueba que devuelve 2 resultados
test.it('Buscador - buscar la palabra Hermitage', function() {
    driver.findElement(webdriver.By.id('mod-search-searchword')).sendKeys('hermitage');
    driver.findElement(webdriver.By.className('form-inline')).submit();

    driver.wait(function() {
        return driver.getTitle().then(function(title) {
            return title.indexOf("Buscar")>-1;
        });
    }, 10000).then(function()
    {
        driver.findElements(webdriver.By.tagName('dt')).then(function(resultados)
        {
            resultados.length.should.be.above(0); //Encuentra más de 1 resultado.
        });
    });
});

Para ejecutar el test, como se ha dicho antes,  simplemente hay que ejecutar en la línea de comandos “mocha testRus.js” y deberían pasar los 3 test indicados. Si en alguno de ellos no fuera positivo el resultado, devolverá un aviso indicando qué test ha fallado y por qué razón gracias a Should.js y Mocha.

Finalmente se cierra la instancia del driver de Chrome para Webdriver.

Espero que este post os anime a mejorar la calidad de vuestras aplicaciones (y páginas) Web mediante la utilización de sistemas de testing unitarios (quizá empleando Karma) y testing finales como es el caso de Webdriver.

Objetos en Javascript (II). Clases y su relación con prototipos y constructores

En la entrada anterior sobre objetos en Javascript se trató la peculiaridad de los prototipos: cada objeto, además de sus propiedades, tiene asociado un objeto prototipo que le dota de todas sus propiedades. A su vez un objeto prototipo de otro puede tener sus propios prototipos, hasta llegar al objeto prototipo final que es Object.prototype.

Es fácil suponer que este mecanismo de objectos prototipos y su herencia pueden simular de algún modo el sistema de clases de los lenguajes orientados a objetos como Java: si se crea un objeto con el fin de que sea prototipo de otros podríamos considerarlo como una clase; a su vez, los objetos que tengan por prototipo una clase común, podrán ser asimilados como instancias de esa clase.

Para crear objetos “de una clase” se puede usar la función Object.create que está reconocida en ECMAScript 5 o también una función con el operador new. En este primer ejemplo vamos a emplear Object.create() para generar nuevos objetos. Si además creamos una función “factory” que genera objetos de la clase indicada, separando los métodos y los valores nos quedará un código mucho más adecuado.

Veámoslo con un ejemplo:


function operadorNumeros(x, y)
{
	var on = Object.create(operadorNumeros.operaciones);	//Se crea un nuevo objeto con ese prototipo
	on.x = x;
	on.y = y;

	return on;
}

//Prototipo con las operaciones que van a compartir los objetos de esta "clase"
operadorNumeros.operaciones = {
	suma:function()
	{
		return (this.x + this.y);
	},

	multiplica: function()
	{
		return(this.x * this.y);
	},

	toString:function()
	{
		return ("operador con valores x:" + this.x + "; y:" + this.y);
	}
}

var opNum = operadorNumeros(2,3);	//Factory que devuelve un nuevo objeto
opNum.suma();		//Devuelve 5
opNum.multiplica();	//Devuelve 6
opNum.toString();

A pesar de que pueda parecer extraño, no es mala idea que las operaciones de la clase queden como una propiedad de la función factory que genera los objetos de la clase. Nótese que recibe el nombre de operaciones, pero no prototype. Estas asignación es interna, y la hace al invocar el Object.create(prototipo);

Es importante destacar que en este caso la clase de la cual parten los objetos creados es realmente un objeto, y que solamente se le han dotado de métodos. Esto es así porque si se hubieran creado atributos serían compartidos entre todas las instancias. No es como en Java o C++: aquí el concepto de clase se emula mediante compartir un objeto prototipo, pero que no deja de ser un objeto y por tanto una instancia única que comparten los objetos generados.

También es posible utilizar constructores para la creación de los objetos de la clase, mediante el empleo de la palabra reservada new precediendo a la función generadora que actúa como constructor. En este caso no se usa Object.create porque con new se genera el objeto, y la asignación del prototipo debe hacerse a mano.

En esta ocasión, la función constructura no hace ningún llamamiento a Object.create


function OperadorNumeros(x, y)
{
	on.x = x;
	on.y = y;
}

OperadorNumeros.prototype = {
	suma:function()
	{
		return (this.x + this.y);
	},

	multiplica: function()
	{
		return(this.x * this.y);
	},

	toString:function()
	{
		return ("operador con valores x:" + this.x + "; y:" + this.y);
	}
}

//
var opNum = new OperadorNumeros(2,3);	//Genera el nuevo objeto con el constructor
opNum.suma();		//Devuelve 5
opNum.multiplica();	//Devuelve 6
opNum.toString();

Comparado con el ejemplo anterior los cambios son claros. En primer lugar se emplea mayúscula como convención para indicar que se trata de una clase. Esto es algo habitual y si no se hace no tiene por qué dar ningún problema, pero es una buena práctica. Por otra parte la función constructora no genera ningún objecto internamente con Object.create(prototipo) ya que se emplea el operador new para generarlo: es el intérprete el que crea un nuevo objeto basado en ese. Y finalmente, la más clara es la definición del prototipo de forma explícita escribiendo directamente la propiedad prototype.

Otro aspecto a destacar es que como en Javascript, como hemos visto, las funciones son objetos y como cada objeto tiene un prototipo asociado (aunque sea el básico – Object.prototype-), cada función tiene asignada una propiedad prototype. En el ejemplo anterior se ha definido de forma explícita con las operaciones del operador de números, pero es posible dejarla sin definir.

function suma(a,b){return a+b;}
Object.prototype.isPrototypeOf(suma.prototype)	//Devuelve true

Finalmente, para rizar más el rizo cada objeto prototype tiene asociada una propiedad constructor cuyo valor es la función que genera el objeto. En el ejemplo OperadorNumeros se ha definido la función al principio y luego el prototipo. Pues la propiedad constructora del prototipo es la propia función definida más arriba ya que coinciden los nombres y la asignación se realizar de forma automática. No ponerlo sería equivalente a esto:

OperadorNumeros.prototype.constructor = OperadorNumeros;

Si vamos al último ejemplo de la función suma, la propiedad constructor del prototipo tiene como valor la función suma de forma automática:

function suma(a,b){return a+b;}
suma.prototype.constructor   //Devuelve la función suma

De este modo tan enrevesado podemos conocer la clase de la cual proviene un objeto: si comprobamos el constructor del prototipo de un objeto obtendremos su clase. Y claro, está como las priedades del prototipo son heredadas por el objeto, su constructor directamente (sin pasar por el prototipo) nos da de igual modo la clase a la que pertenece.

En resumen, es importante conocer la relación existente entre los prototipos, las clases y los constructores. Si vamos al código fuente de muchas librerías de Javascript, nos encontraremos con cosas del tipo Objeto.prototype.operación = … que ahora podremos interpretar como que se está dotando a la “clase” de los objetos reales que usamos con nuevas operaciones que pueden utilizar. Y del mismo modo podremos añadir nosotros las nuestras.

Objetos en Javascript (I). Prototipos

Conocer el funcionamiento básico y los fundamentos de los objetos en Javascript es necesario para poder entender cómo se utilizan las librerías que existen para este lenguaje que tanto recorrido está teniendo en los últimos años. Sirva esta pequeña guía para refrescar los conceptos.

En  Javascript, casi todo es un objeto. Todo aquello que no es una cadena de texto, un número, un valor boolean (true o false), null o undefined, es un objeto. Incluso las funciones son objetos. Cuando usamos bibliotecas como jQuery, Underscore o AngularJS podemos intuir que hacemos referencia a objetos que nos brindan multitud de operaciones que facilitan la vida. Si vamos más allá y revisamos el código fuente veremos la particular forma que tienen de declarar los objetos.

Los objetos en Javascript no siguen un paradigma similar al de otros lenguajes basados en programación orientada a objetos como por ejemplo Java, donde los objetos son instancias de una clase bien definida que contiene propiedades (datos) y métodos (operaciones)

En Javascript un objeto puede considerarse más como un conjunto de propiedades, es decir, de pares de identificadores y valores asociados. Se podría ver como una tabla hash por ejemplo o algo parecido a las estructuras (structs) de otros lenguajes de programación tipados.

Una cosa muy interesante de Javascript es que se pueden asignar funciones a variables, de manera que así se crean los métodos que operan sobre estos objetos.

Las propiedades de un objeto además son dinámicas, no sólo en la asignación de los valores, sino en su existencia: se pueden añadir o eliminar propiedades en cualquier momento. Por supuesto, los objetos en Javascript se manejan por referencia.

Para crear un objeto en Javascript simplemente se deben definir sus propiedades (y métodos, si es procedente) utilizando literales, esto es, indicando las propiedades entre llaves:

var obj1 = {prop1:2, prop2;3.14};

//Podemos incluir otra propiedad a posteriori
obj1.prop3=23;

//O también se pueden incluir métodos, gracias a la asignación de funciones anónimas a las propiedades
obj1.suma = function(){return this.prop1 + this.prop2};

//Invocamos a la propiedad, sabiendo que es una función - usando los paréntesis
obj1.suma();

//Es posible llamar de diferentes modos a una propiedad de un objeto
obj1.prop1;
obj1["prop1"];

//Y como siempre, está la notación JSON
obj2 = {"p1":2,"p2":3};
typeof(obj2);

//Se puede eliminar si se desea una propiedad mediante el comando delete:
delete(obj1.p1);

También hay otros métodos de creación, como es el caso del operador new, que crea un nuevo objeto y lo inicializa a partir de la llamada a una función que devuelve un objeto. Esto se parece mucho más al modo de funcionar de Java. Por ejemplo:

Date;    //Devuelve que Date es una función nativa del sistema
var fecha = new Date();  //Crea un objeto de la clase Date()

//Creamos una función que como sabemos, es un objeto en Javascript
function MiObjeto(){return {"prop1":1,"prop2":2}};

//Creamos un objeto con el operador new que llama a la función constructora anterior
var miob = new MiObjeto();

//Comprueba que realmente se trata de un objeto
typeof(miob);

//O simplemente hacemos uso de una función anónima que siempre nos devolverá un objeto nuevo al no poder hacer referencia desde otro lado
var unObj = function(){return {prop1:1}};
unObj instanceof Object  //Devuelve true

Hasta aquí todo sencillo y familiar (salvo quizá la posibilidad de definir los métodos como funciones asignadas a una propiedad). Pero ahora es donde surge la característica especial de Javascript: los Prototipos.

Todo objeto en Javascript tiene un objeto secundario asociado llamado Prototipo, y además hereda las propiedades (atributos y métodos) de él. Los objetos que están definidos de forma explícita con literales (al estilo JSON) tienen como objeto prototipo el objeto Object.prototype. Los objetos que se crean utilizando el operador new reciben el indicado en el constructor. Por ejemplo los creados con new Object reciben Object.prototype, y los creados con new Date, el objeto Date().

De este modo el objeto prototipo dota a los objetos a los que está asociado de funcionalidades más o menos básicas: el Object.prototype por ejemplo tiene el método toString().

Los objetos prototipos además pueden tener a su vez otros prototipos. Por ejemplo el objeto asociado a Date, que es el Date.prototype, a su vez tiene como prototipo Object.prototype, que le da el método toString por ejemplo. Sin embardo, el Object.prototype no tiene ningún otro prototipo asociado.

Es importante tener en la mente que los objetos heredan propiedades (atributos y métodos) de sus prototipos.

¿Cómo se crea un objeto y se especifica su prototipo? Utilizando la función Object.create(), que recibe como parámetro el objeto que queremos usar como prototipo.

//Creamos un objeto con dos atributos y un método que los suma
var obj1 = {"p1":1,"p2":2,"suma":function(){return this.p1+this.p2}}

//Vemos que devuelve el valor 3
obj1.suma();

//Creamos un objeto que tiene como prototipo a obj1 y que por lo tanto hereda sus propiedades (atributos y métodos)
var obj11 = Object.create(obj1);

//Vemos que tiene la función suma y que devuelve 3, puesto que los valores de sus atributos son 1 y 2
obj11.suma();

//Cambiamos el valor de p1
obj11.p1 = 6;

//Vemos que ahora devuelve 8
obj11.suma();

Se puede ver cómo el nuevo objeto obj11 tiene los atributos y operaciones de su prototipo. Si cambiamos las propiedades de obj11 que tienen el mismo nombre que las de obj1, se sobreescriben los valores en obj11, pero no cambian para obj1.

Si creamos un tercer objeto que tenga como prototipo a obj1

var obj12 = Object.create(obj1);

//Podremos ver que la suma sigue siendo 3 ya que obj1 permanece invariable
obj12.suma()

//Y que obj11 sigue valiendo 8
obj11.suma()

//Pero si cambiamos el valor de p2 de obj1, cambiarán los valores de p2 en obj11 y obj12, ya que se basan en estos:
obj1.p2 = 10;

obj11.suma();   //16

obj12.suma();  //10

//Pero si cambiamos el valor de p1 en obj1
obj1.p1 = 0

//El valor de obj11.suma() no varía con este cambio, puesto que antes se ha sobreesrcrito al hacer obj11.p1 = 6; Sigue dando 16:
obj11.suma();

//Siempre se puede comprobar si un objeto es el prototipo de otro mediante una función que viene en Object.prototype llamada isPrototypeOf:
var obj1 = {x:1};

var obj11 = Object.create(obj1);

obj1.isPrototypeOf(obj11);

Si se invoca Object.create() sin usar parámetros, se empleará el prototipo por defecto que es Object.prototype.