Creazione della galleria del Google Photography Prize
Di recente abbiamo lanciato la sezione Galleria sul sito del Google Photography Prize. La galleria mostra un elenco a scorrimento continuo di foto recuperate da Google+. L'elenco di foto viene recuperato da un'app AppEngine che utilizziamo per moderare l'elenco di foto nella galleria. Abbiamo anche rilasciato l'app di galleria come progetto open source su Google Code.
Il backend della galleria è un'app AppEngine che utilizza l'API Google+ per cercare i post con uno degli hashtag del Google Photography Prize (ad es. #megpp e #travelgpp). L'app aggiunge poi questi post al suo elenco di foto non moderate. Una volta alla settimana, il nostro team dei contenuti esamina l'elenco delle foto non moderate e segnala quelle che violano le nostre norme relative ai contenuti. Dopo aver fatto clic sul pulsante Modera, le foto non segnalate vengono aggiunte all'elenco delle foto mostrate nella pagina della galleria.
Frontend della galleria
Il frontend della Galleria è stato creato utilizzando la libreria Google Closure. Il widget Galleria è un componente Closure. Nella parte superiore del file di origine, indichiamo a Closure che questo file fornisce un componente denominato photographyPrize.Gallery e richiede le parti della libreria Closure utilizzate dall'app:
goog.provide('photographyPrize.Gallery');
goog.require('goog.debug.Logger');
goog.require('goog.dom');
goog.require('goog.dom.classes');
goog.require('goog.events');
goog.require('goog.net.Jsonp');
goog.require('goog.style');
La pagina della galleria contiene un po' di JavaScript che utilizza JSONP per recuperare l'elenco di foto dall'app AppEngine. JSONP è un semplice hack JavaScript multiorigine che inserisce uno script simile a jsonpcallback("responseValue"). Per gestire gli elementi JSONP, utilizziamo il componente goog.net.Jsonp nella libreria Closure.
Lo script della galleria esamina l'elenco di foto e genera elementi HTML per mostrarle nella pagina della galleria. Lo scorrimento continuo funziona collegandosi all'evento di scorrimento della finestra e caricando un nuovo gruppo di foto quando lo scorrimento della finestra è vicino alla parte inferiore della pagina. Dopo aver caricato il nuovo segmento dell'elenco di foto, lo script della galleria crea elementi per le foto e li aggiunge all'elemento della galleria per visualizzarli.
Visualizzazione dell'elenco delle immagini
Il metodo di visualizzazione dell'elenco di immagini è piuttosto semplice. Esamina l'elenco di immagini, genera elementi HTML e pulsanti +1. Il passaggio successivo consiste nell'aggiungere il segmento di elenco generato all'elemento principale della galleria. Puoi vedere alcune convenzioni del compilatore Closure nel codice riportato di seguito. Nota le definizioni dei tipi nel commento JSDoc e la visibilità @private. I metodi privati hanno un trattino basso (_) dopo il nome.
/**
* Displays images in imageList by putting them inside the section element.
* Edits image urls to scale them down to imageSize x imageSize bounding
* box.
*
* @param {Array.<Object>} imageList List of image objects to show. Retrieved
* by loadImages.
* @return {Element} The generated image list container element.
* @private
*/
photographyPrize.Gallery.prototype.displayImages_ = function(imageList) {
// find the images and albums from the image list
for (var j = 0; j < imageList.length; j++) {
// change image urls to scale them to photographyPrize.Gallery.MAX_IMAGE_SIZE
}
// Go through the image list and create a gallery photo element for each image.
// This uses the Closure library DOM helper, goog.dom.createDom:
// element = goog.dom.createDom(tagName, className, var_childNodes);
var category = goog.dom.createDom('div', 'category');
for (var k = 0; k < items.length; k++) {
var plusone = goog.dom.createDom('g:plusone');
plusone.setAttribute('href', photoPageUrl);
plusone.setAttribute('size', 'standard');
plusone.setAttribute('annotation', 'none');
var photo = goog.dom.createDom('div', {className: 'gallery-photo'}, ...)
photo.appendChild(plusone);
category.appendChild(photo);
}
this.galleryElement_.appendChild(category);
return category;
};
Gestione degli eventi di scorrimento
Per vedere quando il visitatore ha fatto scorrere la pagina fino in fondo e dobbiamo caricare nuove immagini, la galleria si collega all'evento di scorrimento dell'oggetto finestra. Per appianare le differenze nelle implementazioni dei browser, utilizziamo alcune utili funzioni di utilità della libreria Closure: goog.dom.getDocumentScroll() restituisce un oggetto {x, y} con la posizione di scorrimento del documento corrente, goog.dom.getViewportSize() restituisce le dimensioni della finestra e goog.dom.getDocumentHeight() l'altezza del documento HTML.
/**
* Handle window scroll events by loading new images when the scroll reaches
* the last screenful of the page.
*
* @param {goog.events.BrowserEvent} ev The scroll event.
* @private
*/
photographyPrize.Gallery.prototype.handleScroll_ = function(ev) {
var scrollY = goog.dom.getDocumentScroll().y;
var height = goog.dom.getViewportSize().height;
var documentHeight = goog.dom.getDocumentHeight();
if (scrollY + height >= documentHeight - height / 2) {
this.tryLoadingNextImages_();
}
};
/**
* Try loading the next batch of images objects from the server.
* Only fires if we have already loaded the previous batch.
*
* @private
*/
photographyPrize.Gallery.prototype.tryLoadingNextImages_ = function() {
// ...
};
Caricamento delle immagini
Per caricare le immagini dal server, utilizziamo il componente goog.net.Jsonp. Per eseguire una query è necessario un goog.Uri. Una volta creato, puoi inviare una query al provider Jsonp con un oggetto parametro di query e una funzione di callback di esito positivo.
/**
* Loads image list from the App Engine page and sets the callback function
* for the image list load completion.
*
* @param {string} tag Fetch images tagged with this.
* @param {number} limit How many images to fetch.
* @param {number} offset Offset for the image list.
* @param {function(Array.<Object>=)} callback Function to call
* with the loaded image list.
* @private
*/
photographyPrize.Gallery.prototype.loadImages_ = function(tag, limit, offset, callback) {
var jsonp = new goog.net.Jsonp(
new goog.Uri(photographyPrize.Gallery.IMAGE_LIST_URL));
jsonp.send({'tag': tag, 'limit': limit, 'offset': offset}, callback);
};
Come accennato in precedenza, lo script della galleria utilizza il compilatore Closure per compilare e minimizzare il codice. Il compilatore Closure è utile anche per applicare la digitazione corretta (utilizzi la notazione JSDoc @type foo nei commenti per impostare il tipo di una proprietà) e ti indica anche quando non hai commenti per un metodo.
Test delle unità
Avevamo anche bisogno di test delle unità per lo script della galleria, quindi è utile che la libreria Closure abbia un framework di test delle unità integrato. Segue le convenzioni di jsUnit, quindi è facile iniziare.
Per aiutarmi a scrivere i test delle unità, ho scritto un piccolo script Ruby che analizza il file JavaScript e genera un test delle unità con esito negativo per ogni metodo e proprietà nel componente della galleria. Dato uno script come:
Foo = function() {}
Foo.prototype.bar = function() {}
Foo.prototype.baz = "hello";
Il generatore di test genera un test vuoto per ciascuna delle proprietà:
function testFoo() {
fail();
Foo();
}
function testFooPrototypeBar = function() {
fail();
instanceFoo.bar();
}
function testFooPrototypeBaz = function() {
fail();
instanceFoo.baz;
}
Questi test generati automaticamente mi hanno permesso di iniziare facilmente a scrivere test per il codice e tutti i metodi e le proprietà erano coperti per impostazione predefinita. I test non riusciti creano un piacevole effetto psicologico per cui ho dovuto esaminarli uno per uno e scrivere test adeguati. In combinazione con un misuratore di copertura del codice, è un gioco divertente rendere verdi i test e la copertura.
Riepilogo
Gallery+ è un progetto open source per mostrare un elenco moderato di foto di Google+ corrispondenti a un #hashtag. È stato creato utilizzando Go e la libreria Closure. Il backend viene eseguito su App Engine. Gallery+ viene utilizzato sul sito web del Google Photography Prize per mostrare la galleria delle proposte. In questo articolo abbiamo esaminato le parti più interessanti dello script di frontend. Il mio collega Johan Euphrosine del team di relazioni con gli sviluppatori di App Engine sta scrivendo un secondo articolo sull'app di backend. Il backend è scritto in Go, il nuovo linguaggio lato server di Google. Quindi, se ti interessa vedere un esempio di codice Go in produzione, continua a seguirci.
Riferimenti
- Google Photography Prize
- Pagina del progetto Gallery+
- Libreria Closure
- Closure Compiler