25 junio, 2009

Programación con singletons y árbitros de eventos

He traducido el más que interesante artículo de Aaron Newton, Singletons and Event Arbiters:

¿Cuál es la mejor forma de definir código, asignarlo a una variable global y conseguir que se ejecute todo a la vez?

Usamos MooTools en el servidor en mi código ASP por medio de JScript y necesitamos objetos singleton globales como el de inicio de sesión. Puesto que no hay una forma estándar clara de hacerlo, lo he intentado y he generado algo como esto:
var LoggerObject = function() {
  // código de inicio de sesión
} 
var Logger = new LoggerObject() 
Está bien, pero parece que me estoy dejando algo. ¿Cómo lo hacen los profesionales? ¿Cuáles son las ventajas y desventajas de otros métodos?
Para los que no estén familiarizados con el concepto, un singleton (traducido literalmente sería "hijo único") es una clase diseñada para tener sólo una instancia (o un número limitado de ellas). En JavaScript, cualquier objeto puede heredar de cualquier otro objeto y no se puede prevenir que esto pase, por lo que en el sentido más estricto de la expresión, no es posible tener un singleton en JavaScript.

MooTools ofrece una función llamada "Class" que nos ayuda a gestionar la herencia entre objetos. Crear un objeto que es una instancia de una clase se consigue con el operador new:
var myWidget = new Widget()
Si lo que se desea es crear una clase e inmediatamente convertirla en una instancia, es posible hacer los siguiente:
var myInstance = new new Class({...})
El doble new invoca una función devuelta por la clase y que devuelve un objeto (una instancia de esa clase). Raramente se puede encontrar una razón para hacer esto así. De hecho, la única razón que se me ocurre es si se quiere que esa clase extienda a otra:
var myInstance = new new Class({
    Extends: SomeOtherClass,
    // nuevas propiedades aquí
});
El problema con este esquema es que no consigue mucho. JavaScript trata sobre todo con objetos, y hay mejores formas de hacer esta tarea.

Yo uso objetos (como singletons) continuamente para mi código de aplicación (obviamente casi nunca para plugins, que se supone que deben extenderse e instanciarse). Uso objetos "site" para apuntar métodos para gestionar el estado (como por ejemplo si el usuario ha iniciado sesión, su nombre de usuario, etc). Simplemento hago un objeto JavaScript básico, así:
var mySite = {
    login: function(username){ this.username = username; },
    showWelcome: function(){
        $("welcome").set("html", "Welcome " + this.username);
    }
};
No hay nada especial aquí - JavaScript funciona así. Es importante fijarse que las clases en MooTools existen para darnos funcionalidad y dejarnos derivarla a través de la herencia. Si creo una clase llamada Widget y luego quiero crear una versión llamada Widget.Ajax, puedo extender Widget y añadir sólo las partes ajax. Con los singletons el tema está en que no vas a crear más que uno.

En cualquier caso, hay algunas otras cosas que la Class de MooTools nos ofrece, como mixins (clase que ofrece cierta funcionalidad para ser heredada por una subclase, pero no está ideada para ser autónoma). Ejemplos de esto serían Event y Option que ofrecen a las instancias métodos como setOptions o addEvent. En estos casos aún puedes usar la declaración de objeto de arriba y extenderlo:
var mySite = {
    login: function(username){
        this.username = username;
        this.fireEvent("login");
    },
    showWelcome: function(){
        $("welcome").set("html", "Welcome " + this.username);
    }
};
$extend(mySite, new Events());
Aunque generalmente yo hago justo de la otra forma porque creo que es un poco más limpio (aunque se consigue el mismo efecto):
var mySite = new Events();
$extend(mySite, {
    login: function(username){
        this.username = username;
        this.fireEvent("login");
    },
    showWelcome: function(){
        $("welcome").set("html", "Welcome " + this.username);
    }
});
Con esta forma se evita la necesidad del tosco patrón new new Class y resulta más legible en mi opinión.

Árbitros de eventos

Lo que me lleva a una práctica común que suelo usar cuando construyo aplicaciones. Cuando construyo un sitio o aplicación, intento hacer las cosas lo más modulares posible. Lo hago de forma que primero mi sitio funcione sin JavaScript, y entonces empiezo a añadir los elementos de la interfície con ajax y animaciones y tablas ordenables y todo esa fiesta. También hago que el JavaScript mismo sea modular. Todo lo que pueda ser una clase, hago que sea una clase. Lo que queda es el código que instancia esas clases.

