<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" gd:etag="W/&quot;CEcEQXkyeCp7ImA9WxBUFkQ.&quot;"><id>tag:blogger.com,1999:blog-112773462326884407</id><updated>2010-03-04T09:40:00.790+01:00</updated><title>digitta</title><subtitle type="html" /><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://digitta.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://digitta.com/" /><link rel="hub" href="http://pubsubhubbub.appspot.com/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default?start-index=26&amp;max-results=25&amp;redirect=false&amp;v=2" /><author><name>Àl</name><email>noreply@blogger.com</email></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>499</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/Digitta" /><feedburner:info xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" uri="digitta" /><logo>http://3.bp.blogspot.com/_5Dzab2eG9iQ/SQbyqyevlZI/AAAAAAAAALc/wpdADJ2Xobw/S1600-R/2802188461_240de5afd3_o%5B1%5D.png</logo><feedburner:emailServiceId xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">Digitta</feedburner:emailServiceId><feedburner:feedburnerHostname xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">http://feedburner.google.com</feedburner:feedburnerHostname><entry gd:etag="W/&quot;CEcEQXY6eyp7ImA9WxBUFkQ.&quot;"><id>tag:blogger.com,1999:blog-112773462326884407.post-2440745985643777413</id><published>2010-01-25T22:09:00.003+01:00</published><updated>2010-03-04T09:40:00.813+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-03-04T09:40:00.813+01:00</app:edited><title>Las aplicaciones web en 2010. Thin Server Architecture</title><content type="html">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.

&lt;div style="border-left: 2px solid rgb(204, 204, 204); float: right; margin: 10px; width: 250px;"&gt;&lt;ol&gt;&lt;li&gt;&lt;a href="http://bittwiddler.wordpress.com/2008/05/04/sofea-is-beautiful/" name="link1"&gt;SOFEA is beautiful&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.sitepen.com/blog/2008/07/18/clientserver-model-on-the-web/" name="link2"&gt;Client-Server Model on the web&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://mvalente.eu/2009/11/25/requirements-for-a-modern-web-development-framework/" name="link3"&gt;Requirements for a Modern Web Development Framework&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;Hay algunos artículos (por ejemplo &lt;a href="http://www.blogger.com/post-edit.g?blogID=112773462326884407&amp;amp;postID=2440745985643777413#link1"&gt;1&lt;/a&gt;,&lt;a href="http://www.blogger.com/post-edit.g?blogID=112773462326884407&amp;amp;postID=2440745985643777413#link2"&gt;2&lt;/a&gt;,&lt;a href="http://www.blogger.com/post-edit.g?blogID=112773462326884407&amp;amp;postID=2440745985643777413#link3"&gt;3&lt;/a&gt;) 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 &lt;i&gt;odio&lt;/i&gt; 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.



&lt;h2&gt;La capa de presentación ahora en el navegador&lt;/h2&gt;


&lt;a href="http://www.gmail.com/"&gt;Gmail&lt;/a&gt; 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.

&lt;i&gt;Recientemente&lt;/i&gt; 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 &lt;i&gt;mantenibilidad&lt;/i&gt; de ésta se resienta. Sin embargo, la forma tradicional de generar ese código HTML a enviar al navegador ha sido mediante &lt;i&gt;templates&lt;/i&gt; 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 (&lt;a href="http://bittwiddler.wordpress.com/2008/05/04/sofea-is-beautiful/"&gt;fuente&lt;/a&gt;):

&lt;pre name="code" class="xml:nocontrols"&gt;
&amp;lt;select name="type"&gt;
&amp;lt;c:forEach items="${types}" var="type"&gt;
&amp;lt;option value="${type.id}"
&amp;lt;c:if test="${item.defaultType == type.id}"&gt;
 selected&amp;lt;/c:if&gt;&gt;
&amp;lt;c:out value="${type.name}"/&gt;
&amp;lt;/option&gt;
&amp;lt;/c:forEach&gt;
&amp;lt;/select&gt;
&amp;lt;input type="button" value="Add &amp;lt;c:out
value="${item.name}"/&gt; to cart"
onClick="addItem('&amp;lt;c:out value="${item.guid}"/&gt;')"/&gt;&lt;/pre&gt;

Los problemas que esta arquitectura, utilizada ampliamente en la actualidad, presenta son innumerables, entre ellas:

&lt;ul&gt;&lt;li&gt;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.


&lt;/li&gt;
&lt;li&gt;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.


&lt;/li&gt;
&lt;li&gt;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.


&lt;/li&gt;
&lt;li&gt;Consumo elevado de memoria en el servidor para mantener estructuras de datos sobre las sesiones abiertas por los usuarios.


&lt;/li&gt;
&lt;li&gt;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.


&lt;/li&gt;
&lt;li&gt;Complicación a la hora de generar la versión &lt;i&gt;offline&lt;/i&gt; 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 &lt;i&gt;offline&lt;/i&gt;.
&lt;/li&gt;&lt;/ul&gt;

La aparición de &lt;i&gt;frameworks&lt;/i&gt; 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 &lt;a href="http://oreilly.com/catalog/9780596517748"&gt;las mejores partes&lt;/a&gt; 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 &lt;i&gt;frameworks&lt;/i&gt; anteriores, sino la creación de dichos elementos para constituir complejas interfícies de usuario desde el mismo navegador. Por ejemplo:

&lt;ul&gt;&lt;li&gt;El estándar E4X (&lt;a href="http://en.wikipedia.org/wiki/ECMAScript_for_XML"&gt;ECMAScript for XML&lt;/a&gt;) permite definir elementos directamente como variables JavaScript:

&lt;pre name="code" class="javascript"&gt;var sales = &amp;lt;sales vendor="John"&gt;
  &amp;lt;item type="peas" price="4" quantity="6"/&gt;
  &amp;lt;item type="carrot" price="3" quantity="10"/&gt;
  &amp;lt;item type="chips" price="5" quantity="3"/&gt;
&amp;lt;/sales&gt;;

alert( sales.item.(@type == "carrot").@quantity );
alert( sales.@vendor );
for each( var price in sales..@price ) {
alert( price );
}
&lt;/pre&gt;

&lt;/li&gt;&lt;li&gt;&lt;a href="http://edspencer.net/2009/11/jaml-beautiful-html-generation-for-javascript.html"&gt;JAML&lt;/a&gt; permite definir elementos usando una sintaxis muy sencilla basada en llamadas a funciones:

&lt;pre class="javascript" name="code"&gt;div(
h1("Un titulo"),
p("Un texto o parrafo cualquiera"),
br(),
ul(
 li("Primer item"),
 li("Segundo item"),
 li("Tercer item")
)
);&lt;/pre&gt;

&lt;/li&gt;&lt;li&gt;&lt;a href="http://jsonml.org/"&gt;JSONML&lt;/a&gt; es una aplicación del formato JSON que puede representar XHTML perfectamente:

&lt;pre class="javascript" name="code"&gt;[ "ul", { style : "list-style-type:none"},
 [ "li",
   "Primer item"],
 [ "li",
   "Segundo item"],
 [ "li",
   "Tercer item"],
 ];&lt;/pre&gt;

&lt;/li&gt;&lt;li&gt;El &lt;a href="http://www.smashingmagazine.com/2009/11/21/zen-coding-a-new-way-to-write-html-code/"&gt;Zen-coding&lt;/a&gt; es un formato pensado para generar código XHTML rápidamente en un editor de texto, pero &lt;a href="http://james.padolsey.com/javascript/satisfy-that-selector/"&gt;más&lt;/a&gt; &lt;a href="http://davidwalsh.name/mootools-expand-abbreviation"&gt;de&lt;/a&gt; &lt;a href="http://code.google.com/p/fluentzen/"&gt;uno&lt;/a&gt; ha adaptado a jQuery o Mootools la misma sintaxis permitiendo, por ejemplo, generar el mismo código XHTML del ejemplo anterior con la cadena
&lt;code style="white-space: nowrap;"&gt;ul&amp;gt;jQuery('ul li:3[innerHTML="item"]').satisfy();&lt;/code&gt;
o &lt;code style="white-space: nowrap;"&gt;$('body').zen('ul&amp;gt;li*3&amp;gt;span');&lt;/code&gt;
&lt;/li&gt;&lt;/ul&gt;


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 &lt;a href="https://developer.mozilla.org/En/XUL"&gt;XUL&lt;/a&gt; para navegadores Mozilla, o Flash, o &lt;a href="http://www.adobe.com/devnet/air/"&gt;Flex&lt;/a&gt;, o &lt;a href="http://www.openlaszlo.org/"&gt;OpenLaszlo&lt;/a&gt;, o un applet Java, o &lt;a href="http://www.adobe.com/devnet/air/"&gt;Air&lt;/a&gt;, o &lt;a href="http://www.appcelerator.com/"&gt;Titanium&lt;/a&gt;, o un framework como &lt;a href="http://draft.blogger.com/www.extjs.com"&gt;Ext.js&lt;/a&gt; o &lt;a href="http://www.ajax.org/"&gt;Ajax.org&lt;/a&gt;, o una aplicación para Android, Palm Pre o iPhone con &lt;a href="http://phonegap.com/"&gt;PhoneGap&lt;/a&gt;.



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:

&lt;ul&gt;&lt;li&gt;&lt;a href="http://dev.w3.org/html5/websockets/"&gt;WebSockets&lt;/a&gt; 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 &lt;a href="http://es.wikipedia.org/wiki/Comet"&gt;Comet&lt;/a&gt;.


&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.whatwg.org/specs/web-workers/current-work/"&gt;WebWorkers&lt;/a&gt; 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.


&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.w3.org/TR/webdatabase/"&gt;Web SQL Database&lt;/a&gt; para ofrecer el lenguaje SQL en el cliente y posibilitar trabajar &lt;a href="http://draft.blogger.com/Las%20aplicaciones%20web%20en%202010.%20Thin%20Server%20Architecture."&gt;&lt;i&gt;offline&lt;/i&gt;&lt;/a&gt; con una aplicación web.&lt;/li&gt;
&lt;/ul&gt;



&lt;h2&gt;Javascript en el servidor&lt;/h2&gt;


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 &lt;a href="http://wiki.commonjs.org/"&gt;CommonJS&lt;/a&gt; y &lt;a href="http://wiki.commonjs.org/wiki/JSGI%22"&gt;JSGI&lt;/a&gt; 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 &lt;i&gt;forma Javascript&lt;/i&gt; 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 &lt;a href="http://blog.webfaction.com/a-little-holiday-present"&gt;comparaciones&lt;/a&gt; entre los servidores NginX y Apache, que siguen precisamente cada una de esas técnicas, se puede ver que no sólo &lt;a href="http://www.webfaction.com/blog/nginx-apache-memory.png"&gt;NginX consume mucha menos memoria&lt;/a&gt; sino que también es capaz de soportar &lt;a href="http://www.webfaction.com/blog/nginx-apache-reqs-sec.png"&gt;muchas más conexiones concurrentes&lt;/a&gt;.


Siguiendo esa misma filosofía han aparecido proyectos como &lt;a href="http://twistedmatrix.com/"&gt;Twisted para Python&lt;/a&gt;, &lt;a href="http://rubyeventmachine.com/"&gt;EventMachine para Ruby&lt;/a&gt; y ahora &lt;a href="http://nodejs.org/"&gt;Node.js para Javascript&lt;/a&gt;. 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 &lt;i&gt;callback&lt;/i&gt; 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 &lt;a href="http://wiki.commonjs.org/wiki/Promises"&gt;promises&lt;/a&gt; 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).


&lt;a href="http://helma.org/ng/"&gt;Helma NG&lt;/a&gt; es la nueva versión del proyecto activo más antiguo que, siguiendo el estilo &lt;a href="http://es.wikipedia.org/wiki/Modelo_Vista_Controlador"&gt;MVC&lt;/a&gt; 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:

&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.persvr.org/"&gt;Persevere&lt;/a&gt; es un servidor de aplicaciones que ofrece una &lt;a href="http://es.wikipedia.org/wiki/Representational_State_Transfer"&gt;arquitectura REST&lt;/a&gt; para intercambiar datos (preferentemente en JSON) con un cliente &lt;i&gt;rico&lt;/i&gt; 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).


&lt;/li&gt;&lt;li&gt;&lt;a href="http://flusspferd.org/"&gt;Flusspferd&lt;/a&gt; es un proyecto que aúna SpiderMonkey (el motor de JS en C++) con una serie de &lt;a href="http://flusspferd.org/#bindings"&gt;módulos&lt;/a&gt; (SQLite,GMP,Zest,Juice,cUrl...) para ofrecer un entorno de ejecución muy rápido aunque está un poco &lt;i&gt;verde&lt;/i&gt;.


&lt;/li&gt;&lt;li&gt;En la misma situación se encuentra &lt;a href="http://www.ejscript.org/"&gt;EJScript&lt;/a&gt;, 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.


&lt;/li&gt;&lt;li&gt;&lt;a href="http://narwhaljs.org/"&gt;Narwhal&lt;/a&gt; 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.


&lt;/li&gt;&lt;li&gt;&lt;a href="http://nodejs.org/"&gt;node.js&lt;/a&gt; está generando una auténtica riada de reacciones en la comunidad web. Básicamente porque mata &lt;b&gt;tres&lt;/b&gt; 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 &lt;i&gt;callback&lt;/i&gt; 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.


&lt;/li&gt;&lt;li&gt;&lt;a href="http://appjet.com/"&gt;appjet.com&lt;/a&gt; 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.


&lt;/li&gt;&lt;li&gt;&lt;a href="http://jaxer.org/"&gt;Aptana Jaxer&lt;/a&gt; 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.&lt;/li&gt;
&lt;/ul&gt;



&lt;h2&gt;Futuro&lt;/h2&gt;


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:

&lt;ul&gt;&lt;li&gt;Datos. Existen ya servidores de bases de datos que hablan JS como &lt;a href="http://couchdb.apache.org/"&gt;CouchDB&lt;/a&gt; con su revolucionario enfoque a la hora de realizar consultas de datos basadas en el sistema &lt;a href="http://es.wikipedia.org/wiki/MapReduce"&gt;MapReduce&lt;/a&gt;. También &lt;a href="http://www.mongodb.org/"&gt;MongoDB&lt;/a&gt; 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 &lt;a href="http://www.dosideas.com/base-de-datos/657-nosql-el-movimiento-en-contra-de-las-bases-de-datos.html"&gt;NoSQL&lt;/a&gt;, donde el paradigma relacional que se ha demostrado como problemático para la escalabilidad es dejado atrás a favor de otras opciones menos &lt;i&gt;rígidas&lt;/i&gt;.


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 &lt;a href="http://msdn.microsoft.com/es-es/library/bb397906.aspx"&gt;Microsoft Linq&lt;/a&gt; pero siguiendo la sintaxis de Javascript, o bien basándose en la base de datos para navegador &lt;a href="http://taffydb.com"&gt;TaffyDB&lt;/a&gt;.


&lt;/li&gt;&lt;li&gt;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.


&lt;/li&gt;&lt;li&gt;En el navegador se echa en falta un sistema de empaquetado de aplicaciones al estilo los &lt;code&gt;.jar&lt;/code&gt; 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 &lt;code&gt;index.jsml&lt;/code&gt; empezase a sustituir al omnipresente &lt;code&gt;index.html&lt;/code&gt; sería un buen paso. Ya hay proyectos que &lt;a href="http://maraksquires.com/json_stylesheets/"&gt;sustituyen el CSS&lt;/a&gt; y al &lt;a href="http://jsonml.org/"&gt;XHTML&lt;/a&gt;, 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...&lt;/li&gt;
&lt;/ul&gt;



