25 enero, 2010

Las aplicaciones web en 2010. Thin Server Architecture

Aviso que se trata de un artículo un poco largo en el que trato las tecnologías web más actuales relacionadas con Javascript y el cambio de paradigma que se está produciendo con la capa de presentación en las aplicaciones web.

Hay algunos artículos (por ejemplo 1,2,3) que hacen hincapié en la que probablemente constituya la nueva plataforma web más importante de la década. La combinación de las mejoras y los estándares que rodean a ECMAScript (Javascript) junto con los nuevos motores que han multiplicado en varios factores la velocidad del lenguaje, el interés por AJAX y nuevas formas de comunicación (Comet, Google SPDY, websockets nativos), y los proyectos emergentes que no dejan de aparecer para usar el lenguaje en el lado del servidor, se están uniendo para empujar el desarrollo web basado en Javascript hacia la vanguardia de la tecnología web. Puede que los entornos corporativos tarden más en aceptar la simplicidad, flexibilidad y del lenguaje, al igual que resisten con XML respecto a JSON o con Java respecto a Ruby o Python, pero es cuestión de tiempo que la fuerza que alberga un lenguaje extremadamente simple y a la vez extremadamente potente salga a flote y por fin se le haga justicia. Recordemos que JS parte con una resistencia adicional y es que muchos programadores, especialmente de Java, mantienen cierto grado de odio hacia el lenguaje, procedente de la falta de distinción entre el lenguaje en sí, que apenas varía entre navegadores, y la patética implementación del DOM en cada navegador.

La capa de presentación ahora en el navegador

Gmail supuso un antes y un después en la tecnología web. Sigue siendo la aplicación modelo, la que demuestra que es posible tener un aplicativo casi tan rápido como el de escritorio. Tradicionalmente, la presentación se ha generado en el servidor escupiendo una secuencia HTML que debería ser interpretada por el navegador. Esto es así porque se tiene un control de la salida desde el punto en el reside la lógica de negocio. Recientemente se ha extendido el paradigma MVC (Modelo-Vista-Controlador) que permite separar de forma lógica estas tres capas de una aplicación de forma que el código pueda crecer fácilmente sin que la mantenibilidad de ésta se resienta. Sin embargo, la forma tradicional de generar ese código HTML a enviar al navegador ha sido mediante templates o lo que es mucho peor: entrelazando código del servidor de aplicaciones con el de presentación hasta alcanzar límites como el siguiente (fuente):
<select name="type">
<c:forEach items="${types}" var="type">
<option value="${type.id}"
<c:if test="${item.defaultType == type.id}">
 selected</c:if>>
<c:out value="${type.name}"/>
</option>
</c:forEach>
</select>
<input type="button" value="Add <c:out
value="${item.name}"/> to cart"
onClick="addItem('<c:out value="${item.guid}"/>')"/>
Los problemas que esta arquitectura, utilizada ampliamente en la actualidad, presenta son innumerables, entre ellas:
  • Mala distribución de la potencia de procesamiento. Prácticamente todo el trabajo lo realiza el servidor, a pesar de que los clientes a los que se sirve la aplicación tienen potentes máquinas que apenas se aprovechan.
  • Modelo de programación complicado, ya que toda interacción con el servidor implica una interfície de petición/respuesta, que además genera esperas al usuario.
  • Mayor vector de ataque y, por tanto, menor seguridad. Entremezclar lógica de negocio con la presentación supone que el crecimiento de la aplicación genere nuevos puntos débiles en la seguridad por la que ésta puede ser atacada.
  • Consumo elevado de memoria en el servidor para mantener estructuras de datos sobre las sesiones abiertas por los usuarios.
  • Uso ineficiente de los recursos de conexión al requerir que documentos relativamente extensos en HTML (o sea, poco optimizados para el ancho de banda) sean enviados al navegador para cualquier cambio de estado en la aplicación.
  • Complicación a la hora de generar la versión offline de las aplicaciones web. Una capa de presentación generada por el servidor necesita de conexión para ser accesible desde el navegador. Readaptar la aplicación para ello implicará, probablemente, reescribir entera dicha capa a parte de la lógica propia del funcionamiento offline.
La aparición de frameworks como jQuery, MooTools, Prototype o Dojo han eliminado la parte de peor fama que injustamente cargaba Javascript: la de las incompatibilidades con el modelo de objetos del documento o DOM. Y con ello han aparecido las mejores partes del lenguaje. Además, una serie de proyectos están facilitando no ya la manipulación de los elementos generados en las que destacan las frameworks anteriores, sino la creación de dichos elementos para constituir complejas interfícies de usuario desde el mismo navegador. Por ejemplo:
  • El estándar E4X (ECMAScript for XML) permite definir elementos directamente como variables JavaScript:
    var sales = <sales vendor="John">
      <item type="peas" price="4" quantity="6"/>
      <item type="carrot" price="3" quantity="10"/>
      <item type="chips" price="5" quantity="3"/>
    </sales>;
    
    alert( sales.item.(@type == "carrot").@quantity );
    alert( sales.@vendor );
    for each( var price in sales..@price ) {
    alert( price );
    }
    
  • JAML permite definir elementos usando una sintaxis muy sencilla basada en llamadas a funciones:
    div(
    h1("Un titulo"),
    p("Un texto o parrafo cualquiera"),
    br(),
    ul(
     li("Primer item"),
     li("Segundo item"),
     li("Tercer item")
    )
    );
  • JSONML es una aplicación del formato JSON que puede representar XHTML perfectamente:
    [ "ul", { style : "list-style-type:none"},
     [ "li",
       "Primer item"],
     [ "li",
       "Segundo item"],
     [ "li",
       "Tercer item"],
     ];
  • El Zen-coding es un formato pensado para generar código XHTML rápidamente en un editor de texto, pero más de uno ha adaptado a jQuery o Mootools la misma sintaxis permitiendo, por ejemplo, generar el mismo código XHTML del ejemplo anterior con la cadena ul>jQuery('ul li:3[innerHTML="item"]').satisfy(); o $('body').zen('ul>li*3>span');
Una de las ventajas de dejar la parte de presentación exclusivamente al navegador es que es posible utilizar fácilmente distintas opciones para reescribir la interfície sin tocar la parte del servidor. Por ejemplo, una aplicación web 2.0 pura puede convertirse fácilmente en una aplicación XUL para navegadores Mozilla, o Flash, o Flex, o OpenLaszlo, o un applet Java, o Air, o Titanium, o un framework como Ext.js o Ajax.org, o una aplicación para Android, Palm Pre o iPhone con PhoneGap. Además los clientes web están en medio de un profundo proceso de mejora en el que tendrán disponibles una cantidad de nuevas opciones impensables hasta hoy:
  • WebSockets que les permitirá mantener una conexión abierta bi-direccional con el servidor, optimizando así la información transmitida, el tamaño de los datos, el tiempo de respuesta y posibilitando la nuevas aplicaciones web en tiempo real (chats, información de bolsa o juegos multijugador, por ejemplo) y dejando AJaX desfasado ante la llegada, por fin, de Comet.
  • WebWorkers que permitirá que las aplicaciones web puedan disponer de un sucedáneo de la programación concurrente en el navegador, de forma que procesos pesados no hagan más lenta la respuesta de la interfície de cara al usuario.
  • Web SQL Database para ofrecer el lenguaje SQL en el cliente y posibilitar trabajar offline con una aplicación web.

Javascript en el servidor

Como comentaba al principio, JavaScript está escapando del navegador con una fuerza destacable, sólo achacable a la potencia de un lenguaje bastante viejo que sólo unos pocos han sabido reconocer. No sólo aparece en la capa del servidor de aplicaciones, también lo está haciendo en la base de datos. Dos estándares CommonJS y JSGI están cubriendo rápidamente la falta de acuerdo en un terreno tan cambiante. EL primero define una serie de APIs críticas para construir módulos reutilizables en el entorno del servidor. Por ello cubre facetas como la interacción con la base de datos, el acceso al sistema de archivos, el servidor web o la generación de formatos. El segundo define la forma Javascript con la que interactuar desde el servidor web con las conexiones HTTP, definiendo una representación del protocolo mediante un objeto JSON/Javascript. Entre lo más puntero de la tecnología web en el servidor se encuentra la utilización de bucles asíncronos de eventos para tratar con las conexiones entrantes en lugar de usar hilos del sistema operativo para ello. En las comparaciones entre los servidores NginX y Apache, que siguen precisamente cada una de esas técnicas, se puede ver que no sólo NginX consume mucha menos memoria sino que también es capaz de soportar muchas más conexiones concurrentes. Siguiendo esa misma filosofía han aparecido proyectos como Twisted para Python, EventMachine para Ruby y ahora Node.js para Javascript. Los tres han sido creados de forma que no contienen llamadas bloqueantes como las que se realizan al sistema operativo para leer un archivo. Para ello se basan en un mecanismo que JavaScript ya incorpora de forma natural: las funciones callback que son llamadas cuando el trabajo solicitado ha sido completado, dejando que mientras tanto el sistema siga ejecutando otros procesos del bucle de eventos y, por tanto, incrementando exageradamente el rendimiento. Muchos incorporan un mecanismo llamado promises que permiten escribir código de la forma más parecida al sistema procedimental tradicional (en secuencia) y evitar así romper una misma función como bloque conceptual en varias funciones distintas cada vez que se necesite una llamada bloqueante (acceso a disco, base de datos o nueva conexión). Helma NG es la nueva versión del proyecto activo más antiguo que, siguiendo el estilo MVC ofrecía la posibilidad de usar el motor Rhino de Java y el servidor web Jetty para generar aplicaciones con JS. Pero en relativamente poco tiempo han aparecido muchas más opciones:
  • Persevere es un servidor de aplicaciones que ofrece una arquitectura REST para intercambiar datos (preferentemente en JSON) con un cliente rico posibilitando así aplicaciones complejas. Así mismo ofrece persistencia de datos, gestión de la seguridad, notificaciones Comet y capacidades avanzadas de comunicación para múltiples plataformas. Con la aparición de nuevas plataformas CommonJS, este proyecto probablemente ofrecerá opciones para instalarse sobre ellos (por ejemplo sobre Node.js).
  • Flusspferd es un proyecto que aúna SpiderMonkey (el motor de JS en C++) con una serie de módulos (SQLite,GMP,Zest,Juice,cUrl...) para ofrecer un entorno de ejecución muy rápido aunque está un poco verde.
  • En la misma situación se encuentra EJScript, que además añade algunas extensiones de JavaScript que fueron eliminadas del estándar. Su principal baza es el rendimiento que se acerca al de Node.js pero con una décima parte del consumo de memoria de aquel.
  • Narwhal es la implementación de CommonJS más avanzada y madura ya que incluye todos sus módulos, ofreciendo soporte completo de entrada/salida, una colección de módulos de librería, un sistema de gestión de paquetes llamado tusk y se ha diseñado para funcionar sobre varias plataformas Javascript.
  • node.js está generando una auténtica riada de reacciones en la comunidad web. Básicamente porque mata tres pájaros de un tiro: consigue un rendimiento escandaloso para tratarse de Javascript aunando el motor V8 y su filosofía asíncrona; su tratamiento de bajo nivel de las conexiones permite usarlo como servidor HTTP pero también Comet adelantándose al futuro; y aprovecha el mecanismo de funciones callback al que estamos más que acostumbrados los programadores. Actualmente está muy bien posicionado para coronarse en el rey de los servidores de aplicaciones basados en lenguajes dinámicos. Además se espera que Narwhal funcione sobre Node con las ventajas adicionales de un sólido software orientado totalmente a la gestión segura de datos en el servidor. Como limitaciones: es demasiado inmaduro porque acaba de ser publicado; de momento sólo implementa el sistema de módulos de CommonJS y no el resto de APIs; aún no puede aprovechar los servidores con varios núcleos; no soporta la especificación JSGI.
  • appjet.com era un entorno de ejecución de aplicaciones JS basado en Rhino/Java. Por desgracia ha sido comprado por Google y el enlace de descarga del archivo .jar que contenía todo el entorno ya no está accesible.
  • Aptana Jaxer también se adelantó en unos meses a su tiempo. Enfocaron el desarrollo en incorporar el motor de renderizado Mozilla Gecko en el lado del servidor y en reutilizar el mismo código en cliente y servidor en un sistema que quizá no resulte demasiado intuitivo para el programador tradicional y que fomenta la generación de la presentación en el servidor. Por desgracia, aunque no ha desaparecido, este proyecto también ha sido relegado por la empresa que lo desarrolla.