Pero ¿qué pasa si el código que instancia un widget necesita información de otro widget? Por ejemplo, ¿qué hacer si nuestro widget welcome (bienvenida) necesita saber el estado del widget de inicio de sesión? Bueno, si nuestro widget de inicio de sesión almacena su estado en el objeto mySite, entonces el widget de welcome puede inspeccionarlo, ¿no? Eso está bien; si la página carga y mySite.username no está inicializado (undefined) entonces el widget de welcome sabe que el usuario no ha iniciado sesión. Todo correcto.

Pero ¿qué pasa cuando el estado cambia? ¿Qué pasa cuando el usuario inicia sesión? Ahora necesitamos un evento - onLogin o similar, pero eso significa que Welcome necesita apuntarse al widget de inicio de sesión y entonces hemos perdido algo de modularidad al introducir una dependencia. Welcome no puede funcionar sin el inicio de sesión. En este caso, no parece que se trate de una mala dependencia - después de todo, un mensaje de bienvenida sin una funcionalidad de inicio de sesión no tiene mucho sentido - pero podría haber docenas de otros widgets que necesiten hacer cosas cuando el usuario inicia sesión y podríamos queere que esos widgets funcionen incluso si el usuario no ha iniciado la sesión. Es más, podríamos querer cargar el widget de inicio de sesión bajo demanda - cuando el usuario intente iniciar sesión por ejemplo. No es posible hacer eso si todos nuestros widgets necesitan apuntar eventos al widget de inicio de sesión.

Esta es la razón por la que hago que mySite sea una instancia de Events. mySite realmente no tiene eventos nativos en sí mismo. En su lugar, otras clases le apuntan eventos y disparan eventos por ella. Así, en nuestro puzzle de arriba sobre nuestro widget de inicio de sesión, en lugar de que todos los widgets se apunten a un evento onLogin, pueden apuntarse a mySite, y entonces nuestro widget de inicio de sesión dispara ese evento pero no sobre sí mismo sino sobre mySite:
var mySite = new Events();
var Login = new Class({
    //...login logic and stuff
    loginSuccess: function(username){
        mySite.username = username;
        mySite.fireEvent("login");
    }
});
var Welcome = new Class({
    initialize: function(){
        this.showMessage();
        mySite.addEvent("login", this.showMessage.bind(this));
    },
    showMessage: function(){
        $("welcome").set("html", mySite.username ? "Welcome " + mySite.username : "Please log in");
    }
});
Ahora nuestras clases ya no dependen entre ellas. Todas dependen ahora de mySite pero eso ya pasaba antes. Ahora pueden venir y marcharse cuando quieran sin preocuparse por las demás. Este incremento de la modularidad se agradece cuando el sitio crece en complejidad. Algunas páginas podrían tener algunos widgets y algunos podrían tener otros. Puede apuntar la lógica al objeto mySite sin tener que lidiar realmente con esas dependencias.

20 junio, 2009

La desfachatez de la E azul

Está mal abandonar a los usuarios de Internet Explorer durante cinco años (Agosto 2001-Octubre 2006), lo que podríamos equiparar a que un fabricante de automóviles no diseñe un nuevo vehículo durante 30 años. Y está mal despertar del letargo sólo porque la competencia ha estado haciendo los deberes hasta el punto de conseguir arrancar un pedazo del pastel pese a la integración de Windows con el sistema operativo y su ya famoso icono azul titulado "Internet" sin más.



Las versiones 7 y 8 se han sucedido más rápidamente, pero el camino que ahora debe recorrer Microsoft para ponerse al día con los estándares es, todavía, muy largo. Con una tendencia muy marcada a inventar por su cuenta, su último navegador queda muy atrás en cuanto a velocidad o respeto a esos estándares, a los cuales, por cierto tiende a menospreciar tal y como indica claramente Ian Hickson (el responsable de la especificación HTML5) en esta entrevista:

Personalmente me gustaría que Microsoft se implicase más con HTML 5. Han enviado muy pocas respuestas a lo largo de los años, muchísimas menos que el resto de fabricantes. Incluso cuando se les ha solicitado su opinión sobre características que estan implementando, raramente recibo una respuesta. Es muy triste. Si les envío un e-mail con una pregunta sobre qué puedo hacer para ayudarles, normalmente no recibo respuesta; como mucho recibo una promesa de que me contestarán más adelante, pero eso es todo.