&lt;h2&gt;Conclusiones&lt;/h2&gt;


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 &lt;b&gt;lingua franca&lt;/b&gt; 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 &lt;i&gt;statu quo&lt;/i&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/112773462326884407-2440745985643777413?l=digitta.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://digitta.com/feeds/2440745985643777413/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://digitta.com/2010/01/las-aplicaciones-web-en-2010-thin.html#comment-form" title="6 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/2440745985643777413?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/2440745985643777413?v=2" /><link rel="alternate" type="text/html" href="http://digitta.com/2010/01/las-aplicaciones-web-en-2010-thin.html" title="Las aplicaciones web en 2010. Thin Server Architecture" /><author><name>Àl</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11672226968306882234" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">6</thr:total></entry><entry gd:etag="W/&quot;AkYHSX4_eCp7ImA9WxBTGEs.&quot;"><id>tag:blogger.com,1999:blog-112773462326884407.post-3188487712865447218</id><published>2009-12-15T10:03:00.001+01:00</published><updated>2009-12-15T10:08:58.040+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-15T10:08:58.040+01:00</app:edited><title>Empezando con Kohana, ¿mejor que CodeIgniter?</title><content type="html">He traducido &lt;a href="http://net.tutsplus.com/tutorials/php/kohana-the-swift-php-framework/"&gt;este tutorial de NetTuts+&lt;/a&gt; creado por Cristian Gilè sobre el &lt;i&gt;framework&lt;/i&gt; PHP Kohana, al igual que &lt;a href="/2009/01/empezando-con-codeigniter.html"&gt;hice en su día con el de CodeIgniter&lt;/a&gt;.&lt;br&gt;&lt;br&gt;Kohana es un &lt;i&gt;framework&lt;/i&gt; 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.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;h1&gt;Paso 1: ¿Qué es Kohana?&lt;/h1&gt;&lt;img src="http://farm3.static.flickr.com/2698/4185017587_ab38e59783_o.png"&gt;&lt;br&gt;&lt;br&gt;Kohana es un &lt;i&gt;framework&lt;/i&gt; de PHP5 que sigue la arquitectura MVC. MVC mantiene separadas la lógica de la presentación. Esto permite&amp;nbsp; 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:&lt;br&gt;&lt;ul&gt;&lt;li&gt;Un &lt;b&gt;modelo&lt;/b&gt; representa datos sobre los que la aplicación opera. Normalmente es una base de datos.&lt;/li&gt;&lt;li&gt;Una &lt;b&gt;vista&lt;/b&gt; contiene el código de presentación como HTML, CSS y JavaScript.&lt;/li&gt;&lt;li&gt;Un &lt;b&gt;controlador&lt;/b&gt; interpreta la entrada del usuario y redirige al modelo y/o vista.&lt;/li&gt;&lt;/ul&gt;Kohana era originalmente una escisión de &lt;a href="/2009/01/empezando-con-codeigniter.html"&gt;CodeIgniter&lt;/a&gt; (CI), que es un producto &lt;i&gt;open source&lt;/i&gt; 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 &lt;a href="http://www.kohanaphp.com/"&gt;página oficial de Kohana&lt;/a&gt;, las características principales son:&lt;br&gt;&lt;ul&gt;&lt;li&gt;Altamente seguro&lt;/li&gt;&lt;li&gt;Extremadamente ligero&lt;/li&gt;&lt;li&gt;Breve curva de aprendizaje&lt;/li&gt;&lt;li&gt;Utiliza el patrón MVC&lt;/li&gt;&lt;li&gt;100% compatible con la codificación UTF-8&lt;/li&gt;&lt;li&gt;Arquitectura débilmente acoplada&lt;/li&gt;&lt;li&gt;Extremadamente fácil de extender&lt;/li&gt;&lt;/ul&gt;&lt;br&gt;&lt;h1&gt;Paso 2: Descargando Kohana&lt;/h1&gt;&lt;img src="http://farm3.static.flickr.com/2718/4185777220_5bc611a60e_o.jpg"&gt;&lt;br&gt;&lt;br&gt;Empecemos. Visita la &lt;a href="http://www.kohanaphp.com/"&gt;página oficial de Kohana&lt;/a&gt; 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.&lt;br&gt;&lt;br&gt;&lt;img src="http://farm3.static.flickr.com/2522/4185017245_9cb663949a_o.jpg"&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;h1&gt;Paso 3: Instalando Kohana&lt;/h1&gt;Una vez que se ha terminado la descarga:&lt;br&gt;&lt;ol&gt;&lt;li&gt;Descomprimir el zip&lt;/li&gt;&lt;li&gt;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.&lt;/li&gt;&lt;li&gt;Edita el fichero de configuración global &lt;b&gt;application/config/config.php&lt;/b&gt; de esta forma:&lt;br/&gt;&lt;code&gt;&amp;nbsp;&amp;nbsp; $config['site_domain'] = 'localhost/kohana';&lt;/code&gt;&lt;/li&gt;&lt;li&gt;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 &lt;code&gt;chmod&lt;/code&gt; a 755 de todas ellas ejecutando &lt;code&gt;find . -type d -exec chmod 755 {} \;&lt;/code&gt; desde la carpeta raíz de la instalación de Kohana.&lt;/li&gt;&lt;li&gt;Asegúrate de que &lt;b&gt;application/logs&lt;/b&gt; y &lt;b&gt;application/cache&lt;/b&gt; no sean de sólo lectura. Haz &lt;code&gt;chmod&lt;/code&gt; a 666 sobre ellas.&lt;/li&gt;&lt;li&gt;Ahora, apunta el navegador a &lt;a href="http://localhost/kohana/" target="_blank"&gt;http://localhost/kohana/&lt;/a&gt; . Automáticamente, el &lt;i&gt;framework&lt;/i&gt; llamará a &lt;code&gt;install.php&lt;/code&gt; y comprobará los requerimientos con el servidor.&lt;/li&gt;&lt;/ol&gt;Kohana se ejecutará en prácticamente cualquier entorno que cumpla la configuración mínima:&lt;br&gt;&lt;ul&gt;&lt;li&gt;Servidor con soporte de Unicode&lt;/li&gt;&lt;li&gt;PHP con una versión igual o posterior a la 5.2.3&lt;/li&gt;&lt;li&gt;Un servidor HTTP. Sugiero &lt;a href="http://www.apachefriends.org/"&gt;XAMPP&lt;/a&gt;, que es una herramienta sencilla todo-en-uno para instalar MySQL, PHP y Perl.&lt;/li&gt;&lt;li&gt;Una base de datos (MsSQL, MySQL, MySQLi, PostgreSQL, PDOSqlite).&lt;/li&gt;&lt;/ul&gt;También hay extensiones necesarias:&lt;br&gt;&lt;ul&gt;&lt;li&gt;PCRE&lt;/li&gt;&lt;li&gt;iconv&lt;/li&gt;&lt;li&gt;mcrypt&lt;/li&gt;&lt;li&gt;SPL&lt;/li&gt;&lt;/ul&gt;Si la instalación se completa con éxito, serás dirigido a esta página de prueba:&lt;br&gt;&lt;br&gt;&lt;img src="http://farm3.static.flickr.com/2559/4185017499_09eec54f95_o.jpg"&gt;&lt;br&gt;Si alguna de las pruebas falla, deberás corregirla antes de continuar.&lt;br&gt;&lt;br&gt;Si todas las comprobaciones tienen éxito, dirígete a la carpeta de Kohana y elimina o renombra el archivo &lt;code&gt;install.php&lt;/code&gt;. Recarga la página en el navegador y verás una página de bienvenida como esta:&lt;br&gt;&lt;br&gt;&lt;img src="http://farm3.static.flickr.com/2566/4185777814_0fdb7101aa_o.jpg"&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;h1&gt;Paso 4: Configurando Kohana&lt;/h1&gt;Kohana está preparado para funcionar. No se necesita realizar ninguna configuración. Este &lt;i&gt;framework&lt;/i&gt; es genial, ¿no te parece? Vamos a ver un poco de código, sígueme.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;h1&gt;Paso 5: El primer proyecto con Kohana&lt;/h1&gt;Los tutoriales de programación tradicionales empiezan con el ejemplo &lt;i&gt;"hello world"&lt;/i&gt;. Pero yo creo que una aplicación simple puede dar una idea clara de cómo funciona el &lt;i&gt;framework&lt;/i&gt;. 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.&lt;br&gt;&lt;br&gt;&lt;img src="http://farm5.static.flickr.com/4044/4185777738_f2c272e5da_o.jpg"&gt;&lt;br&gt;&lt;br&gt;Nuestra aplicación se ubicará en la carpeta &lt;b&gt;application&lt;/b&gt;. En esta carpeta hay varias subcarpetas pero sólo necesitamos lo siguiente para nuestro proyecto:&lt;br&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;config&lt;/b&gt; guarda los archivos de configuración codificados como simples &lt;i&gt;arrays&lt;/i&gt; estáticos.&lt;/li&gt;&lt;li&gt;&lt;b&gt;controllers&lt;/b&gt; guarda nuestra clase de controladores personalizada&lt;/li&gt;&lt;li&gt;&lt;b&gt;models&lt;/b&gt; guarda nuestra clase de modelos personalizada&lt;/li&gt;&lt;li&gt;&lt;b&gt;views&lt;/b&gt; guarda nuestro archivos personalizados escritos en HTML (o cualquier lenguaje de &lt;i&gt;markup&lt;/i&gt; o &lt;i&gt;scripts&lt;/i&gt; necesarios para mostrar los datos y los controles de interfície para el usuario)&lt;/li&gt;&lt;/ul&gt;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.&lt;br&gt;&lt;br&gt;La carpeta &lt;b&gt;system&lt;/b&gt; guarda el núcleo de Kohana y herramientas como bibliotecas (&lt;i&gt;library&lt;/i&gt;), asistentes (&lt;i&gt;helper&lt;/i&gt;) y archivos de configuración predefinidos. En este proyecto vamos a usar algunas bibliotecas y algunos asistentes - buenas herramientas para acelerar tu trabajo.&lt;br&gt;&lt;br&gt;La carpeta &lt;b&gt;assets&lt;/b&gt;&amp;nbsp; 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.&lt;br&gt;&lt;br&gt;La carpeta &lt;b&gt;modules&lt;/b&gt; 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.&lt;br&gt;&lt;br&gt;É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.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;h1&gt;Paso 6: La base de datos del proyecto&lt;/h1&gt;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 &lt;i&gt;SQL&lt;/i&gt; mediante phpMyAdmin o cualquier herramienta para administrar la base de datos.&lt;br&gt;&lt;pre name="code" class="brush:sql"&gt;&lt;br&gt;CREATE TABLE `albums` (&lt;br&gt;&amp;nbsp; `id` int(11) NOT NULL auto_increment,&lt;br&gt;&amp;nbsp; `name` varchar(50) collate utf8_bin NOT NULL,&lt;br&gt;&amp;nbsp; `author` varchar(50) collate utf8_bin NOT NULL,&lt;br&gt;&amp;nbsp; `genre_id` int(11) NOT NULL,&lt;br&gt;&amp;nbsp; PRIMARY KEY&amp;nbsp; (`id`),&lt;br&gt;&amp;nbsp; KEY `genre_id` (`genre_id`)&lt;br&gt;) ENGINE=InnoDB&amp;nbsp; DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=19 ;&lt;br&gt;&lt;br&gt;INSERT INTO `albums` (`id`, `name`, `author`, `genre_id`) VALUES&lt;br&gt;(2, 'Lines, Vines And Trying Times', 'Jonas Brothers', 16),&lt;br&gt;(3, 'The E.N.D.', 'The Black Eyed Peas', 16),&lt;br&gt;(4, 'Relapse', 'Eminem', 18),&lt;br&gt;(5, 'Monuments And Melodies', 'Incubus', 1),&lt;br&gt;(6, 'Thriller', 'Michael Jackson', 16),&lt;br&gt;(7, 'Back in Black', 'AC/DC', 4),&lt;br&gt;(8, 'The Dark Side of the Moon', 'Pink Floyd', 4),&lt;br&gt;(9, 'Bat out of Hell', 'Meat Loaf', 4),&lt;br&gt;(10, 'Backstreet Boys', 'Millennium', 16),&lt;br&gt;(11, 'Rumours', 'Fleetwood Mac', 4),&lt;br&gt;(12, 'Come on Over', 'Shania Twain', 16),&lt;br&gt;(13, 'Led Zeppelin IV', 'Led Zeppelin', 4),&lt;br&gt;(14, 'Jagged Little Pill', 'Alanis Morissette', 4),&lt;br&gt;(15, 'Sgt. Pepper''s Lonely Hearts Club Band', 'The Beatles', 16),&lt;br&gt;(16, 'Falling into You', 'Cv©line Dion', 16),&lt;br&gt;(17, 'Music Box', 'Mariah Carey', 16),&lt;br&gt;(18, 'Born in the U.S.A.', 'Bruce Springsteen', 4);&lt;br&gt;&lt;br&gt;CREATE TABLE `genres` (&lt;br&gt;&amp;nbsp; `id` int(11) NOT NULL auto_increment,&lt;br&gt;&amp;nbsp; `name` varchar(50) collate utf8_bin NOT NULL,&lt;br&gt;&amp;nbsp; PRIMARY KEY&amp;nbsp; (`id`),&lt;br&gt;&amp;nbsp; UNIQUE KEY `name` (`name`)&lt;br&gt;) ENGINE=InnoDB&amp;nbsp; DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=22 ;&lt;br&gt;&lt;br&gt;INSERT INTO `genres` (`id`, `name`) VALUES&lt;br&gt;(1, 'Alternative Rock'),&lt;br&gt;(2, 'Blues'),&lt;br&gt;(3, 'Classical'),&lt;br&gt;(4, 'Rock'),&lt;br&gt;(5, 'Country'),&lt;br&gt;(6, 'Dance'),&lt;br&gt;(7, 'Folk'),&lt;br&gt;(8, 'Metal'),&lt;br&gt;(9, 'Hawaiian'),&lt;br&gt;(10, 'Imports'),&lt;br&gt;(11, 'Indie Music'),&lt;br&gt;(12, 'Jazz'),&lt;br&gt;(13, 'Latin'),&lt;br&gt;(14, 'New Age'),&lt;br&gt;(15, 'Opera'),&lt;br&gt;(16, 'Pop'),&lt;br&gt;(17, 'Soul'),&lt;br&gt;(18, 'Rap'),&lt;br&gt;(20, 'Soundtracks'),&lt;br&gt;(21, 'World Music');&lt;br&gt;&lt;br&gt;ALTER TABLE `albums`&lt;br&gt;&amp;nbsp; ADD CONSTRAINT `genre_inter_relational_constraint` FOREIGN KEY (`genre_id`) REFERENCES `genres` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;&lt;br&gt;&lt;/pre&gt;&lt;br&gt;Como puedes ver, el &lt;i&gt;SQL&lt;/i&gt; crea dos tablas, álbumes y géneros, y los puebla con algunos datos. La última sentencia &lt;i&gt;SQL&lt;/i&gt; agrega una restricción para la clave ajena &lt;code&gt;"genre_id"&lt;/code&gt;.&lt;br&gt;&lt;br&gt;La estructura de la base de datos es muy simple y no necesita mayor explicación:&lt;br&gt;&lt;br&gt;&lt;img src="http://farm3.static.flickr.com/2788/4185777466_ded672d7d3_o.jpg"&gt;&lt;br&gt;&lt;br&gt;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 &lt;b&gt;system/config/database.php&lt;/b&gt; de esta forma:&lt;br&gt;&lt;pre name="code" class="brush:php"&gt;&lt;br&gt;$config['default'] = array&lt;br&gt;(&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;'benchmark'&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; =&amp;gt; TRUE,&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;'persistent'&amp;nbsp;&amp;nbsp;&amp;nbsp; =&amp;gt; FALSE,&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;'connection'&amp;nbsp;&amp;nbsp;&amp;nbsp; =&amp;gt; array&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;(&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;'type'&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; =&amp;gt; 'mysql',&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;'user'&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; =&amp;gt; 'root',&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;'pass'&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; =&amp;gt; 'root',&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;'host'&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; =&amp;gt; 'localhost',&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;'port'&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; =&amp;gt; FALSE,&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;'socket'&amp;nbsp;&amp;nbsp; =&amp;gt; FALSE,&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;'database' =&amp;gt; 'cd_collection'&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;),&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;'character_set' =&amp;gt; 'utf8',&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;'table_prefix'&amp;nbsp; =&amp;gt; '',&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;'object'&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; =&amp;gt; TRUE,&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;'cache'&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; =&amp;gt; FALSE,&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;'escape'&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; =&amp;gt; TRUE&lt;br&gt;);&lt;br&gt;&lt;/pre&gt;&lt;br&gt;Este código le dice a Kohana que se conecte a una base de datos MySQL llamada &lt;code&gt;"cd_collection"&lt;/code&gt; en &lt;i&gt;localhost&lt;/i&gt;  con el nombre de usuario &lt;code&gt;"root"&lt;/code&gt; y la clave &lt;code&gt;"root"&lt;/code&gt;. Debes cambiarlas de acuerdo con tu configuración de servidor de base de datos.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;h1&gt;Paso 7: Creando el controlador&lt;/h1&gt;Creémos nuestro primer controlador. Recuerda estas convenciones:&lt;br&gt;&lt;ul&gt;&lt;li&gt;el nombre de archivo del controlador debe estar en minúsculas, por ejemplo &lt;code&gt;album.php&lt;/code&gt;&lt;/li&gt;&lt;li&gt;la clase controlador debe coincidir con su nombre de archivo pero iniciada con mayúscula y debe añadir &lt;code&gt;_Controller&lt;/code&gt;, por ejemplo Album_Controller&lt;/li&gt;&lt;li&gt;Debe tener la clase &lt;code&gt;Controller&lt;/code&gt; como padre&lt;/li&gt;&lt;/ul&gt;Igualmente, recuerda cómo Kohana estructura sus URLs y cómo puedes llamar a un método de controlador; por ejemplo &lt;code&gt;http://hostname/carpeta_de_kohana/index.php/controller/function&lt;/code&gt;&lt;br&gt;&lt;br&gt;Echa un vistazo a este simple controlador.&lt;br&gt;&lt;pre name="code" class="brush:php"&gt;&lt;br&gt;&amp;lt;?php defined('SYSPATH') OR die('No direct access allowed.');&lt;br&gt;&lt;br&gt;class Album_Controller extends Controller&lt;br&gt;{ &lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;public function __construct()&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt; &amp;nbsp;&amp;nbsp; &amp;nbsp;{ &lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;parent::__construct();&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;}&lt;br&gt; &amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;public function index()&lt;br&gt; &amp;nbsp;&amp;nbsp; &amp;nbsp;{&lt;br&gt; &amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "My first controller";&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;}&lt;br&gt;}&lt;br&gt;&lt;/pre&gt;&lt;br&gt;PHP5 OOP (programación orientada a objetos) es un prerrequisito. Para aprender más sobre ello, puedes leer &lt;a href="http://www.php.net/manual/en/language.oop5.php"&gt;aquí&lt;/a&gt;.&lt;br&gt;&lt;br&gt;La función constructor, llamada &lt;code&gt;__construct&lt;/code&gt;, inicializa la clase y llama al constructor padre. La función &lt;code&gt;index&lt;/code&gt; 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).&lt;br&gt;&lt;br&gt;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.&lt;br&gt;&lt;br&gt;Crea un archivo llamado &lt;code&gt;album.php&lt;/code&gt; en &lt;b&gt;application/controllers/&lt;/b&gt; y pega los siguiente:&lt;br&gt;&lt;pre name="code" class="brush:php"&gt;&lt;br&gt;&amp;lt;?php defined('SYSPATH') OR die('No direct access allowed.');&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;class Album_Controller extends Controller&lt;br&gt;{&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; private $album_model; &lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; private $genre_model;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; private $list_view;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; private $create_view;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; private $update_view;&lt;br&gt; &lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;public function __construct()&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt; &amp;nbsp;&amp;nbsp; &amp;nbsp;{ &lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;parent::__construct();&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$this-&amp;gt;album_model&amp;nbsp;&amp;nbsp; = new Album_Model;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$this-&amp;gt;genre_model &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;= new Genre_Model;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$this-&amp;gt;list_view&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;= new View('list');&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$this-&amp;gt;update_view &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;= new View('update');&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$this-&amp;gt;create_view &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;= new View('create');&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;}&lt;br&gt; &lt;br&gt; &amp;nbsp;&amp;nbsp; &amp;nbsp;public function index()&lt;br&gt; &amp;nbsp;&amp;nbsp; &amp;nbsp;{&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$this-&amp;gt;show_albums_list();&lt;br&gt; &amp;nbsp;&amp;nbsp; &amp;nbsp;}&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;private function show_albums_list()&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;{&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$albums_list = $this-&amp;gt;album_model-&amp;gt;get_list(); &lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$this-&amp;gt;list_view-&amp;gt;set('albums_list',$albums_list);&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$this-&amp;gt;list_view-&amp;gt;render(TRUE); &lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;}&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt; &amp;nbsp;&amp;nbsp; &amp;nbsp;public function show_create_editor()&lt;br&gt; &amp;nbsp;&amp;nbsp; &amp;nbsp;{&lt;br&gt; &amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$this-&amp;gt;create_view-&amp;gt;set('genres_list',$this-&amp;gt;get_genres_list());&lt;br&gt;  &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$this-&amp;gt;create_view-&amp;gt;render(TRUE);&lt;br&gt; &amp;nbsp;&amp;nbsp; &amp;nbsp;}&lt;br&gt; &lt;br&gt; &amp;nbsp;&amp;nbsp; &amp;nbsp;public function show_update_editor($id)&lt;br&gt; &amp;nbsp;&amp;nbsp; &amp;nbsp;{&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$album_data = $this-&amp;gt;album_model-&amp;gt;read($id);&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$this-&amp;gt;update_view-&amp;gt;set('album_id',$album_data[0]-&amp;gt;id);&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$this-&amp;gt;update_view-&amp;gt;set('name',$album_data[0]-&amp;gt;name);&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$this-&amp;gt;update_view-&amp;gt;set('author',$album_data[0]-&amp;gt;author);&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$this-&amp;gt;update_view-&amp;gt;set('genre_id',$album_data[0]-&amp;gt;genre_id);&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$this-&amp;gt;update_view-&amp;gt;set('genres_list',$this-&amp;gt;get_genres_list());&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$this-&amp;gt;update_view-&amp;gt;render(TRUE);&lt;br&gt; &amp;nbsp;&amp;nbsp; &amp;nbsp;}&lt;br&gt; &lt;br&gt; &amp;nbsp;&amp;nbsp; &amp;nbsp;public function create()&lt;br&gt; &amp;nbsp;&amp;nbsp; &amp;nbsp;{ &lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$album_data=array(&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;'name'&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;=&amp;gt; $this-&amp;gt;input-&amp;gt;post('name'),&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;'author' &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;=&amp;gt; $this-&amp;gt;input-&amp;gt;post('author'),&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;'genre_id'&amp;nbsp; =&amp;gt; $this-&amp;gt;input-&amp;gt;post('genre_id')&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;);&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$this-&amp;gt;album_model-&amp;gt;create($album_data);&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;url::redirect('album');&lt;br&gt; &amp;nbsp;&amp;nbsp; &amp;nbsp;}&lt;br&gt; &lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;public function update()&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;{ &lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$album_data = array(&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;'name'&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;=&amp;gt; $this-&amp;gt;input-&amp;gt;post('name'),&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;'author' &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;=&amp;gt; $this-&amp;gt;input-&amp;gt;post('author'),&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;'genre_id'&amp;nbsp; =&amp;gt; $this-&amp;gt;input-&amp;gt;post('genre_id')&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;);&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$this-&amp;gt;album_model-&amp;gt;update($this-&amp;gt;input-&amp;gt;post('album_id'),$album_data);&lt;br&gt;  &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;url::redirect('album');&lt;br&gt; &amp;nbsp;&amp;nbsp; &amp;nbsp;}&lt;br&gt; &amp;nbsp;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;public function delete($id)&lt;br&gt; &amp;nbsp;&amp;nbsp; &amp;nbsp;{&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$this-&amp;gt;album_model-&amp;gt;delete($id);&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;url::redirect('album');&lt;br&gt; &amp;nbsp;&amp;nbsp; &amp;nbsp;}&lt;br&gt; &lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;private function get_genres_list()&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;{&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$db_genres_list&amp;nbsp; = $this-&amp;gt;genre_model-&amp;gt;get_list(); &lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$genres_list&amp;nbsp; = array();&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;if(sizeof($db_genres_list) &amp;gt;= 1)&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;{&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;foreach($db_genres_list as $item)&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;{&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$genres_list[$item-&amp;gt;id] = $item-&amp;gt;name;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;}&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;}&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;return $genres_list;&lt;br&gt; &amp;nbsp;&amp;nbsp; &amp;nbsp;}&lt;br&gt;}&lt;br&gt;&lt;/pre&gt;&lt;br&gt;Déjame explicar lo que hace este código.&lt;br&gt;&lt;br&gt;&lt;b&gt;Se declaran cinco variables miembro al inicio:&lt;/b&gt;&lt;br&gt;&lt;pre name="code" class="brush:php"&gt;&lt;br&gt;&amp;nbsp; private $album_model; &lt;br&gt;&amp;nbsp; private $genre_model;&lt;br&gt;&lt;br&gt;&amp;nbsp; private $list_view;&lt;br&gt;&amp;nbsp; private $create_view;&lt;br&gt;&amp;nbsp; private $update_view;&lt;br&gt;&lt;/pre&gt;&lt;br&gt;Estos miembros son privados porque quiero limitar su visibilidad sólo a esta clase.&lt;br&gt;&lt;br&gt;&lt;b&gt;En el método &lt;code&gt;construct&lt;/code&gt; los objetos de modelo y vista son creados usando los cinco miembros:&lt;/b&gt;&lt;br&gt;&lt;pre name="code" class="brush:php"&gt;&lt;br&gt;&amp;nbsp; $this-&amp;gt;album_model&amp;nbsp;&amp;nbsp; = new Album_Model;&lt;br&gt;&amp;nbsp; $this-&amp;gt;genre_model &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;= new Genre_Model;&lt;br&gt;&amp;nbsp; $this-&amp;gt;list_view&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;= new View('list');&lt;br&gt;&amp;nbsp; $this-&amp;gt;update_view &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;= new View('update');&lt;br&gt;&amp;nbsp; $this-&amp;gt;create_view &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;= new View('create');&lt;br&gt;&lt;/pre&gt;&lt;br&gt;Para crear un objeto modelo usa esta sintaxis:&lt;br&gt;&lt;pre name="code" class="brush:php"&gt;$obj_name = new Name_Model;&lt;/pre&gt;&lt;br&gt;Para crear un objeto vista, usa esta sintaxis:&lt;br&gt;&lt;pre name="code" class="brush:php"&gt;$obj_name = new View('view_filename_without_extension');&lt;/pre&gt;&lt;br&gt;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.&lt;br&gt;&lt;br&gt;&lt;b&gt;El método &lt;code&gt;index&lt;/code&gt; llama al método &lt;code&gt;show_albums_list&lt;/code&gt; que lista todos los álbumes guardados en la base de datos.&lt;/b&gt;&lt;br&gt;&lt;pre name="code" class="brush:php"&gt;$albums_list = $this-&amp;gt;album_model-&amp;gt;get_list();&amp;nbsp; &amp;nbsp;&lt;br&gt;$this-&amp;gt;list_view-&amp;gt;set('albums_list',$albums_list); &amp;nbsp;&lt;br&gt;$this-&amp;gt;list_view-&amp;gt;render(TRUE);&lt;/pre&gt;&lt;br&gt;En este método puedes ver cómo el modelo y la vista objeto son usada para acceder a métodos relativos. &lt;code&gt;"get_list"&lt;/code&gt; 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 &lt;i&gt;array&lt;/i&gt; &lt;code&gt;"$album_list"&lt;/code&gt;. Para pasar el &lt;i&gt;array&lt;/i&gt; de resultados del controlador a la vista, el método &lt;code&gt;"set"&lt;/code&gt; del objeto vista es llamado. Este método requiere dos parámetros: una nueva variable vacía (&lt;code&gt;album_list&lt;/code&gt;) contiene datos de una variable existente (&lt;code&gt;$album_list&lt;/code&gt;). Ahora la nueva variable vacía &lt;code&gt;"album_list"&lt;/code&gt; contine el &lt;i&gt;array&lt;/i&gt; &lt;code&gt;$album_list&lt;/code&gt; (veremos después como mostrar su contenido en la vista). El método &lt;code&gt;"render"&lt;/code&gt;, con el parámetro &lt;code&gt;TRUE&lt;/code&gt;, es necesario para generar datos para el navegador.&lt;br&gt;&lt;br&gt;&lt;b&gt;El método &lt;code&gt;show_create_editor&lt;/code&gt; muestra la interfície de usuario para insertar un nuevo álbum.&lt;/b&gt;&lt;br&gt;&lt;pre name="code" class="brush:php"&gt;&amp;nbsp;&amp;nbsp; 1. $this-&amp;gt;create_view-&amp;gt;set('genres_list',$this-&amp;gt;get_genres_list());&lt;br&gt;$this-&amp;gt;create_view-&amp;gt;render(TRUE);&lt;/pre&gt;&lt;br&gt;La lista de géneros es pasada a la vista.&lt;br&gt;&lt;br&gt;&lt;b&gt;El método &lt;code&gt;show_update_editor&lt;/code&gt; muestra la interfície de usuario para actualizar un álbum existente.&lt;/b&gt;&lt;br&gt;&lt;pre name="code" class="brush:php"&gt;&lt;br&gt;$album_data = $this-&amp;gt;album_model-&amp;gt;read($id); &amp;nbsp;&lt;br&gt;$this-&amp;gt;update_view-&amp;gt;set('album_id',$album_data[0]-&amp;gt;id); &amp;nbsp;&lt;br&gt;$this-&amp;gt;update_view-&amp;gt;set('name',$album_data[0]-&amp;gt;name); &amp;nbsp;&lt;br&gt;$this-&amp;gt;update_view-&amp;gt;set('author',$album_data[0]-&amp;gt;author); &amp;nbsp;&lt;br&gt;$this-&amp;gt;update_view-&amp;gt;set('genre_id',$album_data[0]-&amp;gt;genre_id); &amp;nbsp;&lt;br&gt;$this-&amp;gt;update_view-&amp;gt;set('genres_list',$this-&amp;gt;get_genres_list());&lt;br&gt;$this-&amp;gt;update_view-&amp;gt;render(TRUE);&lt;/pre&gt;&lt;br&gt;&lt;code&gt;"read"&lt;/code&gt;  es un método del modelo (lo veremos después) que devuelve datos (&lt;code&gt;$album_data&lt;/code&gt;) del álbum con un identificador igual a &lt;code&gt;$id&lt;/code&gt;. Entonces, cada elemento del álbum de datos devuelto es pasado a la vista.&lt;br&gt;&lt;br&gt;&lt;b&gt;El método &lt;code&gt;create&lt;/code&gt; recibe datos para un nuevo álbum a partir de la vista y guarda los datos recibidos en la base de datos.&lt;/b&gt;&lt;br&gt;&lt;pre name="code" class="brush:php"&gt;$album_data=array( &amp;nbsp;&lt;br&gt;'name'&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; =&amp;gt; $this-&amp;gt;input-&amp;gt;post('name'), &amp;nbsp;&lt;br&gt;'author'&amp;nbsp;&amp;nbsp;&amp;nbsp; =&amp;gt; $this-&amp;gt;input-&amp;gt;post('author'), &amp;nbsp;&lt;br&gt;'genre_id'&amp;nbsp; =&amp;gt; $this-&amp;gt;input-&amp;gt;post('genre_id') &amp;nbsp;&lt;br&gt;); &amp;nbsp;&lt;br&gt;$this-&amp;gt;album_model-&amp;gt;create($album_data); &amp;nbsp;&lt;br&gt;url::redirect('album');&lt;/pre&gt;&lt;br&gt;&lt;code&gt;$album_data&lt;/code&gt;  es un &lt;i&gt;array&lt;/i&gt; que contiene los datos &lt;code&gt;POST&lt;/code&gt; de la vista. Para guardar el álbum, el &lt;i&gt;array&lt;/i&gt; se pasa al método del modelo &lt;code&gt;create&lt;/code&gt;. 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 &lt;code&gt;"redirect"&lt;/code&gt; del asistente &lt;code&gt;"url"&lt;/code&gt; 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).&lt;br&gt;&lt;br&gt;&lt;b&gt;Los métodos &lt;code&gt;update&lt;/code&gt; (actualizar) y &lt;code&gt;delete&lt;/code&gt; (borrar) funcionan de la misma forma que &lt;code&gt;create&lt;/code&gt;.&lt;/b&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;El último método &lt;code&gt;get_genres_list&lt;/code&gt; obtiene la lista de géneros del modelo (&lt;code&gt;$db_genres_list&lt;/code&gt;) y construye un nuevo &lt;i&gt;array&lt;/i&gt; (&lt;code&gt;$genres_list&lt;/code&gt;) para el control de selección (select) en las vistas.&lt;/b&gt;&lt;br&gt;&lt;pre name="code" class="brush:php"&gt;$db_genres_list&amp;nbsp; = $this-&amp;gt;genre_model-&amp;gt;get_list();&amp;nbsp; &amp;nbsp;&lt;br&gt;$genres_list&amp;nbsp; = array(); &amp;nbsp;&lt;br&gt; &amp;nbsp;&lt;br&gt;if(sizeof($db_genres_list) &amp;gt;= 1) &amp;nbsp;&lt;br&gt;{ &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; foreach($db_genres_list as $item) &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; { &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; $genres_list[$item-&amp;gt;id] = $item-&amp;gt;name; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; } &amp;nbsp;&lt;br&gt;} &amp;nbsp;&lt;br&gt;return $genres_list;&lt;/pre&gt;&lt;br&gt;&lt;br&gt;&lt;h1&gt;Paso 8: Creando el modelo para el proyecto&lt;/h1&gt;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 &lt;code&gt;album.php&lt;/code&gt; en &lt;b&gt;application/models/&lt;/b&gt; y pega en él el siguiente código:&lt;br&gt;&lt;pre name="code" class="brush:php"&gt;&lt;br&gt;&amp;lt;?php defined('SYSPATH') OR die('No direct access allowed.');&lt;br&gt;&lt;br&gt;class Album_Model extends Model&lt;br&gt;{&lt;br&gt; &amp;nbsp;&amp;nbsp; &amp;nbsp;private $album_table;&lt;br&gt; &amp;nbsp;&amp;nbsp; &amp;nbsp;private $genre_table; &lt;br&gt; &lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;public function __construct()&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;{&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;parent::__construct();&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$this-&amp;gt;album_table = 'albums';&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$this-&amp;gt;genre_table = 'genres';&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;}&lt;br&gt; &amp;nbsp;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;public function read($id)&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;{&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$this-&amp;gt;db-&amp;gt;where('id', $id);&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$query = $this-&amp;gt;db-&amp;gt;get($this-&amp;gt;album_table); &lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;return $query-&amp;gt;result_array();&lt;br&gt; &amp;nbsp;&amp;nbsp; &amp;nbsp;}&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;public function delete($id)&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;{&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$this-&amp;gt;db-&amp;gt;delete($this-&amp;gt;album_table, array('id' =&amp;gt; $id));&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;}&lt;br&gt; &amp;nbsp;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;public function update($id,$data)&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;{&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$this-&amp;gt;db-&amp;gt;update($this-&amp;gt;album_table, $data, array('id' =&amp;gt; $id));&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;}&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;public function create($data)&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;{&lt;br&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$this-&amp;gt;db-&amp;gt;insert($this-&amp;gt;album_table, $data);&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;}&lt;br&gt; &amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;public function get_list()&lt;br&gt; &amp;nbsp;&amp;nbsp; &amp;nbsp;{&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$this-&amp;gt;db-&amp;gt;select('albums.id as id,albums.name as name,albums.author as author, genres.name as genre'); &amp;nbsp;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$this-&amp;gt;db-&amp;gt;from($this-&amp;gt;album_table); &amp;nbsp;&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$this-&amp;gt;db-&amp;gt;join($this-&amp;gt;genre_table,'genres.id','albums.genre_id');&lt;br&gt;  &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;$query = $this-&amp;gt;db-&amp;gt;get();&lt;br&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;return $query-&amp;gt;result_array();&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;}&lt;br&gt;}&lt;br&gt;&lt;/pre&gt;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.&lt;br&gt;&lt;br&gt;Dos variables miembro son declaradas al inicio de la clase:&lt;br&gt;&lt;pre name="code" class="brush:php"&gt;private $album_table; &amp;nbsp;&lt;br&gt;private $genre_table;&lt;/pre&gt;&lt;br&gt;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.&lt;br&gt;&lt;br&gt;La primera línea en el método del constructor carga la biblioteca de base de datos de Kohana en &lt;code&gt;$this-&amp;gt;db&lt;/code&gt;. En las dos siguientes líneas, se inicializan las dos variables miembro.&lt;br&gt;&lt;pre name="code" class="brush:php"&gt;parent::__construct(); &amp;nbsp;&lt;br&gt;$this-&amp;gt;album_table = 'albums'; &amp;nbsp;&lt;br&gt;$this-&amp;gt;genre_table = 'genres';&lt;/pre&gt;&lt;br&gt;La consulta en el método &lt;code&gt;read&lt;/code&gt; recupera los registros de álbum que tengan un cierto identificador (&lt;code&gt;"$id"&lt;/code&gt;).&lt;br&gt;&lt;pre name="code" class="brush:php"&gt;$this-&amp;gt;db-&amp;gt;where('id', $id); &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; $query = $this-&amp;gt;db-&amp;gt;get($this-&amp;gt;album_table);&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return $query-&amp;gt;result_array();&lt;br&gt;&lt;/pre&gt;&lt;br&gt;La consulta en el método &lt;code&gt;delete&lt;/code&gt; elimina la fila de la tabla de álbumes que tenga un cierto identificador (&lt;code&gt;"$id"&lt;/code&gt;).&lt;br&gt;&lt;pre name="code" class="brush:php"&gt;$this-&amp;gt;db-&amp;gt;delete($this-&amp;gt;album_table, array('id' =&amp;gt; $id));&lt;/pre&gt;&lt;br&gt;La consulta en el método &lt;code&gt;update&lt;/code&gt; actualiza la fila de la tabla de álbumes que tenga un cierto identificador (&lt;code&gt;"$id"&lt;/code&gt;) con los nuevos valores del &lt;i&gt;array&lt;/i&gt; &lt;code&gt;"$data"&lt;/code&gt;.&lt;br&gt;&lt;pre name="code" class="brush:php"&gt;$this-&amp;gt;db-&amp;gt;update($this-&amp;gt;album_table, $data, array('id' =&amp;gt; $id));&lt;/pre&gt;&lt;br&gt;El &lt;i&gt;array&lt;/i&gt; &lt;code&gt;"$data"&lt;/code&gt; debe contener nombres de registros como claves del &lt;i&gt;array&lt;/i&gt;, y sus valores como valores del &lt;i&gt;array&lt;/i&gt;. El &lt;i&gt;array&lt;/i&gt; debe tener esta forma:&lt;br&gt;&lt;pre name="code" class="brush:php"&gt;$data = array( &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 'name'&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; =&amp;gt;&amp;nbsp;&amp;nbsp; 'album_name', &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 'author'&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; =&amp;gt;&amp;nbsp;&amp;nbsp; 'author_name', &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 'genre_id'&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; =&amp;gt;&amp;nbsp;&amp;nbsp; 'genre_id'&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; );&lt;/pre&gt;&lt;br&gt;La consulta en el método &lt;code&gt;get_list&lt;/code&gt; recupera todas las filas de álbumes.&lt;br&gt;&lt;pre name="code" class="brush:php"&gt;$this-&amp;gt;db-&amp;gt;select('albums.id as id,albums.name as name,albums.author as author, genres.name as genre');&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;$this-&amp;gt;db-&amp;gt;from($this-&amp;gt;album_table);&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;$this-&amp;gt;db-&amp;gt;join($this-&amp;gt;genre_table,'genres.id','albums.genre_id'); &amp;nbsp;&lt;br&gt;$query = $this-&amp;gt;db-&amp;gt;get(); &amp;nbsp;&lt;br&gt;return $query-&amp;gt;result_array();&lt;/pre&gt;&lt;br&gt;Ahora el modelo para los géneros. Crea un archivo llamado &lt;code&gt;genre.php&lt;/code&gt;  en &lt;b&gt;application/models/&lt;/b&gt; y pega el siguiente código en él:&lt;br&gt;&lt;pre name="code" class="brush:php"&gt;&amp;lt;?php defined('SYSPATH') OR die('No direct access allowed.');&lt;br&gt;&lt;br&gt;class Genre_Model extends Model&lt;br&gt;{&lt;br&gt;&amp;nbsp; private $genre_table;&lt;br&gt;&lt;br&gt;&amp;nbsp; function __construct()&lt;br&gt;&amp;nbsp; {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; parent::__construct();&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; $this-&amp;gt;genre_table = 'genres';&lt;br&gt;&amp;nbsp; }&lt;br&gt;&lt;br&gt;&amp;nbsp; function get_list()&lt;br&gt;&amp;nbsp; {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; $query = $this-&amp;gt;db-&amp;gt;get($this-&amp;gt;genre_table);&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; return&amp;nbsp; $query-&amp;gt;result_array();&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp; }&lt;br&gt;}&lt;br&gt;&lt;/pre&gt;&lt;br&gt;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.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;h1&gt;Paso 9: Creando la vista del proyecto&lt;/h1&gt;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.&lt;br&gt;&lt;br&gt;Crea un archivo llamado &lt;code&gt;list.php&lt;/code&gt; en &lt;b&gt;application/views/&lt;/b&gt; y pega el siguiente código en él:&lt;br&gt;&lt;pre name="code" class="brush:php"&gt;&amp;lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&amp;gt;&lt;br&gt;&amp;lt;html&amp;gt;&lt;br&gt;&amp;lt;head&amp;gt;&lt;br&gt;&amp;lt;?php&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo html::stylesheet(array&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;(&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp; 'assets/css/style'&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;),&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;array&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;(&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp; 'screen'&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;), FALSE);&lt;br&gt;?&amp;gt;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;lt;title&amp;gt;CD COLLECTION&amp;lt;/title&amp;gt;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;lt;/head&amp;gt;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;lt;body&amp;gt;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;lt;?php &lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo html::image('assets/images/add.png');&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo html::anchor('album/show_create_editor', 'Add new album'); &lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;?&amp;gt;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;lt;table class="list" cellspacing="0"&amp;gt;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;lt;tr&amp;gt;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;lt;td colspan="5" class="list_title"&amp;gt;CD Collection&amp;lt;/td&amp;gt;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;lt;/tr&amp;gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;lt;tr&amp;gt;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;lt;td class="headers"&amp;gt;Album name&amp;lt;/td&amp;gt;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;lt;td class="headers"&amp;gt;Author&amp;lt;/td&amp;gt;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;lt;td colspan='3' class="headers"&amp;gt;Genre&amp;lt;/td&amp;gt;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;lt;/tr&amp;gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;lt;?php&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;foreach($albums_list as $item)&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;{&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;tr&amp;gt;";&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;td class='item'&amp;gt;".$item-&amp;gt;name."&amp;lt;/td&amp;gt;";&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;td class='item'&amp;gt;".$item-&amp;gt;author."&amp;lt;/td&amp;gt;";&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;td class='item'&amp;gt;".$item-&amp;gt;genre."&amp;lt;/td&amp;gt;";&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;td class='item'&amp;gt;".html::anchor('album/delete/'.$item-&amp;gt;id,html::image('assets/images/delete.png'))."&amp;lt;/td&amp;gt;";&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;td class='item'&amp;gt;".html::anchor('album/show_update_editor/'.$item-&amp;gt;id,html::image('assets/images/edit.png'))."&amp;lt;/td&amp;gt;";&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;/tr&amp;gt;";&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;}&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;?&amp;gt;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;lt;/table&amp;gt;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;lt;/body&amp;gt;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;lt;/html&amp;gt;&lt;br&gt;&lt;/pre&gt;&lt;br&gt;Esta vista muestra una página &lt;i&gt;html&lt;/i&gt; que contiene una lista de todos los álbumes. Esta lista ha sido creada usando un bucle &lt;code&gt;foreach&lt;/code&gt; que imprime la información en una tabla &lt;i&gt;html&lt;/i&gt;. Para cada fila de álbum, hay dos imágenes: una "cruz roja" y una "libro de bolsillo". Enlazan respectivamente al método &lt;code&gt;delete&lt;/code&gt; y al método &lt;code&gt;update&lt;/code&gt; del controlador. Ambos pasan el identificador del álbum al controlador usando una petición &lt;code&gt;GET&lt;/code&gt;. Sobre la lista hay un botón para crear un nuevo álbum. En este código también se usa un asistente &lt;i&gt;html&lt;/i&gt; ofrecido por Kohana que acelera las operaciones para escribir páginas &lt;i&gt;html&lt;/i&gt;.&lt;br&gt;&lt;br&gt;Creémos ahora un archivo llamado &lt;code&gt;create.php&lt;/code&gt; en &lt;b&gt;application/views/&lt;/b&gt;.&lt;br&gt;&lt;pre name="code" class="brush:php"&gt;&amp;lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&amp;gt;&lt;br&gt;&amp;lt;html&amp;gt;&lt;br&gt;&amp;lt;head&amp;gt;&lt;br&gt;&amp;lt;?php&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo html::stylesheet(array&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;(&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 'assets/css/style'&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;),&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;array&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;(&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 'screen'&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;), FALSE);&lt;br&gt;?&amp;gt;&lt;br&gt;&amp;lt;title&amp;gt;CD COLLECTION&amp;lt;/title&amp;gt;&lt;br&gt;&amp;lt;/head&amp;gt;&lt;br&gt;&amp;lt;body&amp;gt;&lt;br&gt;&amp;lt;?php echo form::open('album/create'); ?&amp;gt;&lt;br&gt;&amp;lt;table class='editor'&amp;gt;&lt;br&gt;&amp;lt;tr&amp;gt;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;lt;td colspan='2' class='editor_title'&amp;gt;Create new album&amp;lt;/td&amp;gt;&lt;br&gt;&amp;lt;/tr&amp;gt;&lt;br&gt;&amp;lt;?php&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;tr&amp;gt;";&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;td&amp;gt;".form::label('name', 'Name: ')."&amp;lt;/td&amp;gt;";&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;td&amp;gt;".form::input('name', '')."&amp;lt;/td&amp;gt;";&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;/tr&amp;gt;";&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;tr&amp;gt;";&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;td&amp;gt;".form::label('author', 'Author: ')."&amp;lt;/td&amp;gt;";&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;td&amp;gt;".form::input('author', '')."&amp;lt;/td&amp;gt;";&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;tr/&amp;gt;";&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;tr&amp;gt;";&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;td&amp;gt;".form::label('genre', 'Genre: ')."&amp;lt;/td&amp;gt;";&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;td&amp;gt;".form::dropdown('genre_id',$genres_list)."&amp;lt;/td&amp;gt;";&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;tr/&amp;gt;";&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;tr&amp;gt;";&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;td colspan='2' align='left'&amp;gt;".form::submit('submit', 'Create album')."&amp;lt;/td&amp;gt;";&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;/tr&amp;gt;";&lt;br&gt;?&amp;gt;&lt;br&gt;&amp;lt;/table&amp;gt;&lt;br&gt;&amp;lt;?php echo form::close(); ?&amp;gt;&lt;br&gt;&amp;lt;/body&amp;gt;&lt;br&gt;&amp;lt;/html&amp;gt;&lt;br&gt;&lt;/pre&gt;&lt;br&gt;La última es la vista de actualización. Crea un archivo &lt;code&gt;update.php&lt;/code&gt; en &lt;b&gt;application/views/&lt;/b&gt;.&lt;br&gt;&lt;pre name="code" class="brush:php"&gt;&amp;lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&amp;gt;&lt;br&gt;&amp;lt;html&amp;gt;&lt;br&gt;&amp;lt;head&amp;gt;&lt;br&gt;&amp;lt;?php&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo html::stylesheet(array&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;(&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 'assets/css/style'&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;),&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;array&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;(&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 'screen'&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;), FALSE);&lt;br&gt;?&amp;gt;&lt;br&gt;&amp;lt;title&amp;gt;CD COLLECTION&amp;lt;/title&amp;gt;&lt;br&gt;&amp;lt;/head&amp;gt;&lt;br&gt;&amp;lt;body&amp;gt;&lt;br&gt;&amp;lt;?php echo form::open('album/update'); ?&amp;gt;&lt;br&gt;&amp;lt;table class='editor'&amp;gt;&lt;br&gt;&amp;lt;tr&amp;gt;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;lt;td colspan='2' class='editor_title'&amp;gt;Update album&amp;lt;/td&amp;gt;&lt;br&gt;&amp;lt;/tr&amp;gt;&lt;br&gt;&amp;lt;?php&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;tr&amp;gt;";&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;td&amp;gt;".form::label('name', 'Name: ')."&amp;lt;/td&amp;gt;";&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;td&amp;gt;".form::input('name', $name)."&amp;lt;/td&amp;gt;";&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;/tr&amp;gt;";&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;tr&amp;gt;";&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;td&amp;gt;".form::label('author', 'Author: ')."&amp;lt;/td&amp;gt;";&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;td&amp;gt;".form::input('author', $author)."&amp;lt;/td&amp;gt;";&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;tr/&amp;gt;";&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;tr&amp;gt;";&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;td&amp;gt;".form::label('genre', 'Genre: ')."&amp;lt;/td&amp;gt;";&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;td&amp;gt;".form::dropdown('genre_id',$genres_list, $genre_id)."&amp;lt;/td&amp;gt;";&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;tr/&amp;gt;";&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;tr&amp;gt;";&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;td colspan='2' align='left'&amp;gt;".form::submit('submit', 'Update album')."&amp;lt;/td&amp;gt;";&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo "&amp;lt;/tr&amp;gt;";&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br&gt;?&amp;gt;&lt;br&gt;&amp;lt;/table&amp;gt;&lt;br&gt;&amp;lt;?php &lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo form::hidden('album_id',$album_id);&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;echo form::close(); &lt;br&gt;?&amp;gt;&lt;br&gt;&amp;lt;/body&amp;gt;&lt;br&gt;&amp;lt;/html&amp;gt;&lt;br&gt;&lt;/pre&gt;&lt;br&gt;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 &lt;i&gt;html&lt;/i&gt; y el género usando un &lt;i&gt;combo&lt;/i&gt;. Una vez que el usuario hace click en el botón &lt;i&gt;create&lt;/i&gt;, toda la información se pasa como una petición &lt;code&gt;POST&lt;/code&gt; al método &lt;code&gt;create&lt;/code&gt;/&lt;code&gt;update&lt;/code&gt; del controlador de álbum. Cuando el controlador recibe estas variables &lt;i&gt;posteadas&lt;/i&gt; llama al modelo que inserta un nuevo álbum en la base de datos. Los formularios de ambas vistas hacen uso del asistente de formularios.&lt;br&gt;&lt;br&gt;Para dar un poco de estilo a nuestra aplicación, crea la carpeta &lt;b&gt;assets&lt;/b&gt; en la carpeta raíz de Kohana al mismo nivel de la carpeta de aplicaciones. Ábrelo y crea dos nuevas carpetas: &lt;b&gt;css&lt;/b&gt; e &lt;b&gt;images&lt;/b&gt;.&lt;br&gt;&lt;br&gt;En la carpeta &lt;b&gt;css&lt;/b&gt; crea un nuevo archivo llamado &lt;code&gt;style.css&lt;/code&gt; y pega lo siguiente en él:&lt;br&gt;&lt;pre name="code" class="brush:css"&gt;a {&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;font-family: Verdana, Geneva, Arial, Helvetica, sans-serif ;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;font-weight: normal;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;font-size: 12px;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;color: #00F;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;vertical-align:text-top;&lt;br&gt;}&lt;br&gt;&lt;br&gt;img {&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;border: 0;&lt;br&gt;}&lt;br&gt;&lt;br&gt;label {&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;font-family: Verdana, Geneva, Arial, Helvetica, sans-serif ;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;font-weight: normal;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;font-size: 12px;&lt;br&gt;}&lt;br&gt;&lt;br&gt;input {&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;border: 1px solid #000;&lt;br&gt;}&lt;br&gt;&lt;br&gt;select {&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;width:185px;&lt;br&gt;}&lt;br&gt;&lt;br&gt;table.editor&lt;br&gt;{&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;text-align: center;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;font-family: Verdana, Geneva, Arial, Helvetica, sans-serif ;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;font-weight: normal;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;font-size: 11px;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;color: #fff;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;width: 280px;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;background-color: #666;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;border: 0px;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;border-collapse: collapse;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;border-spacing: 0px;&lt;br&gt;}&lt;br&gt;&lt;br&gt;table.editor td.editor_title&lt;br&gt;{&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;background-color: #666;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;color: #fff;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;padding: 4px;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;text-align: left;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;font-weight: bold;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;font-size: 16px;&lt;br&gt;} &lt;br&gt;&lt;br&gt;table.editor td&lt;br&gt;{&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;padding: 4px;&lt;br&gt;} &lt;br&gt;&lt;br&gt;table.list&lt;br&gt;{&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;text-align: center;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;font-family: Verdana, Geneva, Arial, Helvetica, sans-serif ;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;font-weight: normal;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;font-size: 11px;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;color: #fff;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;width: 280px;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;background-color: #666;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;border: 0px;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;border-collapse: collapse;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;border-spacing: 0px;&lt;br&gt;}&lt;br&gt;&lt;br&gt;table.list td.item&lt;br&gt;{&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;background-color: #CCC;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;color: #000;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;padding: 4px;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;text-align: left;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;border: 1px #fff solid;&lt;br&gt;}&lt;br&gt;&lt;br&gt;table.list td.list_title,table.list td.headers&lt;br&gt;{&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;background-color: #666;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;color: #fff;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;padding: 4px;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;text-align: left;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;border-bottom: 2px #fff solid;&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;font-weight: bold;&lt;br&gt;} &lt;br&gt;&lt;br&gt;table.list td.list_title&lt;br&gt;{&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;font-size: 16px;&lt;br&gt;} &lt;br&gt;&lt;br&gt;table.list td.headers&lt;br&gt;{&lt;br&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;font-size: 12px;&lt;br&gt;}&lt;/pre&gt;&lt;br&gt;Ahora copia las siguientes imágenes en la carpeta &lt;b&gt;images&lt;/b&gt;: &lt;img src="http://nettuts.s3.amazonaws.com/376_kohana/add.png"&gt; &lt;img src="http://nettuts.s3.amazonaws.com/376_kohana/delete.png"&gt; &lt;img src="http://nettuts.s3.amazonaws.com/376_kohana/edit.png"&gt;&lt;br&gt;&lt;br&gt;Eso es todo. Apunta el navegador a &lt;a href="http://localhost/kohana/index.php/album"&gt;http://localhost/kohana/index.php/album&lt;/a&gt;  y deberías ver algo parecido a esto:&lt;br&gt;&lt;br&gt;&lt;img src="http://farm3.static.flickr.com/2503/4185016953_856b5a6acd_o.jpg"&gt;&lt;br&gt;&lt;br&gt;Si intentas crear un nuevo álbum o editar uno existente, deberías ver algo similar a esto:&lt;br&gt;&lt;br&gt;&lt;img src="http://farm3.static.flickr.com/2696/4185777152_39b1a274fd_o.jpg"&gt;&lt;br&gt;&lt;br&gt;&lt;h1&gt;Paso 10: Pensamientos finales&lt;/h1&gt;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 &lt;a href="http://docs.kohanaphp.com/"&gt;documentación oficial&lt;/a&gt;.&lt;br&gt;&lt;br&gt;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.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/112773462326884407-3188487712865447218?l=digitta.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://digitta.com/feeds/3188487712865447218/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://digitta.com/2009/12/empezando-con-kohana-mejor-que.html#comment-form" title="4 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/3188487712865447218?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/3188487712865447218?v=2" /><link rel="alternate" type="text/html" href="http://digitta.com/2009/12/empezando-con-kohana-mejor-que.html" title="Empezando con Kohana, ¿mejor que CodeIgniter?" /><author><name>Àl</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11672226968306882234" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">4</thr:total></entry><entry gd:etag="W/&quot;A08MQ38_eCp7ImA9WxNaGUs.&quot;"><id>tag:blogger.com,1999:blog-112773462326884407.post-7222618646797350872</id><published>2009-12-05T00:38:00.000+01:00</published><updated>2009-12-05T00:38:02.140+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-05T00:38:02.140+01:00</app:edited><title>No toquéis el "with"</title><content type="html">En los últimos tiempos se ha satanizado la orden &lt;code&gt;with&lt;/code&gt;, por parte de personas de renombre como el mismo Douglas Crockford. En su artículo &lt;a href="http://yuiblog.com/blog/2006/04/11/with-statement-considered-harmful/"&gt;&lt;code&gt;With&lt;/code&gt; statement considered harmful&lt;/a&gt; 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 &lt;code&gt;with&lt;/code&gt; se asigna un valor a una variable, no hay forma de saber si se está haciendo sobre un miembro del objeto pasado con el &lt;code&gt;with&lt;/code&gt; o si se está modificando una variable global:

