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