En este contexto en el que siguen manteniendo una más que importante cuota de mercado (recordemos que para muchos usuarios, "Internet" sigue siendo ese icono con una "E" azul) al tiempo que mantienen un desprecio evidente ante los estándares acordados por todos, resulta especialmente provocativo lo que ha sucedido esta semana.



Mentiras para vender: Firefox viejo, Safari aburrido y Chrome oxidado


Hace relativamente poco que la versión 8 de su navegador está disponible para descargar, y Microsoft no deja de ser una casa de software con un departamento tradicional de márketing. Está demostrado que la inmensa mayoría de internautas no sabe distinguir el navegador de un motor de búsqueda como Google. Sin embargo, su nueva campaña está basada en insultos a la competencia (Genbeta, Ajaxian, WebReflection) y falsedades para encubrir las enormes carencias del navegador de Microsoft.

Probablemente Microsoft se encuentra en la incómoda situación en la que no quiere invertir en un producto que no le genera beneficios y que además amenaza a Windows, pero tampoco desea que la competencia siga en esa vorágine de mejoras continuas en la que está sumida y que poco a poco están desplazando su producto gratuito. Con esos precedentes, quizá pueda entenderse la decisión de atacar a la competencia como un intento desesperado de conservar cuota de mercado con un navegador que no quiere evolucionar.

Por ello resulta enormemente ofensiva la página de promoción del nuevo Internet Explorer y la comparativa con la competencia, plagadas de incorrecciones, medias verdades y, ¿por qué no decirlo?, auténticas mentiras para intentar demostrar lo indemostrable: que IE no sólo está a la altura del resto (recordemos que llevan 5 años de retraso) sino que además es mucho más mejor que ellos. Aunque para "demostrarlo" tachen de mitos...
  • la lentitud (más que demostrada con todas las comparativas de estos últimos meses)
  • la seguridad (todos hemos visto instalarse "software" automáticamente al visitar una página con IE)
  • la menor riqueza respecto a Firefox (ya quisieran tener una décima parte de las extensiones de éste)
  • y que su navegador no respeta los estándares, ante lo que tienen la enorme desfachatez de afirmar que "Internet Explorer 8 pasa más casos de prueba del W3C que cualquier otro navegador".

Lo peor de todo es la sensación de que, conscientes de que no van a alcanzar el nivel al que ahora mismo están Chrome, Safari, Opera o Firefox, optan por la opción más fácil: invertir en desprestigiar a la competencia, aunque con ello detengan el progreso de las tecnologías web durante unos cuantos años más. Quizá legalmente toque callar, pero moralmente merecen que su flamante E azul deje para siempre de estar unida a la palabra Internet. Lástima que sólo siga siendo un deseo...

17 junio, 2009

Opera Unite, no tan buena idea

Han habido otros intentos anteriores de introducir un servidor en el mismo navegador. ¿Para qué nos puede servir eso? Pues entre otras cosas para obtener una aplicación web que se ejecute en el mismo equipo, sin conexión... pero ¡espera! ¿No era eso lo que promete Gears y buena parte del estándar HTML5? Visto desde este punto de vista, Opera no ofrece más que Chrome que ya incluye Gears.

Por otra parte, Opera lleva innovando desde hace años y muchas de las características que hoy día traen los navegadores (pestañas, página personalizada de nueva pestaña, bloqueos de popups o sesiones) fueron inventados por ellos. Incluso en la versión anterior osaron integrar un cliente de BitTorrent. En este contexto, el de compartir, el nuevo Opera Unite puede ser un invento importante: compartir fácilmente información residente en nuestro propio equipo sin tener que preocuparse de proxies y otros problemas tradicionales (algo ya resuelto por no-ip.com). ¿Pero no hay otras tecnologías más apropiadas para ello como FTP o el propio BitTorrent? En cualquier caso tampoco es una idea nueva.


Instalado la beta de Opera 10 y activado el servicio a través del registro de una cuenta, obtenemos una página de inicio con la forma unite://equipo.usuario.operaunite.com/_root/content/ que resulta accesible desde cualquier punto de Internet cambiando el protocolo de unite:// a http://. Pero para evitar tener que pelearse con configuraciones de routers y demás, la información viaja a través de los servidores de Opera, aunque se esté sirviendo desde el mismo equipo desde el que se accede, lo que reduce sensiblemente la velocidad de respuesta. Luego hay otros puntos por resolver, como la seguridad y la disponibilidad, que evidentemente depende de que el equipo esté funcionando y con Opera corriendo en todo momento.