&lt;pre name="code" class="brush:js"&gt;
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
&lt;/pre&gt;

De hecho &lt;a href="http://www.crockford.com/javascript/recommend.html"&gt;propone en sus recomendaciones&lt;/a&gt; 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 &lt;a href="http://webreflection.blogspot.com/2009/12/with-some-good-example.html"&gt;le saca punta al asunto&lt;/a&gt; como &lt;a href="http://webreflection.blogspot.com/2008/07/scary-eval-to-create-most-safe.html"&gt;ya hizo&lt;/a&gt; con la polémica del &lt;i&gt;desconocido&lt;/i&gt; segundo parámetro de &lt;code&gt;eval&lt;/code&gt;, 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 &lt;code&gt;callee&lt;/code&gt; ante la que Andrea &lt;a href="http://webreflection.blogspot.com/2009/05/ecmascript-5-do-not-remove.html"&gt;defendió&lt;/a&gt; &lt;a href="http://webreflection.blogspot.com/2009/06/do-not-remove-argumentscallee-part-ii.html"&gt;que no&lt;/a&gt; &lt;a href="http://webreflection.blogspot.com/2009/06/es5-arguments-and-callee-i-was-wrong.html"&gt;se hiciese&lt;/a&gt;.

Con esta sencilla estructura se consigue mucha más potencia que con el uso tradicional pasando al &lt;code&gt;with&lt;/code&gt; una variable con un objeto:

&lt;pre name="code" class="brush:js"&gt;
with({o:myreference}){
    o.doStuff();
    o.var1 = "loquesea";
    // etc etc ...
};
&lt;/pre&gt;

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:

&lt;pre name="code" class="brush:js"&gt;
with(document)
  with(documentElement)
    insertBefore(
      createElement("script"),
      firstChild
    )
    .text = "alert(1)"
;
&lt;/pre&gt;

Sólo que además permite aprovechar esa definición &lt;i&gt;en línea&lt;/i&gt; del parámetro-objeto para realizar llamadas a funciones, usar el operador terciario ( condicion ? cierto : falso ), o especificar parámetros por defecto:

&lt;pre name="code" class="brush:js"&gt;
// 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 &lt; length; ++i){
    tmp = callback.call(obj, collection[i]);
    if(tmp){
      tmp.doStuff();
      collection[i] = tmp;
    };
  };
};
&lt;/pre&gt;

Que resulta ser equivalente a los típicos &lt;i&gt;closures&lt;/i&gt; tan utilizados. Otro uso interesante sería la tradicional llamada Ajax:

&lt;pre name="code" class="brush:js"&gt;
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);
}&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/112773462326884407-7222618646797350872?l=digitta.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://digitta.com/feeds/7222618646797350872/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://digitta.com/2009/12/no-toqueis-el-with.html#comment-form" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/7222618646797350872?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/7222618646797350872?v=2" /><link rel="alternate" type="text/html" href="http://digitta.com/2009/12/no-toqueis-el-with.html" title="No toquéis el &quot;with&quot;" /><author><name>Àl</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11672226968306882234" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;Ak4CR3g_eip7ImA9WxNaEk4.&quot;"><id>tag:blogger.com,1999:blog-112773462326884407.post-5844778637562893144</id><published>2009-11-26T13:25:00.004+01:00</published><updated>2009-11-26T13:36:06.642+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-11-26T13:36:06.642+01:00</app:edited><title>Empezando con las extensiones de Chrome</title><content type="html">En breve aparecerá la primera versión de &lt;a href="http://code.google.com/chrome/"&gt;Chrome&lt;/a&gt;/&lt;a href="http://www.chromium.org/"&gt;Chromium&lt;/a&gt; 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:
&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt; Mostrar iconos en la barra de direcciones al visitar ciertas páginas:

&lt;br /&gt;&lt;br /&gt;&lt;img src="http://code.google.com/chrome/extensions/images/index/page_action.png" /&gt;&lt;br /&gt;&lt;br /&gt;

&lt;/li&gt;
&lt;li&gt;Añadir botones a la barra de herramientas (con opción a mostrar un desplegable con más acciones):&lt;br /&gt;&lt;br /&gt;

&lt;img src="http://code.google.com/chrome/extensions/images/index/browser_action.png" /&gt;&lt;br /&gt;&lt;br /&gt;

&lt;/li&gt;
&lt;li&gt;Modificar la apariencia y comportamiento de las páginas web mostradas
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;


Tutorial: Hola mundo!&lt;/h1&gt;
Nota: traducido de &lt;a href="http://code.google.com/chrome/extensions/index.html"&gt;aquí&lt;/a&gt;.


Vamos a crear un nuevo botón en Chrome que al ser pulsado mostrará una página generada automáticamente. Algo así:&lt;br /&gt;
&lt;br /&gt;
&lt;img src="http://code.google.com/chrome/extensions/images/hello-world-small.png" /&gt;&lt;br /&gt;
&lt;br /&gt;
Para empezar, necesitarás la &lt;a href="http://dev.chromium.org/getting-involved/dev-channel"&gt;versión de Chrome del canal de desarrolladores&lt;/a&gt;, 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:
&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt; Crea una carpeta llamada "hola".
&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt; Crea un archivo llamado &lt;code&gt;manifest.json&lt;/code&gt; con este contenido:



&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:js" name="code"&gt;{
  "name": "Mi Primera Extension",
  "version": "1.0",
  "description": "Esta es la primera extension.",
  "browser_action": {
    "default_icon": "icon.png"
  },
  "permissions": [
    "http://api.flickr.com/"
  ]
}

&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt; Descarga el siguiente icono a la misma carpeta.
&lt;br /&gt;&lt;a href="http://code.google.com/chrome/extensions/examples/tutorials/getstarted/icon.png"&gt;&lt;img src="http://code.google.com/chrome/extensions/examples/tutorials/getstarted/icon.png" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/a&gt;

&lt;/li&gt;
&lt;li&gt; Carga la extensión:



&lt;br /&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;Abre la página de gestión de extensiones pulsando el menú &lt;img align="absmiddle" src="http://code.google.com/chrome/extensions/images/toolsmenu.gif" /&gt; y seleccionando &lt;code&gt;Extensiones...&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Si el &lt;code&gt;modo desarrollador&lt;/code&gt; tiene un símbolo &lt;code&gt;&lt;b&gt;+&lt;/b&gt;&lt;/code&gt;, púlsalo para añadir información sobre el desarrollador. El símbolo &lt;code&gt;&lt;b&gt;+&lt;/b&gt;&lt;/code&gt; cambia a &lt;code&gt;&lt;b&gt;-&lt;/b&gt;&lt;/code&gt; y aparecerán más botones e información.&lt;br /&gt; &lt;br /&gt;&lt;/li&gt;
&lt;li&gt;Pulsa el botón &lt;code&gt;Carga extensión...&lt;/code&gt; y aparecerá un diálogo de selección de archivo.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;En el diálogo selecciona la carpeta "hola" y pulsa &lt;code&gt;OK&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
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:

&lt;br /&gt;&lt;br /&gt;&lt;img src="http://code.google.com/chrome/extensions/images/load_after.png" /&gt;&lt;br /&gt;&lt;br /&gt;

&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt; Añade código a la extensión. Edita el archivo &lt;code&gt;manifest.json&lt;/code&gt; y agrega la siguiente línea:



&lt;br /&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;...
"browser_action": {
  "default_icon": "icon.png"&lt;span style="background: none repeat scroll 0% 0% yellow;"&gt;,
  "popup": "popup.html"&lt;/span&gt;
},
...
&lt;/pre&gt;
&lt;br /&gt;

&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Dentro de la carpeta "hola", crea un archivo de texto llamado &lt;code&gt;popup.html&lt;/code&gt; y añade el siguiente código:

&lt;a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/tutorials/getstarted/popup.html?content-type=text/plain" target="_blank"&gt;Sigue este enlace para ver el código&lt;br /&gt;&lt;br /&gt;&lt;/a&gt;

&lt;/li&gt;
&lt;li&gt; Vuelve a la página de gestión de las extensiones y recárgala para ver la nueva versión de la extensión.

&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt; 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 &lt;code&gt;popup.html&lt;/code&gt;:

&lt;br /&gt;&lt;br /&gt;&lt;img src="http://code.google.com/chrome/extensions/images/hello-world.png" /&gt;
&lt;/li&gt;
&lt;/ol&gt;
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á.&lt;br /&gt;
&lt;br /&gt;
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 &lt;a href="http://code.google.com/chrome/extensions/devguide.html"&gt;Developer's Guide&lt;/a&gt;. 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.
&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/112773462326884407-5844778637562893144?l=digitta.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://digitta.com/feeds/5844778637562893144/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://digitta.com/2009/11/empezando-con-las-extensiones-de-chrome.html#comment-form" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/5844778637562893144?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/5844778637562893144?v=2" /><link rel="alternate" type="text/html" href="http://digitta.com/2009/11/empezando-con-las-extensiones-de-chrome.html" title="Empezando con las extensiones de Chrome" /><author><name>Àl</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11672226968306882234" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;Ak8GQHs-eCp7ImA9WxNbF0g.&quot;"><id>tag:blogger.com,1999:blog-112773462326884407.post-8903476306611993861</id><published>2009-11-21T00:13:00.000+01:00</published><updated>2009-11-21T00:13:41.550+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-11-21T00:13:41.550+01:00</app:edited><title>Persevere, Node.JS y HelmaNG</title><content type="html">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:&lt;br /&gt;
&lt;br /&gt;
&lt;h1&gt;&lt;a href="http://www.persvr.org/"&gt;Persevere 1.0&lt;/a&gt;&lt;/h1&gt;&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Proyecto perteneciente a la Dojo Foundation, autores del framework cliente de JavaScript&lt;/li&gt;
&lt;li&gt;Usa una interfície basada en estándares: HTTP/REST, JSON-RPC, JSONPath, y canales REST&lt;/li&gt;
&lt;li&gt;Está basado en el motor Rhino (Java) al igual que Helma&lt;/li&gt;
&lt;li&gt;Ofrece almacenamiento persistente de datos JSON dinámicos (objetos, arrays y funciones)&lt;/li&gt;
&lt;li&gt;Operaciones de creación, lectura, actualización y borrado a través de una interfície web &lt;a href="http://www.ietf.org/rfc/rfc2616.txt"&gt;HTTP/REST&lt;/a&gt; con &lt;a href="http://www.json.org/"&gt;JSON&lt;/a&gt; como formato&lt;/li&gt;
&lt;li&gt;Ejecución remota de funciones JavaScript en el servidor usando &lt;a href="http://json-rpc.org/"&gt;JSON-RPC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Capacidad de consultas indexadas flexible y rápida mediante &lt;a href="http://www.sitepen.com/blog/2008/07/16/jsonquery-data-querying-beyond-jsonpath/"&gt;JSONQuery&lt;/a&gt;/&lt;a href="http://goessner.net/articles/JsonPath/"&gt;JSONPath&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Capacidades de monitorización basadas en &lt;a href="http://www.sitepen.com/technology/"&gt;Comet&lt;/a&gt; a través de canales HTTP con transporte &lt;a href="http://www.sitepen.com/labs/cometd.php"&gt;Bayeux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Seguridad a nivel de objeto basada en reglas de datos y con gestión de usuarios&lt;/li&gt;
&lt;li&gt;Soporte de &lt;a href="http://www.json.com/2007/10/19/json-referencing-proposal-and-library/"&gt;Json Referencing&lt;/a&gt; y &lt;a href="http://groups.google.com/group/json-schema"&gt;JSON Schema&lt;/a&gt; (Validación e integridad de datos)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://persevere.sitepen.com/#structure"&gt;Jerarquía de datos basada en clases&lt;/a&gt; para facilitar consultas y herencia&lt;/li&gt;
&lt;li&gt;Arquitectura de fuentes de datos conectables: servicios web remotos, tablas SQL y archivos XML pueden ser usados como almacenes de datos&lt;/li&gt;
&lt;li&gt;Versiones de objetos con histórico transaccional de estados de los registros&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
&lt;blockquote&gt;En definitiva, &lt;b&gt;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&lt;/b&gt;. Ver &lt;a href="http://docs.persvr.org/"&gt;documentación&lt;/a&gt;.&lt;br /&gt;
&lt;/blockquote&gt;&lt;br /&gt;
La siguiente versión estará basada en &lt;a href="http://github.com/kriszyp/pintura"&gt;Pintura&lt;/a&gt;, que es una nueva abstracción del núcleo JavaScript de Persevere, de forma que respeta los estándares de JavaScript en el servidor (&lt;a href="http://wiki.commonjs.org/"&gt;CommonJS&lt;/a&gt; y &lt;a href="http://jackjs.org/jsgi-spec.html"&gt;JSGI&lt;/a&gt;) para permitir el soporte de motores compatibles (V8, JSCore, y Spidermonkey).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h1&gt;&lt;a href="http://nodejs.org/"&gt;Node.JS&lt;/a&gt;&lt;/h1&gt;&lt;br /&gt;
Node.js es un &lt;i&gt;framework&lt;/i&gt; 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 &lt;a href="http://twistedmatrix.com/"&gt;Twisted&lt;/a&gt; de Python o &lt;a href="http://rubyeventmachine.com/"&gt;EventMachine&lt;/a&gt; de Ruby.&lt;br /&gt;
&lt;blockquote&gt;La idea principal detrás de Node.js es la simplicidad. Aprovechando las funciones típicas &lt;i&gt;callback&lt;/i&gt; 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, &lt;b&gt;asíncronas&lt;/b&gt;, 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.&lt;br /&gt;
&lt;/blockquote&gt;&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:js" name="code"&gt;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/");
&lt;/pre&gt;&lt;br /&gt;
En la llamada a la función &lt;code&gt;http.createServer&lt;/code&gt; se pasa como parámetro la función &lt;i&gt;callback&lt;/i&gt; que será llamada cuando se reciba una petición de conexión. Más simple imposible. De hecho Simon Willison &lt;a href="http://ajaxian.com/archives/full-frontal-09-simon-willison-on-server-side-javascript-and-node-js"&gt;ha cambiado en el último momento el asunto de su charla&lt;/a&gt; en Full Frontal '09 para hablar de los resultados que está obteniendo con esta revolucionaria herramienta.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h1&gt;&lt;a href="http://helma.org/wiki/Helma+NG/"&gt;HelmaNG&lt;/a&gt;&lt;/h1&gt;&lt;br /&gt;
Es la última versión del más veterano de los servidores de aplicación basados en JavaScript. NG quiere decir &lt;i&gt;Next Generation&lt;/i&gt; 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.&lt;br /&gt;
&lt;br /&gt;
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 &lt;i&gt;framework&lt;/i&gt; web construida sobre los elementos anteriores y con código JavaScript 100%. &lt;br /&gt;
&lt;blockquote&gt;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 &lt;a href="http://dev.helma.org/ng"&gt;equipo de programadores&lt;/a&gt; es principalmente centro-europeo, donde cuenta con una importante base de usuarios.&lt;br /&gt;
&lt;/blockquote&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/112773462326884407-8903476306611993861?l=digitta.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://digitta.com/feeds/8903476306611993861/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://digitta.com/2009/11/persevere-nodejs-y-helmang.html#comment-form" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/8903476306611993861?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/8903476306611993861?v=2" /><link rel="alternate" type="text/html" href="http://digitta.com/2009/11/persevere-nodejs-y-helmang.html" title="Persevere, Node.JS y HelmaNG" /><author><name>Àl</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11672226968306882234" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;DU8DQHo4eyp7ImA9WxNbEEs.&quot;"><id>tag:blogger.com,1999:blog-112773462326884407.post-1274785919423201162</id><published>2009-11-13T00:14:00.001+01:00</published><updated>2009-11-13T00:17:51.433+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-11-13T00:17:51.433+01:00</app:edited><title>spdy://</title><content type="html">Los responsables del navegador Chromium proponen la adopción de un nuevo protocolo llamado &lt;a href="http://dev.chromium.org/spdy/spdy-whitepaper"&gt;spdy&lt;/a&gt; (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:

&lt;ul&gt;&lt;li&gt;Una sola petición por conexión, suplido en parte por el HTTP pipelining o conexiones múltiples en el navegador&lt;/li&gt;
&lt;li&gt;Peticiones iniciadas exclusivamente por el cliente, aunque el servidor tenga información que deba ser recibida por el cliente, no puede avisar a éste&lt;/li&gt;
&lt;li&gt;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&lt;/li&gt;
&lt;li&gt;Cabeceras redundantes se repiten en cada petición debido a que para el servidor el cliente siempre es "nuevo"&lt;/li&gt;
&lt;li&gt;La compresión de datos es opcional y no obligatoria&lt;/li&gt;
&lt;/ul&gt;

&lt;a href="http://dev.chromium.org/spdy/spdy-whitepaper#TOC-Previous-approaches"&gt;Otros enfoques&lt;/a&gt; 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:
&lt;ul&gt;&lt;li&gt;Reducir el 50% en los tiempos de carga&lt;/li&gt;
&lt;li&gt;Aprovechar el protocolo TCP existente para evitar requerir una infraestructura de red distinta a la actual&lt;/li&gt;
&lt;li&gt;Evitar modificaciones en el contenido existente (HTML)&lt;/li&gt;
&lt;li&gt;Conseguir varias peticiones concurrentes sobre una misma sesión TCP&lt;/li&gt;
&lt;li&gt;Comprimir las cabeceras y reducir campos innecesarios&lt;/li&gt;
&lt;li&gt;Simplificar el protocolo para facilitar su implementación y a la vez optimizarlo&lt;/li&gt;
&lt;li&gt;Mantener SSL como protocolo subyacente para aumentar la seguridad a pesar del incremento en latencia&lt;/li&gt;
&lt;li&gt;Permitir que el servidor envíe información al cliente cuando lo desee&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Características&lt;/h2&gt;

&lt;code&gt;spdy&lt;/code&gt; 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).

&lt;ul&gt;&lt;li&gt;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.&lt;/li&gt;&lt;li&gt;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.&lt;/li&gt;&lt;li&gt;Compresión de las cabeceras HTTP&lt;/li&gt;&lt;li&gt;El servidor puede enviar información al cliente mediante una cabecera &lt;code&gt;X-Associated-Content&lt;/code&gt; que informa al cliente de nueva información disponible antes de que el éste la solicite.&lt;/li&gt;&lt;li&gt;El servidor usa la cabecera &lt;code&gt;X-Subresources&lt;/code&gt; para sugerir al cliente que solicite ciertos recursos aunque no se los envíe directamente al cliente.&lt;/li&gt;&lt;/ul&gt;

Más información en &lt;a href="http://dev.chromium.org/spdy/spdy-protocol"&gt;el borrador de la especificación&lt;/a&gt;, en &lt;a href="http://alex.dojotoolkit.org/2009/11/spdy-the-web-only-faster/"&gt;este artículo&lt;/a&gt; de Alex Russell (Dojo Toolkit) y en &lt;a href="http://dev.chromium.org/spdy/spdy-protocol"&gt;el artículo introductorio&lt;/a&gt;. 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.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/112773462326884407-1274785919423201162?l=digitta.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://digitta.com/feeds/1274785919423201162/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://digitta.com/2009/11/spdy.html#comment-form" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/1274785919423201162?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/1274785919423201162?v=2" /><link rel="alternate" type="text/html" href="http://digitta.com/2009/11/spdy.html" title="spdy://" /><author><name>Àl</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11672226968306882234" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;DkMEQnk5eyp7ImA9WxNUGEs.&quot;"><id>tag:blogger.com,1999:blog-112773462326884407.post-2479086816413620833</id><published>2009-11-10T15:45:00.002+01:00</published><updated>2009-11-10T15:46:43.723+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-11-10T15:46:43.723+01:00</app:edited><title>NodeJS</title><content type="html">&lt;a href="http://nodejs.org/"&gt;&lt;img src="http://nodejs.org/logo.png" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
“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 &lt;a href="http://nodejs.org/api.html"&gt;la API&lt;/a&gt; es soberbio — todo se consigue usando eventos y llamadas &lt;i&gt;callback&lt;/i&gt; 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. &lt;br /&gt;
&lt;br /&gt;
En general es muy similar a &lt;a href="http://twistedmatrix.com/"&gt;Twisted&lt;/a&gt; (Python) y otras por el estilo, pero la sintaxis de funciones anónimas de JavaScript parece más natural que su equivalente en Python.&lt;br /&gt;
&lt;br /&gt;
El protocolario "Hola mundo":&lt;br /&gt;
&lt;br /&gt;
&lt;pre name="code" class="brush:js;toolbar:false;"&gt;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/');
&lt;/pre&gt;&lt;br /&gt;
O un servidor que escucha en el puerto 7000 y devuelve lo mismo que recibe (&lt;i&gt;echo&lt;/i&gt;):&lt;br /&gt;
&lt;br /&gt;
&lt;pre name="code" class="brush:js;toolbar:false;"&gt;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");
&lt;/pre&gt;&lt;br /&gt;
El objetivo de &lt;code&gt;Node&lt;/code&gt; 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. &lt;code&gt;Node&lt;/code&gt; indica al sistema operativo (a través de &lt;code&gt;epoll&lt;/code&gt;, &lt;code&gt;kqueue&lt;/code&gt;, &lt;code&gt;/dev/poll&lt;/code&gt;, o &lt;code&gt;select&lt;/code&gt;) 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 &lt;i&gt;callback&lt;/i&gt;, si el tiempo de espera se termina, se ejecuta la llamada &lt;i&gt;callback&lt;/i&gt; interna. Cada conexión consume sólo una pequeña parte del &lt;i&gt;heap&lt;/i&gt;.&lt;br /&gt;
&lt;br /&gt;
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 &lt;a href="http://www.sics.se/~joe/apachevsyaws.html"&gt;es&lt;/a&gt; &lt;a href="http://www.kegel.com/c10k.html"&gt;relativamente&lt;/a&gt; &lt;a href="http://bulk.fefe.de/scalable-networking.pdf"&gt;ineficiente&lt;/a&gt; y muy difícil de usar. &lt;code&gt;Node&lt;/code&gt; 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 &lt;code&gt;node&lt;/code&gt; no deben preocuparse de bloquear el proceso - no hay &lt;i&gt;locks&lt;/i&gt;. Casi ninguna función en &lt;code&gt;node&lt;/code&gt; 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.&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;Node&lt;/code&gt; es similar en diseño a sistemas como la &lt;a href="http://rubyeventmachine.com/"&gt;Event Machine&lt;/a&gt; de Ruby o &lt;a href="http://twistedmatrix.com/"&gt;Twisted&lt;/a&gt; de Python, y está influido por ellos. &lt;code&gt;Node&lt;/code&gt; 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 &lt;i&gt;callback&lt;/i&gt; al comienzo de un &lt;i&gt;script&lt;/i&gt;, y al final se inicia un servidor a través de una llamada bloqueante como &lt;code&gt;EventMachine::run()&lt;/code&gt;. En &lt;code&gt;node&lt;/code&gt; no existe esa llamada &lt;i&gt;bucle-de-inicio-del-evento&lt;/i&gt;. &lt;code&gt;node&lt;/code&gt; simplemente entra en el bucle de eventos después de ejecutar el &lt;i&gt;script&lt;/i&gt; de entrada. &lt;code&gt;node&lt;/code&gt; sale del bucle de eventos cuando no hay más llamadas &lt;i&gt;callback&lt;/i&gt; a realizar. Este comportamiento es como el del Javascript del navegador — el bucle de eventos está oculto para el usuario.&lt;br /&gt;
&lt;br /&gt;
HTTP es un protocolo de primera clase en &lt;code&gt;node&lt;/code&gt;. La Biblioteca HTTP de &lt;code&gt;node&lt;/code&gt; ha crecido a partir de las experiencias del autor desarrollando y trabajando con servidores web. Por ejemplo, enviar datos por &lt;i&gt;streaming&lt;/i&gt; a través de la mayoría de frameworks web es imposible. &lt;code&gt;node&lt;/code&gt; intenta corregir estos problemas con su &lt;a href="http://github.com/ry/http-parser/tree/master"&gt;parser&lt;/a&gt; HTTP y su API. La infraestructura puramente basada en eventos de &lt;code&gt;node&lt;/code&gt; constituye una buena base para crear bibliotecas web o &lt;i&gt;frameworks&lt;/i&gt;.&lt;br /&gt;
&lt;br /&gt;
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, &lt;code&gt;node&lt;/code&gt; podrá generar nuevos procesos (usando la &lt;a href="http://www.whatwg.org/specs/web-workers/current-work/"&gt;API de Web Workers&lt;/a&gt;), pero eso es algo que ya encaja muy bien en el diseño actual.&lt;br /&gt;
&lt;br /&gt;
Via &lt;a href="http://simonwillison.net/2009/Nov/9/node/"&gt;Simon Willison&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/112773462326884407-2479086816413620833?l=digitta.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://digitta.com/feeds/2479086816413620833/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://digitta.com/2009/11/nodejs.html#comment-form" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/2479086816413620833?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/2479086816413620833?v=2" /><link rel="alternate" type="text/html" href="http://digitta.com/2009/11/nodejs.html" title="NodeJS" /><author><name>Àl</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11672226968306882234" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;DE4MSH46eCp7ImA9WxNUGEo.&quot;"><id>tag:blogger.com,1999:blog-112773462326884407.post-7972445882168560186</id><published>2009-11-09T09:49:00.003+01:00</published><updated>2009-11-10T19:16:29.010+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-11-10T19:16:29.010+01:00</app:edited><title>Herramientas de Google Closure</title><content type="html">He traducido el &lt;a href="http://googlecode.blogspot.com/2009/11/introducing-closure-tools.html"&gt;artículo   de presentación&lt;/a&gt; y otros de la documentación sobre las nuevas herramientas presentadas por Google hace unos días:&lt;br /&gt;
&lt;br /&gt;
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 &lt;i&gt;Googlers&lt;/i&gt; (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.&lt;br /&gt;
&lt;br /&gt;
&lt;h1&gt;El compilador de Closure&lt;/h1&gt;&lt;br /&gt;
&lt;a href="http://code.google.com/closure/compiler"&gt;El compilador de Closure&lt;/a&gt;  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 &lt;a href="http://code.google.com/closure/compiler/docs/inspector.html"&gt;inspector de Closure&lt;/a&gt;, 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.&lt;br /&gt;
&lt;br /&gt;
Los beneficios de usar el compilador son:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;b&gt;Eficiencia&lt;/b&gt;.  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.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Comprobación de código&lt;/b&gt;. 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.&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
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 &lt;a href="http://code.google.com/p/closure-compiler/downloads/list"&gt;línea de  comandos&lt;/a&gt;. Hemos creado una &lt;a href="http://closure-compiler.appspot.com/"&gt;aplicación web&lt;/a&gt; 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 &lt;a href="http://code.google.com/speed/page-speed/download.html"&gt;Page Speed&lt;/a&gt;  para ver convenientemente los beneficios en rendimiento de las páginas  web.&lt;br /&gt;
&lt;br /&gt;
Para &lt;a href="http://code.google.com/closure/compiler/docs/gettingstarted_app.html"&gt;probar el compilador&lt;/a&gt;:&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;&lt;b&gt;Descarga el paquete del compilador de  Closure&lt;/b&gt;.&lt;br /&gt;
&lt;br /&gt;
Crea un directorio llamado &lt;code&gt;closure-compiler&lt;/code&gt;.&lt;br /&gt;
&lt;br /&gt;
Descarga   el compilador del archivo &lt;a href="http://closure-compiler.googlecode.com/files/compiler-latest.zip"&gt;compiler.jar&lt;/a&gt;  y guárdalo en el directorio &lt;code&gt;closure-compiler&lt;/code&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Crea   un archivo JavaScript&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Crea un archivo llamado &lt;code&gt;hello.js&lt;/code&gt;  que contenga el siguiente código:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="js:nocontrols" name="code"&gt;// Una función sencilla.
function hello(longName) {
  alert('Hola, ' + longName);
}
hello('Nuevo usuario');&lt;/pre&gt;&lt;br /&gt;
Guárdalo  en el directorio &lt;code&gt;closure-compiler&lt;/code&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt; &lt;b&gt;Compila el archivo JavaScript&lt;/b&gt;.&lt;br /&gt;
&lt;br /&gt;
Ejecuta el siguiente comando desde el directorio &lt;code&gt;closure-compiler&lt;/code&gt;:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;java -jar compiler.jar --js hello.js --js_output_file  hello-compiled.js&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
Este comando crea un nuevo archivo llamado &lt;code&gt;hello-compiled.js&lt;/code&gt;,  que contiene el siguiente JavaScript:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="js:nocontrols" name="code"&gt;function hello(a){alert("Hola, "+a)}hello("Nuevo usuario");
&lt;/pre&gt;&lt;br /&gt;
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 &lt;code&gt;longName&lt;/code&gt; por el más corto &lt;code&gt;a&lt;/code&gt;.  Esto genera un archivo mucho más pequeño que el original.&lt;br /&gt;
&lt;br /&gt;
Para confirmar que el código JavaScript compilado aún funciona, incluye &lt;code&gt;hello-compiled.js&lt;/code&gt;  en un archivo HTML como este:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="html:nocontrols" name="code"&gt;&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;script src="closure-library-read-only/closure/goog/base.js"&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script src="hello.js"&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body onload="sayHi()"&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/pre&gt;&lt;br /&gt;
Ahora carga el HTML en el navegador y deberías ver una bienvenida  amigable. Para aprender más visita &lt;a href="http://code.google.com/closure/compiler/docs/api-tutorial3.html"&gt;este   documento sobre compilación avanzada y con archivos externos&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;br /&gt;
&lt;h1&gt;El inspector de Closure&lt;/h1&gt;&lt;br /&gt;
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 &lt;code&gt;$EDITOR&lt;/code&gt;.&lt;br /&gt;
&lt;br /&gt;
Sigue   estos pasos para habilitar el mapeo de código fuente al usar el  inspector:&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;Instala Firebug y &lt;a href="http://code.google.com/closure/compiler/docs/inspector.html"&gt;el  inspector&lt;/a&gt;. &lt;br /&gt;
&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Compila tu código JavaScript con el  compilador de Closure y añade el  parámetro &lt;code&gt;--create_source_map&lt;/code&gt;. Por ejemplo, si tu archivo  JavaScript se llama &lt;code&gt;example.js&lt;/code&gt;, ejecuta este comando desde  el directorio &lt;code&gt;closure-compiler&lt;/code&gt; para crear un mapa de código  fuente en el fichero &lt;code&gt;example-map&lt;/code&gt;:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;$ java -jar compiler.jar --js example.js --create_source_map ./example-map --js_output_file example-compiled.js&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Para   depurar tu código en Firebug, abre una página web en Firefox que  incluya &lt;code&gt;example.js&lt;/code&gt;. Abre una ventana de Firebug y carga el  archivo JavaScript compilado &lt;code&gt;example-compiled.js&lt;/code&gt; en el  panel izquierdo. Selecciona la pestaña &lt;b&gt;Source Mapping&lt;/b&gt; del panel  derecho y especifica la localización del archivo de mapa de código  fuente &lt;code&gt;example-map&lt;/code&gt; introduciendo la URL o haciendo clic en &lt;b&gt;Open Local File&lt;/b&gt; y  seleccionándolo.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;div align="center"&gt;&lt;img alt="Screenshot of Closure Inspector source map dialog." class="border" src="http://code.google.com/closure/compiler/docs/images/inspector_sourcemap.png" /&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;/div&gt;&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt; 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 &lt;b&gt;Show Original Source&lt;/b&gt;, tal y como se muestra a  continuación:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;div align="center"&gt;&lt;img alt="Screenshot of Closure Inspector: show original source." class="border" src="http://code.google.com/closure/compiler/docs/images/inspector_originalsource.png" /&gt;&lt;br /&gt;
&lt;/div&gt;&lt;br /&gt;
Si has indicado la variable de entorno &lt;code&gt;$EDITOR&lt;/code&gt; 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í:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;div align="center"&gt;&lt;img alt="Screenshot of Closure Inspector pop-up with original source file 
information." class="border" src="http://code.google.com/closure/compiler/docs/images/inspector_orig_linenum.png" /&gt; &lt;br /&gt;
&lt;/div&gt;&lt;br /&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;h2&gt;Traza mejorada de pila &lt;/h2&gt;Firebug incluye una representación de la traza de pila que puedes ver  pulsando&amp;nbsp; &lt;b&gt;Stack&lt;/b&gt;  en la parte derecha de la ventana.&lt;br /&gt;
El inspector de Closure mejora la representación de la traza de pila  mediante:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Mostrando los nombres de función originales si se ha cargado el  mapa del código fuente.&lt;/li&gt;
&lt;li&gt;Mostrando el archivo que contiene cada  función.&lt;/li&gt;
&lt;li&gt;Facilitando un botón&amp;nbsp; &lt;b&gt;Copy Stack&lt;/b&gt; de forma que se  pueda copiar el texto de la traza de pila.&lt;/li&gt;
&lt;/ul&gt;A continuación hay un ejemplo de traza mejorada de pila del inspector  Closure:&lt;br /&gt;
&lt;br /&gt;
&lt;div align="center"&gt;&lt;img alt="Screenshot of Closure Inspector's enchanced stack trace." clas="border" src="http://code.google.com/closure/compiler/docs/images/inspector_stack.png" /&gt; &lt;br /&gt;
&lt;/div&gt;&lt;h2&gt;Integración de Tests Unitarios&lt;/h2&gt;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ú &lt;b&gt;Script&lt;/b&gt; de Firebug y habilita &lt;b&gt;Handle JSUnit Failures&lt;/b&gt;. 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.&lt;br /&gt;
&lt;br /&gt;
Aquí hay un ejemplo de punto de ruptura sobre una aserción:&lt;br /&gt;
&lt;br /&gt;
&lt;div align="center"&gt;&lt;img alt="Screenshot of Closure Inspector assertion breakpoint." class="border" src="http://code.google.com/closure/compiler/docs/images/inspector_breakpoint.png" /&gt;&lt;br /&gt;
&lt;/div&gt;&lt;br /&gt;
&lt;h1&gt;La Biblioteca de Closure&lt;/h1&gt;&lt;br /&gt;
La &lt;a href="http://code.google.com/closure/library"&gt;biblioteca de Closure&lt;/a&gt;  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 &lt;a href="http://closure-library.googlecode.com/svn/trunk/closure/goog/docs/index.html"&gt;documentación&lt;/a&gt;.)&lt;br /&gt;
&lt;br /&gt;
JavaScript   carece de una biblioteca de clases estándar como &lt;a href="http://en.wikipedia.org/wiki/Standard_Template_Library"&gt;STL&lt;/a&gt; o &lt;a href="http://en.wikipedia.org/wiki/Java_Development_Kit"&gt;JDK&lt;/a&gt;. 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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Empezando con la biblioteca de Closure &lt;br /&gt;
&lt;/h2&gt;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 &lt;a href="http://www.google.com/url?q=http://subversion.tigris.org/links.html#clients"&gt;cliente   de Subversion&lt;/a&gt;. Podrías tener ya uno. Lo sabrás intentando ejecutar  el comando del paso 1. &lt;br /&gt;
&lt;h2&gt;Hola Closure&lt;/h2&gt;Para empezar con la librería Closure, utiliza funciones JavaScript de  Closure en una página web simple siguiendo estos pasos: &lt;br /&gt;
&lt;h3 id="step1"&gt;Paso 1: Descargar la biblioteca&lt;br /&gt;
&lt;/h3&gt;Descarga la biblioteca de Closure desde el repositorio de Subversion  ejecutando siguiente comando desde consola: &lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;svn checkout http://closure-library.googlecode.com/svn/trunk/ closure-library-read-only
&lt;/pre&gt;&lt;br /&gt;
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&amp;nbsp; &lt;a href="http://www.google.com/url?q=http://subversion.tigris.org/links.html#clients"&gt;cliente   de Subversion&lt;/a&gt;. &lt;br /&gt;
Tras ejecutar el comando deberías tener un directorio llamado &lt;code&gt;closure-library-read-only&lt;/code&gt;  que contenga el código de la librería. &lt;br /&gt;
&lt;h3 id="step2"&gt;Paso 2: Crear un archivo JavaScript que use la biblioteca  de Closure&lt;/h3&gt;Guarda el siguiente JavaScript en un archivo llamado &lt;code&gt;hello.js&lt;/code&gt;.  Pon este archivo junto al directorio&amp;nbsp; &lt;code&gt;closure-library-read-only&lt;/code&gt;. &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="js:nocontrols" name="code"&gt;goog.require('goog.dom');

