گالری جایزه عکاسی Google

Ilmari Heikkinen

وب‌سایت Google Photography Prize

اخیراً بخش گالری را در سایت جایزه عکاسی Google راه‌اندازی کردیم. گالری فهرست پیمایش بی‌پایانی از عکس‌های واکشی‌شده از Google+‎ را نشان می‌دهد. این گالری فهرست عکس‌ها را از برنامه AppEngine دریافت می‌کند که ما از آن برای تعدیل فهرست عکس‌ها در گالری استفاده می‌کنیم. ما همچنین برنامه گالری را به‌عنوان یک پروژه متن‌باز در Google Code منتشر کردیم.

صفحه گالری

بخش پشتیبان گالری یک برنامه AppEngine است که از Google+ API برای جستجوی پست‌هایی با یکی از هشتگ‌های Google Photography Prize (مثلاً #megpp و #travelgpp) استفاده می‌کند. سپس برنامه آن پست‌ها را به فهرست عکس‌های مدیریت‌نشده‌اش اضافه می‌کند. هفته‌ای یک‌بار، تیم محتوای ما فهرست عکس‌های تعدیل‌نشده را بررسی می‌کند و عکس‌هایی را که رهنمودهای محتوای ما را نقض می‌کنند پرچم‌گذاری می‌کند. پس‌از زدن دکمه «مدیریت»، عکس‌های پرچم‌گذاری‌نشده به فهرست عکس‌های نشان‌داده‌شده در صفحه گالری اضافه می‌شوند.

زیرینه تعدیل

نرم‌افزار نهایی «گالری» بااستفاده از کتابخانه Google Closure ساخته شده است. خود ابزاره «گالری» یک مؤلفه Closure است. در بالای فایل منبع، به Closure می‌گوییم که این فایل مؤلفه‌ای به‌نام photographyPrize.Gallery ارائه می‌دهد و به بخش‌هایی از کتابخانه Closure که برنامه استفاده می‌کند نیاز دارد:

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

صفحه گالری کمی جاوا اسکریپت دارد که از JSONP برای بازیابی فهرست عکس‌ها از برنامه AppEngine استفاده می‌کند. JSONP یک هک جاوا اسکریپت میان‌مبدئی ساده است که دستورگانی را تزریق می‌کند که شبیه jsonpcallback("responseValue") است. برای مدیریت موارد JSONP، از مؤلفه goog.net.Jsonp در کتابخانه Closure استفاده می‌کنیم.

اسکریپت گالری فهرست عکس‌ها را بررسی می‌کند و عناصر HTML را برای آن‌ها تولید می‌کند تا آن‌ها را در صفحه گالری نشان دهد. پیمایش بی‌پایان با اتصال به رویداد پیمایش پنجره و بار کردن دسته جدیدی از عکس‌ها هنگام نزدیک شدن پیمایش پنجره به پایین صفحه کار می‌کند. پس‌از بار کردن بخش جدید فهرست عکس، اسکریپت گالری عناصری برای عکس‌ها ایجاد می‌کند و آن‌ها را به عنصر گالری اضافه می‌کند تا نمایش داده شوند.

نمایش فهرست تصاویر

روش نمایش فهرست تصویر بسیار ساده است. این کد فهرست تصاویر را بررسی می‌کند و عناصر HTML و دکمه‌های ‎+1 را تولید می‌کند. مرحله بعدی اضافه کردن بخش فهرست تولیدشده به عنصر گالری اصلی گالری است. می‌توانید برخی‌از قراردادهای کامپایلر Closure را در کد زیر ببینید، به تعریف‌های نوع در نظر JSDoc و رؤیت‌پذیری @private توجه کنید. روش‌های خصوصی بعداز نامشان زیرخط (_) دارند.

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

مدیریت رویدادهای پیمایش

برای اینکه ببینیم بازدیدکننده چه زمانی صفحه را به پایین پیمایش کرده است و باید تصاویر جدید را بار کنیم، گالری به رویداد پیمایش شیء پنجره متصل می‌شود. برای پوشاندن تفاوت‌ها در پیاده‌سازی‌های مرورگر، از برخی توابع سودمندی مفید از کتابخانه Closure استفاده می‌کنیم: goog.dom.getDocumentScroll() یک شیء {x, y} با موقعیت پیمایش سند فعلی برمی‌گرداند، goog.dom.getViewportSize() اندازه پنجره را برمی‌گرداند، و goog.dom.getDocumentHeight() ارتفاع سند 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() {
  // ...
};

درحال بار کردن تصاویر

برای بار کردن تصاویر از سرور، از مؤلفه goog.net.Jsonp استفاده می‌کنیم. برای پُرسمان، goog.Uri لازم است. پس‌از ایجاد، می‌توانید پُرسمانی را با شیء پارامتر پُرسمان و تابع بازخوانی موفقیت به ارائه‌دهنده Jsonp ارسال کنید.

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

همان‌طور که در بالا ذکر شد، اسکریپت گالری از کامپایلر Closure برای کامپایل و کوچک کردن کد استفاده می‌کند. کامپایلر Closure همچنین در اجرای تایپ صحیح مفید است (شما از نماد @type foo JSDoc در نظرات خود برای تنظیم نوع یک ویژگی استفاده می‌کنید) و همچنین زمانی که نظری برای یک روش ندارید به شما اطلاع می‌دهد.

آزمون‌های واحد

همچنین به آزمون‌های واحد برای اسکریپت گالری نیاز داشتیم، بنابراین مفید است که کتابخانه Closure دارای یک چارچوب آزمون واحد داخلی است. این ابزار از قراردادهای jsUnit پیروی می‌کند، بنابراین شروع کار با آن آسان است.

برای کمک به من در نوشتن آزمون‌های واحد، یک اسکریپت کوچک Ruby نوشتم که فایل JavaScript را تجزیه می‌کند و یک آزمون واحد ناموفق برای هر روش و ویژگی در مؤلفه گالری ایجاد می‌کند. با فرض داشتن متنی مثل:

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

تولیدکننده آزمون برای هریک از دارایی‌ها آزمونی خالی تولید می‌کند:

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

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

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

این آزمایش‌های تولیدشده خودکار شروع آسانی را برای نوشتن آزمایش‌های کد برای من فراهم کرد و همه روش‌ها و ویژگی‌ها به‌طور پیش‌فرض پوشش داده شدند. آزمایش‌های ناموفق اثر روانی خوبی ایجاد می‌کنند که در آن باید آزمایش‌ها را یکی‌یکی بررسی کنم و آزمایش‌های مناسب بنویسم. این بازی همراه با سنجه پوشش کد، بازی سرگرم‌کننده‌ای است که آزمایش‌ها و پوشش را کاملاً سبز می‌کند.

خلاصه

Gallery+‎ پروژه‌ای متن‌باز برای نمایش فهرستی از عکس‌های Google+‎ است که با یک #هشتگ مطابقت دارند. این برنامه بااستفاده از Go و کتابخانه Closure ساخته شده است. بخش پشتی در App Engine اجرا می‌شود. از Gallery+‎ در وب‌سایت Google Photography Prize برای نمایش گالری ارسال استفاده می‌شود. در این مقاله، بخش‌های جذاب اسکریپت پیش‌نما را بررسی کردیم. My collague Johan Euphrosine from the App Engine Developer Relations team is writing a second article talking about the backend app. The backend is written in Go, Google's new server-side language. بنابراین اگر علاقه‌مندید نمونه‌ای از کد Go را در محیط تولید ببینید، با ما همراه باشید!

مرجع‌ها