Los servicios que ofrece son:

  • Compartición de archivos: se selecciona una carpeta local con el contenido a compartir. Permite seleccionar quién tiene acceso y cómo (público, limitado con contraseña o sólo para el equipo local)
  • Nevera: para dejar notas al propietario del servidor (no deja de ser como el e-mail)
  • Reproductor de multimedia: para acceder a la librería de música y películas remotamente con las mismas posibilidades que la compartición de archivos
  • Compartición de fotos: ídem que el anterior pero con fotos
  • Un chat de grupo con control del propietario y alojado en el propio equipo
  • Servidor web: permite indicar una carpeta que contendrá proyectos web que pueden ser publicados para wur otros usuarios los visiten. Sin embargo, hay que tener en cuenta que los servicios ofrecidos son muy limitados (páginas estáticas)
  • La idea es que otros servicios sean añadidos en el futuro, por parte de Opera o de terceros mediante JavaScript ejecutado en el servidor en forma de Widgets

Personalmente no me gusta la idea tal y como Opera la ofrece. Sin duda es mucho más potente y con muchas más posibilidades que la tecnología de almacenamiento local y utilización del navegador sin conexión que ofrece HTML5/Gears. Igualmente hay que tener en cuenta que se trata de una beta. Pero quizá algunas de las decisiones técnicas tomadas se alejan un poco de los estándares que han estado proponiéndose en los últimos tiempos, desde el W3 Database API hasta la idea de empaquetar en un mismo archivo todo el código, evitando absurdamente una de las mayores ventajas del desarrollo web: usar un simple editor de texto para modificar el código, guardarlo y ver los cambios en el navegador.

04 junio, 2009

El proyecto Vice-versa

He traducido la introducción del Proyecto Vice-versa presentado en este post de Andrea Giammarchi. Se trata de una librería que nace con la idea de aproximar las implementaciones de las características JavaScript disponibles desde cada navegador.

El proyecto Vice-versa


Cada navegador tiene alguna característica deseable: puede ser su rendimiento, puede ser algo que "aún no se ha estandarizado", puede ser una característica preparada para ECMAScript 5. El proyecto Vice-versa pretende traer lo bueno de cada navegador a un único archivo JavaScript ligero y que valga para todos los navegadores.

Filosofía


Array.forEach(document.all, function(node, i, all){
    // proyecto vice-versa
});
Como un Ninja JavaScript tengo que tratar diariamente con centenares de problemas diferentes a causa de "esta o aquella" implementación por parte de un navegador. La web está llena de librerías pero a menudo lo que necesitamos hacer requiere usar el navegador "tal cual" y estas librerías puede ayudar o simplemente pueden hacer más lentas las aplicaciones a causa de sobrecargas innecesarias sobre tareas simples o comunes. Estudiando el DOM, que es caótico, a menudo "viajo" entre los sitios de MDC y de MSDN para resolver un problema específico que puede causar muchos dolores de cabeza a causa de "alguna" implementación del estándar W3C que alguien ha olvidado realizar. Al mismo tiempo, teniendo en cuenta que el navegador más usado es el que tiene el motor de JavaScript más lento, siempre intento encontrar soluciones que puedan completar una tarea sin afectar al rendimiento. En la mayoría de los casos, he notado que, ya que otros navegadores son más rápidos y más potentes, tiene sentido traer funcionalidades no estándar a esos navegadores en lugar de implementar, cuando sea posible, una funcionalidad omitida en Internet Explorer. Como ejemplo básico, para convertir un documento XML en una cadena, nos gustaría usar una instancia de XMLSerializer, pero ¿qué problema hay con el atajo del atributo xml de Microsoft?

// al estilo de otros navegadores (estándar)
var xmls = new XMLSerializer;
var string = xmls.serializeToString(myXMLDocument);

// al estilo de Internet Explorer (no estándar)
var string = myXMLDocument.xml;
Es más, document.evaluate es una característica muy buena, pero la llamada más común a esta función es la siguiente:

var xpresult = document.evaluate(XPathSelector, myXMLDocument, null, 0 /* as ANY_TYPE */, null);

// En internet explorer hay una función ligeramente diferente: selectNodes
var xpresult = myXMLDocument.selectNodes(XPathSelector);
La diferencia principal es el número de caracteres necesarios para llamar al método estándar en lugar de el de IE, aunque el resultado es exactamente el mismo durante las iteraciones (donde IE usa nextNode, el resto usa iterateNext). De nuevo, si estamos en Internet Explorer podemos confiar en la propiedad más fea que hayamos visto nunca desde la primera implementación de JScript sonbre el DOM: document.all. Ahora, ¿por qué demonios nos encanta la "miniaturización" de YUICompressor más gzip pero preferimos una sintaxis sin sentido como document.getElementsByTagName("*") en lugar de document.all ?