function sayHi() {
  var newDiv = goog.dom.createDom('h1', {'style': 'background-color:#EEE'},
    'Hola Mundo!');
  goog.dom.appendChild(document.body, newDiv);
}
&lt;/pre&gt;&lt;br /&gt;
&lt;h3 id="step3"&gt;Paso 3: Crea un archivo HTML&lt;/h3&gt;Guarda el siguiente HTML en un archivo llamado &lt;code&gt;hello.html&lt;/code&gt;.  Pone este archivo junto al directorio &lt;code&gt;closure-library-read-only&lt;/code&gt;  y el archivo &lt;code&gt;hello.js&lt;/code&gt;. &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="html:nocontrols"&gt;&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;script src="&gt;&amp;lt;/script&amp;gt;
    &amp;lt;script src="hello.js"&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body onload="sayHi()"&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/pre&gt;&lt;h3 id="step4"&gt;Paso 4: Dí Hola a la biblioteca&lt;/h3&gt;Abre el archivo HTML en el navegador. Deberías de ver las palabras  "Hello  world!": &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;img src="http://code.google.com/closure/library/docs/images/closurehelloworld.png" /&gt; &lt;br /&gt;
&lt;h2 id="wha"&gt;¿Cómo funciona el ejemplo?&lt;/h2&gt;El JavaScript de &lt;code&gt;hello.js&lt;/code&gt; usa dos funciones que no  define: &lt;code&gt;goog.dom.createDom()&lt;/code&gt; y &lt;code&gt;goog.dom.appendChild()&lt;/code&gt;.  ¿De dónde vienen esas funciones?&lt;br /&gt;
&lt;br /&gt;
Esas funciones están definidas en la biblioteca de Closure que  descargaste en el paso 1, dentro del archivo&amp;nbsp; &lt;code&gt;closure-library-read-only/dom/dom.js&lt;/code&gt;. &lt;br /&gt;
Para hacer uso de esas funciones, el ejemplo hace dos cosas: &lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Incluye la orden &lt;code&gt;goog.require('goog.dom')&lt;/code&gt; al principio del JavaScript del paso 2.&lt;/li&gt;
&lt;li&gt;Incluye el archivo de  arranque de la biblioteca&amp;nbsp; &lt;code&gt;base.js&lt;/code&gt; en el HTML del paso 3.&lt;/li&gt;
&lt;/ul&gt;El archivo &lt;code&gt;base.js&lt;/code&gt; define la función&amp;nbsp; &lt;code&gt;goog.require()&lt;/code&gt;.  La llamada de función &lt;code&gt;goog.require('goog.dom')&lt;/code&gt; carga el  archivo JavaScript que define las funciones del espacio de nombres &lt;code&gt;goog.dom&lt;/code&gt;, junto con otros archivos de la biblioteca de Closure que esas funciones  necesitan.&lt;br /&gt;
La biblioteca de Closure carga esos archivos agregando dinámicamente  una etiqueta &lt;code&gt;script&lt;/code&gt; al documento por cada archivo   necesitado de la biblioteca. Así, por ejemplo, la orden&amp;nbsp; &lt;code&gt;goog.require('goog.dom')&lt;/code&gt;  hace que la siguiente etiqueta sea agregada al document, donde &lt;code&gt;path-to-closure&lt;/code&gt;  es el camino desde el archivo HTML hasta el directorio que contiene &lt;code&gt;base.js&lt;/code&gt;: &lt;br /&gt;
&lt;pre class=""&gt;&lt;script src="path-to-closure/dom/dom.js"&gt;
&lt;/script&gt;&lt;/pre&gt;Normalmente una sola orden &lt;code&gt;goog.require()&lt;/code&gt; cargará sólo una  fracción de la base de código de la biblioteca. &lt;br /&gt;
Incluir &lt;code&gt;base.js&lt;/code&gt; 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 &lt;code&gt;goog.require()&lt;/code&gt;  para declarar las partes de la librería que necesitas. &lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Construir una aplicación con la biblioteca de Closure&lt;br /&gt;
&lt;/h2&gt;Este otro tutorial te introduce en la experiencia de usar la  biblioteca  dando un paseo por la construcción de una aplicación simple.&lt;br /&gt;
Usa estos enlaces para descargar los dos archivos de código fuente  usados en el tutorial: &lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;a href="http://code.google.com/closure/library/samples/notepad.js"&gt;notepad.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://code.google.com/closure/library/samples/notepad.html"&gt;notepad.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;Este tutorial explica las diferentes partes de estos archivos paso a  paso. Fíjate que el archivo &lt;code&gt;notepad.html&lt;/code&gt; no funcionará  hasta que no descargues y enlaces tu copia de la biblioteca, tal y como  se describe a continuación. &lt;br /&gt;
&lt;h2&gt;La aplicación de Notas&lt;br /&gt;
&lt;/h2&gt;Este tutorial ilustra el proceso de construir una simple aplicación  para mostrar notas. El ejemplo: &lt;br /&gt;
&lt;ul&gt;&lt;li&gt;crea un espacio de nombres para la aplicación,&lt;/li&gt;
&lt;li&gt;usa la  función de la biblioteca &lt;code&gt;goog.dom.createDom()&lt;/code&gt; para crear la  estructura de Document  Object Model (DOM) para la lista, y&lt;/li&gt;
&lt;li&gt;usa una clase de la biblioteca en la lista de  notas para permitir al usuario abrir y cerrar elementos de la lista.&lt;/li&gt;
&lt;/ul&gt;&lt;h2&gt;Creando un espacio de nombres con goog.provide()&lt;/h2&gt;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 &lt;code&gt;goog.provide()&lt;/code&gt;  de la librería para crear un espacio de nombres para tu código. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Por ejemplo, la aplicación de Notas usa objetos &lt;code&gt;Note&lt;/code&gt;  creados por una función constructor &lt;code&gt;Note()&lt;/code&gt;. Si cualquier  otro archivo JavaScript define una función global o una variable &lt;code&gt;Note&lt;/code&gt;,  sobreescribiría al constructor. Por tanto, el ejemplo crea un espacio  de nombres para este constructor con la siguiente llamada a &lt;code&gt;goog.provide&lt;/code&gt;:  &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="js:nocontrols" name="code"&gt;goog.provide('tutorial.notepad.Note');&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
La función &lt;span style="font-family: monospace;"&gt;&lt;/span&gt;&lt;code&gt;goog.provide()&lt;/code&gt;  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: &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="js:nocontrols" name="code"&gt;tutorial = tutorial || {};
tutorial.notepad = tutorial.notepad || {};
tutorial.notepad.Note = tutorial.notepad.Note || {};
&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
Puesto que &lt;code&gt;goog.provide()&lt;/code&gt; sólo inicializa propiedades si  no existen previamente, nunca sobreescribirá una propiedad.&lt;br /&gt;
Fíjate que las órdenes&lt;span style="font-family: monospace;"&gt; &lt;/span&gt;&lt;code&gt;goog.provide()&lt;/code&gt;  tiene la ventaja añadida de que el script de resolución de dependencias  &lt;code&gt;calcdeps.py&lt;/code&gt; puede usarlas. Echa un vistazo a &lt;a href="http://code.google.com/closure/library/docs/calcdeps.html"&gt;Usar el  script de cálculo de dependencias&lt;/a&gt; para aprender cómo usar &lt;code&gt;calcdeps.py&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
Una vez que la estructura de objeto &lt;code&gt;tutorial.notepad.Note&lt;/code&gt;  exista,   el ejemplo asigna la función constructor a la propiedad &lt;code&gt;Note&lt;/code&gt;.   &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="js:nocontrols" name="code"&gt;tutorial.notepad.Note = function(title, content, noteContainer) {
  this.title = title;
  this.content = content;
  this.parent = noteContainer;
};&lt;/pre&gt;&lt;br /&gt;
El constructor &lt;code&gt;Note&lt;/code&gt; está ahora en el espacio de nombre &lt;code&gt;tutorial.notepad&lt;/code&gt;  creado con &lt;code&gt;goog.provide()&lt;/code&gt;. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Creando una estructura DOM con goog.dom.createDom()&lt;/h2&gt;Para mostrar una &lt;code&gt;Note&lt;/code&gt; en el documento HTML, el ejemplo  facilita a la clase &lt;code&gt;Note&lt;/code&gt; el siguiente método: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="js:controls" name="code"&gt;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);
};&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
Este método usa la función de la biblioteca &lt;code&gt;goog.dom.createDom()&lt;/code&gt;. La siguiente orden &lt;code&gt;goog.require()&lt;/code&gt;  incluye el código para esta función: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="js:nocontrols" name="code"&gt;goog.require('goog.dom');&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
Para incluir una función como &lt;code&gt;goog.dom.createDom()&lt;/code&gt; que  no sea un constructor, pasa el espacio de nombres que contenga la  función a &lt;code&gt;goog.require&lt;/code&gt; (en este caso solamente &lt;code&gt;goog.dom&lt;/code&gt;).  No necesitas incluir el nombre de la función en la orden &lt;code&gt;goog.require()&lt;/code&gt;  a menos que requieras de una clase. &lt;a href="http://code.google.com/closure/library/docs/tutorial.html#zippy"&gt;Usando  una clase de la biblioteca&lt;/a&gt; ilustra una órden &lt;code&gt;goog.require()&lt;/code&gt;  para este caso. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
La función &lt;code&gt;goog.dom.createDom()&lt;/code&gt; crea un nuevo elemento  DOM. Por ejemplo, la siguiente órden de   &lt;code&gt;makeNoteDom()&lt;/code&gt; crea un nuevo elemento&amp;nbsp; &lt;code&gt;div&lt;/code&gt;. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="js:nocontrols" name="code"&gt;this.headerElement = goog.dom.createDom('div', 
      {'style': 'background-color:#EEE'}, this.title);&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
El segundo argumento en esta llamada a &lt;code&gt;createDom()&lt;/code&gt;  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 &lt;a href="http://closure-library.googlecode.com/svn/trunk/closure/goog/docs/closure_goog_dom_dom.js.html"&gt;referencia  de la API&lt;/a&gt; para más información sobre &lt;code&gt;createDom()&lt;/code&gt;. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
El método &lt;code&gt;makeNoteDom()&lt;/code&gt; simplemente crea un argumento de  una sola Note. Para crear una lista de notas, el ejemplo incluye una  función &lt;code&gt;makeNotes()&lt;/code&gt; que toma un &lt;span style="font-style: italic;"&gt;array&lt;/span&gt; de&amp;nbsp; de datos de notas e instancia un objeto &lt;code&gt;Note&lt;/code&gt; por cada uno,  llamando al método &lt;code&gt;makeNoteDom()&lt;/code&gt;   de &lt;code&gt;Note&lt;/code&gt;. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="js:nocontrols" name="code"&gt;tutorial.notepad.makeNotes = function(data, noteContainer) {
  var notes = [];
  for (var i = 0; i &amp;lt; data.length; i++) {
    var note = 
      new tutorial.notepad.Note(data[i].title, data[i].content, noteContainer);
    notes.push(note);
    note.makeNoteDom();
  }
  return notes;
};&lt;/pre&gt;&lt;h2 id="zippy"&gt;Usando una clase de la biblioteca&lt;/h2&gt;Con sólo dos líneas de código, el ejemplo hace de cada nota un &lt;code&gt;Zippy&lt;/code&gt;.  Un &lt;code&gt;Zippy&lt;/code&gt; es un elemento que puede ser plegado y desplegado  para ocultar o mostrar contenido.  Primero el ejemplo agrega una nueva orden &lt;code&gt;require()&lt;/code&gt; para  la clase  &lt;code&gt;Zippy&lt;/code&gt;&lt;span style="font-family: monospace;"&gt;&lt;/span&gt;:   &lt;br /&gt;
&lt;pre class="js:nocontrols" name="code"&gt;goog.require('goog.ui.Zippy');&lt;/pre&gt;Entonces añade una línea al final del método&amp;nbsp; &lt;code&gt;makeNoteDom&lt;/code&gt;  :  &lt;br /&gt;
&lt;pre class="js:nocontrols" name="code"&gt;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);
};&lt;/pre&gt;La llamada al constructor &lt;code&gt;new goog.ui.Zippy(this.headerElement,  this.contentElement)&lt;/code&gt; asigna un comportamiento al elemento nota  que conmutará la visibilidad de&amp;nbsp; &lt;code&gt;contentElement&lt;/code&gt; cuando el  usuario haga clic sobre&amp;nbsp; &lt;code&gt;headerElement&lt;/code&gt;.   Para más información sobre la clase Zippy, echa un vistazo a la &lt;a href="http://closure-library.googlecode.com/svn/trunk/closure/goog/docs/class_goog_ui_Zippy.html"&gt;documentación  de la API de Zippy&lt;/a&gt;.  &lt;br /&gt;
&lt;h2&gt;Usando el Notepad en un documento HTML &lt;/h2&gt;A continuación se puede ver el código JavaScript completo para esta  aplicación de ejemplo:   &lt;br /&gt;
&lt;pre class="js:nocontrols"&gt;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 &amp;lt; 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);
};&lt;/pre&gt;Puedes tener este código descargando el archivo &lt;a href="http://code.google.com/closure/library/samples/notepad.js"&gt;notepad.js&lt;/a&gt;.   El siguiente HTML usa este código de bloc de notas para mostrar una  lista de notas en una página web:   &lt;br /&gt;
&lt;pre class="html:nocontrols" name="code"&gt;&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;title&amp;gt;Notepad&amp;lt;/title&amp;gt;
&amp;lt;script src="closure-library/base.js"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src="notepad.js"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;

&amp;lt;div id="notes"&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;script&amp;gt;
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();
&amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/pre&gt;Puedes tener esta página descargando el archivo &lt;a href="http://code.google.com/closure/library/samples/notepad.html"&gt;notepad.html&lt;/a&gt;,  Esta página: &lt;br /&gt;
&lt;ul&gt;&lt;li&gt;     incluye una etiqueta &lt;code&gt;script&lt;/code&gt; para el archivo &lt;code&gt;base.js&lt;/code&gt;  de la biblioteca. El atributo &lt;code&gt;src&lt;/code&gt; de esta etiqueta es el  camino desde el archivo HTML hasta el archivo&amp;nbsp; &lt;code&gt;base.js&lt;/code&gt; de  la biblioteca.   &lt;/li&gt;
&lt;li&gt;     incluye una etiqueta&amp;nbsp; &lt;code&gt;script&lt;/code&gt; para el archivo que  contiene el código para el bloc de notas.   &lt;/li&gt;
&lt;li&gt;     incluye un &lt;span style="font-style: italic;"&gt;script&lt;/span&gt; en la  página que inicializa la lista de notas con una llamada a &lt;code&gt;makeNotes()&lt;/code&gt;.  La función &lt;code&gt;makeNotes()&lt;/code&gt; 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&lt;/li&gt;
&lt;/ul&gt;&lt;h1&gt;Plantillas de  Closure&lt;/h1&gt;Las &lt;a href="http://code.google.com/closure/templates"&gt;plantillas  de Closure&lt;/a&gt;  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 &lt;a href="http://googleblog.blogspot.com/2006/05/googles-20-percent-time-in-action.html"&gt;proyectos   de 20% del tiempo&lt;/a&gt; de &lt;i&gt;Googlers&lt;/i&gt; 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!

&lt;br/&gt;&lt;br/&gt;&lt;hr/&gt;&lt;br/&gt;&lt;b&gt;Addendum&lt;/b&gt;: Parece que &lt;a href="http://webreflection.blogspot.com/2009/11/google-closure-im-not-impressed.html"&gt;Andrea Giammarchi&lt;/a&gt; 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...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/112773462326884407-7972445882168560186?l=digitta.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://digitta.com/feeds/7972445882168560186/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://digitta.com/2009/11/herramientas-de-google-closure.html#comment-form" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/7972445882168560186?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/7972445882168560186?v=2" /><link rel="alternate" type="text/html" href="http://digitta.com/2009/11/herramientas-de-google-closure.html" title="Herramientas de Google Closure" /><author><name>Àl</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11672226968306882234" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;C0QMQX0-eSp7ImA9WxNUEkU.&quot;"><id>tag:blogger.com,1999:blog-112773462326884407.post-6209027003083622684</id><published>2009-11-03T21:49:00.000+01:00</published><updated>2009-11-03T21:49:40.351+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-11-03T21:49:40.351+01:00</app:edited><title>Incrustación automática de URIs de datos en archivos CSS</title><content type="html">Traduzco &lt;a href="http://www.nczonline.net/blog/2009/11/03/automatic-data-uri-embedding-in-css-files/"&gt;la continuación&lt;/a&gt; del artículo de Nicholas Zakas en el que presenta dos herramientas para facilitar la adopción de esta tecnología:&lt;br /&gt;
&lt;br /&gt;
La semana pasada posteé sobre URIs de datos y por qué son una característica interesante de los navegadores web. En conclusión, mencioné que el mejor uso de URIs de datos es incrustarlos en archivos CSS para que tuviesen la mejor oportunidad de ser cacheados por el navegador. Recibí un montón de respuestas en este blog y por otros medios que demostraban que la gente estaba muy entusiasmado con las URIs de datos y estaban listos para dar el salto y usarlos en CSS. This left me with me believing that an interesting time is upon us. Esto me hizo pensar que estamos viviendo en un momento interesante.&lt;br /&gt;
&lt;br /&gt;
&lt;h1&gt;El problema&lt;/h1&gt;&lt;br /&gt;
Aunque Internet Explorer anterior a la versión 8 no es compatible con los URIs de datos, la mayoría de la gente parece dispuesta a servir contenido alternativo para los navegadores menos capaces y servir el contenido más óptimo para aquellos que pueden manejarlo. El problema es que millones de sitios web están utilizando actualmente archivos CSS que referencian archivos de imagen a la manera tradicional, y todos ellos que tendrían que ser convertidos para usar URIs de datos. Nadie quiere mantener varias versiones de los archivos CSS, por lo que sería mejor si este proceso de conversión pudiera ser automático.&lt;br /&gt;
&lt;br /&gt;
&lt;h1&gt;Presentación de CSSEmbed&lt;/h1&gt;&lt;br /&gt;
Pasé los últimos dos días programando y estoy orgulloso de introducir &lt;code&gt;&lt;a href="http://github.com/nzakas/cssembed"&gt;CSSEmbed&lt;/a&gt;&lt;/code&gt;, una herramienta para integrar automáticamente imágenes dentro de archivos CSS como URIs de datos. Esta es una sencilla herramienta muy pequeña que lee un fichero CSS, identifica las imágenes referenciadas en su interior, las convierte a URIs de datos, y genera una hoja de estilos resultante. La hoja de estilos recién creada es una copia exacta del original, con comentarios y sangrías intactos, la única diferencia es que todas las referencias a archivos de imagen han sido sustituidos por URIs de datos. Because it preserves the original formatting of the style sheet, it can be used both on nicely-formatted source code and crunched source code without worry. Debido a que preserva el formato original de la hoja de estilo, puede ser utilizado tanto con código fuente bien formateado como con código compactado sin problemas.&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;CSSEmbed&lt;/code&gt; requiere Java 1.5+ para funcionar. Para empezar, descarga el &lt;a href="http://github.com/nzakas/cssembed/downloads/"&gt;archivo JAR&lt;/a&gt;. Todas las dependencias están incluidos en el archivo JAR, así que no hay necesidad de ocuparse de la creación de &lt;i&gt;classpaths&lt;/i&gt; o de descargar otras dependencias. El uso básico es el siguiente:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;java -jar cssembed-xyzjar -o &amp;lt;output filename&gt; &amp;lt;input filename&gt; &lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
Por ejemplo:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;java -jar cssembed-xyzjar -o styles_new.css styles.css &lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
When the -o flag is omitted, the output ends up on stdout , thus you can direct the output to a file directly: Cuando se omite el parámetro &lt;code&gt;-o&lt;/code&gt; el código se genera sobre la salida estándar, por lo que se puede dirigir a un archivo directamente:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;java -jar cssembed-xyzjar styles.css &gt; styles_new.css &lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
Las instrucciones de uso completas están disponibles mediante el parámetro &lt;code&gt;-h&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;
Usage: java -jar cssembed-x.y.z.jar [options] [input file]

Global Options
   -h, --help           Muestra esta información
   --charset            Codificación de carácteres del archivo de entrada
   -v, --verbose        Muestra mensajes de informacion y avisos
   -root                Antepone a todas las URLs relativas
   -o                   Genera la salida en . Por defecto a stdout.
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
CSSEmbed es inteligente en la forma en que identifica las imágenes. Si la ubicación de la imagen comienza con "http://", entonces la herramienta descarga automáticamente el archivo y lo convierte a una URI. Si la ubicación de la imagen es una ruta relativa (por ejemplo, contiene "../"), CSSEmbed busca el archivo local en relación a la ubicación del archivo de hoja de estilos. Si los archivos indican una ruta absoluta, sin especificar "http://", como "/images/image.png", tendrás que proporcionar una raíz a través de la opción &lt;code&gt;--root&lt;/code&gt;. Cuando se especifica, la raíz se antepone a todas las ubicaciones de imágenes que no comiencen por "http://".&lt;br /&gt;
&lt;br /&gt;
Para obtener más información acerca de CSSEmbed, echa un vistazo a la &lt;a href="http://wiki.github.com/nzakas/cssembed"&gt;documentación&lt;/a&gt;. También puede descargar el código fuente (Licencia MIT) del &lt;a href="http://github.com/nzakas/cssembed/"&gt;proyecto GitHub&lt;/a&gt;. CSSEmbed se encuentra en etapas muy tempranas, por lo que se agradecerán opiniones. Si encuentras errores, por favor usa el &lt;a href="http://github.com/nzakas/cssembed/issues"&gt;seguimiento de incidencias&lt;/a&gt; para informar sobre ellos.&lt;br /&gt;
&lt;br /&gt;
&lt;h1&gt;...y presentamos DataURI&lt;/h1&gt;&lt;br /&gt;
When I was researching data URIs, I didn't come across any simple data URIs generators. Cuando estaba investigando URIs de datos, no encontré ningún generador simple de URIs de datos. Está la cocina de URIs de datos de Hixie, pero sólo está disponible &lt;i&gt;online&lt;/i&gt;. Lo que realmente buscaba era algo para ser ejecutado en la línea de comandos para poder crear &lt;code&gt;CSSEmbed&lt;/code&gt;. Por eso he creado &lt;code&gt;&lt;a href="http://github.com/nzakas/datauri"&gt;DataURI&lt;/a&gt;&lt;/code&gt;, que es una sencilla herramienta que lee un archivo y muestra su representación como una URI de datos. &lt;code&gt;DataURI&lt;/code&gt; es el motor subyacente utilizado por &lt;code&gt;CSSEmbed&lt;/code&gt; para crear sus URIs de datos.&lt;br /&gt;
&lt;br /&gt;
DataURI is also written in Java and requires Java 1.5+ to run. &lt;code&gt;DataURI&lt;/code&gt; también está escrito en Java y requiere Java 1.5 o superior para funcionar. Al igual que con CSSEmbed, hay un &lt;a href="http://github.com/nzakas/datauri/downloads/"&gt;archivo JAR&lt;/a&gt; que contiene todas las dependencias. Las mismas opciones generales de línea de comandos están disponibles, por lo que el uso básico es muy similar a &lt;code&gt;CSSEmbed&lt;/code&gt;:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;java -jar datauri-xyzjar -o &amp;lt;output filename&gt; &amp;lt;input filename&gt; &lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
Por ejemplo:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;java -jar datauri-xyzjar -o output.txt image.png &lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
Los archivos a codificar no tienen por qué ser locales, se pueden incluir URLs en la línea de comandos y se descargan y codifican:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;ava -jar datauri-xyzjar -o output.txt http://www.your.domain.com/image.png &lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
Cuando se omite la opción &lt;code&gt;-o&lt;/code&gt;, la salida termina en stdout con lo que se puede redirigir a un archivo directamente:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;java -jar datauri-xyzjar styles.css &gt; styles_new.css &lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
Las instrucciones de uso completas están disponibles mediante &lt;code&gt;-h&lt;/code&gt;:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;code&gt;Usage: java -jar datauri-x.y.z.jar [options] [input file]

Global Options
   -h, --help            Displays this information.
   --charset &lt;charset&gt;   Character set of the input file.
   -v, --verbose         Display informational messages and warnings.
   -m, --mime &lt;type&gt;     Mime type to encode into the data URI.
   -o &lt;file&gt;             Place the output into &lt;file&gt;. Defaults to stdout. 
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
DataURI es capaz de crear URIs de datos de URIs de las imágenes y de algunos archivos de texto (para la lista completa, consulta la &lt;a href="http://wiki.github.com/nzakas/datauri"&gt;documentación&lt;/a&gt;). El código fuente está disponible (Licencia MIT) del &lt;a href="http://github.com/nzakas/datauri"&gt;proyecto GitHub&lt;/a&gt; y puedes &lt;a href="http://github.com/nzakas/datauri/issues"&gt;informar de errores&lt;/a&gt; igualmente.&lt;br /&gt;
&lt;br /&gt;
&lt;h1&gt;Agradecimientos&lt;/h1&gt;&lt;br /&gt;
I was inspired to create these tools after reading,  Me inspiré para crear estas herramientas después de leer &lt;a href="http://developer.yahoo.net/blog/archives/2009/09/search_performance.html"&gt;Not Just a Pretty Face: Performance and the New Yahoo!&lt;/a&gt; de &lt;a href="http://www.wonko.com/"&gt;Ryan Grove&lt;/a&gt;, &lt;a href="http://www.phpied.com/"&gt;Stoyan Stefanov&lt;/a&gt;, y Venkateswaran Udayasankar del equipo de búsquedas de Yahoo!. La sección sobre las URIs de datos me llevó a investigar más y, finalmente, a la creación de &lt;code&gt;CSSEmbed&lt;/code&gt; y de &lt;code&gt;DataURI&lt;/code&gt;.&lt;br /&gt;
&lt;br /&gt;
Estoy lejos de ser tan bueno en Java como lo soy con JavaScript, así que tener un gran ejemplo funcionando ha hecho este desarrollo más fácil. Debo agradecer a Julien Lecomte porque he utilizado el código fuente del YUI Compressor como una guía sobre cómo organizar mi código. El código de Julien es tan limpio que era fácil configurar mis proyectos de la misma manera. También utilicé su archivo Ant como base para el mío.&lt;br /&gt;
&lt;br /&gt;
&lt;h1&gt;Seguimiento&lt;/h1&gt;&lt;br /&gt;
Las URIs de datos son definitivamente excitantes como medio para mejorar el rendimiento de sitios web. Cada gran impulso hacia adelante en las técnica de desarrollo necesita de un buen conjunto de herramientas para aumentar su adopción. Espero sinceramente que &lt;code&gt;CSSEmbed&lt;/code&gt; y &lt;code&gt;DataURI&lt;/code&gt; puede ayudar a la gente a empezar con la creación y el uso de URIs de datos.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/112773462326884407-6209027003083622684?l=digitta.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://digitta.com/feeds/6209027003083622684/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://digitta.com/2009/11/incrustacion-automatica-de-uris-de.html#comment-form" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/6209027003083622684?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/6209027003083622684?v=2" /><link rel="alternate" type="text/html" href="http://digitta.com/2009/11/incrustacion-automatica-de-uris-de.html" title="Incrustación automática de URIs de datos en archivos CSS" /><author><name>Àl</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11672226968306882234" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;C04HRn07cCp7ImA9WxNVGE4.&quot;"><id>tag:blogger.com,1999:blog-112773462326884407.post-437376866241112949</id><published>2009-10-29T16:30:00.012+01:00</published><updated>2009-10-29T16:58:57.308+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-10-29T16:58:57.308+01:00</app:edited><title>URIs de datos (Data URIs)</title><content type="html">He traducido el &lt;a href="http://www.nczonline.net/blog/2009/10/27/data-uris-explained/"&gt;artículo de Nicholas C. Zakas&lt;/a&gt; sobre URIs de datos por su interés.
&lt;h2&gt;Explicando las URIs de datos&lt;/h2&gt;Uno de las peticiones más frecuentes para características de los navegadores es el soporte de URIs de datos. Ha habido bastante literatura últimamente sobre URIs de datos: mi colega Stoyan Stefanov ha escrito un &lt;a href="http://phpied.com/data-urls-what-are-they-and-how-to-use"&gt;par&lt;/a&gt; de &lt;a href="http://www.phpied.com/mhtml-when-you-need-data-uris-in-ie7-and-under/"&gt;artículos&lt;/a&gt; sobre ellos, y mi ex colega Hedger Wang también escribió &lt;a href="http://www.hedgerwow.com/360/dhtml/base64-image/demo.php"&gt;otro&lt;/a&gt; acerca de cómo utilizar URIs de datos en IE. Sorprendentemente, todavía hay un montón de malentendidos y confusión sobre las URIs de datos, qué son, cómo funcionan, y por qué querrías usar una.

&lt;h2&gt;URI, no URL&lt;/h2&gt;URL es la abreviatura de Uniform Resource Locator, que es una combinación de protocolo (cómo recuperar los datos) y la dirección donde se puede encontrar un recurso determinado. Todos los recursos públicos accesibles, como una imagen, un archivo JavaScript, archivo HTML, o una hoja de estilo CSS, tienen una dirección URL que indica al navegador desde donde descargar el archivo. Entonces el navegador establece una conexión y empieza a descargar y / o ejecutar el archivo.