Futuro

Con todo ello, es fácil plantearse si sería posible realizar hoy día una aplicación web conociendo únicamente JavaScript. La respuesta obvia es que no, pero ya son muy pocas las piezas que quedan:
  • Datos. Existen ya servidores de bases de datos que hablan JS como CouchDB con su revolucionario enfoque a la hora de realizar consultas de datos basadas en el sistema MapReduce. También MongoDB ofrece una interfície REST para tratar con sus documentos JSON, el formato de datos propio de Javascript. No es casualidad que ambas pertenezcan al ámbito de la las nuevas bases de datos conocidas como NoSQL, donde el paradigma relacional que se ha demostrado como problemático para la escalabilidad es dejado atrás a favor de otras opciones menos rígidas. Se echa en falta un estándar para definir consultas basadas en el lenguaje Javascript. SQL está muy extendido, pero estoy seguro que se puede hacer lo mismo que CouchDB ofrece con menos complejidad. Quizá al estilo de Microsoft Linq pero siguiendo la sintaxis de Javascript, o bien basándose en la base de datos para navegador TaffyDB.
  • Servidor de aplicaciones. Son muchas las opciones, como he mencionado antes. Pero la amplia adopción de CommonJS crea un rico ecosistema de opciones que otros lenguajes no cubren. Esa variedad que ha aparecido en muy poco tiempo es la mejor prueba de que, probablemente, esta nueva década sea la de la amplia adopción de Javascript. Otros estándares que ya están en el cliente, como los Web Workers o el ya famoso y estudiado XMLHTTPRequest que mueve a AJaX, serían de mucha utilidad en el servidor.
  • En el navegador se echa en falta un sistema de empaquetado de aplicaciones al estilo los .jar de Java que optimizaría la descarga al no exigir varias conexiones para obtener todos los datos. Otro estándar que echo en falta es un lenguaje basado en JSON, que equivalga a HTML y que sea aceptado por los navegadores. Que index.jsml empezase a sustituir al omnipresente index.html sería un buen paso. Ya hay proyectos que sustituyen el CSS y al XHTML, pero un estándar que, al igual que XUL, permitiese crear formularios optimizados para aplicaciones web podría dar un empuje definitivo que arrinconase más a las aplicaciones tradicionales de escritorio...

Conclusiones

El abanico de estándares, plataformas, bases de datos, motores de interpretación y mecanismos de interoperación que se ha abierto en relativamente poco tiempo abre una cantidad de opciones inmensa que hace posible lo que hasta hace poco era impensable: que la lingua franca de la web gane terreno en el servidor con soluciones de tecnología punta vetadas hasta ahora a plataformas mucho más extendidas como PHP, Java o ASP.NET. Resulta sorprendente como tres pares de símbolos: [] arrays/listas/pilas, {} objetos y () funciones permiten componerse con tanta flexibilidad como para dar cabida a todos estos proyectos y además desafiar el statu quo.

15 diciembre, 2009

Empezando con Kohana, ¿mejor que CodeIgniter?

He traducido este tutorial de NetTuts+ creado por Cristian Gilè sobre el framework PHP Kohana, al igual que hice en su día con el de CodeIgniter.

Kohana es un framework de PHP5 que sigue la arquitectura MVC (Modelo-Vista-Controlador). Hay varias razones por las que deberías elegir Kohana, pero las principales son seguridad, ligereza y simplicidad. En este tutorial, introduciré sus características principales y, con una simple demostración, mostraré cuánto tiempo Kohana puede ahorrarte potencialmente.


Paso 1: ¿Qué es Kohana?



Kohana es un framework de PHP5 que sigue la arquitectura MVC. MVC mantiene separadas la lógica de la presentación. Esto permite  crear un código más limpio y ahorrar tiempo en la búsqueda de errores. Para los que no estén familiarizados con esta arquitectura:
  • Un modelo representa datos sobre los que la aplicación opera. Normalmente es una base de datos.
  • Una vista contiene el código de presentación como HTML, CSS y JavaScript.
  • Un controlador interpreta la entrada del usuario y redirige al modelo y/o vista.
Kohana era originalmente una escisión de CodeIgniter (CI), que es un producto open source de la empresa EllisLab. Hay muchas similitudes entre CI y Kohana, pero todo el código es nuevo o ha sido completamente reescrito. Tal y como puedes leer en la página oficial de Kohana, las características principales son:
  • Altamente seguro
  • Extremadamente ligero
  • Breve curva de aprendizaje
  • Utiliza el patrón MVC
  • 100% compatible con la codificación UTF-8
  • Arquitectura débilmente acoplada
  • Extremadamente fácil de extender

Paso 2: Descargando Kohana



Empecemos. Visita la página oficial de Kohana y haz click sobre la caja verde de la esquina derecha para descargar la última versión. Todas las bibliotecas, asistentes y vistas de Kohana están incluidos en el paquete de descarga por defecto, pero puedes seleccionar módulos extra, herramientas de terceros, y los lenguajes que desees. Para el propósito de este tutorial, el paquete por defecto será suficiente. Haz click en "Download Kohana!" para iniciar la descarga.




Paso 3: Instalando Kohana

Una vez que se ha terminado la descarga:
  1. Descomprimir el zip
  2. Renombrar la carpeta "Kohana_vx.x.x" (x.x.x=número de versión) a "kohana" y súbela a la carpeta raíz de tu servidor web.
  3. Edita el fichero de configuración global application/config/config.php de esta forma:
       $config['site_domain'] = 'localhost/kohana';
  4. Si estás usando un sistema unix (linux, MacOSX), las subcarpetas de la instalación podrían haber perdido sus permisos durante la descompresión del zip. Haz un chmod a 755 de todas ellas ejecutando find . -type d -exec chmod 755 {} \; desde la carpeta raíz de la instalación de Kohana.
  5. Asegúrate de que application/logs y application/cache no sean de sólo lectura. Haz chmod a 666 sobre ellas.
  6. Ahora, apunta el navegador a http://localhost/kohana/ . Automáticamente, el framework llamará a install.php y comprobará los requerimientos con el servidor.
Kohana se ejecutará en prácticamente cualquier entorno que cumpla la configuración mínima:
  • Servidor con soporte de Unicode
  • PHP con una versión igual o posterior a la 5.2.3
  • Un servidor HTTP. Sugiero XAMPP, que es una herramienta sencilla todo-en-uno para instalar MySQL, PHP y Perl.
  • Una base de datos (MsSQL, MySQL, MySQLi, PostgreSQL, PDOSqlite).
También hay extensiones necesarias:
  • PCRE
  • iconv
  • mcrypt
  • SPL
Si la instalación se completa con éxito, serás dirigido a esta página de prueba:


Si alguna de las pruebas falla, deberás corregirla antes de continuar.

Si todas las comprobaciones tienen éxito, dirígete a la carpeta de Kohana y elimina o renombra el archivo install.php. Recarga la página en el navegador y verás una página de bienvenida como esta:




Paso 4: Configurando Kohana

Kohana está preparado para funcionar. No se necesita realizar ninguna configuración. Este framework es genial, ¿no te parece? Vamos a ver un poco de código, sígueme.


Paso 5: El primer proyecto con Kohana

Los tutoriales de programación tradicionales empiezan con el ejemplo "hello world". Pero yo creo que una aplicación simple puede dar una idea clara de cómo funciona el framework. Vamos a construir un gestor de colecciones de CDs -- sólo como divertida demostración. Antes de empezar a escribir código es necesaria una introducción breve a la distribución de carpetas de Kohana.



Nuestra aplicación se ubicará en la carpeta application. En esta carpeta hay varias subcarpetas pero sólo necesitamos lo siguiente para nuestro proyecto:
  • config guarda los archivos de configuración codificados como simples arrays estáticos.
  • controllers guarda nuestra clase de controladores personalizada
  • models guarda nuestra clase de modelos personalizada
  • views guarda nuestro archivos personalizados escritos en HTML (o cualquier lenguaje de markup o scripts necesarios para mostrar los datos y los controles de interfície para el usuario)
El resto de subcarpetas no se necesitan para este tutorial, por lo que te invito a aprender más posteriormente sobre ellos en la página de Kohana.

La carpeta system guarda el núcleo de Kohana y herramientas como bibliotecas (library), asistentes (helper) y archivos de configuración predefinidos. En este proyecto vamos a usar algunas bibliotecas y algunos asistentes - buenas herramientas para acelerar tu trabajo.

La carpeta assets  no pertenece a Kohana por defecto. Yo la he creado para guardar archivos estáticos como CSS, JS o imágenes. Después mostraré como incluirlos en el proyecto.

La carpeta modules es el lugar donde guardar las colecciones reusables de archivos relacionados que unidos aportan una funcionalidad particular a una aplicación. El módulo de autenticación de usuarios, ofrecido por el equipo Kohana, es un ejemplo de módulo.

Ésta es una muy breve introducción al sistema de archivos de Kohana, pero es suficiente para los propósitos de este tutorial. No quiero aburrirte con más teoría.


