Galeria do Prêmio de Fotografia do Google

Ilmari Heikkinen

Site do Prêmio de Fotografia do Google

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

Página da galeria

O back-end da galeria é um app do App Engine que usa a API Google+ para pesquisar postagens com uma das hashtags do Prêmio de Fotografia do Google (por exemplo, #megpp e #travelgpp). 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. Depois de clicar no botão "Moderar", as fotos sem sinalização são adicionadas à lista de fotos exibidas na página da galeria.

O back-end de moderação

O front-end da galeria é criado usando a biblioteca Closure do Google. O widget da Galeria é um componente de fechamento. Na parte de cima 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 AppEngine. O JSONP é um hack JavaScript simples entre origens que injeta um script semelhante a jsonpcallback("responseValue"). Para processar o JSONP, usamos 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 mostradas na página da galeria. A rolagem infinita funciona conectando-se ao evento de rolagem da janela e carregando um novo lote de fotos quando a rolagem da janela está perto da parte de baixo 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ção.

Mostrar a lista de imagens

O método de exibição da lista de imagens é bem básico. Ele percorre a lista de imagens, gera elementos HTML e botões +1. A próxima etapa é adicionar o segmento de lista gerado ao elemento principal da galeria. Você pode conferir algumas convenções do compilador Closure no código abaixo. Observe as definições de tipo no comentário JSDoc e a visibilidade @private. Os métodos particulares têm um sublinhado (_) depois do 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 saber quando o visitante rolou a página até o fim e precisamos carregar novas imagens, a galeria se conecta ao evento de rolagem do objeto da janela. Para cobrir as diferenças nas implementações do navegador, usamos algumas funções de utilitário ú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, usamos o componente goog.net.Jsonp. É necessário um goog.Uri para consultar. Depois de criada, você pode enviar uma consulta ao provedor Jsonp com um objeto de parâmetro de consulta e uma função de callback 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 da galeria usa o Closure Compiler para compilar e minimizar o código. O Closure Compiler também é útil para aplicar a digitação correta (você usa a notação @type foo JSDoc nos comentários para definir o tipo de uma propriedade) e informa quando você não tem comentários para um método.

Testes de unidade

Também precisávamos de testes de unidade para o script da galeria. Por isso, é útil que a biblioteca Closure tenha um framework de teste de unidade integrado. Ele segue as convenções do jsUnit, então é fácil começar.

Para me ajudar a escrever os testes de unidade, criei um pequeno script Ruby que analisa o arquivo JavaScript e gera um teste de unidade com falha para cada método e propriedade no componente da 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 ajudaram a começar a escrever testes para o código, e todos os métodos e propriedades foram cobertos por padrão. Os testes com falhas criam um bom efeito psicológico, em que tive que passar por cada um deles e escrever testes adequados. Em conjunto com um medidor de cobertura de código, é um jogo divertido para deixar os testes e a cobertura totalmente verdes.

Resumo

O Gallery+ é um projeto de código aberto que mostra uma lista moderada de fotos do Google+ que correspondem a uma #hashtag. Ele foi criado usando o Go e a biblioteca Closure. O back-end é executado no App Engine. O Gallery+ é usado no site do Prêmio de Fotografia do Google para mostrar a galeria de envios. Neste artigo, abordamos os pontos importantes do script de front-end. Meu colega Johan Euphrosine, da equipe de relações com desenvolvedores do App Engine, está escrevendo um segundo artigo sobre o app de back-end. O back-end é escrito em Go, a nova linguagem do lado do servidor do Google. Se você quiser conferir um exemplo de produção de código Go, fique ligado!

Referências