Cada URL también es una URI, que es la abreviatura de uniform resource identifier. Una URI indica un protocolo para recuperar información, así como información adicional sobre el recurso. Esa información adicional puede ser una dirección (si lo es, entonces la URI es una dirección URL) o no serla, pero siempre está relacionada con el protocolo especificado. Por lo tanto, las URIs de datos no son direcciones URL, ya que no contienen información de dirección.

&lt;h2&gt;Formato de URI de datos&lt;/h2&gt;El formato de datos URI es bastante simple y se explica en el &lt;a href="http://tools.ietf.org/html/rfc2397"&gt;RFC 2397&lt;/a&gt; (que en realidad es lo suficientemente corto para que se puede leer todo). El formato básico es el siguiente:

&lt;pre&gt;&lt;code&gt;data:[&amp;lt;mime type&amp;gt;][;charset=&amp;lt;charset&amp;gt;][;base64],&amp;lt;encoded data&amp;gt;&lt;/code&gt;&lt;/pre&gt;

En este formato, &lt;code&gt;data:&lt;/code&gt; es el protocolo de la URI, que indica que ésta es una URI de datos. La segunda parte, el tipo MIME, indica el tipo de datos que se representa. Para imágenes PNG, por ejemplo, éste sería &lt;code&gt;image/png&lt;/code&gt;. Cuando no se especifican, el valor predeterminado para el tipo MIME es text/plain. El conjunto de caracteres generalmente puede ser omitido sin problemas y no se utiliza para imágenes. La próxima sección indica la codificación utilizada. Contrariamente a la creencia popular, no se está obligado a usar la codificación Base 64. Si el contenido no está codificado en base 64, los datos se codifican mediante la norma de codificación de URLs (caracteres ASCII seguros para URLs representados directamente y todos los demás representados como una codificación hexadecimal en formato &lt;code&gt;%xx&lt;/code&gt;). Los datos codificados pueden contener espacios en blanco, el cuál no se considera significativo.

&lt;h2&gt;Base 64&lt;/h2&gt;Base 64 es un sistema de codificación mediante el cual los datos se convierten en bits y luego se agrupan numéricamente en un conjunto de dígitos en base 64. Los dígitos de Base 64 incluyen de la A a la Z, mayúsculas y minúsculas, los números y los símbolos más (+) y barra (/). El signo igual (=) se utiliza para indicar que se ha producido relleno (por favor lee el &lt;a href="http://en.wikipedia.org/wiki/Base64"&gt;artículo de Wikipedia&lt;/a&gt; para más información sobre esto). Todo lo que realmente necesitas entender es que la codificación en base 64 hace que los datos codificados sean mucho más pequeños.

He aquí un ejemplo de una imagen GIF como una URI de datos codificados en base 64 (&lt;a href="http://www.websiteoptimization.com/speed/tweak/inline-images/"&gt;via&lt;/a&gt;):

&lt;pre&gt;&lt;a href="data:image/gif;base64,R0lGODlhEAAOALMAAOazToeHh0tLS/7LZv/0jvb29t/f3//Ub//ge%0A8WSLf/rhf/3kdbW1mxsbP//mf///yH5BAAAAAAALAAAAAAQAA4AAARe8L1Ekyky67QZ1h%0ALnjM5UUde0ECwLJoExKcppV0aCcGCmTIHEIUEqjgaORCMxIC6e0CcguWw6aFjsVMkkIr7g%0A77ZKPJjPZqIyd7sJAgVGoEGv2xsBxqNgYPj/gAwXEQA7"&gt;data:image/gif;base64,R0lGODlhEAAOALMAAOazToeHh0tLS/7LZv/0jvb29t/f3//Ub//ge
8WSLf/rhf/3kdbW1mxsbP//mf///yH5BAAAAAAALAAAAAAQAA4AAARe8L1Ekyky67QZ1h
LnjM5UUde0ECwLJoExKcppV0aCcGCmTIHEIUEqjgaORCMxIC6e0CcguWw6aFjsVMkkIr7g
77ZKPJjPZqIyd7sJAgVGoEGv2xsBxqNgYPj/gAwXEQA7&lt;/a&gt;
&lt;/pre&gt;La misma imagen puede ser representada sin la codificación de base 64 de la siguiente forma:

&lt;pre&gt;&lt;code&gt;data:image/gif,GIF89a&lt;b&gt;%&lt;/b&gt;22&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;1B&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;F7&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00lll&lt;b&gt;%&lt;/b&gt;D6&lt;b&gt;%&lt;/b&gt;D6&lt;b&gt;%&lt;/b&gt;D6&lt;b&gt;%&lt;/b&gt;FF&lt;b&gt;%&lt;/b&gt;EB&lt;b&gt;%&lt;/b&gt;85
&lt;b&gt;%&lt;/b&gt;FF&lt;b&gt;%&lt;/b&gt;E0&lt;b&gt;%&lt;/b&gt;7B&lt;b&gt;%&lt;/b&gt;FF&lt;b&gt;%&lt;/b&gt;F7&lt;b&gt;%&lt;/b&gt;91&lt;b&gt;%&lt;/b&gt;FF&lt;b&gt;%&lt;/b&gt;D4o&lt;b&gt;%&lt;/b&gt;DF&lt;b&gt;%&lt;/b&gt;DF&lt;b&gt;%&lt;/b&gt;DF&lt;b&gt;%&lt;/b&gt;F6&lt;b&gt;%&lt;/b&gt;F6&lt;b&gt;%&lt;/b&gt;F6&lt;b&gt;%&lt;/b&gt;87&lt;b&gt;%&lt;/b&gt;87&lt;b&gt;%&lt;/b&gt;87&lt;b&gt;%&lt;/b&gt;FE
&lt;b&gt;%&lt;/b&gt;CBf&lt;b&gt;%&lt;/b&gt;FF&lt;b&gt;%&lt;/b&gt;F4&lt;b&gt;%&lt;/b&gt;8E&lt;b&gt;%&lt;/b&gt;E6&lt;b&gt;%&lt;/b&gt;B3NKKK&lt;b&gt;%&lt;/b&gt;C5&lt;b&gt;%&lt;/b&gt;92-&lt;b&gt;%&lt;/b&gt;FF&lt;b&gt;%&lt;/b&gt;FF&lt;b&gt;%&lt;/b&gt;99&lt;b&gt;%&lt;/b&gt;FF&lt;b&gt;%&lt;/b&gt;FF&lt;b&gt;%&lt;/b&gt;FF&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;2C&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00
&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;22&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;1B&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;08&lt;b&gt;%&lt;/b&gt;A9&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;1F&lt;b&gt;%&lt;/b&gt;08&lt;b&gt;%&lt;/b&gt;1CH&lt;b&gt;%&lt;/b&gt;B0&lt;b&gt;%&lt;/b&gt;A0&lt;b&gt;%&lt;/b&gt;C1&lt;b&gt;%&lt;/b&gt;83&lt;b&gt;%&lt;/b&gt;08
&lt;b&gt;%&lt;/b&gt;13*&lt;b&gt;%&lt;/b&gt;5C&lt;b&gt;%&lt;/b&gt;C8&lt;b&gt;%&lt;/b&gt;B0&lt;b&gt;%&lt;/b&gt;A1&lt;b&gt;%&lt;/b&gt;C3&lt;b&gt;%&lt;/b&gt;87&lt;b&gt;%&lt;/b&gt;10&lt;b&gt;%&lt;/b&gt;23J&lt;b&gt;%&lt;/b&gt;9CH&lt;b&gt;%&lt;/b&gt;91&lt;b&gt;%&lt;/b&gt;60&lt;b&gt;%&lt;/b&gt;83&lt;b&gt;%&lt;/b&gt;8B&lt;b&gt;%&lt;/b&gt;0D&lt;b&gt;%&lt;/b&gt;0C&lt;b&gt;%&lt;/b&gt;1C
&lt;b&gt;%&lt;/b&gt;A8h&lt;b&gt;%&lt;/b&gt;B0&lt;b&gt;%&lt;/b&gt;81&lt;b&gt;%&lt;/b&gt;C5&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;1B9&lt;b&gt;%&lt;/b&gt;0A&lt;b&gt;%&lt;/b&gt;F4&lt;b&gt;%&lt;/b&gt;E8&lt;b&gt;%&lt;/b&gt;A0&lt;b&gt;%&lt;/b&gt;A4&lt;b&gt;%&lt;/b&gt;83&lt;b&gt;%&lt;/b&gt;07&lt;b&gt;%&lt;/b&gt;181j&lt;b&gt;%&lt;/b&gt;9C&lt;b&gt;%&lt;/b&gt;D8&lt;b&gt;%&lt;/b&gt;80
&lt;b&gt;%&lt;/b&gt;80&lt;b&gt;%&lt;/b&gt;82&lt;b&gt;%&lt;/b&gt;97&lt;b&gt;%&lt;/b&gt;2F&lt;b&gt;%&lt;/b&gt;0B6&lt;b&gt;%&lt;/b&gt;40&lt;b&gt;%&lt;/b&gt;60&lt;b&gt;%&lt;/b&gt;80&lt;b&gt;%&lt;/b&gt;A5&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;01)s&lt;b&gt;%&lt;/b&gt;AA&lt;b&gt;%&lt;/b&gt;94&lt;b&gt;%&lt;/b&gt;D8&lt;b&gt;%&lt;/b&gt;60&lt;b&gt;%&lt;/b&gt;80G&lt;b&gt;%&lt;/b&gt;84
&lt;b&gt;%&lt;/b&gt;02P&lt;b&gt;%&lt;/b&gt;22&lt;b&gt;%&lt;/b&gt;E0Y&lt;b&gt;%&lt;/b&gt;A0&lt;b&gt;%&lt;/b&gt;81&lt;b&gt;%&lt;/b&gt;C9&lt;b&gt;%&lt;/b&gt;A3&lt;b&gt;%&lt;/b&gt;25&lt;b&gt;%&lt;/b&gt;138h&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;80g&lt;b&gt;%&lt;/b&gt;02&lt;b&gt;%&lt;/b&gt;A3&lt;b&gt;%&lt;/b&gt;04&lt;b&gt;%&lt;/b&gt;A2J&lt;b&gt;%&lt;/b&gt;8D
&lt;b&gt;%&lt;/b&gt;BA&lt;b&gt;%&lt;/b&gt;60i&lt;b&gt;%&lt;/b&gt;D3&lt;b&gt;%&lt;/b&gt;88&lt;b&gt;%&lt;/b&gt;0D&lt;b&gt;%&lt;/b&gt;9E&lt;b&gt;%&lt;/b&gt;3A&lt;b&gt;%&lt;/b&gt;B8&lt;b&gt;%&lt;/b&gt;C9&lt;b&gt;%&lt;/b&gt;95kU&lt;b&gt;%&lt;/b&gt;A6N&lt;b&gt;%&lt;/b&gt;8D&lt;b&gt;%&lt;/b&gt;0E&lt;b&gt;%&lt;/b&gt;18Kv&lt;b&gt;%&lt;/b&gt;EC&lt;b&gt;%&lt;/b&gt;D7
&lt;b&gt;%&lt;/b&gt;AB&lt;b&gt;%&lt;/b&gt;10&lt;b&gt;%&lt;/b&gt;B3&lt;b&gt;%&lt;/b&gt;1A-&lt;b&gt;%&lt;/b&gt;C0&lt;b&gt;%&lt;/b&gt;B6-&lt;b&gt;%&lt;/b&gt;5B&lt;b&gt;%&lt;/b&gt;A3&lt;b&gt;%&lt;/b&gt;60&lt;b&gt;%&lt;/b&gt;23&lt;b&gt;%&lt;/b&gt;1A&lt;b&gt;%&lt;/b&gt;D0I&lt;b&gt;%&lt;/b&gt;97&lt;b&gt;%&lt;/b&gt;C1&lt;b&gt;%&lt;/b&gt;D0&lt;b&gt;%&lt;/b&gt;88&lt;b&gt;%&lt;/b&gt;07
&lt;b&gt;%&lt;/b&gt;02&lt;b&gt;%&lt;/b&gt;20&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;C0&lt;b&gt;%&lt;/b&gt;B7&lt;b&gt;%&lt;/b&gt;AF_&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;08&lt;b&gt;%&lt;/b&gt;02L&lt;b&gt;%&lt;/b&gt;3C&lt;b&gt;%&lt;/b&gt;60&lt;b&gt;%&lt;/b&gt;20&lt;b&gt;%&lt;/b&gt;80&lt;b&gt;%&lt;/b&gt;E1&lt;b&gt;%&lt;/b&gt;C3&lt;b&gt;%&lt;/b&gt;88&lt;b&gt;%&lt;/b&gt;03
&lt;b&gt;%&lt;/b&gt;AC&lt;b&gt;%&lt;/b&gt;14&lt;b&gt;%&lt;/b&gt;C9&lt;b&gt;%&lt;/b&gt;B8&lt;b&gt;%&lt;/b&gt;B1&lt;b&gt;%&lt;/b&gt;E3&lt;b&gt;%&lt;/b&gt;C7&lt;b&gt;%&lt;/b&gt;90&lt;b&gt;%&lt;/b&gt;23K&lt;b&gt;%&lt;/b&gt;9EL0&lt;b&gt;%&lt;/b&gt;20&lt;b&gt;%&lt;/b&gt;00&lt;b&gt;%&lt;/b&gt;3B
&lt;/code&gt;&lt;/pre&gt;

El claro ganador en tamaño es la versión codificada en base 64 de la imagen, que es significativamente menor.

Nota: La codificación en Base-64 hace que las imágenes sean más grandes en realidad. Sin embargo, si estás utilizando compresión HTTP, probablemente no notarás la diferencia, porque los datos codificados en base 64 comprimen muy bien. Si por alguna razón no puedes usar la compresión HTTP, puede que desees comprobar la cantidad de bytes que estás enviando para determinar si el compromiso vale la pena.

&lt;h2&gt;No sólo para imágenes&lt;/h2&gt;Aunque la mayoría de la gente habla de URIs de datos como la forma de incluir imágenes dentro de un archivo HTML o CSS, no hay nada aquí que lo haga específico para imágenes. Puedes codificar e incorporar cualquier tipo de archivo, incluso HTML mismo. Ian Hickson, conocido por HTML 5, tiene una &lt;a href="http://software.hixie.ch/utilities/cgi/data/data"&gt;herramienta&lt;/a&gt; que te permite jugar con URIs de datos. El ejemplo por defecto es un archivo HTML que puede convertirse en una URI de datos ya sea con o sin codificación en Base 64. Jugando con este generador de datos URI un rato realmente ayuda a solidificar los conceptos básicos.

Nota: Internet Explorer 8 tiene restricciones de seguridad sobre las URIs de datos que las hacen menos útiles para datos que no sean imágenes.

&lt;h2&gt;Implicaciones de rendimiento&lt;/h2&gt;
La parte más interesante de la historia sobre URIs de datos es que te da la capacidad de insertar archivos dentro de otros archivos. La mayoría de artículos se centran en incorporar URIs de datos en archivos CSS como una forma de mejorar el rendimiento. De hecho, ha habido una gran cantidad de investigaciones que indican que las peticiones HTTP son uno de los principales agujeros de rendimiento para sitios web, y la disminución del número de solicitudes resulta en un mejor rendimiento de página. "Minimizar las peticiones HTTP" es en realidad la primera regla de las "mejores prácticas para un rendimiento excepcional" de Yahoo!, y se mencionan específicamente las URIs de datos:

&lt;blockquote&gt;Las imágenes en línea usan el esquema de URIs &lt;code&gt;data:&lt;/code&gt; para integrar los datos de imagen en la página. Esto puede aumentar el tamaño de tu documento HTML. La combinación de imágenes en línea en tus  hojas de estilo (en caché) es una manera de reducir las peticiones HTTP y evitar el aumento del tamaño de tus páginas. las imágenes en línea no son todavía compatibles con todos los principales navegadores.&lt;/blockquote&gt;

Este es un buen consejo para el uso de URIs de datos: úsalos donde se almacenen en caché con más frecuencia. Las imágenes regulares descargadas se almacenan en caché a través de HTTP de acuerdo con sus cabeceras y / o con la configuración específica del navegador de modo que no será necesario volver a descargarlas cada vez. Las URIs de datos se consideran parte del archivo que los contiene, por lo que son parte del código HTML o CSS archivo en el que están insertadas. Esto significa que las URIs de datos no tienen una política independiente de control de la caché. Incrustar URIs de datos en los archivos los hace más grandes, y si el archivo cambia con frecuencia (como la página principal de un blog), entonces ese archivo más grande debe ser descargado con frecuencia. Eso ralentiza tu sitio.

La forma más fácil de usar URIs de datos es incrustarlos en hojas de estilos externas que se almacenan sistemáticamente en caché. De esta manera, la experiencia con el caché vacío es más rápida (debido a las se generan menos peticiones) y la experiencia con el caché cargado es la misma.