Paso 6: La base de datos del proyecto

He seleccionado MySQL para mi proyecto, pero recuerda que Kohana soporta igualmente MsSQL, MySQLi, PostgreSQL, o PDOSqlite. Crea una base de datos llamada "cd_collection" o elige el nombre que prefieras, y ejecuta la siguiente SQL mediante phpMyAdmin o cualquier herramienta para administrar la base de datos.

CREATE TABLE `albums` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(50) collate utf8_bin NOT NULL,
  `author` varchar(50) collate utf8_bin NOT NULL,
  `genre_id` int(11) NOT NULL,
  PRIMARY KEY  (`id`),
  KEY `genre_id` (`genre_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=19 ;

INSERT INTO `albums` (`id`, `name`, `author`, `genre_id`) VALUES
(2, 'Lines, Vines And Trying Times', 'Jonas Brothers', 16),
(3, 'The E.N.D.', 'The Black Eyed Peas', 16),
(4, 'Relapse', 'Eminem', 18),
(5, 'Monuments And Melodies', 'Incubus', 1),
(6, 'Thriller', 'Michael Jackson', 16),
(7, 'Back in Black', 'AC/DC', 4),
(8, 'The Dark Side of the Moon', 'Pink Floyd', 4),
(9, 'Bat out of Hell', 'Meat Loaf', 4),
(10, 'Backstreet Boys', 'Millennium', 16),
(11, 'Rumours', 'Fleetwood Mac', 4),
(12, 'Come on Over', 'Shania Twain', 16),
(13, 'Led Zeppelin IV', 'Led Zeppelin', 4),
(14, 'Jagged Little Pill', 'Alanis Morissette', 4),
(15, 'Sgt. Pepper''s Lonely Hearts Club Band', 'The Beatles', 16),
(16, 'Falling into You', 'Cv©line Dion', 16),
(17, 'Music Box', 'Mariah Carey', 16),
(18, 'Born in the U.S.A.', 'Bruce Springsteen', 4);

CREATE TABLE `genres` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(50) collate utf8_bin NOT NULL,
  PRIMARY KEY  (`id`),
  UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=22 ;

INSERT INTO `genres` (`id`, `name`) VALUES
(1, 'Alternative Rock'),
(2, 'Blues'),
(3, 'Classical'),
(4, 'Rock'),
(5, 'Country'),
(6, 'Dance'),
(7, 'Folk'),
(8, 'Metal'),
(9, 'Hawaiian'),
(10, 'Imports'),
(11, 'Indie Music'),
(12, 'Jazz'),
(13, 'Latin'),
(14, 'New Age'),
(15, 'Opera'),
(16, 'Pop'),
(17, 'Soul'),
(18, 'Rap'),
(20, 'Soundtracks'),
(21, 'World Music');

ALTER TABLE `albums`
  ADD CONSTRAINT `genre_inter_relational_constraint` FOREIGN KEY (`genre_id`) REFERENCES `genres` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;

Como puedes ver, el SQL crea dos tablas, álbumes y géneros, y los puebla con algunos datos. La última sentencia SQL agrega una restricción para la clave ajena "genre_id".

La estructura de la base de datos es muy simple y no necesita mayor explicación:



Ahora, debes decirle a Kohana dónde está tu base de datos y cómo acceder a ella. Edita el archivo de configuración global system/config/database.php de esta forma:

$config['default'] = array
(
    'benchmark'     => TRUE,
    'persistent'    => FALSE,
    'connection'    => array
    (
        'type'     => 'mysql',    
        'user'     => 'root',    
        'pass'     => 'root',    
        'host'     => 'localhost',    
        'port'     => FALSE,        
        'socket'   => FALSE,        
        'database' => 'cd_collection'
    ),
    'character_set' => 'utf8',
    'table_prefix'  => '',
    'object'        => TRUE,
    'cache'         => FALSE,
    'escape'        => TRUE
);

Este código le dice a Kohana que se conecte a una base de datos MySQL llamada "cd_collection" en localhost con el nombre de usuario "root" y la clave "root". Debes cambiarlas de acuerdo con tu configuración de servidor de base de datos.


Paso 7: Creando el controlador

Creémos nuestro primer controlador. Recuerda estas convenciones:
  • el nombre de archivo del controlador debe estar en minúsculas, por ejemplo album.php
  • la clase controlador debe coincidir con su nombre de archivo pero iniciada con mayúscula y debe añadir _Controller, por ejemplo Album_Controller
  • Debe tener la clase Controller como padre
Igualmente, recuerda cómo Kohana estructura sus URLs y cómo puedes llamar a un método de controlador; por ejemplo http://hostname/carpeta_de_kohana/index.php/controller/function

Echa un vistazo a este simple controlador.

<?php defined('SYSPATH') OR die('No direct access allowed.');

class Album_Controller extends Controller
{
    public function __construct()    
    {
        parent::__construct();
    }
    
    public function index()
    {
          echo "My first controller";    
    }
}

PHP5 OOP (programación orientada a objetos) es un prerrequisito. Para aprender más sobre ello, puedes leer aquí.

La función constructor, llamada __construct, inicializa la clase y llama al constructor padre. La función index es la función por defecto, por lo que será usada si llamamos al controlador sin especificar una función (por ejemplo, http://localhost/index.php/kohana/album. Tras el nombre del controlador no hay ninguna función, la función de índice será llamada por defecto).

Dadas estas reglas básicas, enfoquémonos en la aplicación. El controlador de album implementa todas las acciones para la gestión de colección de álbumes. Este controlador nos permite crear un nuevo álbum, mostrar los álbumes guardados en la base de datos, actualizar un álbum o borrar un álbum.

Crea un archivo llamado album.php en application/controllers/ y pega los siguiente:

<?php defined('SYSPATH') OR die('No direct access allowed.');
    
class Album_Controller extends Controller
{
     private $album_model;
     private $genre_model;
    
     private $list_view;
     private $create_view;
     private $update_view;

    public function __construct()    
    {
        parent::__construct();
        $this->album_model   = new Album_Model;
        $this->genre_model      = new Genre_Model;
          $this->list_view       = new View('list');
          $this->update_view      = new View('update');
          $this->create_view      = new View('create');
    }

    public function index()
    {
         $this->show_albums_list();
    }
        
    private function show_albums_list()
    {
        $albums_list = $this->album_model->get_list();
        $this->list_view->set('albums_list',$albums_list);
        $this->list_view->render(TRUE);
    }
    
    public function show_create_editor()
    {
        $this->create_view->set('genres_list',$this->get_genres_list());
         $this->create_view->render(TRUE);
    }

    public function show_update_editor($id)
    {
        $album_data = $this->album_model->read($id);
        $this->update_view->set('album_id',$album_data[0]->id);
        $this->update_view->set('name',$album_data[0]->name);
        $this->update_view->set('author',$album_data[0]->author);
        $this->update_view->set('genre_id',$album_data[0]->genre_id);
        $this->update_view->set('genres_list',$this->get_genres_list());
        $this->update_view->render(TRUE);
    }

    public function create()
    {
        $album_data=array(
        'name'        => $this->input->post('name'),
        'author'      => $this->input->post('author'),
        'genre_id'  => $this->input->post('genre_id')
        );
        $this->album_model->create($album_data);
        url::redirect('album');
    }

    public function update()
    {
        $album_data = array(
            'name'        => $this->input->post('name'),
            'author'      => $this->input->post('author'),
            'genre_id'  => $this->input->post('genre_id')
        );
        $this->album_model->update($this->input->post('album_id'),$album_data);
         url::redirect('album');
    }
 
     public function delete($id)
    {
        $this->album_model->delete($id);
        url::redirect('album');
    }

    private function get_genres_list()
    {
        $db_genres_list  = $this->genre_model->get_list();
        $genres_list  = array();
        
        if(sizeof($db_genres_list) >= 1)
        {
            foreach($db_genres_list as $item)
            {
                $genres_list[$item->id] = $item->name;
            }
        }
        return $genres_list;
    }
}

Déjame explicar lo que hace este código.

Se declaran cinco variables miembro al inicio:

  private $album_model;
  private $genre_model;

  private $list_view;
  private $create_view;
  private $update_view;

Estos miembros son privados porque quiero limitar su visibilidad sólo a esta clase.

En el método construct los objetos de modelo y vista son creados usando los cinco miembros:

  $this->album_model   = new Album_Model;
  $this->genre_model      = new Genre_Model;
  $this->list_view       = new View('list');
  $this->update_view      = new View('update');
  $this->create_view      = new View('create');

Para crear un objeto modelo usa esta sintaxis:
$obj_name = new Name_Model;

Para crear un objeto vista, usa esta sintaxis:
$obj_name = new View('view_filename_without_extension');

Ahora hay dos objetos para acceder al modelo de álbum y género, y tres objetos para acceder a las vistas necesarias para generar la presentación.

El método index llama al método show_albums_list que lista todos los álbumes guardados en la base de datos.
$albums_list = $this->album_model->get_list();   
$this->list_view->set('albums_list',$albums_list);  
$this->list_view->render(TRUE);

En este método puedes ver cómo el modelo y la vista objeto son usada para acceder a métodos relativos. "get_list" es un método del modelo (lo veremos luego) que devuelve todos los álbumes guardados en la base de datos. El resultado se guarda en el array "$album_list". Para pasar el array de resultados del controlador a la vista, el método "set" del objeto vista es llamado. Este método requiere dos parámetros: una nueva variable vacía (album_list) contiene datos de una variable existente ($album_list). Ahora la nueva variable vacía "album_list" contine el array $album_list (veremos después como mostrar su contenido en la vista). El método "render", con el parámetro TRUE, es necesario para generar datos para el navegador.

El método show_create_editor muestra la interfície de usuario para insertar un nuevo álbum.
   1. $this->create_view->set('genres_list',$this->get_genres_list());
$this->create_view->render(TRUE);

La lista de géneros es pasada a la vista.

El método show_update_editor muestra la interfície de usuario para actualizar un álbum existente.

$album_data = $this->album_model->read($id);  
$this->update_view->set('album_id',$album_data[0]->id);  
$this->update_view->set('name',$album_data[0]->name);  
$this->update_view->set('author',$album_data[0]->author);  
$this->update_view->set('genre_id',$album_data[0]->genre_id);  
$this->update_view->set('genres_list',$this->get_genres_list());
$this->update_view->render(TRUE);

"read" es un método del modelo (lo veremos después) que devuelve datos ($album_data) del álbum con un identificador igual a $id. Entonces, cada elemento del álbum de datos devuelto es pasado a la vista.

El método create recibe datos para un nuevo álbum a partir de la vista y guarda los datos recibidos en la base de datos.
$album_data=array(  
'name'      => $this->input->post('name'),  
'author'    => $this->input->post('author'),  
'genre_id'  => $this->input->post('genre_id')  
);  
$this->album_model->create($album_data);  
url::redirect('album');

$album_data es un array que contiene los datos POST de la vista. Para guardar el álbum, el array se pasa al método del modelo create. La última línea es una llamada a un método asistente. Los asistentes (helpers) son funciones que te asisten en el desarrollo. Las clases asistente son automáticamente cargadas por Kohana. Los asistentes se declaran como métodos estáticos de una clase, por lo que no hay necesidad de instanciar la clase. En este caso, el método "redirect" del asistente "url" es llamado y le dice a Kohana que redirija el navegador al controlador de álbum. Esto evita una nueva inserción (por ejemplo, al pulsar F5).

Los métodos update (actualizar) y delete (borrar) funcionan de la misma forma que create.

El último método get_genres_list obtiene la lista de géneros del modelo ($db_genres_list) y construye un nuevo array ($genres_list) para el control de selección (select) en las vistas.
$db_genres_list  = $this->genre_model->get_list();   
$genres_list  = array();  
 
if(sizeof($db_genres_list) >= 1)  
{  
    foreach($db_genres_list as $item)  
    {  
        $genres_list[$item->id] = $item->name;  
    }  
}  
return $genres_list;


Paso 8: Creando el modelo para el proyecto

Creemos los modelos para nuestra aplicación web. Conviene recordar las convenciones indicadas anteriormente a la hora de crear las clases. A continuación está el código para el modelo del álbum. Crea un archivo llamado album.php en application/models/ y pega en él el siguiente código:

<?php defined('SYSPATH') OR die('No direct access allowed.');

class Album_Model extends Model
{
    private $album_table;
    private $genre_table;

     public function __construct()
        {
              parent::__construct();
         $this->album_table = 'albums';
        $this->genre_table = 'genres';
     }
 
     public function read($id)
     {
        $this->db->where('id', $id);
        $query = $this->db->get($this->album_table);
         return $query->result_array();
    }
     
    public function delete($id)
     {
        $this->db->delete($this->album_table, array('id' => $id));
     }
 
     public function update($id,$data)
     {
        $this->db->update($this->album_table, $data, array('id' => $id));
     }
     
    public function create($data)
     {
           $this->db->insert($this->album_table, $data);
     }
    
    public function get_list()
    {
         $this->db->select('albums.id as id,albums.name as name,albums.author as author, genres.name as genre');  
         $this->db->from($this->album_table);  
         $this->db->join($this->genre_table,'genres.id','albums.genre_id');
         $query = $this->db->get();
         return $query->result_array();
    }
}
Todos los métodos de los modelos usan la sintaxis del constructor de consultas (Query builder). Esta herramienta de Kohana acelera el tiempo de desarrollo con bases de datos y simplifica la creación de consultas.

Dos variables miembro son declaradas al inicio de la clase:
private $album_table;  
private $genre_table;

Estos miembros son privados porque quiero limitar la visibilidad sólo a esta clase. Son los contenedores de los nombres de las tablas de la base de datos.

La primera línea en el método del constructor carga la biblioteca de base de datos de Kohana en $this->db. En las dos siguientes líneas, se inicializan las dos variables miembro.
parent::__construct();  
$this->album_table = 'albums';  
$this->genre_table = 'genres';

La consulta en el método read recupera los registros de álbum que tengan un cierto identificador ("$id").
$this->db->where('id', $id);  
    $query = $this->db->get($this->album_table);   
        return $query->result_array();

La consulta en el método delete elimina la fila de la tabla de álbumes que tenga un cierto identificador ("$id").
$this->db->delete($this->album_table, array('id' => $id));

La consulta en el método update actualiza la fila de la tabla de álbumes que tenga un cierto identificador ("$id") con los nuevos valores del array "$data".
$this->db->update($this->album_table, $data, array('id' => $id));

El array "$data" debe contener nombres de registros como claves del array, y sus valores como valores del array. El array debe tener esta forma:
$data = array(  
    'name'          =>   'album_name',  
    'author'        =>   'author_name',  
    'genre_id'      =>   'genre_id'    
    );

La consulta en el método get_list recupera todas las filas de álbumes.
$this->db->select('albums.id as id,albums.name as name,albums.author as author, genres.name as genre');    
$this->db->from($this->album_table);    
$this->db->join($this->genre_table,'genres.id','albums.genre_id');  
$query = $this->db->get();  
return $query->result_array();

Ahora el modelo para los géneros. Crea un archivo llamado genre.php en application/models/ y pega el siguiente código en él:
<?php defined('SYSPATH') OR die('No direct access allowed.');

class Genre_Model extends Model
{
  private $genre_table;

  function __construct()
  {
    parent::__construct();
    $this->genre_table = 'genres';
  }

  function get_list()
  {
    $query = $this->db->get($this->genre_table);
    return  $query->result_array();        
  }
}

Este modelo es muy simple, por lo que no malgasteré tiempo comentándolo. Los modelos y el controlador están preparados para funcionar. Ahora trabajemos sobre las vistas.


Paso 9: Creando la vista del proyecto

Las vistas son archivos que contienen la capa de presentación de la aplicación. Su propósito es mantener esta información separada de la lógica de la aplicación para facilitar la reusabilidad y mantener el código limpio. Para este proyecto se necesitan tres vistas: una para listar la colección de álbumes, otra para crear un nuevo álbum, y otra para editar un álbum existente.

Crea un archivo llamado list.php en application/views/ y pega el siguiente código en él:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<?php
    echo html::stylesheet(array
        (
         'assets/css/style'
        ),
        array
        (
         'screen'
        ), FALSE);
?>
        <title>CD COLLECTION</title>
        </head>
        <body>
        <?php
                echo html::image('assets/images/add.png');
                echo html::anchor('album/show_create_editor', 'Add new album');
        ?>
        <table class="list" cellspacing="0">
        <tr>
            <td colspan="5" class="list_title">CD Collection</td>
        </tr>    
        <tr>
            <td class="headers">Album name</td>
            <td class="headers">Author</td>
            <td colspan='3' class="headers">Genre</td>
        
        </tr>    
        <?php
            foreach($albums_list as $item)
            {
                echo "<tr>";
                echo "<td class='item'>".$item->name."</td>";
                echo "<td class='item'>".$item->author."</td>";
                echo "<td class='item'>".$item->genre."</td>";
                echo "<td class='item'>".html::anchor('album/delete/'.$item->id,html::image('assets/images/delete.png'))."</td>";        
                echo "<td class='item'>".html::anchor('album/show_update_editor/'.$item->id,html::image('assets/images/edit.png'))."</td>";        
                echo "</tr>";
            }
        ?>
        </table>
        </body>
        </html>

Esta vista muestra una página html que contiene una lista de todos los álbumes. Esta lista ha sido creada usando un bucle foreach que imprime la información en una tabla html. Para cada fila de álbum, hay dos imágenes: una "cruz roja" y una "libro de bolsillo". Enlazan respectivamente al método delete y al método update del controlador. Ambos pasan el identificador del álbum al controlador usando una petición GET. Sobre la lista hay un botón para crear un nuevo álbum. En este código también se usa un asistente html ofrecido por Kohana que acelera las operaciones para escribir páginas html.

Creémos ahora un archivo llamado create.php en application/views/.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<?php
    echo html::stylesheet(array
    (
        'assets/css/style'
    ),
    array
    (
        'screen'
    ), FALSE);
?>
<title>CD COLLECTION</title>
</head>
<body>
<?php echo form::open('album/create'); ?>
<table class='editor'>
<tr>
    <td colspan='2' class='editor_title'>Create new album</td>
</tr>
<?php
    echo "<tr>";
    echo "<td>".form::label('name', 'Name: ')."</td>";
    echo "<td>".form::input('name', '')."</td>";
    echo "</tr>";
    
    echo "<tr>";
    echo "<td>".form::label('author', 'Author: ')."</td>";    
    echo "<td>".form::input('author', '')."</td>";    
    echo "<tr/>";
    
    echo "<tr>";
    echo "<td>".form::label('genre', 'Genre: ')."</td>";    
    echo "<td>".form::dropdown('genre_id',$genres_list)."</td>";
    echo "<tr/>";
    
    echo "<tr>";
    echo "<td colspan='2' align='left'>".form::submit('submit', 'Create album')."</td>";
    echo "</tr>";
?>
</table>
<?php echo form::close(); ?>
</body>
</html>

La última es la vista de actualización. Crea un archivo update.php en application/views/.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<?php
    echo html::stylesheet(array
    (
        'assets/css/style'
    ),
    array
    (
        'screen'
    ), FALSE);
?>
<title>CD COLLECTION</title>
</head>
<body>
<?php echo form::open('album/update'); ?>
<table class='editor'>
<tr>
    <td colspan='2' class='editor_title'>Update album</td>
</tr>
<?php
    echo "<tr>";
    echo "<td>".form::label('name', 'Name: ')."</td>";
    echo "<td>".form::input('name', $name)."</td>";
    echo "</tr>";
    
    echo "<tr>";
    echo "<td>".form::label('author', 'Author: ')."</td>";    
    echo "<td>".form::input('author', $author)."</td>";    
    echo "<tr/>";
    
    echo "<tr>";
    echo "<td>".form::label('genre', 'Genre: ')."</td>";    
    echo "<td>".form::dropdown('genre_id',$genres_list, $genre_id)."</td>";
    echo "<tr/>";
    
    echo "<tr>";
    echo "<td colspan='2' align='left'>".form::submit('submit', 'Update album')."</td>";
    echo "</tr>";
    
?>
</table>
<?php
    echo form::hidden('album_id',$album_id);
    echo form::close();
?>
</body>
</html>

El primero es un editor simple que permite al usuario insertar información sobre un nuevo álbum. Los campos como autor y nombre serán insertados usando una entrada html y el género usando un combo. Una vez que el usuario hace click en el botón create, toda la información se pasa como una petición POST al método create/update del controlador de álbum. Cuando el controlador recibe estas variables posteadas llama al modelo que inserta un nuevo álbum en la base de datos. Los formularios de ambas vistas hacen uso del asistente de formularios.

Para dar un poco de estilo a nuestra aplicación, crea la carpeta assets en la carpeta raíz de Kohana al mismo nivel de la carpeta de aplicaciones. Ábrelo y crea dos nuevas carpetas: css e images.

En la carpeta css crea un nuevo archivo llamado style.css y pega lo siguiente en él:
a {
    font-family: Verdana, Geneva, Arial, Helvetica, sans-serif ;
    font-weight: normal;
    font-size: 12px;
    color: #00F;
    vertical-align:text-top;
}

img {
    border: 0;
}

label {
    font-family: Verdana, Geneva, Arial, Helvetica, sans-serif ;
    font-weight: normal;
    font-size: 12px;
}

input {
    border: 1px solid #000;
}

select {
    width:185px;
}

table.editor
{
    text-align: center;
    font-family: Verdana, Geneva, Arial, Helvetica, sans-serif ;
    font-weight: normal;
    font-size: 11px;
    color: #fff;
    width: 280px;
    background-color: #666;
    border: 0px;
    border-collapse: collapse;
    border-spacing: 0px;
}

table.editor td.editor_title
{
    background-color: #666;
    color: #fff;
    padding: 4px;
    text-align: left;
    font-weight: bold;
    font-size: 16px;
}

table.editor td
{
    padding: 4px;
}

table.list
{
    text-align: center;
    font-family: Verdana, Geneva, Arial, Helvetica, sans-serif ;
    font-weight: normal;
    font-size: 11px;
    color: #fff;
    width: 280px;
    background-color: #666;
    border: 0px;
    border-collapse: collapse;
    border-spacing: 0px;
}

table.list td.item
{
    background-color: #CCC;
    color: #000;
    padding: 4px;
    text-align: left;
    border: 1px #fff solid;
}

table.list td.list_title,table.list td.headers
{
    background-color: #666;
    color: #fff;
    padding: 4px;
    text-align: left;
    border-bottom: 2px #fff solid;
    font-weight: bold;
}

table.list td.list_title
{
    font-size: 16px;
}

table.list td.headers
{
    font-size: 12px;
}

Ahora copia las siguientes imágenes en la carpeta images:

Eso es todo. Apunta el navegador a http://localhost/kohana/index.php/album y deberías ver algo parecido a esto:



Si intentas crear un nuevo álbum o editar uno existente, deberías ver algo similar a esto:



Paso 10: Pensamientos finales

Desde luego, algunas mejoras son necesarias para esta aplicación, pero con muy poco código has creado una pequeña aplicación web. Ahora, sabes cómo usar el patrón MVC con Kohana, y cómo usar las bibliotecas y asistentes de la base de datos. Para aprender más, lee la documentación oficial.

Gracias a Kohana, el mantenimiento de código se convierte en una tarea fácil, y añadir nuevas características es pan comido. Espero que hayas disfrutado del tutorial.

05 diciembre, 2009

No toquéis el "with"

En los últimos tiempos se ha satanizado la orden with, por parte de personas de renombre como el mismo Douglas Crockford. En su artículo With statement considered harmful explica que, a pesar de ser similar a otros lenguajes, la falta en JavaScript de que un bloque tenga un ámbito propio puede fomentar errores difíciles de detectar. Si dentro del bloque del with se asigna un valor a una variable, no hay forma de saber si se está haciendo sobre un miembro del objeto pasado con el with o si se está modificando una variable global:

var bing=1;
with(ooo.eee.ooo.eee.ooo) {
  bing=2;
}
// en este punto, que toma el valor 2? 
// bing==2 o ooo.eee.ooo.eee.ooo.bing==2
De hecho propone en sus recomendaciones para la próxima versión de ECMAScript que está orden sea declarada obsoleta. Sin embargo las cosas no son tan sencillas como parecen. De nuevo, Andrea Giammarchi le saca punta al asunto como ya hizo con la polémica del desconocido segundo parámetro de eval, acusando a los que lo quitaban del lenguaje de perder una oportunidad perfecta de generar un entorno controlado para ejecutar secuencias en las que no se confía. Y también pasó con la propuesta de eliminar callee ante la que Andrea defendió que no se hiciese. Con esta sencilla estructura se consigue mucha más potencia que con el uso tradicional pasando al with una variable con un objeto:
with({o:myreference}){
    o.doStuff();
    o.var1 = "loquesea";
    // etc etc ...
};
También se facilita construcciones commo la siguiente que resulta extremadamente compacta y, por tanto, poco susceptible de ganar espacio con una compresión de código tradicional:
with(document)
  with(documentElement)
    insertBefore(
      createElement("script"),
      firstChild
    )
    .text = "alert(1)"
;
Sólo que además permite aprovechar esa definición en línea del parámetro-objeto para realizar llamadas a funciones, usar el operador terciario ( condicion ? cierto : falso ), o especificar parámetros por defecto:
// ejemplo de argumentos con nombre y sin usar closures
with({
  obj:myObject,
  // valores por defecto en línea por si acaso
  collection:collection || [],
  callback:myCaseAnalyzer,
  // variables locales
  i:0,
  length:collection ? collection.length : 0,
  // variable local no inicializada
  tmp:null
}){
  for(;i < length; ++i){
    tmp = callback.call(obj, collection[i]);
    if(tmp){
      tmp.doStuff();
      collection[i] = tmp;
    };
  };
};
Que resulta ser equivalente a los típicos closures tan utilizados. Otro uso interesante sería la tradicional llamada Ajax:
with(this.XMLHttpRequest ?
  new XMLHttpRequest :
  new ActiveXObject("Microsoft.MSXML")
){
  open("get", "?ajax=true", true);
  onreadystatechange = function(){
    if(readyState === 4)
      // realiza acciones con responseText/XML
      alert(responseText)
    ;
  };
  send(null);
}

26 noviembre, 2009

Empezando con las extensiones de Chrome

En breve aparecerá la primera versión de Chrome/Chromium con soporte de extensiones. Es el mecanismo ofrecido por el navegador para modificar y mejorar la funcionalidad, personalizándola al gusto del usuario. En el caso de Chromium, bastará con tener conocimientos de HTML, CSS y JavaScript para poder escribirlas. Por ejemplo:

  1. Mostrar iconos en la barra de direcciones al visitar ciertas páginas:



  2. Añadir botones a la barra de herramientas (con opción a mostrar un desplegable con más acciones):



  3. Modificar la apariencia y comportamiento de las páginas web mostradas

Tutorial: Hola mundo!

Nota: traducido de aquí. Vamos a crear un nuevo botón en Chrome que al ser pulsado mostrará una página generada automáticamente. Algo así:



Para empezar, necesitarás la versión de Chrome del canal de desarrolladores, ya que aún no está disponible (al menos a fecha de hoy 26/nov/09) en los canales estable o beta. Los pasos para crear la extensión son los siguientes:
  1. Crea una carpeta llamada "hola".

  2. Crea un archivo llamado manifest.json con este contenido:

    {
      "name": "Mi Primera Extension",
      "version": "1.0",
      "description": "Esta es la primera extension.",
      "browser_action": {
        "default_icon": "icon.png"
      },
      "permissions": [
        "http://api.flickr.com/"
      ]
    }
    
    
  3. Descarga el siguiente icono a la misma carpeta.


  4. Carga la extensión:


    • Abre la página de gestión de extensiones pulsando el menú y seleccionando Extensiones...

    • Si el modo desarrollador tiene un símbolo +, púlsalo para añadir información sobre el desarrollador. El símbolo + cambia a - y aparecerán más botones e información.

    • Pulsa el botón Carga extensión... y aparecerá un diálogo de selección de archivo.

    • En el diálogo selecciona la carpeta "hola" y pulsa OK.

    Si la extensión es válida, aparecerá el icono junto a la barra de direcciones y la información sobre la extensión en la página de gestión, tal y como se ve en la siguiente imagen:




  5. Añade código a la extensión. Edita el archivo manifest.json y agrega la siguiente línea:


    ...
    "browser_action": {
      "default_icon": "icon.png",
      "popup": "popup.html"
    },
    ...
    


  6. Dentro de la carpeta "hola", crea un archivo de texto llamado popup.html y añade el siguiente código: Sigue este enlace para ver el código

  7. Vuelve a la página de gestión de las extensiones y recárgala para ver la nueva versión de la extensión.

  8. Pulsa el icono de la nueva extensión que aparece en la barra de herramientas. Debería aparecer una ventana emergente con el contenido de popup.html:

Si no ves la ventana, sigue las instrucciones de nuevo prestando atención. No intentes cargar un HTML que no esté dentro de la carpeta de la extensión porque no funcionará.

Esta ha sido una introducción básica al sistema de extensiones de Chrome/Chromium. Puedes continuar leyendo sobre las opciones disponibles y sobre cómo realizar tareas más concretas en la Developer's Guide. Intentaré publicar un tutorial más completo enfocando más en el código JavaScript y la API ofrecida para acceder a las opciones del navegador.

21 noviembre, 2009

Persevere, Node.JS y HelmaNG

Se trata de tres servidores de aplicaciones basados en JavaScript. Cada uno de ellos tiene sus propias características que lo hacen apto para un tipo de solución particular:

Persevere 1.0


  • Proyecto perteneciente a la Dojo Foundation, autores del framework cliente de JavaScript
  • Usa una interfície basada en estándares: HTTP/REST, JSON-RPC, JSONPath, y canales REST
  • Está basado en el motor Rhino (Java) al igual que Helma
  • Ofrece almacenamiento persistente de datos JSON dinámicos (objetos, arrays y funciones)
  • Operaciones de creación, lectura, actualización y borrado a través de una interfície web HTTP/REST con JSON como formato
  • Ejecución remota de funciones JavaScript en el servidor usando JSON-RPC
  • Capacidad de consultas indexadas flexible y rápida mediante JSONQuery/JSONPath
  • Capacidades de monitorización basadas en Comet a través de canales HTTP con transporte Bayeux
  • Seguridad a nivel de objeto basada en reglas de datos y con gestión de usuarios
  • Soporte de Json Referencing y JSON Schema (Validación e integridad de datos)
  • Jerarquía de datos basada en clases para facilitar consultas y herencia
  • Arquitectura de fuentes de datos conectables: servicios web remotos, tablas SQL y archivos XML pueden ser usados como almacenes de datos
  • Versiones de objetos con histórico transaccional de estados de los registros

En definitiva, la idea de Persevere es ofrecer la mayor cantidad de opciones en el lado del servidor para almacenar los datos y ofrecerlos óptimamente a los clientes web, de forma que se pueda desplazar el peso de la aplicación web al cliente manteniendo en el servidor la seguridad y la persistencia de los datos. Ver documentación.

La siguiente versión estará basada en Pintura, que es una nueva abstracción del núcleo JavaScript de Persevere, de forma que respeta los estándares de JavaScript en el servidor (CommonJS y JSGI) para permitir el soporte de motores compatibles (V8, JSCore, y Spidermonkey).


Node.JS


Node.js es un framework de entrada/salida basado en eventos y construido sobre el motor de JavaScript de Google V8. Facilita la creación de aplicaciones escalables y altamente eficientes sin necesidad de saber sobre hilos, procesos, semáforos, depuración concurrente, ... gracias a que está basado en el mismo modelo de programación basada en eventos que Twisted de Python o EventMachine de Ruby.
La idea principal detrás de Node.js es la simplicidad. Aprovechando las funciones típicas callback de JavaScript (lanzadas cuando ocurre un evento) con las que cualquier programador está familiarizado, se ha implementado una tecnología revolucionaria basada en el potente motor V8. Las llamadas a la API son, generalmente, asíncronas, por lo que nunca bloquean la ejecución. Resulta fácil para principiantes generar aplicaciones complejas sin preocuparse de las complejidades típicas de aplicaciones concurrentes.

Por ejemplo, para implementar un servidor HTTP totalmente funcional sobre el puerto 8000 que responde a las peticiones con un documento "Hello World", harían falta ocho líneas de código perfectamente comprensible:

var sys = require("sys"),
   http = require("http");
http.createServer(function (request, response) {
  response.sendHeader(200, {"Content-Type": "text/plain"});
  response.sendBody("Hello World\n");
  response.finish();
}).listen(8000);
sys.puts("Server running at http://127.0.0.1:8000/");

En la llamada a la función http.createServer se pasa como parámetro la función callback que será llamada cuando se reciba una petición de conexión. Más simple imposible. De hecho Simon Willison ha cambiado en el último momento el asunto de su charla en Full Frontal '09 para hablar de los resultados que está obteniendo con esta revolucionaria herramienta.



HelmaNG


Es la última versión del más veterano de los servidores de aplicación basados en JavaScript. NG quiere decir Next Generation y es una versión de Helma empezada de cero, con un código Java mucho más pequeño y ligero (principalmente el código en tiempo de ejecución, la consola y el cargador de módulos y recursos). Sigue el estándar de interoperabilidad CommonJS.

Consta de un entorno de ejecución JavaScript basado en Rhino, un sistema de carga de módulos compatible con CommonJS, una consola interactiva, y una biblioteca implementada JavaScript que cubre funcionalidades básicas como extensiones a objetos predefinidos, entrada/salida de archivos, persistencia, tests unitarios, soporte HTTP de cliente y servidor y una framework web construida sobre los elementos anteriores y con código JavaScript 100%.
Helma sigue una filosofía de programación un tanto peculiar basada en objetos y similar al paradigma MVC, pero que puede resultar extremadamente eficaz una vez pasado el proceso de aprendizaje inicial. Su estructura modular permite mantener la aplicación dividida desde el principio según el modelo de objetos y las acciones que se permiten sobre éstos, lo que facilita mucho su crecimiento posterior. El equipo de programadores es principalmente centro-europeo, donde cuenta con una importante base de usuarios.

13 noviembre, 2009

spdy://

Los responsables del navegador Chromium proponen la adopción de un nuevo protocolo llamado spdy (por SPeeDY, veloz) para sustituir al omnipresente HTTP y optimizado para una carga rápida y óptima de contenidos. En sus experimentos han logrado mejoras de hasta un 64% en el tiempo de carga. HTTP es un protocolo a nivel de aplicación que funciona sobre el protocolo a nivel de red TCP. Con 10 años de historia, mejorar la latencia no era precisamente una de las preocupaciones de sus diseñadores. Entre sus limitaciones:

  • Una sola petición por conexión, suplido en parte por el HTTP pipelining o conexiones múltiples en el navegador
  • Peticiones iniciadas exclusivamente por el cliente, aunque el servidor tenga información que deba ser recibida por el cliente, no puede avisar a éste
  • Las cabeceras de petición y respuesta no se comprimen a pesar de alcanzar los 2KB fácilmente por las cookies, lo que afecta sobre todo a las líneas lentas
  • Cabeceras redundantes se repiten en cada petición debido a que para el servidor el cliente siempre es "nuevo"
  • La compresión de datos es opcional y no obligatoria
Otros enfoques han intentado resolver esas carencias, por ejemplo, intentando optimizar el protocolo TCP, pese a los problemas que podría generar en la infraestructura existente. spdy, por su parte, persigue los siguientes objetivos:
  • Reducir el 50% en los tiempos de carga
  • Aprovechar el protocolo TCP existente para evitar requerir una infraestructura de red distinta a la actual
  • Evitar modificaciones en el contenido existente (HTML)
  • Conseguir varias peticiones concurrentes sobre una misma sesión TCP
  • Comprimir las cabeceras y reducir campos innecesarios
  • Simplificar el protocolo para facilitar su implementación y a la vez optimizarlo
  • Mantener SSL como protocolo subyacente para aumentar la seguridad a pesar del incremento en latencia
  • Permitir que el servidor envíe información al cliente cuando lo desee

Características

spdy permite crear una conexión TCP con el servidor sobre la que una serie de flujos concurrentes y entrelazados permiten una comunicación bidireccional (desde el cliente o desde el servidor).
  • Flujos multiplexados sobre una sola conexión TCP. Al no necesitar múltiples conexiones TCP, con lo que ello implica, y al estar mejor aprovechada ésta con mayor densidad de información, la eficiencia es mucho mayor.
  • La priorización de peticiones permite que el cliente identifique la información más importante y evitar que contenido de baja prioridad bloquee al resto.
  • Compresión de las cabeceras HTTP
  • El servidor puede enviar información al cliente mediante una cabecera X-Associated-Content que informa al cliente de nueva información disponible antes de que el éste la solicite.
  • El servidor usa la cabecera X-Subresources para sugerir al cliente que solicite ciertos recursos aunque no se los envíe directamente al cliente.
Más información en el borrador de la especificación, en este artículo de Alex Russell (Dojo Toolkit) y en el artículo introductorio. En él se explica la implementación de un servidor y un cliente Chrome modificado para realizar las pruebas del protocolo con los resultados indicados anteriormente, y que son expuestos con detalle.

10 noviembre, 2009

NodeJS



“Evented I/O for V8 JavaScript”— es un entorno JavaScript construido sobre el rapidísimo motor V8 que provee funcionalidad de entrada y salida basada en eventos para construir servidores TCP y HTTP altamente concurrentes. El diseño de la API es soberbio — todo se consigue usando eventos y llamadas callback de JavaScript (incluso el acceso típico a archivos) y la pequeña librería estándar ya contiene soporte completo de HTTP y DNS.

En general es muy similar a Twisted (Python) y otras por el estilo, pero la sintaxis de funciones anónimas de JavaScript parece más natural que su equivalente en Python.

El protocolario "Hola mundo":

var sys = require('sys'), 
   http = require('http');
http.createServer(function (req, res) {
  setTimeout(function () {
    res.sendHeader(200, {'Content-Type': 'text/plain'});
    res.sendBody('Hello World');
    res.finish();
  }, 2000);
}).listen(8000);
sys.puts('Server running at http://127.0.0.1:8000/');

O un servidor que escucha en el puerto 7000 y devuelve lo mismo que recibe (echo):

var tcp = require('tcp');
var server = tcp.createServer(function (socket) {
  socket.setEncoding("utf8");
  socket.addListener("connect", function () {
    socket.send("hello\r\n");
  });
  socket.addListener("receive", function (data) {
    socket.send(data);
  });
  socket.addListener("eof", function () {
    socket.send("goodbye\r\n");
    socket.close();
  });
});
server.listen(7000, "localhost");

El objetivo de Node es proporcionar una manera fácil de crear programas escalables para redes. En el ejemplo anterior, los dos segundos de retraso no impiden que el servidor gestione solicitudes nuevas. Node indica al sistema operativo (a través de epoll, kqueue, /dev/poll, o select) que debe ser notificado cuando los 2 segundos de arriba pasen o si se hace una nueva conexión, y entonces entra en suspenso. Si alguien nuevo se conecta, entonces se ejecuta la función callback, si el tiempo de espera se termina, se ejecuta la llamada callback interna. Cada conexión consume sólo una pequeña parte del heap.

Esto contrasta con el modelo de concurrencia más común hoy en día en el que se usan hilos del sistema operativo. La gestión de redes basada en hilos es relativamente ineficiente y muy difícil de usar. Node mostrará una mejor eficiencia del uso de la memoria bajo altas cargas que los sistemas que asignan pilas de hilos de 2mb para cada conexión. Además, los usuarios de node no deben preocuparse de bloquear el proceso - no hay locks. Casi ninguna función en node usa directamente la entrada/salida, por lo que el proceso nunca se bloquea. Debido a que nada bloquea, los programadores menos expertos pueden desarrollar sistemas rápidos.

Node es similar en diseño a sistemas como la Event Machine de Ruby o Twisted de Python, y está influido por ellos. Node lleva el modelo de eventos un poco más lejos - presenta el bucle de eventos como una construcción del lenguaje en lugar de como una biblioteca. En otros sistemas, siempre hay una llamada bloqueante para iniciar el bucle de eventos. Normalmente se define el comportamiento a través de llamadas callback al comienzo de un script, y al final se inicia un servidor a través de una llamada bloqueante como EventMachine::run(). En node no existe esa llamada bucle-de-inicio-del-evento. node simplemente entra en el bucle de eventos después de ejecutar el script de entrada. node sale del bucle de eventos cuando no hay más llamadas callback a realizar. Este comportamiento es como el del Javascript del navegador — el bucle de eventos está oculto para el usuario.

HTTP es un protocolo de primera clase en node. La Biblioteca HTTP de node ha crecido a partir de las experiencias del autor desarrollando y trabajando con servidores web. Por ejemplo, enviar datos por streaming a través de la mayoría de frameworks web es imposible. node intenta corregir estos problemas con su parser HTTP y su API. La infraestructura puramente basada en eventos de node constituye una buena base para crear bibliotecas web o frameworks.

Pero ¿qué pasa con la concurrencia de múltiples procesadores? Los procesos son necesarios para escalar a ordenadores multi-núcleo, sin hilos con memoria compartida. Los fundamentos de los sistemas escalables son conexiones de red rápidas y diseño no bloqueante - el resto es intercambio de mensajes. En futuras versiones, node podrá generar nuevos procesos (usando la API de Web Workers), pero eso es algo que ya encaja muy bien en el diseño actual.

Via Simon Willison.

09 noviembre, 2009

Herramientas de Google Closure

He traducido el artículo de presentación y otros de la documentación sobre las nuevas herramientas presentadas por Google hace unos días:

Millones de usuarios de Google en todo el mundo usan aplicaciones JavaScript intensivas, como Gmail, Google Docs y Google Maps. Al igual que los desarrolladores de todo el mundo, los Googlers (empleados de Google) queremos grandes aplicaciones web que sean más fáciles de crear, por lo que hemos construido unas cuantas herramientas para ayudarnos a desarrollar estas aplicaciones (y muchas otras). Estamos contentos de anunciar la publicación en código abierto de estas herramientas, y estamos orgullosos de ponerlos a disposición de la comunidad de desarrollo web.

El compilador de Closure


El compilador de Closure es un optimizador de JavaScript que compila aplicaciones web en código JavaScript compacto y de alto rendimiento. El compilador elimina el código muerto, a continuación, reescribe y minimiza lo que queda para que se ejecute rápido en los motores de JavaScript de los navegadores. El compilador también comprueba la sintaxis, las referencias a variables, y los tipos, y advierte sobre otros problemas comunes de JavaScript. Estos controles y optimizaciones ayudan a escribir aplicaciones más depuradas y fáciles de mantener. Puedes utilizar el compilador con el inspector de Closure, una extensión de Firebug que hace que la depuración del código ofuscado casi tan fácil como la depuración de código legible por humanos.

Los beneficios de usar el compilador son:
  • Eficiencia. El compilador reduce el tamaño de los archivos JavaScript y al mismo tiempo los hace más eficientes, ayudando a que las aplicaciones carguen más rápido y necesiten menos ancho de banda.
  • Comprobación de código. El compilador genera avisos sobre JavaScript ilega y sobre operaciones potencialmente peligrosas, ayudándote a producir JavaScript más depurado y fácil de mantener.

Debido a que los desarrolladores de JavaScript son un grupo diverso, hemos creado una serie de formas distintas de ejecutar el compilador de Clausure. Hemos publicado a fuente abierta una herramienta de línea de comandos. Hemos creado una aplicación web que acepta código para compilación a través de un cuadro de texto o de una API REST. También estamos ofreciendo una extensión de Firefox que puede utilizar con Page Speed para ver convenientemente los beneficios en rendimiento de las páginas web.

Para probar el compilador:
  1. Descarga el paquete del compilador de Closure.

    Crea un directorio llamado closure-compiler.

    Descarga el compilador del archivo compiler.jar y guárdalo en el directorio closure-compiler.

  2. Crea un archivo JavaScript

    Crea un archivo llamado hello.js que contenga el siguiente código:


    // Una función sencilla.
    function hello(longName) {
      alert('Hola, ' + longName);
    }
    hello('Nuevo usuario');

    Guárdalo en el directorio closure-compiler.


  3. Compila el archivo JavaScript.

    Ejecuta el siguiente comando desde el directorio closure-compiler:

    java -jar compiler.jar --js hello.js --js_output_file hello-compiled.js

    Este comando crea un nuevo archivo llamado hello-compiled.js, que contiene el siguiente JavaScript:


    function hello(a){alert("Hola, "+a)}hello("Nuevo usuario");
    

    El compilador ha eliminado los comentarios, el espacio en blanco y un punto y coma innecesario. El compilador ha reemplazado el nombre de parámetro longName por el más corto a. Esto genera un archivo mucho más pequeño que el original.

    Para confirmar que el código JavaScript compilado aún funciona, incluye hello-compiled.js en un archivo HTML como este:


    <html>
      <head>
        <script src="closure-library-read-only/closure/goog/base.js"></script>
        <script src="hello.js"></script>
      </head>
      <body onload="sayHi()">
      </body>
    </html>
    

    Ahora carga el HTML en el navegador y deberías ver una bienvenida amigable. Para aprender más visita este documento sobre compilación avanzada y con archivos externos.


El inspector de Closure


El compilador de Closure modifica el código JavaScript original y genera código más pequeño y más eficiente, pero más difícil de leer y depurar. El inspector ayuda ofreciendo un mapeo del código fuente que identifica las líneas del código original y el compilado. De esa forma es posible ver el código original en tu editor preferido indicado en la variable de entorno $EDITOR.

Sigue estos pasos para habilitar el mapeo de código fuente al usar el inspector:
  1. Instala Firebug y el inspector.
     
  2. Compila tu código JavaScript con el compilador de Closure y añade el parámetro --create_source_map. Por ejemplo, si tu archivo JavaScript se llama example.js, ejecuta este comando desde el directorio closure-compiler para crear un mapa de código fuente en el fichero example-map:


    $ java -jar compiler.jar --js example.js --create_source_map ./example-map --js_output_file example-compiled.js


  3. Para depurar tu código en Firebug, abre una página web en Firefox que incluya example.js. Abre una ventana de Firebug y carga el archivo JavaScript compilado example-compiled.js en el panel izquierdo. Selecciona la pestaña Source Mapping del panel derecho y especifica la localización del archivo de mapa de código fuente example-map introduciendo la URL o haciendo clic en Open Local File y seleccionándolo.


    Screenshot of Closure Inspector source map dialog.


  4. Tras cargar el mapa de código fuente, puedes encontrar el código original de cualquier código en los fuentes compilados. Para hacer esto, pulsa el botón derecho del ratón sobre cualquier código compilado y selecciona Show Original Source, tal y como se muestra a continuación:


    Screenshot of Closure Inspector: show original source.

    Si has indicado la variable de entorno $EDITOR al editor de tu elección, el archivo original se abre en ese editor en la línea que corresponde a la que has hecho el click. Si no lo has indicado, verás el nombre de fichero y el número de línea mostrados en un pop-up, así:


    Screenshot of Closure Inspector pop-up with original source file 
information.

Traza mejorada de pila

Firebug incluye una representación de la traza de pila que puedes ver pulsando  Stack en la parte derecha de la ventana.
El inspector de Closure mejora la representación de la traza de pila mediante:
  • Mostrando los nombres de función originales si se ha cargado el mapa del código fuente.
  • Mostrando el archivo que contiene cada función.
  • Facilitando un botón  Copy Stack de forma que se pueda copiar el texto de la traza de pila.
A continuación hay un ejemplo de traza mejorada de pila del inspector Closure:

Screenshot of Closure Inspector's enchanced stack trace.

Integración de Tests Unitarios

El inspector de Closure soporta la integración con el Framework de test de Closure. Para habilitar esta característica, pulsa la flecha en el menú Script de Firebug y habilita Handle JSUnit Failures. Si una aserción falla en el Framework de testeo, el Inspector de Closure establece un punto de ruptura sobre la aserción y pausa la ejecución.

Aquí hay un ejemplo de punto de ruptura sobre una aserción:

Screenshot of Closure Inspector assertion breakpoint.

La Biblioteca de Closure


La biblioteca de Closure es una amplia colección de código JavaScript modular, bien probada y multi-navegador. Los desarrolladores Web pueden coger solamente lo que necesiten de un amplio conjunto de widgets reutilizables y controles de interfície de usuario, así como de utilidades de bajo nivel para el DOM, comunicación con el servidor, animación, estructuras de datos, pruebas unitarias, edición de texto rico, y mucho, mucho más. (De verdad. Echa un vistazo a la documentación.)

JavaScript carece de una biblioteca de clases estándar como STL o JDK. En Google, la biblioteca de Closure hace el papel de "nuestra biblioteca estándar de JavaScript" para la creación de grandes aplicaciones web complejas. Es intencionadamente agnóstica en cuanto a servidor y destinada para ser usada con el compilador de Closure. Puedes hacer un proyecto grande y complejo (con espacios de nombres y comprobación de tipos), y aún así, pequeño y rápido a través del cable (con compilación). La Biblioteca de Closure proporciona utilidades para tareas comunes de manera que pases el tiempo escribiendo la aplicación en lugar de escribir utilidades y abstracciones de navegador.

Si estás desarrollando una aplicación grande o creciente, podrías beneficiarte de la amplitud de la biblioteca. Una biblioteca bien probada puede aislarte de los problemas de compatibilidad entre navegadores y ahorrar tiempo de programación en el lado del cliente, permitiendo que te dediques a la parte divertida.


Empezando con la biblioteca de Closure

Este ejercicio de "Hola Mundo" te introduce en el proceso de usar la biblioteca en una página web. Para hacer este ejercicio necesitas estar familiarizado con JavaScript, y con un cliente de Subversion. Podrías tener ya uno. Lo sabrás intentando ejecutar el comando del paso 1.

Hola Closure

Para empezar con la librería Closure, utiliza funciones JavaScript de Closure en una página web simple siguiendo estos pasos:

Paso 1: Descargar la biblioteca

Descarga la biblioteca de Closure desde el repositorio de Subversion ejecutando siguiente comando desde consola:

svn checkout http://closure-library.googlecode.com/svn/trunk/ closure-library-read-only

Podrías necesitar un cliente de Subversion para ejecutar el comando, aunque podrías tener ya uno. Prueba el comando, y si no funciona, descarga e instala un  cliente de Subversion.
Tras ejecutar el comando deberías tener un directorio llamado closure-library-read-only que contenga el código de la librería.

Paso 2: Crear un archivo JavaScript que use la biblioteca de Closure

Guarda el siguiente JavaScript en un archivo llamado hello.js. Pon este archivo junto al directorio  closure-library-read-only.

goog.require('goog.dom');

function sayHi() {
  var newDiv = goog.dom.createDom('h1', {'style': 'background-color:#EEE'},
    'Hola Mundo!');
  goog.dom.appendChild(document.body, newDiv);
}

Paso 3: Crea un archivo HTML

Guarda el siguiente HTML en un archivo llamado hello.html. Pone este archivo junto al directorio closure-library-read-only y el archivo hello.js.

<html>
  <head>
    <script src="></script>
    <script src="hello.js"></script>
  </head>
  <body onload="sayHi()">
  </body>
</html>

Paso 4: Dí Hola a la biblioteca

Abre el archivo HTML en el navegador. Deberías de ver las palabras "Hello world!":



¿Cómo funciona el ejemplo?

El JavaScript de hello.js usa dos funciones que no define: goog.dom.createDom() y goog.dom.appendChild(). ¿De dónde vienen esas funciones?

Esas funciones están definidas en la biblioteca de Closure que descargaste en el paso 1, dentro del archivo  closure-library-read-only/dom/dom.js.
Para hacer uso de esas funciones, el ejemplo hace dos cosas:
  • Incluye la orden goog.require('goog.dom') al principio del JavaScript del paso 2.
  • Incluye el archivo de arranque de la biblioteca  base.js en el HTML del paso 3.
El archivo base.js define la función  goog.require(). La llamada de función goog.require('goog.dom') carga el archivo JavaScript que define las funciones del espacio de nombres goog.dom, junto con otros archivos de la biblioteca de Closure que esas funciones necesitan.
La biblioteca de Closure carga esos archivos agregando dinámicamente una etiqueta script al documento por cada archivo necesitado de la biblioteca. Así, por ejemplo, la orden  goog.require('goog.dom') hace que la siguiente etiqueta sea agregada al document, donde path-to-closure es el camino desde el archivo HTML hasta el directorio que contiene base.js:
Normalmente una sola orden goog.require() cargará sólo una fracción de la base de código de la biblioteca.
Incluir base.js no es la única forma de incluir el código de la librería, pero es la forma más fácil de empezar. En cualquier caso, no importa como cargues el código de la librería, siempre usarás goog.require() para declarar las partes de la librería que necesitas.

Construir una aplicación con la biblioteca de Closure

Este otro tutorial te introduce en la experiencia de usar la biblioteca dando un paseo por la construcción de una aplicación simple.
Usa estos enlaces para descargar los dos archivos de código fuente usados en el tutorial:
Este tutorial explica las diferentes partes de estos archivos paso a paso. Fíjate que el archivo notepad.html no funcionará hasta que no descargues y enlaces tu copia de la biblioteca, tal y como se describe a continuación.

La aplicación de Notas

Este tutorial ilustra el proceso de construir una simple aplicación para mostrar notas. El ejemplo:
  • crea un espacio de nombres para la aplicación,
  • usa la función de la biblioteca goog.dom.createDom() para crear la estructura de Document Object Model (DOM) para la lista, y
  • usa una clase de la biblioteca en la lista de notas para permitir al usuario abrir y cerrar elementos de la lista.

Creando un espacio de nombres con goog.provide()

Cuando usas bibliotecas JavaScript de fuentes distintas, siempre existe la posibilidad de que algún archivo Javascript redefina una variable global o un nombre de función que ya uses en tu código. Para minimizar el riesgo de este tipo de colisión de nombres, usa la función goog.provide() de la librería para crear un espacio de nombres para tu código.


Por ejemplo, la aplicación de Notas usa objetos Note creados por una función constructor Note(). Si cualquier otro archivo JavaScript define una función global o una variable Note, sobreescribiría al constructor. Por tanto, el ejemplo crea un espacio de nombres para este constructor con la siguiente llamada a goog.provide:



goog.provide('tutorial.notepad.Note');


La función goog.provide() se asegura de la existencia de la estructura de objeto JavaScript indicada por su argumento. Comprueba si existe cada propiedad de objeto en la ruta de la expresión y si no existe la inicializa. La función anterior es equivalente a:

tutorial = tutorial || {};
tutorial.notepad = tutorial.notepad || {};
tutorial.notepad.Note = tutorial.notepad.Note || {};


Puesto que goog.provide() sólo inicializa propiedades si no existen previamente, nunca sobreescribirá una propiedad.
Fíjate que las órdenes goog.provide() tiene la ventaja añadida de que el script de resolución de dependencias calcdeps.py puede usarlas. Echa un vistazo a Usar el script de cálculo de dependencias para aprender cómo usar calcdeps.py

Una vez que la estructura de objeto tutorial.notepad.Note exista, el ejemplo asigna la función constructor a la propiedad Note.


tutorial.notepad.Note = function(title, content, noteContainer) {
  this.title = title;
  this.content = content;
  this.parent = noteContainer;
};

El constructor Note está ahora en el espacio de nombre tutorial.notepad creado con goog.provide().


Creando una estructura DOM con goog.dom.createDom()

Para mostrar una Note en el documento HTML, el ejemplo facilita a la clase Note el siguiente método:



tutorial.notepad.Note.prototype.makeNoteDom = function() {
  // Crea la estructura DOM que representa la nota.
  this.headerElement = goog.dom.createDom('div', 
      {'style': 'background-color:#EEE'}, this.title);
  this.contentElement = goog.dom.createDom('div', null, this.content);
  var newNote = goog.dom.createDom('div', null,
      this.headerElement, this.contentElement);

  // Agrega la estructura DOM de la nota al documento.
  goog.dom.appendChild(this.parent, newNote);
};


Este método usa la función de la biblioteca goog.dom.createDom(). La siguiente orden goog.require() incluye el código para esta función:



goog.require('goog.dom');


Para incluir una función como goog.dom.createDom() que no sea un constructor, pasa el espacio de nombres que contenga la función a goog.require (en este caso solamente goog.dom). No necesitas incluir el nombre de la función en la orden goog.require() a menos que requieras de una clase. Usando una clase de la biblioteca ilustra una órden goog.require() para este caso.


La función goog.dom.createDom() crea un nuevo elemento DOM. Por ejemplo, la siguiente órden de makeNoteDom() crea un nuevo elemento  div.



this.headerElement = goog.dom.createDom('div', 
      {'style': 'background-color:#EEE'}, this.title);


El segundo argumento en esta llamada a createDom() especifica los atributos a agregar al elemento, y el tercer argumento especifica un hijo que añadir al elemento (una cadena en este caso). Ambos, el segundo y el tercer parámetro, son opcionales. Mira la referencia de la API para más información sobre createDom().


El método makeNoteDom() simplemente crea un argumento de una sola Note. Para crear una lista de notas, el ejemplo incluye una función makeNotes() que toma un array de  de datos de notas e instancia un objeto Note por cada uno, llamando al método makeNoteDom() de Note.



tutorial.notepad.makeNotes = function(data, noteContainer) {
  var notes = [];
  for (var i = 0; i < data.length; i++) {
    var note = 
      new tutorial.notepad.Note(data[i].title, data[i].content, noteContainer);
    notes.push(note);
    note.makeNoteDom();
  }
  return notes;
};

Usando una clase de la biblioteca

Con sólo dos líneas de código, el ejemplo hace de cada nota un Zippy. Un Zippy es un elemento que puede ser plegado y desplegado para ocultar o mostrar contenido. Primero el ejemplo agrega una nueva orden require() para la clase Zippy:
goog.require('goog.ui.Zippy');
Entonces añade una línea al final del método  makeNoteDom :
tutorial.notepad.Note.prototype.makeNoteDom = function() {
  // Crea la estructura DOM para representar la nota.
  this.headerElement = goog.dom.createDom('div', 
      {'style': 'background-color:#EEE'}, this.title);
  this.contentElement = goog.dom.createDom('div', null, this.content);
  var newNote = goog.dom.createDom('div', null,
      this.headerElement, this.contentElement);

  // Agrega la estructura DOM de la nota al documento.
  goog.dom.appendChild(this.parent, newNote);

  // NUEVA LINEA:
  return new goog.ui.Zippy(this.headerElement, this.contentElement);
};
La llamada al constructor new goog.ui.Zippy(this.headerElement, this.contentElement) asigna un comportamiento al elemento nota que conmutará la visibilidad de  contentElement cuando el usuario haga clic sobre  headerElement. Para más información sobre la clase Zippy, echa un vistazo a la documentación de la API de Zippy.

Usando el Notepad en un documento HTML

A continuación se puede ver el código JavaScript completo para esta aplicación de ejemplo:
goog.provide('tutorial.notepad');
goog.provide('tutorial.notepad.Note');

goog.require('goog.dom');
goog.require('goog.ui.Zippy');

/**
 * Itera sobre una lista de objetos de datos de nota, crea una instancia de Nota
 * para cada uno, y le dice a la instancia que construya su estructura DOM.
 */
tutorial.notepad.makeNotes = function(data, noteContainer) {
  var notes = [];
  for (var i = 0; i < data.length; i++) {
    var note = 
      new tutorial.notepad.Note(data[i].title, data[i].content, noteContainer);
    notes.push(note);
    note.makeNoteDom();
  }
  return notes;
};

/**
 * Gestiona los datos e interfície de un solo nodo
 */
tutorial.notepad.Note = function(title, content, noteContainer) {
  this.title = title;
  this.content = content;
  this.parent = noteContainer;
};

/**
 * Crea la estructura DOM para la nota y la agrega al documento.
 */
tutorial.notepad.Note.prototype.makeNoteDom = function() {
  // Create DOM structure to represent the note.
  this.headerElement = goog.dom.createDom('div',
      {'style': 'background-color:#EEE'}, this.title);
  this.contentElement = goog.dom.createDom('div', null, this.content);
  var newNote = goog.dom.createDom('div', null,
      this.headerElement, this.contentElement);

  // Agrega la estructura DOM de la nota al documento
  goog.dom.appendChild(this.parent, newNote);
  return new goog.ui.Zippy(this.headerElement, this.contentElement);
};
Puedes tener este código descargando el archivo notepad.js. El siguiente HTML usa este código de bloc de notas para mostrar una lista de notas en una página web:
<html>
<head>
<title>Notepad</title>
<script src="closure-library/base.js"></script>
<script src="notepad.js"></script>
</head>
<body>

<div id="notes">
</div>

<script>
function main() {
  var noteData = [
    {'title': 'Note 1', 'content': 'Content of Note 1'},
    {'title': 'Note 2', 'content': 'Content of Note 2'}];

  var noteListElement = document.getElementById('notes');
  var notes = tutorial.notepad.makeNotes(noteData, noteListElement);
}
main();
</script>
</body>
</html>
Puedes tener esta página descargando el archivo notepad.html, Esta página:
  • incluye una etiqueta script para el archivo base.js de la biblioteca. El atributo src de esta etiqueta es el camino desde el archivo HTML hasta el archivo  base.js de la biblioteca.
  • incluye una etiqueta  script para el archivo que contiene el código para el bloc de notas.
  • incluye un script en la página que inicializa la lista de notas con una llamada a makeNotes(). La función makeNotes() toma dos argumentos: un array de Objetos, cada uno conteniendo los datos de una nota, y el elemento DOM bajo el que construir la estructura DOM de la lista de notas

Plantillas de Closure

Las plantillas de Closure surgieron de un deseo de tener plantillas web que estén precompiladas para ser ejecutadas de forma eficiente por JavaScript. Las plantillas de Closure tienen una sintaxis simple y natural para programadores. A diferencia de los tradicionales sistemas de plantillas, puedes pensar en las plantillas de Closure como pequeños componentes que se unen para formar una interfície de usuario, en lugar de tener que crear una gran plantilla por cada página. Las plantillas de Closure se aplican para JavaScript y para Java, así que puedes usar las mismas plantillas en el servidor y en el cliente. El compilador, la biblioteca, las plantillas y el inspector de Closure se iniciaron como proyectos de 20% del tiempo de Googlers y cientos de ellos han contribuido con miles de parches. Hoy en día, cada herramienta de Closure se ha convertido en una parte clave de la infraestructura de JavaScript detrás de las aplicaciones web en Google. Por eso estamos particularmente entusiasmados de abrir su código para fomentar y apoyar el desarrollo web fuera de Google. Queremos saber lo que piensas, pero más importante aún, queremos ver que podéis hacer con ellas. Así que bájatelo y diviértete!



Addendum: Parece que Andrea Giammarchi no está muy de acuerdo con las ventajas ofrecidas por el nuevo proyecto de Google. Ha encontrado fácilmente un ejemplo en el que el compilador hace que el código deje de funcionar (aunque hay que decir que hace mal uso de las variables globales que un buen estilo de programación debería evitar). Por otro lado analiza el código de la librería y encuentra distintos problemas de rendimiento que una librería orientada a la velocidad no debería tener. Quizás tenga razón y todo se deba a que detrás de esas herramientas hay programadores expertos en Java y no en JavaScript...

Últimos links en indiza.com