معرض جائزة التصوير من Google

الموقع الإلكتروني لجائزة Google للتصوير

أطلقنا مؤخرًا قسم "المعرض" على موقع جائزة التصوير الفوتوغرافي من Google الإلكتروني. يعرض المعرض قائمة غير محدودة يمكن التمرير فيها للأسفل أو للأعلى للصور التي يتم جلبها من +Google. ويحصل على قائمة الصور من تطبيق AppEngine الذي نستخدمه للإشراف على قائمة الصور في المعرض. لقد أصدرنا أيضًا تطبيق معرض الصور كمشروع مفتوح المصدر على Google Code.

صفحة المعرض

إنّ الخلفية في معرض الصور هي تطبيق AppEngine يستخدم Google+ API للبحث عن المشاركات التي تتضمّن أحد الهاشتاغات لجائزة Google للتصوير الفوتوغرافي (مثل #megpp و #travelgpp). بعد ذلك، يضيف التطبيق هذه المشاركات إلى قائمة الصور غير الخاضعة للإشراف. يراجع فريق المحتوى لدينا مرة واحدة في الأسبوع قائمة الصور غير الخاضعة للإشراف ويبلّغ عن الصور التي تنتهك إرشادات المحتوى المتّبعة لدينا. بعد النقر على الزر "الإشراف"، تتم إضافة الصور التي لم يتم الإبلاغ عنها إلى قائمة الصور المعروضة في صفحة المعرض.

الخلفية للإشراف

تم إنشاء واجهة "معرض الصور" باستخدام مكتبة Google Closure. تطبيق Gallery المصغّر نفسه هو مكوّن إغلاق. في أعلى ملف المصدر، نخبر 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');

تحتوي صفحة معرض الصور على جزء من JavaScript يستخدم JSONP لاسترداد قائمة الصور من تطبيق AppEngine. وJSONP هو اختراق بسيط لـ JavaScript من مصادر متعددة يُدخل نصًا برمجيًا يبدو مثل jsonpcallback("responseValue"). لمعالجة عناصر JSONP، نستخدم المكوّن goog.net.Jsonp في مكتبة Closure.

يبحث نصّ معرض الصور في قائمة الصور وينشئ عناصر HTML لعرضها في صفحة المعرض. يعمل وضع "التمرير اللا نهائي" من خلال الربط بحدث "تمرير النافذة" وتحميل مجموعة جديدة من الصور عندما يكون شريط تمرير النافذة قريبًا من أسفل الصفحة. بعد تحميل قسم قائمة الصور الجديد، ينشئ نص معرض الصور عناصر للصور ويضيفها إلى عنصر المعرض لعرضها.

عرض قائمة الصور

إنّ طريقة عرض قائمة الصور بسيطة جدًا. ينتقل هذا الإجراء عبر قائمة الصور، وينشئ عناصر HTML وأزرار +1. الخطوة التالية هي إضافة شريحة القائمة التي تم إنشاؤها إلى عنصر المعرض الرئيسي. يمكنك الاطّلاع على بعض اصطلاحات Closure compiler في الرمز أدناه، مع ملاحظة تعريفات الأنواع في تعليق 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 compiler لتجميع الرمز البرمجي وتصغيره. يكون "مجمّع 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 للتصوير الفوتوغرافي لعرض معرض الصور المرسَلة. في هذه المقالة، اطّلعنا على الأجزاء المهمة من نصّ واجهة المستخدم. يكتب زميلي "يوهان إيفروسين" من فريق العلاقات مع المطوّرين في App Engine مقالة ثانية تتناول تطبيق الواجهة الخلفية. وقد تم كتابة الواجهة الخلفية بلغة Go، وهي لغة Google الجديدة لبرامج الخادم. إذا كنت مهتمًا بالاطّلاع على مثال على رمز Go مُعدّ للنشر، يُرجى متابعتنا.

المراجع