&lt;h2&gt;Soporte en navegadores&lt;/h2&gt;La mayoría de los navegadores actuales son compatibles con las URIs de datos:
&lt;ul&gt;&lt;li&gt;Firefox 2+&lt;/li&gt;&lt;li&gt;Opera 7.2+ - las URIs de datos no deben ser superior a 4.100 caracteres&lt;/li&gt;&lt;li&gt;Chrome (todas las versiones)&lt;/li&gt;&lt;li&gt;Safari (todas las versiones)&lt;/li&gt;&lt;li&gt;Internet Explorer 8+ - Las URIs de datos deben ser menores de 32k&lt;/li&gt;
&lt;/ul&gt;Puesto que las URIs de datos no están soportadas en Internet Explorer anteriores a la versión 8, necesitas decidir si vale la pena servir contenido alternativo para esos navegadores (Leer post Stoyan).
&lt;h2&gt;Conclusión&lt;/h2&gt;Las URIs de datos son un concepto interesante y único en la Web, y es probable que obtengan una mayor cobertura en el futuro. Por el momento, parece que se adaptan mejor a las tareas relacionadas con el rendimiento, pero quién sabe qué nos deparará el futuro. En el corto plazo, puedes ver algunos ahorros en rendimiento mediante la utilización de las URIs de datos para eliminar solicitudes HTTP adicionales de descarga de imágenes. Las URIs de datos también abren la posibilidad de generar imágenes dinámicamente usando JavaScript, aunque el creciente soporte a &amp;lt;canvas&gt; puede hacer que este caso de uso quede obsoleto.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/112773462326884407-437376866241112949?l=digitta.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://digitta.com/feeds/437376866241112949/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://digitta.com/2009/10/uris-de-datos-data-uris.html#comment-form" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/437376866241112949?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/437376866241112949?v=2" /><link rel="alternate" type="text/html" href="http://digitta.com/2009/10/uris-de-datos-data-uris.html" title="URIs de datos (Data URIs)" /><author><name>Àl</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11672226968306882234" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;DUcFR3g5eCp7ImA9WxNQF0w.&quot;"><id>tag:blogger.com,1999:blog-112773462326884407.post-6146394734722314165</id><published>2009-09-19T13:42:00.003+02:00</published><updated>2009-09-23T16:43:36.620+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-09-23T16:43:36.620+02:00</app:edited><title>El fin de la asimetría de HTTP en la web</title><content type="html">Aunque la web original estaba pensada de forma que los navegadores iban a funcionar también como servidores, dicha simetría no tardo en perderse y pronto una web destinada a lectura y escritura se volvió en un sistema de visionado de documentos de hipertexto (enlazados entre sí) ofrecidos por lo que conocemos como servidores web. Es decir, la funcionalidad que iba a ser ofrecida por programas que interpretaban y servían se dividió en dos: los navegadores interpretaban y los servidores web servían. Al principio, no existían documentos dinámicos, es decir, generados &lt;i&gt;al vuelo&lt;/i&gt;, sino más bien, y al igual que con FTP, se trataba de documentos estáticos que se actualizaban y se &lt;i&gt;subían&lt;/i&gt; al servidor. Mucho después llego el CGI y con él el ecosistema de servidor de aplicaciones y las posibilidades de las aplicaciones web.&lt;br /&gt;
&lt;div&gt;&lt;/div&gt;&lt;div&gt;El protocolo HTTP, además, no tiene estado. Cada consulta que recibe un servidor es totalmente nueva para él. Por ello son necesarias las cookies (o mantener un id en la URL) para que el servidor rescate la información de una sesión de un mismo usuario.&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Pronto apareció la necesidad de no tener que recargar una página entera para actualizar una parte de ella, lo que conocemos hoy como &lt;a href="http://es.wikipedia.org/wiki/AJAX"&gt;AJAX&lt;/a&gt;. Microsoft inventó &lt;a href="http://msdn.microsoft.com/en-us/library/ms974566.aspx"&gt;el Remote Scripting&lt;/a&gt; en 1998 seguido pronto por la librería &lt;a href="http://www.ashleyit.com/rs/jsrs/test.htm"&gt;JSRS&lt;/a&gt; de Brent Ashley, pero no fue hasta  2005 cuando Jesse James Garrett acuñó el termino en &lt;a href="http://www.adaptivepath.com/ideas/essays/archives/000385.php"&gt;Ajax: a new approach to Web Applications&lt;/a&gt; con el que conocemos a esta técnica basada en aprovechar una conexión desde JavaScript para solicitar datos al servidor y actualizar la página visualizada en cuanto llegue la respuesta.&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;¿Qué tenemos? Una web asimétrica en la que aplicaciones web en un navegador realizan peticiones (de una página o parciales) a un servidor web. ¿Qué nos falta? Un método para que un servidor nos realice notificaciones (cuando algo cambia). Es lo que se conoce como PUSH y es idéntico a los sistemas de notificación de llegada de correo electrónico en los móviles: con una BlackBerry necesitamos una conexión continua con un servidor para que éste nos notifique las novedades.&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Existen tres formas para conseguir que el navegador &lt;i&gt;se entere&lt;/i&gt; de que hay novedades: &lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Consultar insistentemente al servidor. Es la forma más ineficiente. Consume muchos recursos y la mayor parte de las veces la respuesta va a ser que no hay nada nuevo. Es el sistema que se utiliza con las fuentes RSS o ATOM sobre novedades en una web. Es equiparable al niño en el asiento de atrás del coche preguntando cada 10 segundos: "¿Cuánto queda?"    &lt;/li&gt;
&lt;li&gt;Comet. Abrir una conexión permanente/persistente con el servidor web. También consume recursos y origina un problema al obligar a que el servidor mantenga abiertas un número elevado de conexiones. Es posible implementarla sobre HTTP de forma que el servidor web no cierre una conexión y vaya ofreciendo la información en pequeñas dosis con las actualizaciones. O bien se puede montar un servidor paralelo dedicado a ellas. Es el sistema que se utiliza en los chats web.    &lt;/li&gt;
&lt;li&gt;Finalmente, y he aquí la novedad, se puede rescatar la idea original de &lt;a href="http://es.wikipedia.org/wiki/Tim_Berners-Lee"&gt;Tim Berners-Lee&lt;/a&gt; y añadir un &lt;i&gt;pequeño&lt;/i&gt; servidor a cada navegador. ¿Con qué finalidad? Permitir que un servidor nos haga una petición directamente al navegador.    Si no lo has visto aún, &lt;a href="http://www.reversehttp.net/demos/demo.html"&gt;echa un vistazo a esta demo&lt;/a&gt;. Esa página web, crea una conexión persistente con un servidor, a través de la cuál puede recibir peticiones que la propia página web en el navegador se encarga de contestar (mediante una petición HTTP con la respuesta al mismo servidor). Funciona y permite hacerse una idea de las posibilidades, aunque no haya un servidor realmente en el navegador.    El principal inconveniente de implementar un servidor como tal es que por la arquitectura de Internet resulta muy complejo conseguir que éste reciba peticiones desde el exterior &lt;i&gt;saltándose&lt;/i&gt; proxies y firewalls. Por ello en el ejemplo anterior, conocido como ReverseHTTP, la conexión de servicio la inicia el navegador, porque en ese sentido (navegador -&amp;gt; servidor web) la conexión no es un problema.    La idea que intento transmitir es vencer la asimetría del HTTP (el servidor no puede enviar información al navegador) creando una infraestructura idéntica a la existente pero en sentido contrario (el servidor se convierte en cliente/navegador y realiza peticiones HTTP al servidor incorporado en el navegador del visitante). Se trata de una solución extremadamente elegante que permite la que esta empezando a conocerse como &lt;a href="http://en.wikipedia.org/wiki/Real-time_web"&gt;web en tiempo real&lt;/a&gt;. Al mecanismo por el que un servidor notifica a otro mediante una petición HTTP a una URL acordada entre ambos se conoce como &lt;a href="http://blog.webhooks.org/"&gt;WebHook&lt;/a&gt;. Actualmente se empieza a usar entre servidores de Internet y permite, por ejemplo, que un nuevo artículo de una web sea enviado a nuestro móvil en el instante en que es publicado. Términos como &lt;a href="http://dashes.com/anil/2009/07/the-pushbutton-web-realtime-becomes-real.html"&gt;PushButton&lt;/a&gt;, &lt;a href="http://www.rsscloud.org/"&gt;RSSCloud&lt;/a&gt; o &lt;a href="http://code.google.com/p/pubsubhubbub"&gt;PubHubSubBub&lt;/a&gt; están ahora mismo en pleno auge, pero sigue necesitándose un estándar en el mismo navegador que permita convertirlo en una especie de servidor que reciba notificaciones y actúe en consecuencia. Y estamos a un solo paso: &lt;a href="http://dev.w3.org/html5/eventsource/"&gt;HTML5 Server-sent Events&lt;/a&gt;.  &lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;&lt;b&gt;Addendum:&lt;/b&gt; Añado la &lt;a href="http://www.readwriteweb.com/archives/explaining_the_real-time_web_in_100_words_or_less.php"&gt;definición de Real-Time Web hecha por ReadWriteWeb&lt;/a&gt;:&lt;/div&gt;&lt;blockquote&gt;La Web en Tiempo Real es un paradigma basado en enviar información a los usuarios tan pronto como esté disponible - en lugar de requerir que éstos o su software compruebe periódicamente si hay actualizaciones. Puede ser habilitada de diferentes formas y puede requerir una arquitectura técnica distinta. Se está implementando en redes sociales, búsquedas, noticias, y otros sitios - haciendo esas experiencias más parecidas a la Mensajería Instantánea (chat) y facilitando innovaciones imprevisibles. Los primeros beneficios incluyen un compromiso mayor de los usuarios ("flujo") y cargas más reducidas en los servidores, pero esto es sólo el principio. La entrega de información en tiempo real probablemente se haga ubicua, un requisito para casi cualquier website o servicio.&lt;/blockquote&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/112773462326884407-6146394734722314165?l=digitta.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://digitta.com/feeds/6146394734722314165/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://digitta.com/2009/09/el-fin-de-la-asimetria-de-http-en-la.html#comment-form" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/6146394734722314165?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/6146394734722314165?v=2" /><link rel="alternate" type="text/html" href="http://digitta.com/2009/09/el-fin-de-la-asimetria-de-http-en-la.html" title="El fin de la asimetría de HTTP en la web" /><author><name>Àl</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11672226968306882234" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;CkMGRn06cCp7ImA9WxNTFk8.&quot;"><id>tag:blogger.com,1999:blog-112773462326884407.post-5519556947198117904</id><published>2009-08-18T20:33:00.000+02:00</published><updated>2009-08-18T20:33:47.318+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-08-18T20:33:47.318+02:00</app:edited><title>URLPacks, grupos de páginas en el navegador</title><content type="html">Tengo la mala costumbre de abrir más y más pestañas con páginas a las que quiero dedicar tiempo en algún momento y que se van acumulando hasta que se llena la barra de pestañas. Entre otros problemas, esta mañana me ha pasado que Google Chrome ha dado un error justo cuando intentaba restaurar todas las que tenía abiertas anteriormente y se ha cerrado. Al volverlo a abrir, ha aparecido una triste pestaña en blanco, algo que, por desgracia, ya me ha pasado anteriormente con Firefox. Aunque he conseguido restaurar las páginas abiertas gracias al historial, me he prometido que iba a resolver de una vez por todas el maldito problema de las pestañas y me he puesto manos a la obra.&lt;br /&gt;
&lt;br /&gt;
La idea es tener una aplicación web muy ligera que acumule URLs relacionados con un tema y que se muestre como si fuese una barra de sub-pestañas. La aplicación está incluida en un único archivo PHP que realiza todas las funciones:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;mostrar la página del grupo (&lt;code&gt;get&lt;/code&gt;). Se muestra como una barra horizontal de enlaces que al ser pulsados cargan la página en la parte inferior, justo como si se tratase del mismo navegador. Además hay botones para limpiar la parte inferior, crear un marcador del grupo o agregar una nueva URL&lt;/li&gt;
&lt;li&gt;crear un grupo nuevo (&lt;code&gt;post&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;agregar una nueva url a un grupo existente (&lt;code&gt;put&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;eliminar una URL del grupo (&lt;code&gt;delete&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;listar los grupos disponibles (&lt;code&gt;list&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;mostrar un &lt;a href="http://es.wikipedia.org/wiki/Favicon"&gt;favicon&lt;/a&gt; personalizado para un grupo mostrando las primeras 6 letras del título&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
Para almacenar la información sobre los grupos, he decidido usar un archivo ZIP de nombre "urlpacks.zip" alojado junto al archivo "urlpacks.php" que aparece a continuación. El ZIP contiene un archivo comprimido por cada grupo; el nombre coincide con el título del grupo; y el contenido son las URLs separadas por un salto de línea: Más sencillo imposible.&lt;br /&gt;
&lt;br /&gt;
&lt;pre name="code" class="php"&gt;&amp;lt;?php
$id= $_GET['id'];
$action= $_GET['action'];
$url= $_GET['url'];

if($action=='') {
 if (!empty($url)) $action="post";
 else $action="list";
}

$zip = new ZipArchive();
if ($zip-&gt;open('urlpacks.zip')!==TRUE) {
    exit("cannot open urlpacks.zip\n");
}

switch ($action) {
case 'favicon':
 header('Content-type: image/png');
 $im= imagecreate(16,16);
 $color= imagecolorallocate($im, 255,255,255);
 $string= str_replace(' ','',$id);
 $font = imageloadfont('./terminal9.gdf');
 imagestring($im, $font, 1, -1, $string, imagecolorallocate($im, 0,0,0));
 imagestring($im, $font, 1, 6, substr($string,3), imagecolorallocate($im, 90,90,90));
 imagecolortransparent($im,$color);
 imagepng($im);
 imagedestroy($im);
 exit;
case 'put':
 $data= explode("\n", $zip-&gt;getFromName($id));
 array_push($data,$url);
 $zip-&gt;addFromString($id, implode("\n",$data));
 header("location:?action=get&amp;id=".$id);
 exit;
case 'post':
 if (!$zip-&gt;getFromName($id)) $zip-&gt;addFromString($id, $url);
 header("location:?action=get&amp;id=".$id);
 exit;
case 'delete':
 $data= explode("\n", $zip-&gt;getFromName($id));
 $key= array_search($url, $data);
 if ($key!==FALSE) array_splice($data,$key,1);
 $zip-&gt;addFromString($id, implode("\n",$data));
 header("location:?action=get&amp;id=".$id);
 exit;
case 'list':
 echo "&lt;h1&gt;index&lt;/h1&gt;&lt;ul&gt;";
 for ($i=0; $i&lt;$zip-&gt;numFiles;$i++) {
  $item= $zip-&gt;statIndex($i);
  echo '&lt;li&gt;&lt;a href="?action=get&amp;id=',$item['name'],'"&gt;',$item['name'],'&lt;/a&gt; ';&lt;br /&gt;
  $data= explode("\n", $zip-&gt;getFromName($item['name']));&lt;br /&gt;
  if (count($data)&gt;0) {&lt;br /&gt;
   echo '&lt;ol&gt;';
   foreach($data as $link) echo '&lt;li&gt;&lt;a href="',$link,'"&gt;',$link,'&lt;/a&gt;&lt;/li&gt;';
   echo '&lt;/ol&gt;';&lt;br /&gt;
  }&lt;br /&gt;
  echo '&lt;/li&gt;';
 }
 echo '&lt;/ul&gt;';
 exit;
default:
}
$zip-&gt;close();
$zip-&gt;open('urlpacks.zip');
$data= $zip-&gt;getFromName($id);
$data= explode("\n", $data);
if (!is_array($data)) $data= Array($zip-&gt;getFromName($id));
?&gt;
&amp;lt;head&gt;
&amp;lt;title&gt;&amp;lt;?=$id?&gt; :: URLPacks&amp;lt;/title&gt;
 &amp;lt;link rel="shortcut icon" href="urlpacks.php?action=favicon&amp;id=&amp;lt;?=$id?&gt;" type="image/png" /&gt;
&amp;lt;/head&gt;
&amp;lt;body&gt;
&amp;lt;style&gt;
body {
 font: 9px verdana, arial, sans-serif;
 margin:0;
 background:url(/images/grid.gif);
 overflow:hidden !important;
}
.action{
 text-decoration:none;
 color:red;
 font-size:15px;
 font-weight:bold;
}
li {
 padding:0 3px;
 white-space:nowrap !important;
 width:150px;
 overflow:hidden !important;
 border:1px outset gray; border-bottom-width:0;
 float:left;
 margin-right:2px;
 background:white;
}
li a { text-decoration:none; }
li a:focus { background: yellow; }
ul {
 top:25px;
 left:5px !important;
 position:absolute;
 padding-left:0 !important;
}
.icon {
 position:absolute;
 top:3px;
 width:22px;
}
.titulo {
 margin-left:90px;
 margin-top:-16px;
 font: bold 50px calibri, tahoma, verdana, arial, sans-serif ;
 letter-spacing:-1px;
 position:absolute;
}
iframe { border-top:1px solid black; background:white; }
img { border:0; }
&amp;lt;/style&gt;

&amp;lt;a title="marcador para grupo &amp;lt;?=$id?&gt;" href="/urlpacks.php?&amp;id=&amp;lt;?=$id?&gt;"&gt;&amp;lt;img src="http://www.iconfinder.net/data/icons/oxygen/22x22/actions/bookmark.png" class="icon" /&gt;&amp;lt;/a&gt;
&amp;lt;a href="about:blank" target="page"&gt;&amp;lt;img src="http://www.sonicvisualiser.org/doc/reference/1.3/en/images/icons/erase.png" title="vaciar marco inferior" style="margin-left:24px;" class="icon" /&gt;&amp;lt;/a&gt;
&amp;lt;a title="agrega nuevo enlace" class="action" href="#" onclick="if (newurl=prompt('URL','')) {location.href='?id=&amp;lt;?=$id?&gt;&amp;action=put&amp;url='+newurl; }"&gt;&amp;lt;img src="http://academia.usbbog.edu.co/imagenes/add.png" style="margin-left:50px;" class="icon" /&gt;&amp;lt;/a&gt;
&amp;lt;span class="titulo"&gt;&amp;lt;?=$id?&gt;&amp;lt;/span&gt;
&amp;lt;ul&gt;&amp;lt;?php
foreach($data as $link) {
 echo '&lt;li&gt;&lt;a class="action" href="#" onclick="if(confirm(\'Seguro?\')){ location.href=\'?id=',$id,'&amp;action=delete&amp;url=',$link,'\';}"&gt;&lt;img src="http://data.colorzilla.com/images/close-icon.png" align="absmiddle" title="',$link,'"/&gt;&lt;/a&gt; &lt;a target="page" href="',$link,'"&gt;',$link,'&lt;/a&gt; &lt;/li&gt;';
}
?&gt;&amp;lt;/ul&gt;
&amp;lt;iframe name="page" src="about:blank" style="width:100%;height:100%;margin-top:50px;" frameborder=0&gt;&amp;lt;/iframe&gt;
&amp;lt;/body&gt;
&amp;lt;?php

$zip-&gt;close();
?&gt;
&lt;/pre&gt;&lt;br /&gt;
Para crear un nuevo grupo a partir de la página actual, tengo un bookmarklet en el navegador con el siguiente código:&lt;br /&gt;
&lt;br /&gt;
&lt;pre name="code" class="js:nocontrols"&gt;javascript:id=prompt('nom',Math.random().toString().substr(2));location.href='http://servidor/urlpacks.php?id='+id+'&amp;url='+location.href;&lt;/pre&gt;&lt;br /&gt;
En mi servidor de alojamiento funciona perfectamente y he reducido notablemente las pestañas abiertas. Espero que le sea útil a alguien más. Por cierto, he probado varias extensiones de Firefox para conseguir lo mismo, pero ahora mismo estoy usando Chrome y quería mantener yo la información de los grupos (fácilmente descargable mediante http://servidor/urlpacks.zip)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/112773462326884407-5519556947198117904?l=digitta.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://digitta.com/feeds/5519556947198117904/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://digitta.com/2009/08/urlpacks-grupos-de-paginas-en-el.html#comment-form" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/5519556947198117904?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/5519556947198117904?v=2" /><link rel="alternate" type="text/html" href="http://digitta.com/2009/08/urlpacks-grupos-de-paginas-en-el.html" title="URLPacks, grupos de páginas en el navegador" /><author><name>Àl</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11672226968306882234" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;DUAAQ3ozeip7ImA9WxNTFUQ.&quot;"><id>tag:blogger.com,1999:blog-112773462326884407.post-4577842075581798901</id><published>2009-08-17T18:07:00.001+02:00</published><updated>2009-08-18T14:15:42.482+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-08-18T14:15:42.482+02:00</app:edited><title>La web del Pushbutton (3)</title><content type="html">&lt;a href="http://draft.blogger.com/"&gt;&lt;/a&gt;Traduzco &lt;a href="http://dashes.com/anil/2009/07/the-pushbutton-web-realtime-becomes-real.html"&gt;El "tiempo real" se vuelve "real"&lt;/a&gt; de Anil Dash. Con este artículo termino la serie de tres aunque continuaré publicando sobre estos temas por su interés. Posiblemente "pushbutton" se convierta en un término tan familiar como "Ajax" en un futuro cercano.



&lt;b&gt;Pushbutton&lt;/b&gt; es el nombre para lo que creo que será una actualización para la web, en la que cualquier sitio o aplicación pueda entregar mensajes en tiempo real a una audiencia a la escala de la web, usando tecnologías gratuitas y abiertas de bajo coste y sin confiar en una sola compañía como Twitter o Facebook. Las piezas de esta plataforma acaban de unirse para hacer posible un conjunto de nuevas características y aplicaciones que habrían sido casi imposibles de desarrollar en el pasado para un desarrollador web medio. El área más interesante de nuevo desarrollo en la web es la innovación que tiene lugar alrededor de la mensajería en tiempo real, la habilidad de entregar actualizaciones a un sitio web o a una aplicación en uno o dos segundos. Mientras que varios sistemas como Yahoo News Alerts o lectores de fuentes (feeds) como Google Reader han ofrecido formas simples de entregar notificaciones relativamente rápidas, aún están construidas sobre infraestructuras que confían en solicitar repetidamente una página web. Estos sistemas hacen el equivalente a pulsar repetidamente el botón "actualizar" del navegador.

&lt;img src="http://dashes.com/anil/images/small-pushbutton-diagram.png" style="float: right; margin: 10px;" /&gt;Mientras que esos sistemas han estado usando estos métodos ineficientes para entregar actualizaciones, plataformas más modernas como Twitter, Facebook y FriendFeed se han enfocado en construir la infraestructura para la entrega eficiente a gran escala de actualizaciones usando sus propias redes propietarias. Se ha prestado mucha atención al límite de 140 carácteres de Twitter, o al News Feed de Facebook, pero la impresionante tecnología que permite esa experiencia de usuario en estas plataformas es la inmediatez con la que las actualizaciones son entregadas. Los sistemas anteriores, como la mensajería instantánea o el chat, permitían mensajería a tiempo real a cualquiera de uno a uno o a pequeños grupos, pero ha sido difícil entregar esos mensajes en tiempo real a todo el mundo que quería recibirlos a menos que se tuviese mucho dinero, conocimientos e infraestructura. Otra barrera es que, mientras que hay diferentes programas y clientes que te dejan conectarte a Twitter o Facebook con tus propias aplicaciones, no ha habido opciones abiertas y gratuitas para entregar mensajes en tiempo real a una audiencia mayor si no podías, o no querías, confiar en dichas compañías. Pero recientemente, unas pocas piezas clave han encontrado su sitio y que hacen relativamente fácil y económico añadir mensajería en tiempo real como una mejora incremental a sitios y aplicaciones web. Este conjunto de tecnologías relacionadas, que estoy llamando plataforma &lt;code&gt;Pushbutton&lt;/code&gt;, producirá un amplio conjunto de nuevas capacidades para usuarios, editores y desarrolladores en la web. Y lo mejor, &lt;b&gt;las tecnologías &lt;code&gt;Pushbutton&lt;/code&gt; son gratuitas, abiertas y descentralizadas&lt;/b&gt;, significando con ello que la llegada del tiempo real a la web no será dominado por una sola compañía.

&lt;h2&gt;Definiendo Pushbutton&lt;/h2&gt;El concepto y potencial de Pushbutton es como el de &lt;a href="http://adaptivepath.com/ideas/essays/archives/000385.php"&gt;Ajax&lt;/a&gt; - no es una sola tecnología o invención, es una familia entera de tecnologías, algunas de las cuales han estado en desarrollo o en producción cerca de una década, que juntas permiten esta nueva web en tiempo real. La base de Pushbutton está construida sobre estos sistemas:

&lt;ul&gt;&lt;li&gt;&lt;a href="http://en.wikipedia.org/wiki/Atom_%28standard%29"&gt;Atom&lt;/a&gt; y &lt;a href="http://cyber.law.harvard.edu/rss/rss.html"&gt;RSS&lt;/a&gt;: Los formatos de fuente más comunes para sindicación en la web&lt;/li&gt;
&lt;li&gt;&lt;a href="http://code.google.com/p/pubsubhubbub/"&gt;PubSubHubBub&lt;/a&gt; y &lt;a href="http://rsscloud.org/walkthrough.html"&gt;RSSCloud&lt;/a&gt;: potentes nuevos distribuidores de mensajes&lt;/li&gt;
&lt;li&gt;&lt;a href="http://webhooks.pbworks.com/"&gt;Web Hooks&lt;/a&gt;: Simples servicios web para recibir mensajes en lugar de enviarlos&lt;/li&gt;
&lt;/ul&gt;Los sistemas &lt;code&gt;Pushbutton&lt;/code&gt; usan HTTP, el protocolo fundamental de la web, para la comunicación entre estos componentes. La arquitectura de la entrega de mensajes en &lt;code&gt;Pushbutton&lt;/code&gt; es fácil de comprender. Antes de &lt;code&gt;Pushbutton&lt;/code&gt;, en los sistemas modernos, cuando creas un mensaje (un artículo de un blog, un &lt;i&gt;tweet&lt;/i&gt; u otra actualización) se publica en tu fuente RSS o Atom, cada aplicación o sitio que quiere recibir las actualizaciones tiene que solicitar repetidamente la fuente para saber cuando ha cambiado. Opcionalmente se puede notificar ("ping") a algunas aplicaciones para indicarles que ha llegado el momento de recoger las nuevas actualizaciones, pero esto resulta pesado en tiempo y recursos para ambos lados, especialmente si quieres notificar a un montón de gente. En el mejor caso, el sistema que tenemos ahora es análogo a una persona que viene a tu casa y te dice "eh, hay una nueva edición de tu periódico favorito. Debes ir a por ella". Y entonces tienes que ir a la fábrica donde se imprime el periódico a recoger tu copia. En la web &lt;code&gt;Pushbutton&lt;/code&gt;, esa persona entrega la noticia entera en tu casa en el instante en el que es publicada. Eso es así porque las aplicaciones que implementan &lt;code&gt;Pushbutton&lt;/code&gt; mejorarán el sistema actual entregando no sólo la notificación de que hay un nuevo mensaje, sino el contenido del mismo. Y en lugar de requerir que todas las aplicaciones vengan al sitio a leer la actualización, usan un servidor distribuidor (hub) en la &lt;i&gt;nube&lt;/i&gt; para pasar el mensaje directamente a todos los receptores que estén interesados. &lt;div&gt;&lt;img src="http://dashes.com/anil/images/pushbutton-delivery.png" /&gt;

&lt;ol&gt;&lt;li&gt;Tú, el emisor, creas un mensaje a ser entregado via RSS o Atom&lt;/li&gt;
&lt;li&gt;Tu aplicación da el mensaje a uno o más distribuidores PubSubHubBub o RSSCloud que residan en la "nube"&lt;/li&gt;
&lt;li&gt;Los distribuidores PubHubSubBub o RSSCloud entregan el mensaje a todos los receptores, las aplicaciones o sitios que hubiesen solicitado actualizaciones de tu sitio&lt;/li&gt;
&lt;/ol&gt;De esta forma, cada vez que creas un mensaje, un gran numero de receptores pueden consumir ese mensaje casi en tiempo real (normalmente menos de un segundo) sin apenas complejidad. Este tipo de mensajería ha sido posible con tecnologías propietarias o más oscuras, pero el ecosistema &lt;code&gt;Pushbutton&lt;/code&gt; supone un gran avance por las siguientes razones:

&lt;ul&gt;&lt;li&gt;Enviar mensajes requiere solo &lt;b&gt;un cambio pequeño&lt;/b&gt; a una fuente RSS o Atom, y una notificación simple y bien definida en lugar de cambios importantes a la aplicación donde creas tus mensajes&lt;/li&gt;
&lt;li&gt;Recibir mensajes es también &lt;b&gt;muy simple&lt;/b&gt; requiriendo sólo que un desarrollador programe la gestión de notificaciones de actualizaciones&lt;/li&gt;
&lt;li&gt;La mayoría de &lt;b&gt;la complejidad del sistema la gestionan los servidores de distribución&lt;/b&gt;, que están bien documentados, implementables en una variedad de lenguajes de programación y construido alrededor de código abierto que probablemente atraiga a una gran comunidad de desarrolladores&lt;/li&gt;
&lt;li&gt;La mayoría del esfuerzo de escalado y de los costes tienen lugar a nivel de distribuidor, y todos los distribuidores actuales están &lt;b&gt;diseñados para ejecutarse sobre sistemas "nube" económicos&lt;/b&gt; como Google App Engine o Amazon EC2&lt;/li&gt;
&lt;li&gt;El software para enviar, recibir o ejecutar un distribuidor es &lt;b&gt;gratuito, de fuente abierta y está disponible&lt;/b&gt; en casi cualquier plataforma&lt;/li&gt;
&lt;li&gt;Los mensajes enviados sobre plataformas &lt;code&gt;Pushbutton&lt;/code&gt; son &lt;b&gt;entregados via HTTP&lt;/b&gt;, que es familiar a cualquier desarrollador web y funciona bien sobre cualquier entorno de alojamiento. Todas las peticiones entre las diferentes capas del sistema &lt;code&gt;Pushbutton&lt;/code&gt; pueden ser realizadas como simples llamadas &lt;code&gt;REST&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Las tecnologías &lt;code&gt;Pushbutton&lt;/code&gt; pueden ser &lt;b&gt;adoptadas incrementalmente&lt;/b&gt;, de forma que las características pueden ser añadidas poco a poco en el lado del receptor o del emisor, sin requerir una actualización completa a la arquitectura de la infraestructura o de la aplicación&lt;/li&gt;
&lt;/ul&gt;&lt;h2&gt;Quién está detrás de Pushbutton&lt;/h2&gt;Las tecnologías &lt;code&gt;Pushbutton&lt;/code&gt; han sido creadas y defendidas por algunos de los desarrolladores más creíbles y experimentados de las tecnologías web sociales. A continuación puede verse un breve resumen de la experiencia de dichos componentes:

&lt;ul&gt;&lt;li&gt;PubHubSubBub fue co-creado por &lt;a href="http://brad.livejournal.com/"&gt;Brad Fitzpatrick&lt;/a&gt; y &lt;a href="http://www.google.com/profiles/bslatkin"&gt;Brett Slatkin&lt;/a&gt; de Google. Brad fue el fundador de &lt;a href="http://livejournal.com/"&gt;LiveJournal&lt;/a&gt;, y creó tecnologías web sociales tan fundamentales como &lt;a href="http://en.wikipedia.org/wiki/Memcached"&gt;Memcached&lt;/a&gt; u &lt;a href="http://openid.net/"&gt;OpenID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Las ideas de los &lt;a href="http://www.weblogs.com/api.html"&gt;pings de actualización XML-RPC&lt;/a&gt;, RSS y la RSS Cloud fueron promovidas por &lt;a href="http://scripting.com/"&gt;Dave Winer&lt;/a&gt;, que ha estado desarrollando activamente implementaciones abiertas de estas tecnologías&lt;/li&gt;
&lt;li&gt;Los Web Hooks han sido evangelizados por &lt;a href="http://progrium.com/"&gt;Jeff Lindsay&lt;/a&gt;, y han sido desplegados por una variedad de distintas compañías y plataformas que desarrollaron dicha técnica por su cuenta&lt;/li&gt;
&lt;/ul&gt;Además Google ha apoyado el desarrollo por Brad y Brett de PubSubHubBub, y ha permitido su uso sobre el servicio Google FeedBurner. Varias compañías más pequeñas están desplegando grandes partes de esta infraestructura. En breve, algunas de las mejores reputaciones en el desarrollo de sistemas web abiertos han hecho posible &lt;code&gt;Pushbutton&lt;/code&gt;, desde las compañías tecnológicas más grandes hasta los desarrolladores independientes más firmes de la web.

&lt;h2&gt;Ideas relacionadas y proyectos anteriores&lt;/h2&gt;Hay un montón de tecnologías existentes que han influenciado la creación y evolución de las tecnologías &lt;code&gt;Pushbutton&lt;/code&gt;; Si estás familiarizado con alguno de esos sistemas probablemente ya estés avanzado en comprender parte de lo que &lt;code&gt;Pushbutton&lt;/code&gt; intenta conseguir.

&lt;ul&gt;&lt;li&gt;Twitter &lt;a href="http://apiwiki.twitter.com/Streaming-API-Documentation"&gt;Firehose&lt;/a&gt;, FriendFeed &lt;a href="http://code.google.com/p/simpleupdateprotocol/"&gt;SUP&lt;/a&gt;, el &lt;a href="http://www.sixapart.com/labs/update/"&gt;flujo de actualizaciones&lt;/a&gt; de TypePad: Estos sistemas de entrega en tiempo real ofrecen el contenido de sus respectivas plataformas como un flujo interminable que los desarrolladores pueden consumir y usar en sus aplicaciones. Actualmente, todos tienen licencias y grados de apertura distintos, y formatos ligeramente diferentes para entregar las actualizaciones, pero han probado la utilidad de la "parte de envío" de la funcionalidad en tiempo real de &lt;code&gt;Pushbutton&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://xmpp.org/about/"&gt;XMPP&lt;/a&gt; (Jabber), &lt;a href="http://en.wikipedia.org/wiki/Network_News_Transfer_Protocol"&gt;NNTP&lt;/a&gt; (Usenet), &lt;a href="http://en.wikipedia.org/wiki/Internet_Relay_Chat"&gt;IRC&lt;/a&gt;: Estos viejos protocolos de internet han ofrecido varios grados de mensajería en tiempo real y capacidades de distribución de mensajes, y pueden formar una base de experiencia muy útil de la que los desarrolladores de &lt;code&gt;Pushbutton&lt;/code&gt; pueden aprender. En algunos casos, elecciones fundamentales de arquitectura sobre seguridad, autenticación o arquitectura fueron hechas cuando la Internet estaba menos poblada y era menos compleja, haciendolas inapropiadas para las aplicaciones de hoy. En todos los casos, estos protocolos son menos conocidos por los desarrolladores web más contemporáneos, y por ello se echan en falta &lt;i&gt;toolkits&lt;/i&gt; y recursos de desarrollo, que hace que resulte muy complejo desplegarlos en entornos comunes.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://en.wikipedia.org/wiki/Trackback"&gt;TrackBack&lt;/a&gt; y &lt;a href="http://www.hixie.ch/specs/pingback/pingback"&gt;Pingback&lt;/a&gt;: Estos sistemas de entrega de actualizaciones entre sistemas de publicación (blogs) fueron muy efectivos en permitir conversaciones ricas distribuidas en los primeros días de la blogosfera. Estos han ido disminuyendo en utilidad debido a las implementaciones deficientes o nulas de un sistema de autenticación que han llevado a problemas de spam, y a una falta general de comprensión de su utilidad por parte de los nuevos bloggers. &lt;code&gt;Pushbutton&lt;/code&gt; podría ofrecer una oportunidad de restaurar algo del valor de la idea que originó estos sistemas&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.reversehttp.net/"&gt;Reverse HTTP&lt;/a&gt; podría terminar siendo un componente útil de algunas implementaciones de &lt;code&gt;Pushbutton&lt;/code&gt;, como complemento o como compañía a &lt;a href="http://en.wikipedia.org/wiki/Comet_%28programming%29"&gt;Comet&lt;/a&gt; y &lt;a href="http://en.wikipedia.org/wiki/Comet_%28programming%29#Alternatives"&gt;técnicas relacionadas&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;Posibles problemas:&lt;/div&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;¿Una guerra de formatos?&lt;/b&gt; Si estás familiarizado con las comunidades que discuten de tecnologías como fuentes (feeds) sabrás que tienen una merecida reputación de ser contenciosas e, incluso, de abrir acaloradas disputas sobre detalles arcanos. No creo que sea probable que esto pase aquí, ya que sólo hay uno o dos formatos viables para cada capa de la plataforma y los creadores de cada parte han mostrado esfuerzos consistentes y de buena fé para promover la interoperabilidad donde sea posible y la coexistencia pacífica donde sea necesario. En la comunidad Ajax, por ejemplo, la "X" de Ajax a menudo significa JSON en lugar de XML, pero esto no ha dificultado su amplia adopción. También estoy deseando comprometerme personalmente para intentar prevenir cualquier tipo de conflicto interpersonal que pudiera inhibir la adopción de dichas tecnologías. ¿Preocupado? &lt;b&gt;No&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;¿Temas de escalado?&lt;/b&gt; Inevitablemente habrá aprender cosas sobre cómo escalar una capa de distribución intensiva en recursos de un sistema &lt;code&gt;Pushbutton&lt;/code&gt;. Pero puesto que los distribuidores trabajan en sistemas de "la nube" que hacen fácilmente disponibles enormes cantidades de recursos computacionales, puesto que los programadores que creen las implementaciones de referencia del software de distribución tienen una gran experiencia haciendo sistemas a escala web y puesto que es relativamente simple introducir nuevos distribuidores conformes se necesite, esto probablemente no resulte un factor importante para la adopción de &lt;code&gt;Pushbutton&lt;/code&gt;. ¿Preocupado? &lt;b&gt;No&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;¿Inquietudes sobre propiedad intelectual?&lt;/b&gt; No soy abogado y esto no es consejo legal. Pero ya ha habido un gran interés sobre estos sistemas y es probable que si aparecen malos actores interesados en lanzar sus abogados de patentes sobre esta clase de sistema, probablemente ya estarían demandando a diestra y siniestra. Y los jugadores principales que ya se han implicado han mostrado un deseo consistente en crear sistemas verdaderamente abiertos que no tengan impedimentos de propiedad intelectual. Dicho de forma simple, creo que cualquier lo suficientemente listo para inventar esta clase de tecnologías es lo suficientemente listo para no querer parecer estúpidos demandando a alguien por usarlas. ¿Preocupado? &lt;b&gt;Probablemente no&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;¿Competición por parte de sistemas centralizados?&lt;/b&gt; Las tecnologías &lt;code&gt;Pushbutton&lt;/code&gt; no son sólo gratuitas y abiertas, son descentralizadas, por lo que resultan una seria amenaza al modelo "&lt;a href="http://news.gilbert.org/OutsmartingFacebook"&gt;trampa de langostas&lt;/a&gt;" del software social. Podemos esperar competición seria de las redes centralizadas que están actualmente construyendo esta clase de sistemas. Si hay una amenaza para la adopción de &lt;code&gt;Pushbutton&lt;/code&gt;, esta será probablemente la causa. ¿Preocupado? &lt;b&gt;Sin duda&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;¿Mala experiencia de usuario?&lt;/b&gt; Una de las peores cosas que podemos hacer para usar las nuevas tecnologías es ignorar las implicaciones sociales, personales e incluso políticas de dicho uso. Mensajes que sean entregados inmediatamente no pueden, por su propia naturaleza, ser borrados de todos los sitios en los que aparezcan. La idea de archivar permanentemente estos tipos de mensajes no es familiar para muchos de los usuarios menos familiarizados tecnológicamente. Y cuando vemos algo brillante y nuevo, tenemos la tentación de usar tecnologías porque sí, estemos o no resolviendo un problema real u ofreciendo un valor real. Si &lt;code&gt;Pushbutton&lt;/code&gt; recibe un mal golpe pronto a pesar de su potencial tremendo, esta será la razón. ¿Preocupado? &lt;b&gt;Por descontado, sí&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;h2&gt;Conclusión&lt;/h2&gt;Tengo una excitación tremenda sobre la nueva era en tiempo real de las aplicaciones web. Mientras que me considero fundamentalmente un persona optimista, tengo un gran escepticismo cuando se trata de dar bombo publicitario sin sentido sobre tecnologías nuevas, de forma que si me permito algo de ese bombo publicitario aquí lo hago con algo de desgana.  Pero creo que la web con &lt;code&gt;Pushbutton&lt;/code&gt; tiene la oportunidad de dar, a los individuos y a las organizaciones con voces distintas y apasionadas, la habilidad de ser aún más inmediato y expresivo en la web, y &lt;a href="http://dashes.com/anil/2009/07/ten-years.html"&gt;después de diez años&lt;/a&gt; de publicación en la web, esa es la parte que más me gusta. No tengo dudas de que algunos escépticos dirán que "&lt;code&gt;Pushbutton&lt;/code&gt; es sólo PubSubHubBub pero con otro nombre", justo como dijeron en su día "Ajax es XMLHttpRequest pero con otro nombre", y si eso es lo que los chicos super-geeky quieren creer, me parece bien. Y estoy seguro de que aún habrán algunos detalles técnicos  por resolver. Pero creo que dando al concepto general una nombre comprensible y dando una explicación comprensible por cualquiera con interés, puede catalizar interés en una nueva área de innovación en la web. Y para ser honesto, cuando veo chicos como Brad Fitzpatrick y Dave Winer trastear sobre el mismo conjunto de problemas, no puedo evitar pensar que algo interesante saldrá de ello. Sobre los próximos días, perfilaré las oportunidades de &lt;code&gt;Pushbutton&lt;/code&gt;, exponiendo más de la filosofía que tiene el potencial de darle a &lt;code&gt;Pushbutton&lt;/code&gt; algo más de significado que la mayoría de nuevas tecnologías web, y proviendo algunas explicaciones simples de como puedes empezar aprendiendo sobre cómo sacar ventaja de estas tecnologías. Sobre todo espero que ofrecerás tus críticas meditadas, correcciones detalladas e incluso mejores ideas. Seguiré la conversación en los comentarios, en la blogosfera y en un Twitter con el tag &lt;a href="http://search.twitter.com/search?q=pshb"&gt;#pshb&lt;/a&gt;.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/112773462326884407-4577842075581798901?l=digitta.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://digitta.com/feeds/4577842075581798901/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://digitta.com/2009/08/la-web-del-pushbutton.html#comment-form" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/4577842075581798901?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/4577842075581798901?v=2" /><link rel="alternate" type="text/html" href="http://digitta.com/2009/08/la-web-del-pushbutton.html" title="La web del Pushbutton (3)" /><author><name>Àl</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11672226968306882234" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;AkYNQXo-fCp7ImA9WxNTFkw.&quot;"><id>tag:blogger.com,1999:blog-112773462326884407.post-6876513470249445764</id><published>2009-08-14T12:08:00.006+02:00</published><updated>2009-08-18T19:56:30.454+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-08-18T19:56:30.454+02:00</app:edited><title>Reverse HTTP (2)</title><content type="html">Una de las &lt;i&gt;novedosas&lt;/i&gt; técnicas que están apareciendo para resolver el problema de la asimetría de HTTP (que el servidor web notifique a los navegadores sobre nuevas actualizaciones como los mensajes de un chat) pasa por aprovechar la tecnología &lt;a href="http://es.wikipedia.org/wiki/Comet"&gt;Comet&lt;/a&gt; (una conexión persistente entre navegador y servidor que permite que el servidor le envie datos al navegador en cuanto los tenga disponibles y sin que el navegador los haya solicitado explícitamente).

&lt;div&gt;
La idea es que el navegador abre una conexión persistente con el servidor, el cual le asigna una URL (generalmente un subdominio) propia cuyas peticiones son respondidas por el Javascript residente en la página del navegador. De esa forma, &lt;b&gt;la página &lt;i&gt;estática&lt;/i&gt; mostrada en el navegador se convierte a todos los efectos en un servidor web&lt;/b&gt; capaz de responder a las peticiones entrantes como un servidor web tradicional.

&lt;/div&gt;&lt;div&gt;
Traduzco la página &lt;a href="http://www.reversehttp.net/"&gt;reversehttp.net&lt;/a&gt; donde se explica con más detalle el funcionamiento de esta técnica.&lt;/div&gt;&lt;div&gt;
&lt;blockquote&gt;Realizar solicitudes para averiguar si hay actualizaciones es malo. Sabemos esto desde que existen los ordenadores. Pero si es así, ¿por qué hay tantos servicios basados en web (&lt;a href="http://blog.friendfeed.com/2008/08/simple-update-protocol-fetch-updates.html"&gt;SUP&lt;/a&gt;, fuentes RSS y Atom, Twitter, etc.) basados en esas solicitudes?

&lt;/blockquote&gt;&lt;blockquote&gt;La respuesta está, en primer lugar, en la asimetría de HTTP. La web está dividida en dos piezas: programas que realizan peticiones y programas que las manejan. Es muy raro ver un mismo programa comportándose a la vez como cliente y como servidor.&lt;/blockquote&gt;&lt;blockquote&gt;
Para arreglar esta asimetría necesitamos ser capaces de actuar como si poder responder a las peticiones HTTP fuese fácil para los programas, de forma que podamos notificar los cambios a las partes interesadas simplemente enviándoles una petición HTTP. Esta es la idea principal de los &lt;a href="http://blog.webhooks.org/about"&gt;web hooks&lt;/a&gt;.&lt;/blockquote&gt;&lt;blockquote&gt;
Necesitamos hacer fuerza para empujar los procesos que traten con &lt;a href="http://en.wikipedia.org/wiki/Comet_(programming)"&gt;long-polling&lt;/a&gt; (Comet) lejos del núcleo de la web y hacia sus límites que es donde tiene que estar.&lt;/blockquote&gt;&lt;blockquote&gt;
Necesitamos hacer que los programas pidan dinámicamente un pedazo del espacio de URLs usando un viejo cliente HTTP y gestionar las peticiones enviadas a las URLs en ese espacio usando ese mismo cliente HTTP.&lt;/blockquote&gt;&lt;blockquote&gt;
Una vez que eso esté hecho, la notificación asíncrona de eventos estará al alcance de cualquier programa que tenga acceso a una librería cliente de HTTP, y los protocolos y servicios sobre HTTP no tendrán que contorsionarse para tratar con la asimetríá de HTTP. Pueden suponer que todo el mundo es un servidor y solicitar y enviar contenidos desde y hacia donde quieran.
&lt;h2&gt;La solución&lt;/h2&gt;Crear un túnel HTTP sobre HTTP de una forma estructurada, controlable y segura. Deja que los programas pidan una parte del espacio de URLs y sirve HTTP, todo por medio de una librería ordinaria cliente de HTTP.
&lt;ul&gt;&lt;li&gt;Lee el &lt;a href="http://www.blogger.com/specs.html"&gt;borrador de la especifición&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;Comprueba &lt;a href="http://www.blogger.com/download.html"&gt;implementación&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;Prueba &lt;a href="http://www.reversehttp.net/demos/"&gt;las demos&lt;/a&gt;.&lt;/li&gt;&lt;/ul&gt;Incluso los programas que se ejecutan en entornos muy restrictivos, como Javascript en el navegador, pueden sacar ventaja del servicio ReverseHTTP. La implementación actual provee librerías sencillas y pequeñas para Python, Java y para el Javascript del navegador.
&lt;h2&gt;¿Por qué no se ha hecho esto antes?&lt;/h2&gt;Esta no es una idea completamente nueva, como parece, aunque no parece haberse usado ampliamente hasta el momento.&lt;/blockquote&gt;&lt;blockquote&gt;
Prácticamente al mismo tiempo que estaba escribiendo ReverseHttp (hacia Mayo de 2008), Donovan Preston de &lt;a href="http://secondlife.com/"&gt;Second Life&lt;/a&gt; escribía su versión de la misma idea, que también llamó &lt;a href="http://wiki.secondlife.com/wiki/Reverse_HTTP"&gt;Reverse HTTP&lt;/a&gt; (también conocida como "PTTH"). Su idea es usar la cabecera &lt;code&gt;Upgrade&lt;/code&gt; de HTTP 1.1 para intercambiar la dirección del protocolo, lo que funciona bien para entornos distintos del navegador. Él también tiene una solución basada en &lt;a href="http://en.wikipedia.org/wiki/Comet_(programming)"&gt;Comet&lt;/a&gt; muy similar a la mía, excepto que la suya usa objetos &lt;a href="http://www.ietf.org/rfc/rfc4627.txt"&gt;JSON&lt;/a&gt; para describir las peticiones y respuestas HTTP mientras la mia usa los formatos de los mensajes HTTP. &lt;a href="http://ulaluma.com/pyx/archives/2008/12/ptth_reverse_ht.html"&gt;Donovan Preston escribe más sobre la variante Second Life aquí&lt;/a&gt;, y ha producido, junto a Mark Lentczner, &lt;a href="http://www.ietf.org/internet-drafts/draft-lentczner-rhttp-00.txt"&gt;un borrador (Internet-Draft)&lt;/a&gt; para el protocolo basado en la cabecera &lt;code&gt;Upgrade&lt;/code&gt;.&lt;/blockquote&gt;&lt;h2&gt;Descargas y enlaces&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.reversehttp.net/download.html"&gt;Instrucciones para descargar la implementación&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Hay varios artículos sobre esta técnica en otros sitios:

&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.eflorenzano.com/blog/post/reverse-http/"&gt;http://www.eflorenzano.com/blog/post/reverse-http/&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://ulaluma.com/pyx/archives/2008/12/ptth_reverse_ht.html"&gt;http://ulaluma.com/pyx/archives/2008/12/ptth_reverse_ht.html&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://t0rxon.blogspot.com/2009/02/making-real-time-web-real-time.html"&gt;http://t0rxon.blogspot.com/2009/02/making-real-time-web-real-time.html&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://kirkwylie.blogspot.com/2008/12/scoble-joins-real-time-web-conversation.html"&gt;http://kirkwylie.blogspot.com/2008/12/scoble-joins-real-time-web-conversation.html&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://kirkwylie.blogspot.com/2008/12/rest-requires-asynchronous-notification.html"&gt;http://kirkwylie.blogspot.com/2008/12/rest-requires-asynchronous-notification.html&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.webhooks.org/"&gt;http://www.webhooks.org/&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://news.ycombinator.com/item?id=508238"&gt;http://news.ycombinator.com/item?id=508238&lt;/a&gt;&lt;span class="Apple-tab-span" style="white-space:pre"&gt; &lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;hr /&gt;&lt;h2&gt;Addendum&lt;/h2&gt;Es posible comprender mejor la potencia de esta técnica con la demo online disponible en &lt;a href="http://www.reversehttp.net/demos/demo.html"&gt;http://www.reversehttp.net/demos/demo.html&lt;/a&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/112773462326884407-6876513470249445764?l=digitta.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://digitta.com/feeds/6876513470249445764/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://digitta.com/2009/08/reverse-http-2.html#comment-form" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/6876513470249445764?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/6876513470249445764?v=2" /><link rel="alternate" type="text/html" href="http://digitta.com/2009/08/reverse-http-2.html" title="Reverse HTTP (2)" /><author><name>Àl</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11672226968306882234" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;DkIHQ3Y6cCp7ImA9WxNTEUg.&quot;"><id>tag:blogger.com,1999:blog-112773462326884407.post-3410886458531933813</id><published>2009-08-13T11:04:00.001+02:00</published><updated>2009-08-13T11:08:52.818+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-08-13T11:08:52.818+02:00</app:edited><title>Web Hooks (1)</title><content type="html">Este es la primera de una serie de tres traducciones de artículos que presentan tres tecnologías relacionadas y novedosas en el ámbito de la programación web. Web Hooks, Reverse HTTP y Pushbutton. La idea principal para habilitarla se describe en este &lt;a href="http://webhooks.pbworks.com/"&gt;primer artículo sobre webhooks&lt;/a&gt;: hacer disponible un script en una URL de forma que un servidor envie ahí información sobre cambios a los que se ha suscrito. Usa el famoso HTTP que siempre han usado los navegadores pero internamente entre servidores web para mandarse información.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Visita &lt;a href="http://webhooks.org/"&gt;webhooks.org&lt;/a&gt; para información más actualizada sobre esta tecnología o el &lt;a href="http://groups.google.com/group/webhooks"&gt;Grupo de Google&lt;/a&gt; para debatir&lt;/b&gt;&lt;br /&gt;
&lt;h2&gt;¿Qué son los web hooks?&lt;/h2&gt;Los web hooks (enganches web) permiten personalizar, extender e integrar las aplicaciones web que uses con todo lo que puedas acceder programaticamente. Para los desarrolladores web, los web hooks son un patron de diseño simple que sólo requiere la habilidad de hacer peticiones web y almacenar algo de información extra sobre los usuarios. Para los usuarios, los web hooks son una forma de obtener eventos y datos en tiempo real desde sus aplicaciones web. A partir de esto pueden usar esos datos como deseen, permitiéndoles extender e integrar, y empezar a obtener una verdadera visión de la web programable.&lt;br /&gt;
&lt;h2&gt;¿Cómo funcionan?&lt;/h2&gt;Dejando que el usuario especifique una URL para varios eventos, la aplicación hará un POST con los datos a esas URLs cuando esos eventos se produzcan. Con la disponibilidad económica del alojamiento con PHP y con almacenamiento de aplicaciones o scripts aún más simple como el de &lt;a href="http://appjet.com/"&gt;AppJet&lt;/a&gt; o &lt;a href="http://www.scriptlets.org/"&gt;Scriptlets&lt;/a&gt;, gestionar los datos del POST se convierte en trivial. Cómo usarlo depende de ti y de lo que quieras conseguir. Entre otras cosas puedes:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;crear notificaciones para ti o para cualquiera via email, IRC, Jabber, ...&lt;/li&gt;
&lt;li&gt;poner los datos en otra aplicación (sincronización de datos en tiempo real)&lt;/li&gt;
&lt;li&gt;procesar los datos y reenviarlos usando la API de la aplicación&lt;/li&gt;
&lt;li&gt;validar los datos y potencialmente prevenir su uso por la aplicación&lt;/li&gt;
&lt;/ul&gt;&lt;h2&gt;¿Por qué me debe importar?&lt;/h2&gt;Tan integrada como percibimos la web, la mayoría de aplicaciones operan en silos. Con la aparición de las APIs hemos visto mashups (combinaciones) y algun grado de integración entre aplicaciones. En cualquier caso, no hemos visto la visión de una web programable: una web donde tú como usuario puedes encauzar los datos entre aplicaciones de forma parecida a como se hace en la línea de comandos Unix. Algunos dicen que la respuesta es RSS. &lt;b&gt;Están equivocados&lt;/b&gt;. El corazón está en el sitio correcto pero la implementación es errónea. RSS es útil, pero no va a traernos la verdadera web programable.&lt;br /&gt;
&lt;br /&gt;
Necesitamos una forma simple de extraer datos en tiempo real para dejar que el usuario haga lo que quiera con ellos. Eso significa que nada de consultas repetitivas, nada de restricciones en el contenido, y nada de parsear el XML. Eso significa no usar RSS. Usar HTTP es más simple y más fácil de usar. PHP es un entorno de programación accesible y muy popular, por lo que es probable que se utilice a menudo para programar &lt;i&gt;hooklets&lt;/i&gt;... obtener los datos de una petición POST en PHP es tan sencillo como &lt;code&gt;$_POST['algo']&lt;/code&gt;. Y hacer la petición al script del usuario es tan simple como realizar una petición HTTP tradicional, algo que los entornos de programación tradicionales ya permiten. De hecho, los web hooks son más fáciles de implementar que una API.&lt;br /&gt;
&lt;br /&gt;
Ejemplos de implementaciones pueden encontrarse en Sitios como &lt;a href="http://wiki.shopify.com/WebHook"&gt;shopify&lt;/a&gt;, &lt;a href="http://www.surveygizmo.com/survey-blog/communicate-external-database-http-post/"&gt;SurveyGizmo&lt;/a&gt;, &lt;a href="http://www.zendesk.com/api/targets"&gt;ZenDesk&lt;/a&gt;, &lt;a href="http://github.com/guides/post-receive-hooks"&gt;GitHub&lt;/a&gt;, o &lt;a href="http://code.google.com/p/support/wiki/PostCommitWebHooks"&gt;Google Code&lt;/a&gt;.&lt;br /&gt;
&lt;h2&gt;Para saber más&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.slideshare.net/progrium/web-hooks"&gt;Una presentación con notas&lt;/a&gt;&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blogrium.com/?p=70"&gt;Post original sobre web hooks (Agosto 2006)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.slideshare.net/guest7e115a/web-hooks-on-pbwiki"&gt;Presentación con una explicación sobre como se implementó webhooks en PBwiki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blogrium.com/2006/11/27/lets-make-seeking-bliss-easier/"&gt;Un artículo sobre implementar la tecnología y otras ideas&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/112773462326884407-3410886458531933813?l=digitta.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://digitta.com/feeds/3410886458531933813/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://digitta.com/2009/08/web-hooks-1.html#comment-form" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/3410886458531933813?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/3410886458531933813?v=2" /><link rel="alternate" type="text/html" href="http://digitta.com/2009/08/web-hooks-1.html" title="Web Hooks (1)" /><author><name>Àl</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11672226968306882234" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;AkUESX0-fSp7ImA9WxJUGE8.&quot;"><id>tag:blogger.com,1999:blog-112773462326884407.post-9180843608944706522</id><published>2009-07-17T12:46:00.004+02:00</published><updated>2009-07-17T12:56:48.355+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-07-17T12:56:48.355+02:00</app:edited><title>Un plan de estudios para la web</title><content type="html">He traducido este interesante artículo de Tim Bray ( &lt;a href="http://www.tbray.org/ongoing/When/200x/2009/07/14/Web-Curriculum"&gt;Web Curriculum&lt;/a&gt; ) sobre un plan de estudios basado enteramente en tecnologías web.&lt;br /&gt;
&lt;br /&gt;
Propongo que el World Wide Web sirva también como esquema para estructurar gran parte del currículo académico de informática. Un estudio de la teoría y la práctica de las tecnologías web alcanzaría muchas áreas clave de nuestra disciplina. Es más, hay una forma natural de estructurar ese conocimiento para crear un curso de estudios que cubra varios semestres.&lt;br /&gt;
&lt;br /&gt;
(Un efecto colateral de tal propuesta es que los graduados entrarían en la población activa con una exposición significativa al conjunto de tecnologías que probablemente sería muy valiosa para sus carreras profesionales. Pero ese no es el argumento que estoy defendiendo aquí).&lt;br /&gt;
&lt;br /&gt;
En la siguiente distribución, la numeración de los cursos implica una primera estimación de los niveles y prerequisitos. Ya que nunca he diseñado un currículo y mis propios estudios universitarios han quedado ya décadas atrás, es bastante posible que mis sugerencias respecto al número de cursos y los niveles por semestre necesiten mejorar.&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;Web 101&lt;/code&gt;: Introducción a la arquitectura. Idea-núcleo: Enseñar lo que pasa cuando haces click en un enlace y una página con multimedia y personalizaciones es generada para el usuario.&lt;ul&gt;&lt;li&gt;
Cobertura: Qué es un navegador y un servidor. Qué son HTML, CSS, HTTP y URIs. Qué son servidores CGI y de aplicacion. Un vistazo inicial a la arquitectura subyacente.&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;
Ejercicios de ejemplo: Desplegar un servidor web. Escribir páginas simples HTML/CSS con un editor de textos. Escribir scripts CGI en un lenguaje de alto nivel para producir páginas personalizadas.&lt;/li&gt;&lt;/ul&gt;

&lt;code&gt;Web 210/310/410&lt;/code&gt;: Estructuras de datos y algoritmos. Idea-núcleo: El centro clásico del currículo de informática.
&lt;ul&gt;&lt;li&gt;
Cobertura: Todas las cosas habituales. La dimension web viene del uso de la implementación del modelo de objetos de documento (DOM) como ayuda para la docencia.
&lt;/li&gt;&lt;li&gt;
Ejercicios de ejemplo: Construir incrementalmente una implementación del DOM, probablemente en un lenguaje de bajo nivel como C o Java. Una amplia variedad de estructuras de datos y algoritmos importantes puede ser bien usada en dicha implementación.&lt;/li&gt;&lt;/ul&gt;

&lt;code&gt;Web 220&lt;/code&gt;: Principios de redes. Idea-núcleo: Visión clásica de lo básico de las redes.&lt;ul&gt;&lt;li&gt;
Cobertura: Este es un curso directo de redes de ordenadores, haciendo un recorrido convencional de la pila.&lt;/li&gt;&lt;li&gt;
Ejercicios de ejemplo: La implementación de un pequeño servidor web ligero que acepte conexiones y sirva peticiones de archivos desde su sistema de archivos.&lt;/li&gt;&lt;/ul&gt;

&lt;code&gt;Web 230&lt;/code&gt;: Interfícies de usuario. Idea-núcleo: Inspección de temas de UI, pero suponiendo al navegador como plataforma de entrega, para poner el foco en el contenido y no en la tecnología.&lt;ul&gt;&lt;li&gt;
Cobertura: Principios de usabilidad, diseño de la experiencia de usuario, breve introducción a la tipografía y a problemas de diseño, técnicas de evaluación. En el lado de la implementación, MVC (modelo-vista-controlador).&lt;/li&gt;&lt;li&gt;
Ejercicios de ejemplo: Construir (en grupos) una interfície web de usuario para un sistema que mantenga las puntuaciones de algún deporte, evaluar su usabilidad y mejorarla.&lt;/li&gt;&lt;/ul&gt;

&lt;code&gt;Web 320&lt;/code&gt;: Protocolos de aplicación. Idea-núcleo: las aplicaciones distribuidas están basadas en varios sabores del patrón de intercambio de mensajes. Este sería un recorrido de las opciones.&lt;ul&gt;&lt;li&gt;
Cobertura: Protocolos de flujo (streaming), protocolos orientados a sesión, protocolos de intercambio de mensajes al estilo HTTP.&lt;/li&gt;&lt;li&gt;
Ejercicios de ejemplo: implementar porciones seleccionadas de WebDAV, XMPP y AtomPub.&lt;/li&gt;&lt;/ul&gt;

&lt;code&gt;Web 340/440&lt;/code&gt;: Persistencia. Idea-núcleo: Estudiar los problemas que rodean el almacenamiento persistente de datos.&lt;ul&gt;&lt;li&gt;
Cobertura: Sistemas de archivos. Almacenamiento orientado al registro y al byte. Bases de datos relacionales. Bases de datos documentales y tablas de hash distribuidas.&lt;/li&gt;&lt;li&gt;
Ejercicios de ejemplo: Construir algunas de las piezas de una aplicación como Twitter o Facebook, implementando almacenamiento eficiente y recuperación de diferentes tipos de recursos de información.&lt;/li&gt;&lt;/ul&gt;

&lt;code&gt;Web 350&lt;/code&gt;: Parsing - Análisis sintáctico. Idea-núcleo: estudiar métodos para extraer estructuras de entradas de texto.&lt;ul&gt;&lt;li&gt;
Cobertura: Lo usual, incluyendo gramáticas, autómatas, y otras estrategias de parseo. Representación textual, especialmente Unicode.&lt;/li&gt;&lt;li&gt;
Ejercicios de ejemplo: Construir parseadores para cabeceras HTTP, para JSON, para XML y para ECMAScript(JavaScript) por ese orden.&lt;/li&gt;&lt;/ul&gt;

&lt;code&gt;Web 351&lt;/code&gt;: Diseño de formatos de datos. Idea-núcleo: Estudiar las técnicas y compromisos que aparecen en el diseño de formatos de datos.&lt;ul&gt;&lt;li&gt;
Cobertura: formatos de datos multimedia (JPG, PNG, vídeo), formatos de datos binarios (ASN.1, buffers de protocolo), formatos de datos textuales (JSON, XML). Compresión.&lt;/li&gt;&lt;li&gt;
Ejercicios de ejemplo: implementar un lector de PNG, diseñar (en grupo) un lenguaje XML no trivial.&lt;/li&gt;&lt;/ul&gt;

&lt;code&gt;Web 360&lt;/code&gt;: Seguridad. Idea-núcleo: estudiar técnicas para implementar programas de ordenador suponiendo la presencia de un gran número de usuarios maliciosos con conocimientos técnicos.&lt;ul&gt;&lt;li&gt;
Cobertura: introducción a las firmas digitales y tecnologías de encriptación. Análisis de amenazas. Ingeniería social. Virus, gusanos, botnets, y otros villanos.&lt;/li&gt;&lt;li&gt;
Ejercicios de ejemplo: implementar un ataque de fuerza bruta sobre una tecnología de encriptación obsoleta. Escribir una recomendación sobre cómo algunos exploits criminales con éxito y bien conocidos podrían haber sido evitados.&lt;/li&gt;&lt;/ul&gt;

&lt;code&gt;Web 470&lt;/code&gt;: Temas de grandes sistemas distribuidos. Idea-núcleo: estudiar los problemas que aparecen en sistemas demasiado grandes para un solo ordenador.&lt;ul&gt;&lt;li&gt;
Cobertura: REST, nombrado de objetos (DNS, URIs y otros esquemas de nombrado). Caché. Particionado de cargas de trabajo. Rastreo (crawling) de webs. Programación funcional. Gestión de errores.&lt;/li&gt;&lt;li&gt;
Ejercicios de ejemplo: Implementar algo que use DNS. Aprender ideas detrás de Erlang y Map/Reduce. Escribir un rastreador (crawler) de web de pequeña escala.&lt;/li&gt;&lt;/ul&gt;

&lt;code&gt;Web 480&lt;/code&gt;: Sistemas concurrentes. Idea núcleo: estudiar las opciones para hacer uso de modernas plataformas altamente concurrentes de ordenadores.&lt;ul&gt;&lt;li&gt;
Cobertura: hilos, procesos, actores, paso de mensajes.&lt;/li&gt;&lt;li&gt;
Ejercicios de ejemplo: Implementar la infraestructura de paso de mensajes en un lenguaje de "sabor funcional" (Scala, Erlang) y en uno convencional procedimental (Java, Ruby).&lt;/li&gt;&lt;/ul&gt;

¿Qué falta? El currículo web no cubre toda la informática. Aquí hay una lista (sin duda no exhaustiva) sin ningún orden en particular, de temas importantes donde un enfoque académico centrado en web obviamente no sería de ayuda:&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Gráficos de ordenador.&lt;/li&gt;&lt;li&gt;Orientación a objetos.&lt;/li&gt;&lt;li&gt;Software en tiempo real.&lt;/li&gt;&lt;li&gt;
Aritmética y representación numérica.&lt;/li&gt;&lt;li&gt;
Inteligencia artifical.&lt;/li&gt;&lt;li&gt;
Arquitectura de sistemas de ordenadores.&lt;/li&gt;&lt;li&gt;
Análisis de algoritmos.&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/112773462326884407-9180843608944706522?l=digitta.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://digitta.com/feeds/9180843608944706522/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://digitta.com/2009/07/un-plan-de-estudios-para-la-web.html#comment-form" title="2 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/9180843608944706522?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/9180843608944706522?v=2" /><link rel="alternate" type="text/html" href="http://digitta.com/2009/07/un-plan-de-estudios-para-la-web.html" title="Un plan de estudios para la web" /><author><name>Àl</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11672226968306882234" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total></entry><entry gd:etag="W/&quot;A0YAQ3w7fip7ImA9WxJUEE4.&quot;"><id>tag:blogger.com,1999:blog-112773462326884407.post-4862239350841741967</id><published>2009-07-08T09:44:00.001+02:00</published><updated>2009-07-08T09:45:42.206+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-07-08T09:45:42.206+02:00</app:edited><title>Google Chrome Operative System (segundo OS de Google)</title><content type="html">Empieza a ponerse divertido. Muchos vimos como Android, un sistema operativo abierto dirigido a dispositivos móviles empezaba a ser utilizado por algunos fabricantes de netbooks como alternativa a Windows ante la probabilidad de que se convierta en un estándar para Linux. Google nunca se ha manifestado al respecto, pero es un uso válido dada su licencia abierta.&lt;br /&gt;
&lt;br /&gt;
Pero hoy, Google &lt;a href="http://googleblog.blogspot.com/2009/07/introducing-google-chrome-os.html"&gt;ha anunciado el nuevo Chrome OS&lt;/a&gt;. Un sistema operativo basado exclusivamente en su propio navegador Chrome.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://farm4.static.flickr.com/3038/2874749029_d8cda4531d.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://farm4.static.flickr.com/3038/2874749029_d8cda4531d.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Recordemos que cuando Apple optó por WebKit como motor para su Safari, mucha gente se extraño del movimiento. Aunque ya entonces resultaba apreciablemente más rápido que cualquier otro disponible. Después Google hizo lo mismo con Chrome, mejorado con su motor de JavaScript V8. Una plataforma extremadamente potente que tiene entre sus cualidades el haber empezado de cero y haber repensado muchas de las cosas que damos por sentadas sobre cómo debe ser un navegador.&lt;br /&gt;
&lt;br /&gt;
Con esa misma filosofía, repensar cómo debe ser un sistema operativo, Google anuncia ahora que a mediados de 2010 publicará su projecto como fuente abierta: un sistema operativo ligero dirigido a aquellos que ya pasan cada vez más tiempo en el navegador, cuyo objetivo serán precisamente los netbooks (aunque también podrá ser usado por equipos convencionales) y que hará hincapié en la velocidad, la simplicidad y la seguridad.&lt;br /&gt;
&lt;br /&gt;
Google indica que han estado basándose en las quejas tradicionales de los usuarios: quieren tener acceso al correo electrónico instantáneamente, sin esperar a que arranque el ordenador y pongan en marcha el navegador; &amp;nbsp;quieren que sus equipos funcionen siempre tan rápido como cuando lo compraron (touché Microsoft); quieren que sus datos estén siempre ahí sin preocuparse de perder el ordenador o de tener que hacer copias de seguridad; y lo más importante: no quieren tener que pasar horas configurando sus equipos para que detecten cada elemento hardware (los odiados drivers) o actualizando el software.&lt;br /&gt;
&lt;br /&gt;
La arquitectura es simple: Chrome corriendo sobre un sistema de ventanas montado sobre un núcleo mínimo Linux. Recordemos que Chrome ya incluye Gears (geolocalización, base de datos local, ...) Por tanto, los desarrolladores sólo deben continuar haciendo lo que ya hacen: usar las tecnologías web disponibles en este momento. Definitivamente, JavaScript se posiciona, pese a los desprecios de muchas comunidades de programadores, como una seria opción de cara al futuro.&lt;br /&gt;
&lt;br /&gt;
Sólo una apreciación: &lt;a href="http://twitter.com/jimpick/status/2527915182"&gt;Con este movimiento, Google dispondrá de dos sistemas operativos abiertos basados en Linux y con Chrome sobre ellos&lt;/a&gt;. Los conflictos posiblemente acabarán llegando.&lt;br /&gt;
&lt;br /&gt;
Via &lt;a href="http://ajaxian.com/archives/google-chrome-os-web-developers-rule"&gt;Ajaxian&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/112773462326884407-4862239350841741967?l=digitta.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://digitta.com/feeds/4862239350841741967/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://digitta.com/2009/07/google-chrome-operative-system-no-era.html#comment-form" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/4862239350841741967?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/4862239350841741967?v=2" /><link rel="alternate" type="text/html" href="http://digitta.com/2009/07/google-chrome-operative-system-no-era.html" title="Google Chrome Operative System (segundo OS de Google)" /><author><name>Àl</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11672226968306882234" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;CUAEQH0yfSp7ImA9WxJVFEk.&quot;"><id>tag:blogger.com,1999:blog-112773462326884407.post-4000538680667997173</id><published>2009-07-01T12:21:00.001+02:00</published><updated>2009-07-01T12:21:41.395+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-07-01T12:21:41.395+02:00</app:edited><title>El señor del HTML 5</title><content type="html">En &lt;a href="http://www.cssquirrel.com/2009/06/22/comic-update-who-really-is-the-wizard-of-html5/"&gt;CSS Squirrel&lt;/a&gt;, Kyle Weems explica el significado de su &lt;a href="http://www.cssquirrel.com/comic/?comic=23"&gt;última viñeta&lt;/a&gt;. Como comentaba en &lt;a href="http://0.0.7.217/06/la-desfachatez-de-la-e-azul.html"&gt;un post anterior&lt;/a&gt;, Ian Hickson (o Hixie) es la persona que está coordinando el proyecto de redacción del nuevo estándar HTML 5. Sin embargo, Hickson trabaja para Google, lo que evidencia un posible conflicto de intereses.&lt;br /&gt;
&lt;br /&gt;
Muchos de los participantes en la lista de correo acusan a Hickson de seguir una política que puede resumirse con las palabras: &lt;a href="http://ian.hixie.ch/bible/handling-people"&gt;Negar, Retrasar, Desechar por llegar tarde&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Aunque un estándar tan importante como el que regirá el comportamiento de &lt;i&gt;todos&lt;/i&gt; los navegadores (la cursiva es por Internet Explorer) durante los próximos años debería estar gestionado por un comité, el poder de decisión se centra en una única persona que se ha erigido como editor, y cuyas decisiones vienen muchas veces respaldadas por datos que, pese a proceder de Google, no son públicos ni se tiene la intención de que lo sean.&lt;br /&gt;
&lt;br /&gt;
Estamos hablando de un esfuerzo de repercusiones casi inimaginables: miles de millones de páginas web escritas con este estándar, accesibilidad para usuarios con discapacidades, censura en países sin democracia, compartición de documentos multimedia sin la dependencia de plugins cerrados (flash)...&lt;br /&gt;
&lt;br /&gt;
Estas críticas nacen de lo absurdo que resulta que una única persona sustituya a un comité de expertos, sino que además, y sin resultar una crítica a su capacidad que parece más que sobrada, el proceso -en el que muchas propuestas interesantes son rechazadas sin más o se hace mucho más caso a esos datos "secretos" que a la opinión de los expertos en cada área- dista mucho de ser transparente.&lt;br /&gt;
&lt;br /&gt;
En este contexto no resulta extraño que se empiecen a alzar voces que pongan en duda la capacidad de Hickson para generar un HTML5 que cumpla con las expectativas de todos y no sólo las de Google, más cuando las críticas por el trato con los participantes (sean individuos, expertos o grupos de trabajo del W3C) no dejan de aumentar.&lt;br /&gt;
&lt;br /&gt;
En &lt;a href="http://www.webstandards.org/2009/05/13/interview-with-ian-hickson-editor-of-the-html-5-specification/"&gt;esta entrevista&lt;/a&gt; a Hickson, sus palabras llaman poderosamente la atención:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;Si respondo a comentarios sobre algo que acabo de publicar, me encuentro a veces que tengo un añadido para esa sección, por lo que si alguien sugiere un cambio total, no suele gustarme mucho esa idea. Pero si ha transcurrido un tiempo, encuentro que mi añadido ya no tiene tanta vigencia, y estoy ansioso por cambiar mi vieja estúpida idea por esa otra mejor.&lt;/blockquote&gt;&lt;blockquote&gt;Así que tengo que juzgar sobre lo que vale la pena añadir y lo que no, y eso es duro&lt;/blockquote&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/112773462326884407-4000538680667997173?l=digitta.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://digitta.com/feeds/4000538680667997173/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://digitta.com/2009/07/el-senor-del-html-5.html#comment-form" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/4000538680667997173?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/4000538680667997173?v=2" /><link rel="alternate" type="text/html" href="http://digitta.com/2009/07/el-senor-del-html-5.html" title="El señor del HTML 5" /><author><name>Àl</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11672226968306882234" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;CEcARHY-fyp7ImA9WxJWGUk.&quot;"><id>tag:blogger.com,1999:blog-112773462326884407.post-7477082858971824145</id><published>2009-06-25T17:00:00.002+02:00</published><updated>2009-06-25T17:00:45.857+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-06-25T17:00:45.857+02:00</app:edited><title>Programación con singletons y árbitros de eventos</title><content type="html">He traducido el más que interesante artículo de Aaron Newton, &lt;a href="http://www.clientcide.com/best-practices/singletons-and-event-arbiters/"&gt;Singletons and Event Arbiters&lt;/a&gt;:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;¿Cuál es la mejor forma de definir código, asignarlo a una variable global y conseguir que se ejecute todo a la vez?&lt;br /&gt;
&lt;br /&gt;
Usamos MooTools en el servidor en mi código ASP por medio de JScript y necesitamos objetos &lt;i&gt;singleton&lt;/i&gt; 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:&lt;br /&gt;
&lt;pre class="brush: js"&gt;var LoggerObject = function() {
  // código de inicio de sesión
} 
var Logger = new LoggerObject() 
&lt;/pre&gt;Está bien, pero parece que me estoy dejando algo. ¿Cómo lo hacen los &lt;i&gt;profesionales&lt;/i&gt;? ¿Cuáles son las ventajas y desventajas de otros métodos?&lt;/blockquote&gt;Para los que no estén familiarizados con el concepto, un &lt;i&gt;singleton&lt;/i&gt; (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 &lt;i&gt;singleton&lt;/i&gt; en JavaScript.&lt;br /&gt;
&lt;br /&gt;
MooTools ofrece una función llamada "&lt;code&gt;Class&lt;/code&gt;" que nos ayuda a gestionar la herencia entre objetos. Crear un objeto que es una instancia de una clase se consigue con el operador &lt;code&gt;new&lt;/code&gt;:&lt;br /&gt;
&lt;pre class="brush: js"&gt;var myWidget = new Widget()&lt;/pre&gt;Si lo que se desea es crear una clase e inmediatamente convertirla en una instancia, es posible hacer los siguiente:&lt;br /&gt;
&lt;pre class="brush: js"&gt;var myInstance = new new Class({...})&lt;/pre&gt;El doble &lt;code&gt;new&lt;/code&gt; 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:&lt;br /&gt;
&lt;pre class="brush: js"&gt;var myInstance = new new Class({
    Extends: SomeOtherClass,
    // nuevas propiedades aquí
});&lt;/pre&gt;El problema con este esquema es que no consigue mucho. JavaScript trata sobre todo con objetos, y hay mejores formas de hacer esta tarea.&lt;br /&gt;
&lt;br /&gt;
Yo uso objetos (como &lt;i&gt;singletons&lt;/i&gt;) continuamente para mi código de aplicación (obviamente casi nunca para &lt;i&gt;plugins&lt;/i&gt;, que se supone que deben extenderse e instanciarse). Uso objetos "site" para &lt;i&gt;apuntar&lt;/i&gt; 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í:&lt;br /&gt;
&lt;pre class="brush: js"&gt;var mySite = {
    login: function(username){ this.username = username; },
    showWelcome: function(){
        $("welcome").set("html", "Welcome " + this.username);
    }
};&lt;/pre&gt;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 &lt;em&gt;Widget&lt;/em&gt; y luego quiero crear una versión llamada &lt;em&gt;Widget.Ajax&lt;/em&gt;, puedo extender &lt;i&gt;Widget&lt;/i&gt; y añadir sólo las partes &lt;i&gt;ajax&lt;/i&gt;. Con los &lt;i&gt;singletons&lt;/i&gt; el tema está en que no vas a crear más que uno.&lt;br /&gt;
&lt;br /&gt;
En cualquier caso, hay algunas otras cosas que la &lt;code&gt;Class&lt;/code&gt; de MooTools nos ofrece, como &lt;a href="http://es.wikipedia.org/wiki/Mixin"&gt;mixins&lt;/a&gt; (clase que ofrece cierta funcionalidad para ser heredada por una subclase, pero no está ideada para ser autónoma). Ejemplos de esto serían &lt;code&gt;Event&lt;/code&gt; y &lt;code&gt;Option&lt;/code&gt; que ofrecen a las instancias métodos como &lt;code&gt;setOptions&lt;/code&gt; o &lt;code&gt;addEvent&lt;/code&gt;. En estos casos aún puedes usar la declaración de objeto de arriba y extenderlo:&lt;br /&gt;
&lt;pre class="brush: js"&gt;var mySite = {
    login: function(username){
        this.username = username;
        this.fireEvent("login");
    },
    showWelcome: function(){
        $("welcome").set("html", "Welcome " + this.username);
    }
};
$extend(mySite, new Events());
&lt;/pre&gt;Aunque generalmente yo hago justo de la otra forma porque creo que es un poco más limpio (aunque se consigue el mismo efecto):&lt;br /&gt;
&lt;pre class="brush: js"&gt;var mySite = new Events();
$extend(mySite, {
    login: function(username){
        this.username = username;
        this.fireEvent("login");
    },
    showWelcome: function(){
        $("welcome").set("html", "Welcome " + this.username);
    }
});&lt;/pre&gt;Con esta forma se evita la necesidad del tosco patrón &lt;code&gt;new new Class&lt;/code&gt; y resulta más legible en mi opinión. &lt;br /&gt;
&lt;h2&gt;Árbitros de eventos&lt;/h2&gt;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.&lt;br /&gt;
&lt;br /&gt;
Pero ¿qué pasa si el código que instancia un &lt;i&gt;widget&lt;/i&gt; necesita información de otro widget? Por ejemplo, ¿qué hacer si nuestro &lt;i&gt;widget&lt;/i&gt; welcome (bienvenida) necesita saber el estado del &lt;i&gt;widget&lt;/i&gt; de inicio de sesión? Bueno, si nuestro &lt;i&gt;widget&lt;/i&gt; de inicio de sesión almacena su estado en el objeto &lt;code&gt;mySite&lt;/code&gt;, entonces el &lt;i&gt;widget&lt;/i&gt; de welcome puede inspeccionarlo, ¿no? Eso está bien; si la página carga y &lt;code&gt;mySite.username&lt;/code&gt; no está inicializado (&lt;code&gt;undefined&lt;/code&gt;) entonces el &lt;i&gt;widget&lt;/i&gt; de welcome sabe que el usuario no ha iniciado sesión. Todo correcto.&lt;br /&gt;
&lt;br /&gt;
Pero ¿qué pasa cuando el estado cambia? ¿Qué pasa cuando el usuario inicia sesión? Ahora necesitamos un evento - &lt;code&gt;onLogin&lt;/code&gt; o similar, pero eso significa que Welcome necesita &lt;i&gt;apuntarse&lt;/i&gt; al &lt;i&gt;widget&lt;/i&gt; 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 &lt;i&gt;widgets&lt;/i&gt; que necesiten hacer cosas cuando el usuario inicia sesión y podríamos queere que esos &lt;i&gt;widgets&lt;/i&gt; funcionen incluso si el usuario no ha iniciado la sesión. Es más, podríamos querer cargar el &lt;i&gt;widget&lt;/i&gt; de inicio de sesión bajo demanda - cuando el usuario intente iniciar sesión por ejemplo. No es posible hacer eso si todos nuestros &lt;i&gt;widgets&lt;/i&gt; necesitan apuntar eventos al &lt;i&gt;widget&lt;/i&gt; de inicio de sesión.&lt;br /&gt;
&lt;br /&gt;
Esta es la razón por la que hago que &lt;code&gt;mySite&lt;/code&gt; sea una instancia de &lt;code&gt;Events&lt;/code&gt;. &lt;code&gt;mySite&lt;/code&gt; realmente no tiene eventos nativos en sí mismo. En su lugar, otras clases le apuntan eventos  y disparan eventos por ella. Así, en nuestro &lt;i&gt;puzzle&lt;/i&gt; de arriba sobre nuestro &lt;i&gt;widget&lt;/i&gt; de inicio de sesión, en lugar de que todos los &lt;i&gt;widgets&lt;/i&gt; se apunten a un evento &lt;code&gt;onLogin&lt;/code&gt;, pueden apuntarse a &lt;code&gt;mySite&lt;/code&gt;, y entonces nuestro &lt;i&gt;widget&lt;/i&gt; de inicio de sesión dispara ese evento pero no sobre sí mismo sino sobre &lt;code&gt;mySite&lt;/code&gt;:&lt;br /&gt;
&lt;pre class="brush: js"&gt;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");
    }
});
&lt;/pre&gt;Ahora nuestras clases ya no dependen entre ellas. Todas dependen ahora de &lt;code&gt;mySite&lt;/code&gt; 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 &lt;i&gt;widgets&lt;/i&gt; y algunos podrían tener otros. Puede apuntar la lógica al objeto &lt;code&gt;mySite&lt;/code&gt; sin tener que lidiar realmente con esas dependencias.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/112773462326884407-7477082858971824145?l=digitta.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://digitta.com/feeds/7477082858971824145/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://digitta.com/2009/06/programacion-con-singletons-y-arbitros.html#comment-form" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/7477082858971824145?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/7477082858971824145?v=2" /><link rel="alternate" type="text/html" href="http://digitta.com/2009/06/programacion-con-singletons-y-arbitros.html" title="Programación con &lt;i&gt;singletons&lt;/i&gt; y árbitros de eventos" /><author><name>Àl</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11672226968306882234" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;AkEARn88eip7ImA9WxJWFEg.&quot;"><id>tag:blogger.com,1999:blog-112773462326884407.post-8081996030807700124</id><published>2009-06-20T00:24:00.006+02:00</published><updated>2009-06-20T02:44:07.172+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-06-20T02:44:07.172+02:00</app:edited><title>La desfachatez de la E azul</title><content type="html">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.&lt;br /&gt;
&lt;br /&gt;
&lt;img src="http://farm4.static.flickr.com/3328/3421742213_db946c5ddf_o.png" style="width:750px" /&gt;&lt;br /&gt;
&lt;br /&gt;
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) &lt;a href="http://www.webstandards.org/2009/05/13/interview-with-ian-hickson-editor-of-the-html-5-specification/"&gt;en esta entrevista&lt;/a&gt;:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;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.&lt;/blockquote&gt;&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://www.flickr.com/photos/accessfirefox/3639756383/"&gt;&lt;img src="http://farm4.static.flickr.com/3635/3639756383_c7e3dc86e7.jpg?v=0" style="border:1px solid black;" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Mentiras para vender: Firefox viejo, Safari aburrido y Chrome oxidado&lt;/h2&gt;&lt;br /&gt;
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 &lt;a href="http://thenextweb.com/2009/06/19/google-asked-people-times-square-browser-responses-shock/"&gt;no sabe distinguir el navegador de un motor de búsqueda como Google&lt;/a&gt;. Sin embargo, su nueva campaña está basada en insultos a la competencia (&lt;a href="http://www.genbeta.com/navegadores/internet-explorer-8-es-el-mas-mejor-navegador-del-mundo-mundial"&gt;Genbeta&lt;/a&gt;, &lt;a href="http://ajaxian.com/archives/get-the-facts-from-microsoft-marketing"&gt;Ajaxian&lt;/a&gt;, &lt;a href="http://webreflection.blogspot.com/2009/06/one-with-microsoft-paying-users.html"&gt;WebReflection&lt;/a&gt;) y falsedades para encubrir las enormes carencias del navegador de Microsoft. &lt;br /&gt;
&lt;br /&gt;
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 &lt;i&gt;desesperado&lt;/i&gt; de conservar cuota de mercado con un navegador que no quiere evolucionar.&lt;br /&gt;
&lt;br /&gt;
Por ello resulta enormemente ofensiva la &lt;a href="http://www.microsoft.com/windows/internet-explorer/get-the-facts/mythbusting.aspx"&gt;página de promoción del nuevo Internet Explorer&lt;/a&gt; y &lt;a href="http://www.microsoft.com/windows/internet-explorer/get-the-facts/browser-comparison.aspx"&gt;la comparativa con la competencia&lt;/a&gt;, 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 &lt;i&gt;más&lt;/i&gt; mejor que ellos. Aunque para "demostrarlo" tachen de mitos...&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;la lentitud (más que demostrada con todas las comparativas de estos últimos meses)&lt;/li&gt;&lt;li&gt;la seguridad (todos hemos visto instalarse "software" automáticamente al visitar una página con IE)&lt;/li&gt;&lt;li&gt;la menor riqueza respecto a Firefox (ya quisieran tener una décima parte de las extensiones de éste)&lt;/li&gt;&lt;li&gt;y que su navegador no respeta los estándares, ante lo que tienen la enorme desfachatez de afirmar que &lt;b&gt;"Internet Explorer 8 pasa más casos de prueba del &lt;a href="http://www.w3.org/"&gt;W3C&lt;/a&gt; que cualquier otro navegador"&lt;/b&gt;.&lt;/li&gt;&lt;/ul&gt;
&lt;a href="http://ajaxian.com/archives/ie-8-on-acid"&gt;&lt;img src="http://farm4.static.flickr.com/3298/3641760107_56a5853f2e_o.png" /&gt;&lt;/a&gt;&lt;br /&gt;
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...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/112773462326884407-8081996030807700124?l=digitta.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://digitta.com/feeds/8081996030807700124/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://digitta.com/2009/06/la-desfachatez-de-la-e-azul.html#comment-form" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/8081996030807700124?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/8081996030807700124?v=2" /><link rel="alternate" type="text/html" href="http://digitta.com/2009/06/la-desfachatez-de-la-e-azul.html" title="La desfachatez de la E azul" /><author><name>Àl</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11672226968306882234" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;CEEMRX0_fip7ImA9WxJWEUQ.&quot;"><id>tag:blogger.com,1999:blog-112773462326884407.post-1273345906861885361</id><published>2009-06-17T00:50:00.003+02:00</published><updated>2009-06-17T00:51:24.346+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-06-17T00:51:24.346+02:00</app:edited><title>Opera Unite, no tan buena idea</title><content type="html">&lt;a href="http://unite.opera.com/"&gt;&lt;img src="http://benq.alsanan.operaunite.com/webserver/static/icon_unite_silver.png" style="float:right;margin:10px;" /&gt;&lt;/a&gt;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.&lt;br /&gt;
&lt;br /&gt;
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 &lt;i&gt;osaron&lt;/i&gt; 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 &lt;a href="http://www.no-ip.com/"&gt;ya resuelto por no-ip.com&lt;/a&gt;). ¿Pero no hay otras tecnologías más apropiadas para ello como FTP o el propio BitTorrent? En cualquier caso tampoco es &lt;a href="http://www.rejetto.com/hfs/"&gt;una idea nueva&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;img src="http://dev.opera.com/articles/view/opera-unite-developer-primer/operauni.jpg" /&gt;
&lt;br/&gt;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 &lt;code&gt;unite://equipo.usuario.operaunite.com/_root/content/&lt;/code&gt; que resulta accesible desde cualquier punto de Internet cambiando el protocolo de &lt;code&gt;unite://&lt;/code&gt; a &lt;code&gt;http://&lt;/code&gt;. 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.&lt;br /&gt;
&lt;br /&gt;
Los servicios que ofrece son:&lt;ul&gt;&lt;li&gt;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)&lt;/li&gt;&lt;li&gt;Nevera: para dejar notas al propietario del servidor (no deja de ser como el e-mail)&lt;/li&gt;&lt;li&gt;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&lt;/li&gt;&lt;li&gt;Compartición de fotos: ídem que el anterior pero con fotos&lt;/li&gt;&lt;li&gt;Un chat de grupo con control del propietario y alojado en el propio equipo&lt;/li&gt;&lt;li&gt;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)&lt;/li&gt;&lt;li&gt;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&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Andrea Giammarchi &lt;a href="http://webreflection.blogspot.com/2009/06/opera-unite-or-better-server-widgets.html"&gt;coincide en su decepción&lt;/a&gt; de la implementación de la idea y de su utilidad.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://simonwillison.net/2009/Jun/16/unite/"&gt;Referencia de Simon Willison&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://labs.opera.com/news/2009/06/16/"&gt;Artículo en el blog de Opera Labs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://dev.opera.com/articles/unite"&gt;API de desarrollo&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://dev.opera.com/articles/view/opera-unite-developer-primer/"&gt;introducción para el desarrollador&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://dev.opera.com/libraries/unite/"&gt;API JavaScript del servidor Unite&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/112773462326884407-1273345906861885361?l=digitta.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://digitta.com/feeds/1273345906861885361/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://digitta.com/2009/06/opera-unite-no-tan-buena-idea.html#comment-form" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/1273345906861885361?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/1273345906861885361?v=2" /><link rel="alternate" type="text/html" href="http://digitta.com/2009/06/opera-unite-no-tan-buena-idea.html" title="Opera Unite, no tan buena idea" /><author><name>Àl</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11672226968306882234" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;DU8DQH48fCp7ImA9WxJXEU0.&quot;"><id>tag:blogger.com,1999:blog-112773462326884407.post-8754130228388018547</id><published>2009-06-04T11:29:00.001+02:00</published><updated>2009-06-04T11:31:11.074+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-06-04T11:31:11.074+02:00</app:edited><title>El proyecto Vice-versa</title><content type="html">He traducido la introducción del &lt;a href="http://code.google.com/p/vice-versa/"&gt;Proyecto Vice-versa&lt;/a&gt; presentado en &lt;a href="http://webreflection.blogspot.com/2009/04/vice-versa-project-philosophy-rather.html"&gt;este post de Andrea Giammarchi&lt;/a&gt;. Se trata de una librería que nace con la idea de aproximar las implementaciones de las características JavaScript disponibles desde cada navegador. &lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;El proyecto Vice-versa&lt;/h2&gt;&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Filosofía&lt;/h2&gt;&lt;br /&gt;
&lt;pre name="code" class="js:nocontrols"&gt;Array.forEach(document.all, function(node, i, all){
    // proyecto vice-versa
});&lt;/pre&gt;
Como un &lt;a href="http://jsninja.com/"&gt;Ninja JavaScript&lt;/a&gt; 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 &lt;a href="http://dante.dojotoolkit.org/taskspeed/"&gt;sobrecargas innecesarias&lt;/a&gt; sobre tareas simples o comunes. Estudiando el DOM, &lt;a href="http://ejohn.org/blog/the-dom-is-a-mess/"&gt;que es caótico&lt;/a&gt;, a menudo "viajo" entre los sitios de &lt;a href="https://developer.mozilla.org/en/javascript"&gt;MDC&lt;/a&gt; y de &lt;a href="http://msdn.microsoft.com/en-us/library/default.aspx"&gt;MSDN&lt;/a&gt; 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 &lt;a href="http://www.microsoft.com/windows/products/winfamily/ie/default.mspx"&gt;navegador más usado&lt;/a&gt; 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 &lt;a href="https://developer.mozilla.org/En/XMLSerializer"&gt;XMLSerializer&lt;/a&gt;, pero ¿qué problema hay con el atajo del atributo &lt;code&gt;xml&lt;/code&gt; de Microsoft?&lt;br /&gt;
&lt;br /&gt;
&lt;pre name="code" class="js:nocontrols"&gt;// 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;
&lt;/pre&gt;
Es más, &lt;code&gt;&lt;a href="https://developer.mozilla.org/en/DOM/document.evaluate"&gt;document.evaluate&lt;/a&gt;&lt;/code&gt; es una característica muy buena, pero la llamada más común a esta función es la siguiente:&lt;br /&gt;
&lt;br /&gt;
&lt;pre name="code" class="js:nocontrols"&gt;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);
&lt;/pre&gt;
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 &lt;code&gt;nextNode&lt;/code&gt;, el resto usa &lt;code&gt;iterateNext&lt;/code&gt;). 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: &lt;code&gt;&lt;a href="http://msdn.microsoft.com/en-us/library/ms537434(VS.85).aspx"&gt;document.all&lt;/a&gt;&lt;/code&gt;. Ahora, ¿por qué demonios nos encanta la "miniaturización" de &lt;a href="http://developer.yahoo.com/yui/compressor/"&gt;YUICompressor&lt;/a&gt; más &lt;code&gt;&lt;a href="http://www.gzip.org/"&gt;gzip&lt;/a&gt;&lt;/code&gt; pero preferimos una sintaxis sin sentido como &lt;code&gt;document.getElementsByTagName("*")&lt;/code&gt; en lugar de &lt;code&gt;document.all&lt;/code&gt; ?&lt;br /&gt;
&lt;br /&gt;
La lista podría seguir con &lt;code&gt;&lt;a href="http://msdn.microsoft.com/en-us/library/ms536420(VS.85).aspx"&gt;execScript&lt;/a&gt;&lt;/code&gt; para evaluar código en el ámbito global (relacionado con tiempo de ejecución y con Ajax) y con &lt;a href="http://blogs.msdn.com/ie/archive/2009/01/13/responding-to-change-updated-getter-setter-syntax-in-ie8-rc-1.aspx"&gt;Object.defineProperty&lt;/a&gt;, pero al mismo tiempo tenemos carencias con &lt;code&gt;&lt;a href="https://developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Global_Objects/Array"&gt;Array.prototype&lt;/a&gt;&lt;/code&gt; 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 &lt;code&gt;for in&lt;/code&gt; sobre listas o colecciones, así que &lt;code&gt;Array.prototype&lt;/code&gt; 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.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Características vice-versa provenientes de Internet Explorer&lt;/h2&gt;&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;code&gt;Object.defineProperty&lt;/code&gt; (IE 8 o superior)&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;&lt;code&gt;document.all&lt;/code&gt;, como atajo a &lt;code&gt;document.getElementsByTagName("*")&lt;/code&gt;&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;&lt;code&gt;document.createElement&lt;/code&gt; con soporte html para cada navegador (a veces simplifica las cosas p.e. &lt;code&gt;document.createElement('&amp;lt;em class="test"&gt;&amp;lt;/em&gt;')&lt;/code&gt;)&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;&lt;code&gt;HTMLElement.attachEvent&lt;/code&gt; y &lt;code&gt;detachEvent&lt;/code&gt;&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;objeto global &lt;code&gt;window.event&lt;/code&gt; poblado via &lt;code&gt;attachEvent&lt;/code&gt; con las propiedades &lt;code&gt;cancelBubble&lt;/code&gt; y &lt;code&gt;returnValue&lt;/code&gt;&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;&lt;code&gt;HTMLElement.outerHTML&lt;/code&gt; con &lt;code&gt;set&lt;/code&gt; y &lt;code&gt;get&lt;/code&gt; (la forma más simple de leer un elemento del DOM como una cadena)&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;&lt;code&gt;HTMLElement.innerText&lt;/code&gt; con &lt;code&gt;set&lt;/code&gt; y &lt;code&gt;get&lt;/code&gt; (la forma más rápida en IE de insertar un nodo de texto)&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;&lt;code&gt;HTMLElement.outerText&lt;/code&gt; con &lt;code&gt;set&lt;/code&gt; y &lt;code&gt;get&lt;/code&gt; (la forma más rápida en IE de reemplazar un nodo genérico por uno de texto)&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;&lt;code&gt;execScript&lt;/code&gt; para evaluar en un ámbito global cualquier tipo de cadena (sin soporte a un segundo argumento)&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;XPath &lt;code&gt;selectNodes&lt;/code&gt; y &lt;code&gt;nextNode&lt;/code&gt; via documentos XML&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;propiedad &lt;code&gt;xml&lt;/code&gt;, sólo &lt;code&gt;get&lt;/code&gt;, para recuperar una cadena de un XMLDocument/XMLNode&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;&lt;code&gt;insertAdjacentElement&lt;/code&gt;, &lt;code&gt;insertAdjacentHTML&lt;/code&gt;, y &lt;code&gt;insertAdjacentText&lt;/code&gt;, la forma más rápida de poner algo antes de un nodo, como &lt;code&gt;firstChild&lt;/code&gt;, como &lt;code&gt;lastOne&lt;/code&gt;, o tras el mismo nodo&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;
&lt;h2&gt;Características vice-versa principalmente para Internet Explorer&lt;/h2&gt;&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;code&gt;Array.slice&lt;/code&gt;, para trocear todo sobre una colección genérica (compatible con DOM)&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;&lt;code&gt;Array.forEach&lt;/code&gt;, para recorrer una colección genérica via &lt;code&gt;callback&lt;/code&gt; (compatible con DOM)&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;&lt;code&gt;Array.prototype&lt;/code&gt;, actualizado a la última revisión de Mozilla/FireFox (sin la especificación completa, versión enfocada en rendimiento)&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;&lt;code&gt;XMLHttpRequest&lt;/code&gt;, para el antiguo IE6 (desfasado, será eliminado tan pronto como se pueda, pero debemos esperar primero hasta el final de este navegador)&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;&lt;code&gt;setInterval&lt;/code&gt; y &lt;code&gt;setTimeout&lt;/code&gt; con argumentos extra como los demás&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;
&lt;h2&gt;Características vice-versa de ECMAScript 5&lt;/h2&gt;&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;code&gt;Object.getPrototypeOf&lt;/code&gt;, para recuperar el prototipo heredado por un objeto genérico&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;&lt;code&gt;Object.keys&lt;/code&gt;, compatible (incluyendo &lt;code&gt;toString&lt;/code&gt;), para recuperar un Array de claves a partir de un objeto&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;&lt;code&gt;Object.create&lt;/code&gt;, compatible (incluyendo &lt;code&gt;toString&lt;/code&gt;), para crear una nueva instancia a partir de otro objeto&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;&lt;code&gt;Array.isArray&lt;/code&gt;, para conocer si un objeto genérico es un Array&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;&lt;code&gt;String.prototype.trim&lt;/code&gt;, una implementación rápida de recorte para cada cadena&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;&lt;code&gt;Function.prototype.bind&lt;/code&gt;, un método para asegurar el ámbito &lt;code&gt;this&lt;/code&gt; en una llamada a función&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;
&lt;h2&gt;Características extra en vice-versa&lt;/h2&gt;&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;code&gt;document.query&lt;/code&gt;, librería esencial de selectores para obtenere resultados genéricos mediante consultas comunes de tipo dolar (función $)&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;&lt;code&gt;Object.forIn&lt;/code&gt;, para recorrer una instancia genérica sin considerar al prototipo heredado y sin olvidar las propiedades predefinidas (&lt;code&gt;toString&lt;/code&gt;, &lt;code&gt;valueOf&lt;/code&gt;, etc) &lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;
&lt;h2&gt;Metas de vice-versa&lt;/h2&gt;&lt;br /&gt;
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?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/112773462326884407-8754130228388018547?l=digitta.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://digitta.com/feeds/8754130228388018547/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://digitta.com/2009/06/el-proyecto-vice-versa.html#comment-form" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/8754130228388018547?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/8754130228388018547?v=2" /><link rel="alternate" type="text/html" href="http://digitta.com/2009/06/el-proyecto-vice-versa.html" title="El proyecto Vice-versa" /><author><name>Àl</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11672226968306882234" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;DkMHSHY5cSp7ImA9WxJQF0U.&quot;"><id>tag:blogger.com,1999:blog-112773462326884407.post-1571534809492623932</id><published>2009-05-31T17:40:00.002+02:00</published><updated>2009-05-31T17:40:39.829+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-05-31T17:40:39.829+02:00</app:edited><title>Wave reinventa la comunicación en la red</title><content type="html">&lt;a href="http://wave.google.com/help/wave/about.html"&gt;&lt;img src="http://code.google.com/intl/es/apis/wave/images/wavelogo.png" style="float:right;margin:8px;" /&gt;&lt;/a&gt;Hace unos meses me gustaba seguir a &lt;a href="http://blog.jonudell.net"&gt;Jon Udell&lt;/a&gt; al respecto de posibles herramientas que permitiesen evolucionar el omnipresente correo electrónico martirizado por el spam. Proyectos como &lt;a href="http://www.oreillynet.com/pub/a/network/2002/10/07/udell.html"&gt;Zoë&lt;/a&gt;, &lt;a href="https://www.yammer.com/"&gt;Yammer&lt;/a&gt; o &lt;a href="http://office.microsoft.com/en-us/groove/HA101656331033.aspx"&gt;Groove&lt;/a&gt; han intentado ya mejorar ese gigante.&lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://wave.google.com/help/wave/about.html"&gt;Google Wave&lt;/a&gt; es una nueva &lt;b&gt;idea experimental&lt;/b&gt; y abierta que pretende rediseñar el futuro de la comunicación en tiempo real sobre la web. "Es lo que parecería el correo electrónico si se inventase hoy". &lt;br /&gt;
&lt;br /&gt;
Wave es a partes iguales conversación y documento. Es posible comunicarse y trabajar juntos sobre texto con formato, fotos, vídeos, mapas y, prácticamente, cualquier cosa que pueda representarse sobre un navegador. Un documento wave puede compartirse. Puede responderse desde cualquier lugar en el que el mensaje esté publicado (por ejemplo un artículo en blogger, en el que los comentarios se introducen automáticamente en la conversación), editar su contenido o añadir nuevos participantes en cualquier momento. Y puede reproducirse su evolución para ver cómo ha ido evolucionando el documento a lo largo del tiempo, viendo quién ha dicho qué y cuando. Además permite la transmisión de información en tiempo real, de forma que puede verse cómo los demás teclean, agilizando las conversaciones y facilitando enormemente el trabajo sincronizado.&lt;br /&gt;
&lt;br /&gt;
Wave ha sido diseñado &lt;a href="http://www.waveprotocol.org/"&gt;como un estándar&lt;/a&gt; similar al e-mail, es abierto, y puede ser usado en cualquier servidor. Y puesto que el estándar define un protocolo, distintos servidores pueden entenderse entre ellos aunque no pertenezcan a Google.&lt;br /&gt;
&lt;br /&gt;
Aunque aún no está disponible, en la presentación se muestra cómo Wave consigue mejorar enormemente el correo electrónico y la mensajería instantánea, los dos pilares básicos sobre los que se asienta. Y construye sobre ellos una plataforma de innovación totalmente abierta en la decenas de detalles e ideas han sido implementados para mejorar la experiencia de los usuarios cuando quieren comunicarse. Por ejemplo, contestar un mensaje es tan simple como escribirlo en el área prevista para ello (no hay que pulsar un botón y esperar que la aplicación cargue un formulario... simplemente "ya está ahí"); o la implementación de múltiples atajos basados en "arrastrar y soltar" convierten la experiencia en mucho más intuitiva. Aunque no deja de recordar a un cliente de correo electrónico tradicional pero optimizado, puede convertirse en un chat de forma transparente si los usuarios están conectados (de hecho los mensajes los ve el otro lado conforme se teclean, aunque esta opción puede desactivarse).&lt;br /&gt;
&lt;br /&gt;
Cosas que pueden hacerse con wave:&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;reproducir la conversación paso a paso para que alguien incorporado pueda entender completamente el tema&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;mensajes privados dentro de la conversación que sólo ven los interesados&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;compartición rápida de archivos/fotos&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;aprovecha Gears si está instalado para permitir algo tan intuitivo como arrastrar y soltar archivos desde el escritorio&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;funcionalidades basadas en contactos/robot como "bloggy" para publicar la conversación, de forma que el mismo artículo del blog se convierte en parte de la conversación: cualquier comentario allí se incorpora automáticamente, convirtiendo la conversación en pública. O un traductor simultáneo.&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;edición y control de cambios al estilo wiki (resaltados como novedad)&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;posibilidad de ver la conversación como un documento, de forma que se pueda aprovechar para la creación  de documentos en equipo&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;posibilidad de trabajar con equipos sobre subdocumentos del documento original, de forma que se trate de una parte del documento principal hasta su versión definitiva, que vuelve al original y así puede ser usado o modificado por otros equipos o subdocumentos&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;posibilidad de enlazar desde la actual conversaciones simplemente arrastrando y soltando&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;todo sincronizado en tiempo real, una nueva conversación puede aparecer en los resultados de una búsqueda después de haberla lanzado&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;ortografía basada en diccionarios pero también en contexto. Especialmente espectacular: "icland is an icland" se convierte en "Iceland is an island".&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;sistema de extensiones similar al del propio navegador (instalables)&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;extensiones como realizar votaciones o juegos de red de competición (ajedrez) o colaborativos (sudoku) donde los eventos son reconocidos por wave y por tanto se puede reproducir una partida como si fuese una conversación&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;conversaciones que muestran formularios de configuración con opciones que pueden rellenar más de un usuario a la vez. Es la respuesta a la pregunta de siempre: ¿Cómo creo una encuesta rápidamente?&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;integración de otros sistemas de comunicación (email, twitter) o con sistemas de depuración de errores (bugtrack) para equipos&lt;br /&gt;
&lt;/li&gt;&lt;li&gt;posibilidad de traducción simultánea (al vuelo) de una conversación para usuarios que hablen en distintos idiomas&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;
Otro "detalle" es que se exige un navegador &lt;i&gt;moderno&lt;/i&gt; con soporte de HTML5. La consecuencia inmediata es que, aunque aún no está disponible, si los internautas en masa quieren disponer de esta herramienta, deberán decir adiós a Internet Explorer a menos que Microsoft, por fin, se tome en serio su trabajo.&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;a href="http://wave.google.com/help/wave/about.html"&gt;Página oficial&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.techcrunch.com/2009/05/28/google-wave-drips-with-ambition-can-it-fulfill-googles-grand-web-vision/"&gt;Explicación&lt;/a&gt; en TechCrunch.com&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.youtube.com/watch?v=v_UyVmITiYQ"&gt;Presentación en inglés&lt;/a&gt; (1 hora, 20 minutos). &lt;a href="http://www.youtube.com/watch?v=ANtLSoISnHA"&gt;Versión reducida a 10 minutos&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;&lt;a href="http://code.google.com/intl/es/apis/wave/"&gt;API&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.waveprotocol.org/"&gt;Web oficial&lt;/a&gt; del protocolo abierto (open-source) de Wave.&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/112773462326884407-1571534809492623932?l=digitta.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://digitta.com/feeds/1571534809492623932/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://digitta.com/2009/05/wave-reinventa-la-comunicacion-en-la.html#comment-form" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/1571534809492623932?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/1571534809492623932?v=2" /><link rel="alternate" type="text/html" href="http://digitta.com/2009/05/wave-reinventa-la-comunicacion-en-la.html" title="Wave reinventa la comunicación en la red" /><author><name>Àl</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11672226968306882234" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;CUEAQHY-eip7ImA9WxJQFUw.&quot;"><id>tag:blogger.com,1999:blog-112773462326884407.post-275152542201061437</id><published>2009-05-28T14:18:00.005+02:00</published><updated>2009-05-28T14:27:21.852+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-05-28T14:27:21.852+02:00</app:edited><title /><content type="html">A partir de &lt;a href="http://icant.co.uk/jsplacemaker/"&gt;JS PlaceMaker&lt;/a&gt; que es un script que permite identificar lugares a partir de un texto y que devuelve objetos con su ubicación, he creado el siguiente ejemplo:&lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://jsbin.com/abumi/"&gt;http://jsbin.com/abumi/&lt;/a&gt;&lt;br /&gt;
&lt;iframe src="http://jsbin.com/uharu/" style="width:90%;height:500px;"&gt;&lt;/iframe&gt;&lt;br /&gt;
&lt;br /&gt;
El código es sencillo:&lt;br /&gt;
&lt;br /&gt;
&lt;pre name="code" class="js:nocontrols"&gt;function callback(e) {  
  if (e.match.length) e= e.match; else e=[e.match]; 
  var gmapsApi= "ABQIAAAAGoC4gjFriF0K4FcpSOhWDhTWx8njL9qYLnq46LoOVGM6mIGTuxR2yPgKzvzjHKLSfLHgjUC0vi45-Q"; 
  var imgs=''; 
  console.dir(e); 
  for (loc in e) { 
    center=e[loc].place.centroid.latitude+','+e[loc].place.centroid.longitude; 
    imgs+= '&amp;lt;img src="http://maps.google.com/staticmap?center='+center+'&amp;markers='+center+'&amp;zoom=13&amp;size=350x250&amp;key='+gmapsApi+'" style="float:left;"/&gt;'; 
  } 
  document.getElementById('map').innerHTML=imgs; 
  window.called=false; 
} 
 
