Galleria di Google Photography Prize

Ilmari Heikkinen

Sito web del Google Photography Prize

Di recente abbiamo lanciato la sezione Gallery sul sito Google Photography Prize. La galleria mostra un elenco a scorrimento infinito di foto recuperate da Google+. Recupera l'elenco di foto da un'app AppEngine che utilizziamo per moderare l'elenco di foto presenti nella galleria. Abbiamo anche rilasciato l'app Galleria come progetto open source su Google Code.

La pagina della galleria

Il backend della galleria è un'app AppEngine che utilizza l'API di Google+ per cercare post che contengono uno degli hashtag di Google Photography Prize (ad esempio, #megpp e #travelgpp). Successivamente, l'app aggiunge i post all'elenco delle foto non moderate. Una volta alla settimana, il nostro team addetto ai contenuti esamina l'elenco delle foto non moderate e segnala quelle che violano le linee guida sui contenuti. Una volta premuto il pulsante Modera, le foto non contrassegnate vengono aggiunte all'elenco delle foto visualizzato sulla pagina della galleria.

Il backend di moderazione

Il frontend della galleria è stato creato utilizzando la libreria Google Closure. Il widget Galleria è un componente Chiusura. Nella parte superiore del file sorgente, comunichiamo 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 codice JavaScript che utilizza JSONP per recuperare l'elenco di foto dall'app AppEngine. JSONP è un semplice compromesso JavaScript multiorigine che inserisce uno script simile a jsonpcallback("responseValue"). Per gestire i contenuti JSONP stiamo usando il componente goog.net.Jsonp nella libreria Closure.

Lo script della galleria analizza l'elenco delle foto e genera gli elementi HTML da mostrare nella pagina della galleria. Lo scorrimento continuo funziona unendo l'evento di scorrimento della finestra e caricando un nuovo gruppo di foto quando la finestra di scorrimento si trova vicino alla parte inferiore della pagina. Dopo aver caricato il nuovo segmento dell'elenco di foto, lo script della galleria crea gli 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 è molto semplice. Passa in rassegna l'elenco delle immagini, genera elementi HTML e pulsanti +1. Il passaggio successivo consiste nell'aggiungere il segmento dell'elenco generato all'elemento principale della galleria. Puoi vedere alcune convenzioni di compilazione di Closure nel codice riportato di seguito, prendere nota delle definizioni dei tipi nel commento JSDoc e della visibilità @private. I metodi privati sono preceduti da 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 analizzare le differenze nelle implementazioni del browser, stiamo utilizzando alcune utili funzioni di utilità disponibili nella 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 in corso...

Per caricare le immagini dal server, stiamo utilizzando il componente goog.net.Jsonp. Per eseguire una query è necessario un goog.Uri. Dopo averla creata, puoi inviare una query al provider JSON 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 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 indica anche quando non sono disponibili commenti per un metodo.

Test delle unità

Abbiamo anche bisogno dei test delle unità per lo script della galleria, quindi è utile che nella libreria Closure sia integrato un framework per il test delle unità. 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 errori per ogni metodo e proprietà nel componente 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 i test di scrittura del codice e tutti i metodi e le proprietà sono stati coperti per impostazione predefinita. I test falliti creano un piacevole effetto psicologico in cui ho dovuto affrontare i test uno alla volta e scrivere test appropriati. Insieme a un misuratore di copertura del codice, è un gioco divertente per rendere i test e la copertura completamente verdi.

Riepilogo

Gallery+ è un progetto open source per la visualizzazione di 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 nel sito web Google Photography Prize per mostrare la galleria dei contenuti inviati. In questo articolo abbiamo esaminato i bit succulenti dello script frontend. La mia collega Johan Euphrosine del team per le 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 produzione del codice Go, continua a seguirci.

Riferimenti