Creazione della galleria del Premio Google per la fotografia
Di recente abbiamo lanciato la sezione Galleria sul sito del Premio di fotografia Google. La galleria mostra un elenco infinito di foto tratte da Google+. L'elenco delle foto viene recuperato da un'app AppEngine che utilizziamo per moderare l'elenco delle foto nella galleria. Abbiamo anche rilasciato l'app Galleria come progetto open source su Google Code.
Il backend della galleria è un'app AppEngine che utilizza l'API Google+ per cercare i post contenenti 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 dedicato ai contenuti esamina l'elenco delle foto non moderate e segnala quelle che violano le nostre linee guida sui contenuti. Dopo aver fatto clic sul pulsante Modera, le foto non segnalate vengono aggiunte all'elenco delle foto mostrato nella pagina della galleria.
Frontend della galleria
Il frontend della galleria è realizzato utilizzando la libreria Google Closure. Il widget Galleria è un componente di chiusura. Nella parte superiore del file sorgente diciamo 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 codice JavaScript che utilizza JSONP per recuperare l'elenco delle foto dall'app AppEngine. JSONP è un semplice hack JavaScript cross-origin 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 mostrarli nella pagina della galleria. Lo scorrimento infinito funziona collegandosi all'evento di scorrimento della finestra e caricando un nuovo lotto di foto quando lo scorrimento della finestra è vicino alla fine 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 di 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 galleria principale della galleria. Puoi vedere alcune convenzioni del compilatore Closure nel codice di seguito, prendi nota delle definizioni di tipo nel commento JSDoc e della visibilità @private. I metodi privati hanno un'underscore (_) 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 verso il basso e dobbiamo caricare nuove immagini, la galleria si collega all'evento di scorrimento dell'oggetto window. Per ovviare alle 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 corrente del documento, 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
. La query richiede goog.Uri
. Una volta creato, puoi inviare una query al provider Jsonp con un oggetto parametro di query e una funzione di callback di successo.
/**
* 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 sopra, lo script della galleria utilizza il compilatore Closure per compilare e ridurre al minimo il codice. Il compilatore Closure è utile anche per applicare la tipizzazione corretta (utilizzi la notazione JSDoc @type foo
nei commenti per impostare il tipo di una proprietà) e ti dice anche quando non hai commenti per un metodo.
Test delle unità
Avevamo bisogno anche di test di unità per lo script della galleria, quindi è stato utile che la libreria Closure avesse un framework di test di unità integrato. Segue le convenzioni di jsUnit, quindi è facile iniziare a utilizzarlo.
Per aiutarmi a scrivere i test di unità, ho scritto un piccolo script Ruby che analizza il file JavaScript e genera un test di unità non riuscito per ogni metodo e proprietà del 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 ogni 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 bell'effetto psicologico, in quanto ho dovuto esaminarli uno per uno e scrivere test adeguati. Se abbinato a un misuratore della copertura del codice, è un gioco divertente ottenere il massimo dei risultati nei test e nella copertura.
Riepilogo
Gallery+ è un progetto open source per visualizzare 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 dei contenuti inviati. In questo articolo abbiamo esaminato i punti salienti dello script frontend. Il mio collega Johan Euphrosine del team Developer Relations 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 produzione di codice Go, continua a seguirci.
Riferimenti
- Premio Google per la fotografia
- Pagina del progetto Gallery+
- Libreria di chiusura
- Compilatore di chiusure