window.called=false; 
function changed() { 
  if (window.called) return; 
  setTimeout(function(){ 
    Placemaker.config.appID = "jPkyQyTV34Fi4qlev38PC_eXLDQYqVPkQ5N1aZDxVUSpw3MKOUWD9AHzcVkLFkM-"; 
    Placemaker.getPlaces(document.getElementById('text').value,callback,document.getElementById('locale').value); 
    window.called=true; 
    }, 
  1000); 
}&lt;/pre&gt;&lt;br /&gt;
Puedes probar con el texto: "I'm Jack London and I live in Barcelona but I work at Amsterdam and my parents live in Oslo", (nótese que detecta que la palabra London no se está usando como ubicación) aunque hay un selector para indicar el lenguaje del texto introducido. La detección se produce al salir del campo de texto, aunque también se puede forzar con el botón. Las claves son para el dominio del contenedor de código &lt;a href="http://jsbin.com"&gt;jsbin.com&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/112773462326884407-275152542201061437?l=digitta.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://digitta.com/feeds/275152542201061437/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://digitta.com/2009/05/partir-de-js-placemaker-que-es-un.html#comment-form" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/275152542201061437?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/275152542201061437?v=2" /><link rel="alternate" type="text/html" href="http://digitta.com/2009/05/partir-de-js-placemaker-que-es-un.html" title="" /><author><name>Àl</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11672226968306882234" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry><entry gd:etag="W/&quot;C0ACQXozeip7ImA9WxJQE0s.&quot;"><id>tag:blogger.com,1999:blog-112773462326884407.post-4394719840612345568</id><published>2009-05-26T19:58:00.004+02:00</published><updated>2009-05-26T20:16:00.482+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-05-26T20:16:00.482+02:00</app:edited><title>Compresión JSON con JSON.hpack</title><content type="html">&lt;a href="http://wiki.github.com/WebReflection/json.hpack"&gt;JSON.hpack&lt;/a&gt; es un compresor de conjuntos de datos JSON. Es capaz de reducir hasta un 70% el número de bytes necesarios para representar una colección genérica y homogénea (entendiendo por homogénea la que suele ser producida por una consulta a una base de datos relacional en la que se repite una serie de filas con el mismo número de campos).&lt;br /&gt;
&lt;br /&gt;
Así como un resultado con XML como el siguiente...&lt;br /&gt;
&lt;br /&gt;
&lt;pre name="code" class="xml:nocontrols"&gt;  &amp;lt;result&gt;
    &amp;lt;item&gt;
      &amp;lt;name&gt;Andrea&amp;lt;/name&gt;
      &amp;lt;age&gt;31&amp;lt;/age&gt;
      &amp;lt;gender&gt;Male&amp;lt;/gender&gt;
      &amp;lt;skilled&gt;true&amp;lt;/skilled&gt;
    &amp;lt;/item&gt;
    &amp;lt;item&gt;
      &amp;lt;name&gt;Eva&amp;lt;/name&gt;
      &amp;lt;age&gt;27&amp;lt;/age&gt;
      &amp;lt;gender&gt;Female&amp;lt;/gender&gt;
      &amp;lt;skilled&gt;true&amp;lt;/skilled&gt;
    &amp;lt;/item&gt;
    &amp;lt;item&gt;
      &amp;lt;name&gt;Daniele&amp;lt;/name&gt;
      &amp;lt;age&gt;26&amp;lt;/age&gt;
      &amp;lt;gender&gt;Male&amp;lt;/gender&gt;
      &amp;lt;skilled&gt;false&amp;lt;/skilled&gt;
    &amp;lt;/item&gt;
  &amp;lt;/result&gt;
  &amp;lt;!-- 286 caracteres (sin contar espacios) --&gt;
