Como criar a Galeria 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.
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.
Front-end da galeria
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
- Google Photography Prize (em inglês)
- Página do projeto Gallery+
- Biblioteca Closure
- Closure compiler (link em inglês)