Galeri Google Fotografi Hadiah

Ilmari Heikkinen

Situs Google Photography Prize

Baru-baru ini, kami meluncurkan bagian Galeri di situs Google Photography Prize. Galeri menampilkan daftar foto yang dapat di-scroll tanpa batas yang diambil dari Google+. Galeri ini 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 adalah aplikasi AppEngine yang menggunakan Google+ API untuk menelusuri postingan yang berisi salah satu hashtag Google Photography Prize (misalnya #megpp dan #travelgpp). Kemudian, aplikasi akan menambahkan postingan tersebut ke daftar foto yang tidak dimoderasi. Sekali seminggu, tim konten kami memeriksa daftar foto yang tidak dimoderasi dan menandai foto yang melanggar pedoman konten kami. Setelah menekan tombol Moderasi, foto yang tidak ditandai 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 hack JavaScript lintas-asal sederhana yang memasukkan skrip yang terlihat seperti jsonpcallback("responseValue"). Untuk menangani hal-hal JSONP, kita menggunakan komponen goog.net.Jsonp di library Closure.

Skrip galeri akan memeriksa daftar foto dan membuat elemen HTML untuk menampilkannya 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 akan 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 compiler Closure 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 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, kita menggunakan beberapa fungsi utilitas praktis 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. Kueri memerlukan goog.Uri. Setelah dibuat, Anda dapat mengirim kueri ke penyedia Jsonp dengan objek parameter kueri dan fungsi callback sukses.

/**
 * 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 saat tidak memiliki komentar untuk suatu metode.

Pengujian unit

Kita juga memerlukan pengujian unit untuk skrip galeri, jadi sangat praktis jika library Closure memiliki framework pengujian unit yang terintegrasi. Library ini mengikuti konvensi jsUnit, sehingga mudah untuk memulainya.

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 di komponen galeri. Dengan skrip seperti:

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

Generator pengujian 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 secara otomatis ini memberi saya awal yang mudah dalam menulis pengujian untuk kode, dan semua metode dan properti dicakup secara default. Pengujian yang gagal menciptakan efek psikologis yang baik, yaitu saya harus melakukan 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. Gallery+ digunakan di situs Google Photography Prize untuk menampilkan galeri kiriman. Dalam artikel ini, kita telah membahas bagian menarik 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 Google. Jadi, jika Anda tertarik untuk melihat contoh produksi kode Go, nantikan kabar terbarunya.

Referensi