Galeri Google Photography Prize

Ilmari Heikkinen

Situs Google Photography Prize

Baru-baru ini, kami meluncurkan bagian Galeri di situs Google Photography Prize. Galeri menampilkan daftar foto scrolling tanpa batas yang diambil dari Google+. Galeri mendapatkan daftar foto dari aplikasi AppEngine yang kami gunakan untuk memoderasi daftar foto di galeri. Kami juga merilis aplikasi galeri sebagai project open source di Google Code.

Halaman galeri

Backend galeri ini adalah aplikasi AppEngine yang menggunakan Google+ API untuk menelusuri postingan dengan salah satu hashtag Google Photography Prize (misalnya, #megpp dan #travelgpp). Kemudian, aplikasi akan menambahkan postingan tersebut ke daftar foto yang belum dimoderasi. Seminggu sekali, tim konten kami memeriksa daftar foto yang belum dimoderasi dan menandai foto yang melanggar pedoman konten kami. Setelah menekan tombol Moderasi, foto yang tidak dilaporkan akan ditambahkan ke daftar foto yang ditampilkan di halaman galeri.

Backend moderasi

Frontend Galeri dibuat menggunakan library Google Closure. Widget Galeri itu sendiri adalah komponen Closure. Di bagian atas file sumber, kita memberi tahu Closure bahwa file ini menyediakan komponen bernama photographyPrize.Gallery dan memerlukan bagian library Closure yang digunakan oleh aplikasi:

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');

Halaman galeri memiliki sedikit JavaScript yang menggunakan JSONP untuk mengambil daftar foto dari aplikasi AppEngine. JSONP adalah peretasan JavaScript lintas origin sederhana yang memasukkan skrip yang terlihat seperti jsonpcallback("responseValue"). Untuk menangani hal-hal terkait JSONP, kita menggunakan komponen goog.net.Jsonp di library Closure.

Skrip galeri akan memeriksa daftar foto dan membuat elemen HTML untuk menampilkan foto tersebut di halaman galeri. Scrolling tanpa batas berfungsi dengan menghubungkan ke peristiwa scroll jendela dan memuat kumpulan foto baru saat scroll jendela mendekati bagian bawah halaman. Setelah memuat segmen daftar foto baru, skrip galeri membuat elemen untuk foto dan menambahkannya ke elemen galeri untuk menampilkannya.

Menampilkan daftar gambar

Metode tampilan daftar gambar adalah hal yang cukup mendasar. Fungsi ini akan memeriksa daftar gambar, membuat elemen HTML, dan tombol +1. Langkah berikutnya adalah menambahkan segmen daftar yang dihasilkan ke elemen galeri utama galeri. Anda dapat melihat beberapa konvensi Closure compiler dalam kode di bawah, perhatikan definisi jenis dalam komentar JSDoc dan visibilitas @private. Metode pribadi memiliki garis bawah (_) setelah namanya.

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

Menangani peristiwa scroll

Untuk melihat kapan pengunjung telah men-scroll halaman ke bawah dan kita perlu memuat gambar baru, galeri akan terhubung ke peristiwa scroll objek jendela. Untuk mengatasi perbedaan dalam implementasi browser, kami menggunakan beberapa fungsi utilitas yang berguna dari library Closure: goog.dom.getDocumentScroll() menampilkan objek {x, y} dengan posisi scroll dokumen saat ini, goog.dom.getViewportSize() menampilkan ukuran jendela, dan goog.dom.getDocumentHeight() tinggi dokumen 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() {
  // ...
};

Memuat gambar

Untuk memuat gambar dari server, kita menggunakan komponen goog.net.Jsonp. Perlu goog.Uri untuk membuat kueri. Setelah dibuat, Anda dapat mengirim kueri ke penyedia Jsonp dengan objek parameter kueri dan fungsi callback berhasil.

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

Seperti yang disebutkan di atas, skrip galeri menggunakan compiler Closure untuk mengompilasi dan meminifikasi kode. Compiler Closure juga berguna dalam menerapkan pengetikan yang benar (Anda menggunakan notasi JSDoc @type foo dalam komentar untuk menetapkan jenis properti) dan juga memberi tahu Anda jika Anda tidak memiliki komentar untuk suatu metode.

Pengujian unit

Kami juga memerlukan pengujian unit untuk skrip galeri, jadi sangat berguna bahwa library Closure memiliki framework pengujian unit bawaan. Framework ini mengikuti konvensi jsUnit, sehingga mudah untuk memulai.

Untuk membantu saya menulis pengujian unit, saya menulis skrip Ruby kecil yang mengurai file JavaScript dan menghasilkan pengujian unit yang gagal untuk setiap metode dan properti dalam komponen galeri. Dengan skrip seperti:

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

Generator pengujian akan menghasilkan pengujian kosong untuk setiap properti:

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

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

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

Pengujian yang dibuat otomatis ini memudahkan saya dalam menulis pengujian untuk kode, dan semua metode serta properti tercakup secara default. Pengujian yang gagal menciptakan efek psikologis yang baik, yaitu saya harus menjalani pengujian satu per satu dan menulis pengujian yang tepat. Ditambah dengan pengukur cakupan kode, ini adalah game yang menyenangkan untuk membuat pengujian dan cakupan semuanya berwarna hijau.

Ringkasan

Gallery+ adalah project open source untuk menampilkan daftar foto Google+ yang dimoderasi dan cocok dengan #hashtag. Aplikasi ini dibuat menggunakan Go dan library Closure. Backend berjalan di App Engine. Galeri+ digunakan di situs Google Photography Prize untuk menampilkan galeri kiriman. Dalam artikel ini, kita telah membahas bagian-bagian penting dari skrip frontend. Rekan saya, Johan Euphrosine, dari tim Developer Relations App Engine sedang menulis artikel kedua yang membahas aplikasi backend. Backend ditulis dalam Go, bahasa sisi server baru dari Google. Jadi, jika Anda tertarik untuk melihat contoh produksi kode Go, nantikan terus.

Referensi