La lista podría seguir con execScript para evaluar código en el ámbito global (relacionado con tiempo de ejecución y con Ajax) y con Object.defineProperty, pero al mismo tiempo tenemos carencias con Array.prototype desde hace una eternidad, algo relativamente aburrido ya que el prototipo del constructor global debe permanecer "intocable" por razones de compatibilidad ... Ya es hora de comprender que nadie hace un bucle for in sobre listas o colecciones, así que Array.prototype ha de ser estandarizado, no hay "puede ser" o "quizá", especialmente ahora que se ha implementado como estándar en ECMAScript 5. Como resumen, estos son conceptos dentro del proyecto vice-versa: especialmente si estos comportamientos pudieran enlentecer el rendimiento general de las aplicaciones, o si estos comportamientos no son estándares.

Características vice-versa provenientes de Internet Explorer


  • Object.defineProperty (IE 8 o superior)
  • document.all, como atajo a document.getElementsByTagName("*")
  • document.createElement con soporte html para cada navegador (a veces simplifica las cosas p.e. document.createElement('<em class="test"></em>'))
  • HTMLElement.attachEvent y detachEvent
  • objeto global window.event poblado via attachEvent con las propiedades cancelBubble y returnValue
  • HTMLElement.outerHTML con set y get (la forma más simple de leer un elemento del DOM como una cadena)
  • HTMLElement.innerText con set y get (la forma más rápida en IE de insertar un nodo de texto)
  • HTMLElement.outerText con set y get (la forma más rápida en IE de reemplazar un nodo genérico por uno de texto)
  • execScript para evaluar en un ámbito global cualquier tipo de cadena (sin soporte a un segundo argumento)
  • XPath selectNodes y nextNode via documentos XML
  • propiedad xml, sólo get, para recuperar una cadena de un XMLDocument/XMLNode
  • insertAdjacentElement, insertAdjacentHTML, y insertAdjacentText, la forma más rápida de poner algo antes de un nodo, como firstChild, como lastOne, o tras el mismo nodo

Características vice-versa principalmente para Internet Explorer


  • Array.slice, para trocear todo sobre una colección genérica (compatible con DOM)
  • Array.forEach, para recorrer una colección genérica via callback (compatible con DOM)
  • Array.prototype, actualizado a la última revisión de Mozilla/FireFox (sin la especificación completa, versión enfocada en rendimiento)
  • XMLHttpRequest, para el antiguo IE6 (desfasado, será eliminado tan pronto como se pueda, pero debemos esperar primero hasta el final de este navegador)
  • setInterval y setTimeout con argumentos extra como los demás

Características vice-versa de ECMAScript 5


  • Object.getPrototypeOf, para recuperar el prototipo heredado por un objeto genérico
  • Object.keys, compatible (incluyendo toString), para recuperar un Array de claves a partir de un objeto
  • Object.create, compatible (incluyendo toString), para crear una nueva instancia a partir de otro objeto
  • Array.isArray, para conocer si un objeto genérico es un Array
  • String.prototype.trim, una implementación rápida de recorte para cada cadena
  • Function.prototype.bind, un método para asegurar el ámbito this en una llamada a función

Características extra en vice-versa


  • document.query, librería esencial de selectores para obtenere resultados genéricos mediante consultas comunes de tipo dolar (función $)
  • Object.forIn, para recorrer una instancia genérica sin considerar al prototipo heredado y sin olvidar las propiedades predefinidas (toString, valueOf, etc)

Metas de vice-versa


Pronto llegarán más funcionalidades, esperando mantener por debajo de 10KB de tamaño total minificado y comprimido (actualmente está en 4KB). Esta es sólo una implementación parcial de mi idea original. Las librerías de terceras partes serán bienvenidas, y los colaboradores de otros equipos de librerías serán más que bienvenidos. La idea principal es crear un híbrido completo del escenario JavaScript haciendo que los rendimientos sean similares en cada navegador y ofreciendo una estructura de bajo nivel para construir librerías complejas, aplicaciones, ... respetando donde sea posible a Internet Explorer, y trayendo sus características dentro de otros navegadores rápidos y potentes. ¿Estoy loco?

Últimos links en indiza.com