Cita del Día

Sobre estas líneas se debería ver (¿funciona?) mi último proyecto: la Cita del Día para Blogger. De hecho, debería poder instalarse en cualquier sitio. Sólo necesita que JQuery esté disponible en la página, aunque sería bastante fácil reescribir el código para eliminar la dependencia. Las citas están publicadas en una hoja de cálculo de Google Spreadsheets.

[¡Peligro! ¡Palabrería informática!] El protocolo implementado por las APIs de GData, como extensión de Atom, permite manipular documentos de todo pelaje alojados en los múltiples servicios de Google. Una pequeña adición, la posibilidad de disponer de los resultados en formato JSON, y más concretamente, como JSON evaluado en un script, permite superar gran parte de las limitaciones de seguridad impuestas por los navegadores sobre Ajax. Es posible así realizar cross-site scripting, incorporando contenidos de documentos disponibles mediante feeds de GData en páginas web, sin disponer de control sobre el servidor.

Necesitamos una hoja de cálculo publicada con dos columnas (citas y autores).

Un pequeño script permitirá extraer el contenido de esta hoja conociendo algunos de sus datos:

  • Su clave de publicación (key).
  • El identificador único de la página a publicar (una hoja puede tener varias páginas).
  • El rango de celdas del que se va a extraer la información.

Esos datos pueden obtenerse a partir de la URL de publicación de feeds que aparece en las “opciones adicionales de publicación” de la pestaña “Publicación” de una hoja de cálculo. Armados con la información apropiada, podemos programar algo interesante:

var CiteSpreadsheet = {
    'rows': 159,
    'getJsonUrl': function(idx) {
        var baseSpreadsheet = 'http://spreadsheets.google.com/feeds/';
        var feedType = 'cells/';
        var key = 'o06661241339204957995.1659223542184495700/';
        var sheetId = 'od6/';
        var feedAccess = 'public/basic?';
        var range = 'range=A' + idx + ":B" + idx;
        var output = '&alt=json-in-script';
        var callback = '&callback=CiteSpreadsheet.processJsonResponse';
        return baseSpreadsheet + feedType + key + sheetId + feedAccess + range + output + callback;
    },
    'createScriptElem': function(url) {
        var html = '<script type="text/javascript" src="' + url + '"></script>';
        $(html).appendTo('body');
    },
    'getIndex': function() {
        var today = new Date();
        var startDate = new Date(today.getYear(), 0, 1);
        var delta = today.getTime() - startDate.getTime();
        var dayOfYear = Math.ceil(delta / 86400000);
        return dayOfYear % CiteSpreadsheet.rows;
    },
    'processJsonResponse': function(jsonData) {
        var retrievedContent = "<span>" + jsonData.feed.entry[0].content.$t +
            " <cite>" + jsonData.feed.entry[1].content.$t + "</cite></span>";
        $(".cite").append(retrievedContent);
    }
};

CiteSpreadsheet.createScriptElem(
    CiteSpreadsheet.getJsonUrl(
        CiteSpreadsheet.getIndex()));

El objeto del código es insertar, dinámicamente, una marca <script> en el documento actual, que apunte mediante su atributo src a una URL específica de consulta válida para la hoja de cálculo deseada, con formato de salida json-in-script, lo que devuelve un script listo para ejecutar con los datos pedidos en un objeto (en el enlace anterior se describe su estructura; Firefox, Firebug y un punto de ruptura ayudan considerablemente).

El feed obtenido ofrecerá información sobre celdas concretas de la hoja en el rango An:Bn, donde n es el número de fila deseado. Como en la hoja hay 159 citas, para dar una diferente cada día calculamos el índice como el número del día actual dentro del año módulo el total de filas.

Por último, para incorporar la cita al DOM de la página, necesitamos un elemento al que anclarlo. Para ello, se incluye en la plantilla de la bitácora, en el lugar donde deba aparecer la cita, un módulo HTML/Javascript con el contenido

<span class="cite"/>

(podría valer un <div>). En otras plataformas de blogging esto se podrá hacer editando directamente la plantilla o de algún otro modo.

Quote of the Day

You should see over these lines the result of my last project: a Quote of the Day implementation for Blogger It should be a piece of cake to port to other blogging platforms: the only requirement for the code to work is to have a recent version of the JQuery Javascript library loaded. Rewriting the code to eliminate that dependency should also be easy. Quips are published on Google Spreadsheets.

[Warning! Buzzword-enabled text ahead!] GData APIs implement an Atom extension that enables remote document querying and editing. What’s more, query results can be obtained as evaluated JSON objects; this technique allows to bypass most cross-site scripting security constraints, so Ajax widgets can be implemented without control over the server side.

We will need a published spreadsheet sporting quips and authors in two columns.

A tiny little script will get us the sheet’s contents. We only have to look for these pieces of information in order to make it work:

  • Publishing key.
  • Unique identifier for the desired sheet (a spreadsheet is organized as a book, so it can contain several sheets).
  • Desired cell range.

These can be obtained from the feed publishing URL that appears in Publish/More publishing options in the Google Spreadsheets interface. There goes the script:

var CiteSpreadsheet = {
    'rows': 159,
    'getJsonUrl': function(idx) {
        var baseSpreadsheet = 'http://spreadsheets.google.com/feeds/';
        var feedType = 'cells/';
        var key = 'o06661241339204957995.1659223542184495700/';
        var sheetId = 'od6/';
        var feedAccess = 'public/basic?';
        var range = 'range=A' + idx + ":B" + idx;
        var output = '&alt=json-in-script';
        var callback = '&callback=CiteSpreadsheet.processJsonResponse';
        return baseSpreadsheet + feedType + key + sheetId + feedAccess + range + output + callback;
    },
    'createScriptElem': function(url) {
        var html = '<script type="text/javascript" src="' + url + '"></script>';
        $(html).appendTo('body');
    },
    'getIndex': function() {
        var today = new Date();
        var startDate = new Date(today.getYear(), 0, 1);
        var delta = today.getTime() - startDate.getTime();
        var dayOfYear = Math.ceil(delta / 86400000);
        return dayOfYear % CiteSpreadsheet.rows;
    },
    'processJsonResponse': function(jsonData) {
        var retrievedContent = "<span>" + jsonData.feed.entry[0].content.$t +
            " <cite>" + jsonData.feed.entry[1].content.$t + "</cite></span>";
        $(".cite").append(retrievedContent);
    }
};

CiteSpreadsheet.createScriptElem(
    CiteSpreadsheet.getJsonUrl(
        CiteSpreadsheet.getIndex()));

What the code does, in short, is dynamically inserting a <script> tag in the document, with its src attribute pointing to a specific query URL for the spreadsheet. Output format should be json-in-script; this returns a ready-to-execute script that wraps data in a Javascript object. Its general format is described in the previous link, but Firefox, Firebug and a judicious break point go all the way to get a hold on the gory details.

We’ve got a feed with information about certain cells (range An:Bn, where n is a row index). As the sheet contains 159 quotations, in order to provide a different one daily we compute that row index as the current day in year modulus total number of rows.

Last but not least, we need a hook; someplace in the blog template to anchor the newly retrieved content. We do so by adding to the template an HTML/Javascript module where we want our quip to appear. Its contents should be, at the very least,

<span class="cite"/>

(a <div> would also work —in fact, any container element with class cite). For other blogging platforms the same effect could be achieved by directly editing the template, or by some other means which I leave as exercise for the reader.

Comprime tu código

A pesar de que el ancho de banda disponible para usuarios finales crece sin medida (sobre todo fuera de esta esquinita suroccidental de la Península Europea), sigue siendo un buen consejo optimizar en lo posible el código Javascript de una página web. Es razonable porque los scripts son componentes de la página que contribuyen a la interactividad, prestan servicios variopintos y, con un poco de suerte, funcionan; pero son invisibles. Los usuarios no perciben la necesidad de unos segundos adicionales de descarga, si hay suerte y no tenemos la wifi pillada por algún vecino (o vecina), a cambio de un efecto especial en los botones o de una ensalada de menús dinámicos.

Por eso sigue siendo interesante comprimir el código. La herramienta que he usado para mis últimas creaciones es /packer/ con la opción de codificación Base62. Antes de usar esta herramienta, parece recomendable pasar los scripts candidatos a revoltillos alfanuméricos por un “limpiador” como JSLint.

¿Por qué este paso adicional? Sencillo. Javascript, pese a que su nombre lo liga a un lenguaje serio y formal (algunos dirían sadomaso o bondage) como es Java, es en realidad una de esas creaciones calenturientas y flexibles que, sin llegar al límite de Perl, lenguaje en el que un mono pisoteando un teclado puede escribir un programa ejecutable, permite gran cantidad de licencias de sintaxis. Licencias que, ante cualquier manipulación automática del código (como por ejemplo, la compresión), se vuelven contra el despistado programador y se lo comen. Con patatas.

JSLint sirve para detectar, con diferentes grados de rigurosidad (alto, altísimo e ionosférico), los potenciales problemas que un programa en Javascript puede desarrollar por mor de su sintaxis. Un caso claro: los puntos y coma, o falta de ellos, al final de cada sentencia. Es recomendable escribir cualquier programa candidato a la compresión de forma ordenada, con puntos y coma en los sitios correctos, llaves en los bloques, uso adecuado del cualificador var, uso moderado de eval()… Y así sucesivamente. La documentación de JSLint ofrece más pistas, pero las que he citado serán suficientes para mantener al 99,9% de los scripts libres de problemas.

Por si las moscas, ¡no borres la versión descomprimida! Aunque las moscas no son el único motivo: también están la depuración sana, y no menos importante, la contribución a la comunidad. La seguridad por la obscuridad no es manera de proteger código: cualquiera que desee robar esos algoritmos patentados en Javascript podrá hacerlo con un mínimo de esfuerzo, por mucho que ofusquemos el código. Acepta un consejo: si de veras necesitas el velo del secreto, no programes en Javascript, punto. Internet no funciona así.