&lt;/pre&gt;&lt;br /&gt;
... puede ser compactado con JSON a la cadena siguiente...&lt;br /&gt;
&lt;br /&gt;
&lt;pre name="code" class="js:nocontrols"&gt;[
  {
    "name":"Andrea",
    "age":31,
    "gender":"Male",
    "skilled":true
  },
  {
    "name":"Eva",
    "age":27,
    "gender":"Female",
    "skilled":true
  },
  {
    "name":"Daniele",
    "age":26,
    "gender":"Male",
    "skilled":false
  }
]
// 177 caracteres (sin contar espacios)
&lt;/pre&gt;&lt;br /&gt;
... podemos ver fácilmente que JSON admite una compresión aún mayor, puesto que cada registro repite los nombres de los campos.&lt;br /&gt;
&lt;br /&gt;
El &lt;b&gt;nivel 0 de compresión&lt;/b&gt; con JSON.hpack propone reunir los nombres de los campos en un primer registro:&lt;br /&gt;
&lt;br /&gt;
&lt;pre name="code" class="js:nocontrols"&gt;[["name","age","gender","skilled"],["Andrea",31,"Male",true],["Eva",27,"Female",true],["Daniele",26,"Male",false]]
// 115 caracteres
&lt;/pre&gt;&lt;br /&gt;
Aún así, se repiten valores (como true, false, male y female) que podrían ser sustituidos por índices para ahorrar aún más espacio (aunque en este ejemplo con 3 registros el resultado no es mejor que el anterior):&lt;br /&gt;
&lt;br /&gt;
&lt;pre name="code" class="js:nocontrols"&gt;[["name",["Andrea","Eva","Daniele"],"age","gender",["Male","Female"],"skilled",[true,false]],[0,31,0,0],[1,27,1,0],[2,26,0,1]]
// 167 caracteres
&lt;/pre&gt;&lt;br /&gt;
Como se puede ver en este &lt;b&gt;nivel 1 de compresión&lt;/b&gt;, en el primer registro se indican los nombres de los campos y los posibles valores que pueden adoptar siempre que no sean numéricos (el autor cree que no vale la pena realizar la conversión de unos números por otros (índices)). Por ejemplo, &lt;code&gt;Male&lt;/code&gt; o &lt;code&gt;True&lt;/code&gt; se sustituyen por 0 y &lt;code&gt;Female&lt;/code&gt; o &lt;code&gt;False&lt;/code&gt; por 1.&lt;br /&gt;
&lt;br /&gt;
Esto aún puede optimizarse más con el &lt;b&gt;nivel 2 de compresión&lt;/b&gt;, comparando si vale la pena enumerar los valores en la cabecera y sustituirlos en los datos por índices o si es mejor mantener los datos (en el siguiente caso, los nombres no se enumeran):&lt;br /&gt;
&lt;br /&gt;
&lt;pre name="code" class="js:nocontrols"&gt;[["name","age","gender",["Male","Female"],"skilled",[true,false]],["Andrea",31,0,0],["Eva",27,1,0],["Daniele",26,0,1]]
// 119 caracteres
&lt;/pre&gt;&lt;br /&gt;
Esta comprobación se realiza a nivel de la colección entera. Es posible realizarla a nivel de cada campo, comparando la longitud de la enumeración más los índices que serán necesarios con los de indicar directamente los valores:&lt;br /&gt;
&lt;br /&gt;
&lt;pre name="code" class="js:nocontrols"&gt;// Comparando "gender":
  ["Male","Female",0,1,0] &lt; ["Male","Female","Male"] (23 caracteres es menos que 24)
// comparando "skilled" :
  [true,false,0,0,1]      &gt; [true,true,false]        (18 caracteres es menos que 17)
&lt;/pre&gt;&lt;br /&gt;
Según esto, el resultado de aplicar el &lt;b&gt;nivel 3 de compresión&lt;/b&gt; será:&lt;br /&gt;
&lt;br /&gt;
&lt;pre name="code" class="js:nocontrols"&gt;[["name","age","gender",["Male","Female"],"skilled"],["Andrea",31,0,true],["Eva",27,1,true],["Daniele",26,0,false]]
// 116 caracteres
&lt;/pre&gt;&lt;br /&gt;
Y aún se puede extraer un último &lt;b&gt;nivel 4 de compresión&lt;/b&gt; comparando todos los anteriores y seleccionando el que menos &lt;i&gt;ocupe&lt;/i&gt;. Es posible hacer pruebas en la &lt;a href="http://www.3site.eu/examples/json.hpack/test/"&gt;página de ejemplos&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Aunque parece que la compresión es importante, hay que tener en cuenta que si el contenido se está comprimiendo con gzip desde el servidor, la reducción real en información enviada será mucho menor. Para conjuntos grandes de datos, sí que puede haber una diferencia importante, aunque se pague el precio del procesamiento necesario para comprimir y descomprimir los datos.&lt;br/&gt;

Existen librerías para JavaScript, PHP y C#, aunque según el &lt;a href="http://wiki.github.com/WebReflection/json.hpack"&gt;wiki del proyecto&lt;/a&gt;, es posible que se amplíe a Python, Ruby o plugin PHP mediante C.&lt;br /&gt;
&lt;br /&gt;
En concreto, la versión JavaScript cuenta con los siguientes métodos:&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;code&gt;hpack( Array[, Int[0-4]] ):HArray&lt;/code&gt;, convierte una colección homogénea a otra comprimida con hpack&lt;/li&gt;&lt;li&gt;&lt;code&gt;hunpack( HArray ):Array&lt;/code&gt;, convierte una colección hpack a su original (no es necesario indicar qué compresión se usó)&lt;/li&gt;&lt;li&gt;&lt;code&gt;hbest( Array ):Int[0-4]&lt;/code&gt;, devuelve el nivel de compresión ideal para la colección homogénea especificada&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/112773462326884407-4394719840612345568?l=digitta.com' alt='' /&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://digitta.com/feeds/4394719840612345568/comments/default" title="Enviar comentarios" /><link rel="replies" type="text/html" href="http://digitta.com/2009/05/compresion-json-con-jsonhpack.html#comment-form" title="0 comentarios" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/4394719840612345568?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/112773462326884407/posts/default/4394719840612345568?v=2" /><link rel="alternate" type="text/html" href="http://digitta.com/2009/05/compresion-json-con-jsonhpack.html" title="Compresión JSON con JSON.hpack" /><author><name>Àl</name><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="11672226968306882234" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></entry></feed>
