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

إلماري هاكينين

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

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

صفحة المعرض

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

الواجهة الخلفية للإشراف

تم إنشاء الواجهة الأمامية للمعرض باستخدام مكتبة الإغلاق من Google. أداة المعرض نفسها هي مكون إغلاق. في أعلى ملف المصدر، نخبر 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 في التعليمة البرمجية أدناه، وتدوين تعريفات الأنواع في تعليق 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 Library: تعرض 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;
}

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

ملخّص

معرض + هو مشروع مفتوح المصدر لعرض قائمة خاضعة للإشراف من صور +Google تتطابق مع علامة #هاشتاغ. وتم بناؤه باستخدام Go ومكتبة Closure. تعمل الخلفية على App Engine. يُستخدم المعرض + على موقع جائزة التصوير الفوتوغرافي من Google لعرض معرض الصور المرسلة. في هذه المقالة، تناولنا المقاطع الكثيرة من النص البرمجي للواجهة الأمامية. كتب زميلي "يوهان يوفرزين" من فريق علاقات مطوري App Engine مقالة ثانية تتحدث عن تطبيق الخلفية. تمت كتابة الخلفية بلغة Go، وهي لغة الخادم الجديدة لدى Google. لذلك، إذا كنت مهتمًا بالاطّلاع على مثال عن عملية إنشاء الرمز البرمجي Go، يُرجى متابعتنا باستمرار.

المراجع