HTML5

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

Anuncios

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

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…

Contador de caracteres para Textarea al estilo Twitter con jQuery

(Ejemplo completo y funcional en. http://jsfiddle.net/4lberto/RNpMk/)

Como Twitter está de moda, en gran parte por su sistema de mensajes cortos, parece que muchas otras aplicaciones de la web 2.0 les gusta limitar las intervenciones de sus participantes. Así se evitan los comentarios interminables que no se lee nadie. Obliga a resumir.

En este sencillo ejemplo vamos a limitar la longitud del texto de un elemento textarea a un número determinado de caracteres. Además mostrará un contador con el número de caracteres que le restan. Si sigue escribiendo llegado el límite se le borrarán. Existen plugins para jQuery similares pero aquí vamos a hacerlo usando nuestras propias funciones sin tener que modificar apenas el código HTML.

Comenzamos definiendo en HTML un textarea y un elemento de texto para indicar el número de caracteres que tiene el textarea y los totales:


<p id="contadorTaComentario">0/140</p>
<textarea id="taComentario" rows="10" cols=""></textarea>

Nótese que el texto del contador por defecto corresponde a 0/140, es decir el valor inicial.

Ahora el Javascript. Usando jQuery.


function init_contadorTa(idtextarea, idcontador,max)
{
$("#"+idtextarea).keyup(function()
{
updateContadorTa(idtextarea, idcontador,max);
});

$("#"+idtextarea).change(function()
{
updateContadorTa(idtextarea, idcontador,max);
});

}

function updateContadorTa(idtextarea, idcontador,max)
{
var contador = $("#"+idcontador);
var ta =     $("#"+idtextarea);
contador.html("0/"+max);

contador.html(ta.val().length+"/"+max);
if(parseInt(ta.val().length)>max)
{
ta.val(ta.val().substring(0,max-1));
contador.html(max+"/"+max);
}

}​

La función principal que realiza el cálculo es updateContadorTa. Por su parte init_contadorTa se encarga de inicializar los eventos que vamos a asignar al textarea para que llame a la primera función y actualice el contador. Se hace así por que es necesario controlar el evento onkeyup y onchange: el primero para la introducción usando el teclado y la segunda para cuando se copia y pega.

Finalmente invocamos la función init_contador para cada textarea existente:


init_contadorTa("taComentario","contadorTaComentario", 140);

Indicamos en el primer parámetro el id del textarea que vamos a controlar. El segundo parámetro corresponde al id del elemento contador y finalmente el último parámetro es el número de caracteres permitidos en el textarea.

Puedes usar el ejemplo en: http://jsfiddle.net/4lberto/RNpMk/

 

Obtener imágenes de un enlace (al estilo Facebook) usando YQL, JSON y jQuery

(Ejemplo directo en: http://jsfiddle.net/4lberto/mGxGB/12/)

En Facebook, Google+ y otros sitios actuales es posible compartir un enlace a una página web con nuestros contactos. Una de las características de estos enlaces es que suelen aparecer con imágenes asociadas como ilustración del enlace. En este post veremos cómo podemos obtener en tiempo real las imágenes de un enlace cualquiera para mostrárselas a un usuario.

La herramienta principal que usaremos será YQL, abreviatura de Yahoo Query Language. Se trata de una utilidad que permite utilizar la potencia de análisis de datos de yahoo mediante una interfaz de comandos al estilo SQL, de modo que se evita tener que programar engorrosos métodos de parseo para tratar la información en formato Web.  Permite muchas otras utilidades.

La llamada a la herramienta YQL la realizaremos a través de una llamada AJAX de jQuery por ejemplo y obtendremos como respuesta un JSON con la información requerida. En nuestro caso le pasaremos al motor de YQL una URL y como salida esperaremos un JSON con todos los elementos img que cumplan las caracterísicas que hemos demandado.

YQL tiene una interfaz web donde se pueden programar la consultas al estilo de los clientes SQL para bases de datos relacionales. En esta interfaz podremos tanto programar la consulta y ver sus resultados como configurar el formato de salida. Para ello se accede a http://developer.yahoo.com/yql/ . El acceso a la consola de pruebas es: http://developer.yahoo.com/yql/console/

Nuestra consulta estará basada en la siguiente:


select * from html where url="http://www.engagdet.com" and xpath="//img"

Esta consulta nos devuelve todos los elementos img de la URL http://www.engagdet.com. Si seleccionamos el formato JSON, además de metainformación de la consulta, el resultado que obtenemos será algo similar al siguiente:

"results": {
"img": [
{
"alt": " Foto: BBC MUNDO",
"height": "305",
"src": "http://p2.trrsf.com/image/get?src=http%3A%2F%2Fimages.terra.com%2F2012%2F10%2F27%2F01-morgue.jpg&w=407&h=305&o=cf",
"title": " Foto: BBC MUNDO",
"width": "407"
},
{
"alt": " Foto: BBC MUNDO",
"height": "41",
"src": "http://p2.trrsf.com/image/get?src=http%3A%2F%2Fimages.terra.com%2F2012%2F10%2F27%2F01-morgue.jpg&w=55&h=41&o=cf",
"title": " Foto: BBC MUNDO",
"width": "55"
},
{
"alt": " Foto: DIFUSION",
"height": "41",
"src": "http://p2.trrsf.com/image/get?src=http%3A%2F%2Fimages.terra.com%2F2012%2F10%2F27%2F02-oxford.jpg&w=55&h=41&o=cf",
"title": " Foto: DIFUSION",
"width": "55"
},

Como dentro de una página Web existen multitud de imágenes y no todas ellas están relacionadas con el contenido, se puede filtrar por tamaños, por lo que podemos exigir un tamaño mínimo que nos  evite estas imágenes. Desgraciadamente diferenciar por el contenido es una operación inviable. También podemos ordenar los resultados por tamaño de modo que los primeros sean los mayores. Es probable que las imágenes más grandes de una página Web sean las que acompañan a la noticia. Desafortunadamente aquellas foto que no tengan un tamaño específicos en los atributos width y height de img no podrán ser catalogadas.

La consulta con estas condiciones aparece a continuación:


select * from html where url="http://www.terra.com" and xpath="//img" and width>60 | sort(field='width', descending='true')
<pre>

Finalmente configuraremos el resultado en formato JSON que es lo que vamos a analizar con la llamada AJAX de jQuery. Tomamos la URL de llamada que nos indica YQL. La podemos encontrar en la zona inferior de la consola de YQL. En nuestro ejemplo la URL de llamada será la siguiente (* contiene saltos de línea para que sea legible) :


http://query.yahooapis.com/v1/public/yql?
q=select%20*%20from%20html%20where%20url%3D%22http%3A%2F%2F
www.terra.com%22%20and%20xpath%3D%22%2F%2Fimg%22%20and%20
width%3E60%20%7C%20sort(field%3D'width'%2C%20descending%3D'true')

Una vez tenemos la consulta hecha y probada ejecutando la URL en el navegador y probando la salida en servicios como http://json.parser.online.fr/, podemos pasar a programar la parte de Javascript con jQuery.

Tenemos que tener en cuenta de que se trata de una llamada AJAX a un dominio diferente al de residencia del Javascript. Este tema ya se trató en otro post de este blog (https://mysticalpotato.wordpress.com/2010/11/18/jsonp-salvando-la-limitacion-de-dominio-de-xhr-cuando-se-hace-cross-scripting-xss/). jQuery soluciona fácilmente esto añadiendo un parámetro de callback que completa automáticamente.

También habrá que parametrizar la URL del enlace. Para ello tenemos un formulario cualquiera con un input con id=”input_web”. Mejor lo vemos en código fuente:


function procesa_web() {

var url_web = $("#input_web").val()
//console.log("Valor completo:" + url_web);

$("#resultado").html("");

$.ajax({
url: 'http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20html%20where%20url%3D%22' + escape(url_web) + '%22%20and%0A%20%20%20%20%20%20
xpath%3D"%2F%2Fimg"%20and%20width%3E35%20and%20
height%3E35&format=json&diagnostics=true&callback=?',
async: false,
cache: false,
dataType: 'json',
success: function(data) {
imagenes_from_web = data.query.results.img; //asignamos a la variable global
var salida = "<b>Imágenes de Web</b><br />";
salida += "<a href=\"javascript:void(0);\" onclick=\"javascript:prev_image()\">Anterior</a>&nbsp;|&nbsp;";
salida += "<a href=\"javascript:void(0);\" onclick=\"javascript:next_image()\">Siguiente</a><br />";
salida += "<img id=\"img_from_web\" data-contador=\"0\" src=\"" + imagenes_from_web[0].src + "\"/>";

$('#resultado').html(salida);
}
});
}

Como se puede ver se lee el valor de la URL a procesar del input en la variable url_web y se escapa para incluirla en la URL. Adicionalmente se incluye el parámetro callback=? para salvar el cross-domain indicado anteriormente.

Los parámetros de AJAX son los utilizados en otros post: síncrono, sin caché porque son datos dinámicos y el tipo de datos JSON.

Dentro del sucess de la llamada AJAX se procesa el resultado. En primer lugar tomamos los valores obtenidos en el JSON para asignarlos a una variable global a la página que será compartida por todas las funciones.  Obviamente navegamos por el JSON para sacar sólo el listado de imágenes:

</pre>
imagenes_from_web = data.query.results.img

Posteriormente pintamos en la capa resultado la primera imagen y dos botones que permitirán navegar por las imágenes. Estos dos botones hacen referencia a dos funciones que no hacen otra cosa que ir refrescando el contenido de la imagen que se muestra en ese momento con el de la siguiente o la anterior del vector global a la página que almacena el resultado. Nótese que en el tag img dejamos el atributo data-contador para almacenar el índice del vector de imágenes. La posibilidad de incluir atributos para datos es una de las posibilidades que ofrece HTML5 (http://html5doctor.com/html5-custom-data-attributes/). Las funciones para navegar por las fotos son las siguientes:


function next_image() {

var contador = parseInt($('#img_from_web').attr("data-contador"));
contador++;
var url_imagen = imagenes_from_web[contador].src;

$('#img_from_web').attr("src", url_imagen);
$('#img_from_web').attr("data-contador", contador);
}

function prev_image() {

var contador = parseInt($('#img_from_web').attr("data-contador"));
contador--;
$('#img_from_web').attr("src", imagenes_from_web[contador].src);
$('#img_from_web').attr("data-contador", contador);
}​

El ejemplo acaba aquí, pero si queréis incluirlo al estilo Facebook para una noticia, simplemente, en el momento de que el usuario confirme el enlace, se tomará el índice del vector de imágenes y se leerá la URL para asignarla a un formulario o llamada AJAX.

El ejemplo completo y funcional podéis visitarlo en: http://jsfiddle.net/4lberto/mGxGB/12/

Web Workers en HTML5. Paseo Aleatorio

Ver Demo

Una de las novedades de HTML5 es la posibilidad de crear hilos de ejecución para ejecutar tareas en segundo plano con el fin de no entorpecer la interacción del usario con la página. De este modo acciones que requieran un gran tiempo de procesamiento, especialmente en dispositivos con poca capacidad de proceso, y que bloquean la interface (más de 250ms), pueden ser ejecutadas en segundo plano. Estos hilos han sido denominados Web Workers, y como todo lo de HTML5, se programan usando Javascript. De momento sólo funcionan en las versiones más actuales de Firefox y Google Chrome. El código de este ejemplo está creado usando Google Chrome 20+.

Para usar un WebWorker simplemente hay que instanciarlo en el código JavaScript:

var worker = new WebWorker ("hilo1.js");

Como se puede ver, recibe por parámetro un fichero de Javascript, que contiene el código que se va a ejecutar cuando se lance el hilo. Para ejecutar un hilo simplemente hay que enviarle un mensaje que será captado mediante un listener para ese evento como veremos después.

worker.sendMessage();

Dentro del código del hilo (hilo1.js) está lo necesario para responder a esta llamada, devolviendo otro mensaje.

self.addEventListener('message',function(e)
{
 self.postMessage('fin');
});

Finalmente el hilo principal también registra un listener para “escuchar” los mensajes del web worker:

 worker.addEventListener('message',function(e){
 alert(e.data); //pinta fin en una ventana de alert
 });

El código del WebWorker es Javascript y tiene una serie de limitaciones muy importantes que determinan la arquitectura a la hora de usarlos. No tienen acceso a:

  • document
  • parent
  • window
  • otras librerías como jQuery o Prototype

Con estas limitaciones queda claro que dentro de los hilos no se puede hacer cualquier cosa ni pueden operar de forma independiente sin la interacción con el programa princopal. Realmente los hilos deberían limitarse a recibir y devolver información de tipo texto o JSON al hilo principal, de modo que sea éste el que haga las operaciones de manipulación de componentes del DOM. Por ejemplo, un caso de interacción típico sería:

  1. El hilo principal instancia un WebWorker para que haga una llamada AJAX y le devuelva la información en JSON. Esta información la mostrará en una capa DIV. Si lo hace el hilo principal tarda 1000ms debido al tamaño del JSON (o más en un ordenador/dispositivo lento).
  2. El WebWorker contiene en su código un “EventListener” donde está progrmaado el procesamiento de la acción indicada en el punto 1. Esta acción se ejecutará cuando la pida el hilo principal.
  3. El hilo principal llama al WebWorker y en éste se ejecuta el evento, lanzando la petición AJAX, que recibe una gran cantidad de texto en formato JSON. Parsea esta información a un objeto (y esta operación le lleva bastante tiempo).
  4. El WebWorker notifica al hilo principal que ha acabado, pasándole por parámetro el objeto JSON ya procesado.
  5. El hilo principal recive el evento de ese WebWorker y dispara una función que printa el objeto JSON en el DIV (algo muy rápido una vez se tiene el JSON).

De forma genérica se podría decir que los pasos que tiene que seguir una aplicación que use Web Workers debería ser:

  1. Hilo principal instancia el WebWorker y le ordena comenzar.
  2. El WebWorker tiene un EventListener que se activa cuando el hilo principal le ordena comenzar. En esta llamada se puede incluir información como un texto o un JSON.
  3. El WebWorker procesa y hace una llamada al hilo principal.
  4. El hilo principal tiene otro EventListener que se activa cuando el WebWorker termina. En esta llamada se puede incluir información como un texto o un JSON.

En nuestro ejemplo lo que intentamos programar es un programa que calcule caminos aleatorios (http://es.wikipedia.org/wiki/Camino_aleatorio) partiendo de un valor dado (100). Cada camino tiene 100 valores que son calculados por los hilos que lanzamos, y su resultado será pintado en un objeto canvas.

Un camino aleatorio parte de un punto, que en nuestro caso será el valor 100, y va generando, mediante números aleatorios diferentes valores basados en el paso anterior, así, partiendo del valor 100, el siguiente paso puede ser 101, el siguiente 104, el siguiente 102, luego 98… etcétera. Esto generará una gráfica con forma de rayo. Se trata simplemente de un proceso que puede ser computacionalmente costoso y que si se llevara a cabo en primer plano bloquearía la interfaz de usuario. Los hilos generarán estas series de 100 valores por cada camino en segundo plano y lo enviarán en formato JSON al hilo principal para que simplemente los pinte en el canvas.

En primer lugar vamos a analizar qué necesitamos:

  1. Un worker que genere series de 100 valores que representan caminos aleatorios y que envía al hilo principal en formato JSON
  2. Un generador de caminos aleatorios, incluyendo una función básica como es un generador de números aleatorios.
  3. Un listener en el hilo principal que se encarga de pintar el camino recibido en formato JSON en un canvas.
  4. Un control para parar el proceso del hilo cuando se desee.

Vamos por partes. En primer lugar tenemos que construir el listener para lanzar el evento de paso de mensaje. Hasta que este evento no se produce no hay lanzamiento del hilo:

self.addEventListener('message',function(e)
{
 var iteraciones = e.data.iteraciones;
 for(w=0;w<iteraciones;w++)
 {
 if (!stop)
 main(e);
 else
 {
 break;
 self.close();
 }
 }
});

Hay que notar un par de cosas:
1. Que existe una variable – global al worker – llamada “stop” con un valor booleano. Será modificada por otro evento para indicar que se debe para el hilo (de ahí el break y el self.close(), que cierra el bucle y termina el hilo).
2. Los parámetros que se le pasan al web worker, que será mediante JSON. En este caso se indican en el campo iteraciones el número de paseos aleatorios que va a generar cada hilo. Se lee mediante e.data.iteraciones.

Una vez tenemos el número de iteraciones se llama a la función principal, main(e) que genera un camino aleatorio y lo envía al hilo principal:

function main(e)
{
 var volatilidad = e.data.volatilidad;
 var media = e.data.media;
 var base = e.data.base;

var salida = "[";
 for(j=0;j<100;j++) //series de 100 valores
 {
 salida+="{\"V\":"+base+"},"; //compone la salida en array de JSON
 base=(siguiente_paso(media, volatilidad, base));
 }
 salida = salida.substring(0,salida.length-1);
 salida +="]";
 self.postMessage(JSON.parse(salida)); //Devuelve la salida en formato JSON
}

Se puede ver que se toman los parámetros de volatilidad, media y base desde el JSON de entrada desde el hilo principal. A partir de ahí, llamando a la función siguiente_paso, se va generando mediante un bucle el listado de puntos del camino aleatorio. Se hace en una cadena de texto con formato JSON para posteriormente ser transformada a objeto JSON con la función JSON.parse. Finalmente se envía al hilo principal a través de self.posMessage.

La operación siguiente_paso genera el siguiente valor del camino aleatorio, partiendo del valor actual y aplicándole un número aleatorio de una función normal 0,1 -N(0,1)- . Para su cálculo se opera con la volatilidad y el crecimiento medio indicado. Alta volatilidad permite obtener caminos más dispersos. La media es el crecimiento medio orgánico en porcentaje de cada paso.

function siguiente_paso(media, volatilidad, anterior)
{
 return (anterior+((anterior*media)+(volatilidad*anterior*randN())));
}

La función randN() es la que mayor calculo requiere y hace costoso el programa. Se emplean 100.000 iteraciones para su cálculo, aunque probablemente el óptimo sea mucho menor (12). Se ha elegido un número tan alto de iteraciones para hacer artificialmente costoso (pero preciso) el programa e ilustrar claramente la utilidad de los web workers:

function randN()
{
 var iteraciones = 100000; //lo óptimo son sólo 12, pero para simular carga de CPU
 var salida = 0;
 for(i=0;i<iteraciones;i++)
 {
 salida+=Math.random();
 }
 salida-=(iteraciones/2);
 salida*=Math.sqrt(12/iteraciones);

return salida;
}

Finalmente se ha incluíduo un mecanismo de parada del hilo que actúa en el bucle principal mediante el valor booleano de la variable “stop”, que se comprueba en cada iteración del bucle si debe continuar con el proceso o parar. La API de Web Workers permite enviar otros tipos de mensajes más allá del método “postMessage()”. Mediante el método “terminate()” enviamos un mensaje al Web worker que activa el evento “close” y que se captura mediate el listener correspondiente. Por tanto debemos añadir el siguiente código a nuestro worker:

self.addEventListener('close',function(e)
{
 stop=true;
});

La mejor manera de para un hilo como el que hemos desarrollado es haciendo una parada controlada del proceso, es decir, generando una serie de números completa. Para ello nos valdremos de una vaiable global que se modifica mediante el evento close y que está dentro del bucle de ejecución del worker. De este modo, si esta variable es true se parará el hilo y se cerrará, saliendo del bucle y cerrando el hilo mediante el método “close()”

if (!stop)
 main(e);
else
{
 break;
 self.close();
}

En el lado del hilo principal, el mecanismo consiste en lanzar tantos workers como se indique y registrar un listener por cada uno de ellos con el fin de recibir los mensajes de los hilos. Se guarda la referencia en un array para poder terminarlos posteriormente. El array es una variable global al hilo.

function start_workers()
{
 var num_workers = parseInt(document.getElementById("hilos").value);
 for (i = 0 ;i<num_workers;i++)
 {
 array_workers[i] = new Worker('js/w1.js');
 array_workers[i].addEventListener('message',function(e){
 pinta_serie_canvas(e.data);
 });
 array_workers[i].postMessage({'volatilidad':document.getElementById("volatilidad").value,'media':document.getElementById("media").value,'base':100,'iteraciones':document.getElementById("iteraciones").value}); //ojo:
 }
 }
 

Como se puede ver en el código, se instancia cada objeto web worker pasándole por parámetro el fichero javascript que define su comportamiento y que se ha descrito en líneas anteriores. Posteriormente se hace la llamada mediante la función postMessage. Se le pasa por parámetro JSON los valores de la volatilidad, media, iteraciones y valor 100 de partida. Como se puede ver se leen de unos input text que tienen id’s correspondientes. Cada web worker se mete en un array para tener su referencia.

El listener para cada mensaje de vuelta de los hilos, que contiene el paseo aleatorio de 100 números en formato JSON, hace una llamada la función pinta_serie_canvas, que lo dibuja en un canvas de HTML5

function pinta_serie_canvas(serie)
{
 var x_step = Math.max(1,Math.floor(800/serie.length)); //incremento eje X, al menos 1. siempre entero

 var canvas = document.getElementById('canvas');
 var context = canvas.getContext('2d');
 context.strokeStyle = generaColor();
 context.beginPath();

 var nuevaPosX = 0;
 var nuevaPosY = 250;
 for(j=1;j<serie.length;j++) //el primer valor es la base, es el de partida
 {

 context.moveTo(nuevaPosX,nuevaPosY); //mueve el cursor a la posición de partida para este paso
 nuevaPosX +=x_step;
 nuevaPosY +=-Math.floor(Math.round(serie[j].V - serie[j-1].V)); //avanza la diferencia que haya en v.absoluto

 context.lineTo(nuevaPosX,nuevaPosY);
 context.stroke(); //pinta la línea
 }

 context.closePath();
}

Básicamente se trata de partir del valor medio de la parte izquierda del canvas (punto 0, 250; ya que el canvas tiene 800*500 píxeles) e ir incrementando los valores de X (en 8 píxeles) y de Y (en tantos píxeles como números enteros haya de diferencia entre el punto actual y el siguiente) e ir pinando una línea mediante lineTo. También se genera un color diferente para cada camino pintado. La forma de manejar el canvas es sencilla.

Finalmente se añade el mecanismo para cancelar la ejecución de cada hilo. Tenemos guardadas su referencias dentro de un array, que recorreremos mandando el mensaje de close -mediante terminate()- a cada uno. Este proceso lo realiza la función terminate_workers();

function terminate_workers()
{
 for(l=0;l<array_workers.length;l++)
 {
 array_workers[l].terminate();
 array_workers[l]=null;
 }
}

Mediante la función terminate se envía un mensaje de tipo “close” al hilo, que como se ha visto antes, pone a true la variable “stop” que es tenida en cuenta en el bucle principal del worker.

Finalmente se ha incluído código HTML para crear la interfaz y algo de JavaScript para poder ligar los eventos de los botones al programa principal.

Podéis descargar el ejemplo haciendo un clone del repositorio en: https://github.com/4lberto/webworkers_paseo_aleatorio

Obsérvese cómo el proceso de cálculo de cada camino aleatorio no bloquea la interfaz web del usuario.

HTML5 canvas. Ejemplo de Cielo Estrellado

Una de las principales novedades de HTML5 es el objeto canvas, que como su nombre indica es un lienzo en el que podemos pintar con Javascript el contenido, realizar transformación, pegar imágenes, etcétera. Las demos más llamativas de HTML5 están realizadas utilizando este componente. Es la esperanza para eliminar Flash del mundo Web.

En esta ocasión vamos a ver cómo utilizar el canvas para cear un cielo estrellado que se va pintando poco a poco. Para ello se creará un canvas de fondo negro de dimensiones determinadas y se irán pintando puntos -en realidad líneas de un píxel de longitud- para simular las estrellas.

El primer paso consiste en crear la página de HTML5 que contiene el canvas. No puede ser más sencilla:

<!DOCTYPE html>
<html>
<head>
 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<canvas id="entorno_canvas" style="border:1px solid black; background-color:black" width="800" height="600">
</canvas>
</body>
</html>

El nuevo tag <canvas> es el que nos permite instanciar este nuevo elemento. Supongo que utilizamos un navegador que soporta HTML5 y este componente en concreto (Chrome o Firefox funcionan perfectamente). Como siempre se le da un id para poder manejarlo con JavaScript y unas características de tamaño y color de fondo, negro en nuestro caso.

Una vez tenemos el componente lo podemos tocar con JavaScript. Podemos incluirlo en el tag <script> de la página o meterlo en un fichero y hacer un include desde el HTML.

En primer lugar hay que tomar la referencia del objeto, algo muy sencillo en JavaScript con HTML5 usando el id asignado a la etiqueta canvas:

var canvas = document.getElementById('entorno_canvas');
var context = canvas.getContext('2d');

Hay que tener en cuenta que en el objeto canvas las medidas funcionan con unidades en píxeles. Para pintar los puntos utilizaremos el siguiente algoritmo:

  1. Situamos el “cursor” del canvas en un punto aleatorio dentro de las dimensiones del canvas (2 coordenadas, x e y).
  2. Ordenamos hacer una línea desde ese pixel al pixel aledaño para que la longitud sea uno.
  3. Volvemos al punto 1 para elegir otro punto.
Estas operaciones básicas quedan reflejadas en la función “pintapunto”:
x = Math.random() * max_x;
y = Math.random() * max_y;
context.moveTo(x,y);
context.lineTo(x+1,y);
context.stroke();
Para dar un efecto mucho más atractivo se ha incluido un retardo en el pintado de cada pixel, de ahí que el código sea un poco peculiar por la forma que tiene de operar la función setTimeOut. Se tiene en cuenta que las coordenadas no salgan de los valores máximo (800*600 que son max_x,max_y) y que se repita durante 10.000 iteraciones.
context.strokeStyle = '#FFF';

var max_x = 800;
var max_y = 600;
var x= 0;
var y = 0;
var contador = 0;
var ejecuciones = 10000;
var delay = 0.1;
var sto;

bucle();

function bucle()
{
//console.log("Contador:" + contador);
if (contador<ejecuciones)
{
contador++;
sto=setTimeout(pintaPunto,delay);
}
else
{
alert("Fin");
}
}

function pintaPunto()
{
x = Math.random() * max_x;
y = Math.random() * max_y;
context.moveTo(x,y);
context.lineTo(x+1,y);
context.stroke();
bucle();
}

El ejemplo se puede ver funcionando en esta dirección http://jsfiddle.net/4lberto/CfK5R/5/