Galeria do Prêmio de Fotografia do Google

Ilmari Heikkinen

Site do Prêmio Google Fotografia

Recentemente, lançamos a seção Galeria no site Prêmio de Fotografia do Google. A galeria mostra uma lista de rolagem infinita das fotos extraídas do Google+. Ela busca a lista de fotos de um aplicativo AppEngine que usamos para moderar a lista de fotos na galeria. Também lançamos o aplicativo de galeria como um projeto de código aberto no Google Code.

Página da galeria

O back-end da galeria é um aplicativo do App Engine que usa a API do Google+ para pesquisar postagens com uma das hashtags do Prêmio Google Fotografia (por exemplo, #megpp e #travelgpp). Em seguida, o app adiciona essas postagens à lista de fotos não moderadas. Uma vez por semana, nossa equipe de conteúdo analisa a lista de fotos não moderadas e sinaliza aquelas que violam nossas diretrizes de conteúdo. Após pressionar o botão Moderar, as fotos não sinalizadas são adicionadas à lista de fotos exibidas na página da galeria.

O back-end de moderação

O front-end do Gallery é criado usando a Google closure library (em inglês). O próprio widget Galeria é um componente Fechamento. Na parte superior do arquivo de origem, informamos ao closure que esse arquivo fornece um componente chamado photographyPrize.Gallery e exige as partes da biblioteca closure usadas pelo 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');

A página da galeria tem um pouco de JavaScript que usa JSONP para recuperar a lista de fotos do app App Engine. O JSONP é um hack simples de JavaScript de origem cruzada que injeta um script semelhante a jsonpcallback("responseValue"). Para processar o conteúdo JSONP, estamos usando o componente goog.net.Jsonp na biblioteca closure.

O script da galeria percorre a lista de fotos e gera elementos HTML para que elas sejam exibidas na página da galeria. A rolagem infinita funciona ao se conectar ao evento de rolagem da janela e carregar um novo lote de fotos quando a rolagem da janela está perto da parte inferior da página. Depois de carregar o novo segmento da lista de fotos, o script da galeria cria elementos para as fotos e os adiciona ao elemento da galeria para exibi-los.

Mostrar a lista de imagens

O método de exibição da lista de imagens é bem básico. Ele percorre a lista de imagens e gera elementos HTML e botões +1. A próxima etapa é adicionar o segmento de lista gerado ao elemento principal da galeria. Veja algumas convenções do closure compilador no código abaixo e observe as definições de tipo no comentário do JSDoc e a visibilidade @private. Os métodos particulares têm um sublinhado (_) após o 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;
};

Como processar eventos de rolagem

Para ver quando o visitante rolou a página para a parte inferior e precisamos carregar novas imagens, a galeria conecta-se ao evento de rolagem do objeto da janela. Para lidar com as diferenças nas implementações do navegador, estamos usando algumas funções utilitárias úteis da biblioteca closure: goog.dom.getDocumentScroll() retorna um objeto {x, y} com a posição de rolagem do documento atual, goog.dom.getViewportSize() retorna o tamanho da janela e goog.dom.getDocumentHeight() a altura do 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() {
  // ...
};

Como carregar imagens

Para carregar as imagens do servidor, estamos usando o componente goog.net.Jsonp. É preciso uma goog.Uri para fazer a consulta. Uma vez criada, você pode enviar uma consulta ao provedor Jsonp com um objeto de parâmetro de consulta e uma função de retorno de chamada de sucesso.

/**
 * 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);
};

Como mencionado acima, o script de galeria usa o closure Compiler para compilar e reduzir o código. Ele também é útil para aplicar a digitação correta (use a notação JSDoc @type foo nos seus comentários para definir o tipo de uma propriedade) e informa quando não há comentários para um método.

Testes de unidades

Também precisávamos de testes de unidade para o script da galeria. Portanto, é conveniente que a biblioteca closure tenha uma estrutura de teste de unidade integrada. Ele segue as convenções de jsUnit, por isso é fácil começar a usá-lo.

Para me ajudar a criar os testes de unidade, criei um pequeno script em Ruby que analisa o arquivo JavaScript e gera um teste de unidade com falha para cada método e propriedade no componente de galeria. Considerando um script como:

Foo = function() {}
Foo.prototype.bar = function() {}
Foo.prototype.baz = "hello";

O gerador de testes gera um teste vazio para cada uma das propriedades:

function testFoo() {
  fail();
  Foo();
}

function testFooPrototypeBar = function() {
  fail();
  instanceFoo.bar();
}

function testFooPrototypeBaz = function() {
  fail();
  instanceFoo.baz;
}

Esses testes gerados automaticamente me deram um começo fácil para escrever testes para o código, e todos os métodos e propriedades estavam cobertos por padrão. Os testes reprovados criaram um bom efeito psicológico, no qual eu tive que passar por todos os testes um por um e escrever testes adequados. Com um medidor de cobertura de código, é um jogo divertido que deixa os testes e a cobertura verdes.

Resumo

O Gallery+ é um projeto de código aberto que exibe uma lista moderada de fotos do Google+ que correspondem a uma #hashtag. Ele foi criado usando Go e a closure Library. O back-end é executado no App Engine. O app Gallery+ é usado no site do Prêmio de Fotografia do Google para exibir a galeria de inscrições. Neste artigo, analisamos as partes interessantes do script de front-end. Meu colega Johan Euphrosine, da equipe de relações com desenvolvedores do Google App Engine, está escrevendo um segundo artigo que fala sobre o aplicativo de back-end. O back-end está escrito em Go, a nova linguagem de servidor do Google. Portanto, se você quiser conferir um exemplo de produção de código Go, fique de olho!

Referências