גלריית פרסי הצילום של Google

אילמרי הייקינן

אתר פרס הצילום של Google

לאחרונה השקנו את הקטע 'גלריה' באתר Google Photo Award (פרס הצילום של Google). הגלריה מציגה רשימת גלילה מתמשכת של התמונות שנשלפו מ-Google+. היא מקבלת את רשימת התמונות מאפליקציית AppEngine שבה אנחנו משתמשים לניהול רשימת התמונות בגלריה. השקנו את אפליקציית הגלריה גם כפרויקט קוד פתוח ב-Google Code.

דף הגלריה

הקצה העורפי של הגלריה הוא אפליקציית AppEngine שמשתמשת ב-API של Google+ כדי לחפש פוסטים שמכילים את אחד מתגי ה-hashtag של פרס הצילום של Google (לדוגמה: #megpp ו- #travelgpp). לאחר מכן האפליקציה תוסיף את הפוסטים האלה לרשימת התמונות שלא טופלו. פעם בשבוע, צוות התוכן שלנו עובר על הרשימה של תמונות שלא טופלו ומסמן תמונות שמפרות את הנחיות התוכן. לאחר הלחיצה על הלחצן 'בינוני', התמונות שאינן מסומנות מתווספות לרשימת התמונות המוצגות בדף הגלריה.

הקצה העורפי של ניהול התגובות

החזית של הגלריה נוצרה באמצעות ספריית החסימות של Google. ווידג'ט הגלריה עצמו הוא רכיב סגירה. בחלק העליון של קובץ המקור אנחנו מודיעים ל'סגירה' שהקובץ הזה מספק רכיב בשם photographyPrize.Gallery ונדרשים לו החלקים של ספריית החסימות שהאפליקציה משתמשת בו:

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 בספריית החסימות.

סקריפט הגלריה עובר על רשימת התמונות ויוצר רכיבי HTML כדי להציג אותם בדף הגלריה. הגלילה האינסופית עובדת על ידי הצמדת אירוע הגלילה בחלון וטעינה של קבוצה חדשה של תמונות כאשר הגלילה בחלון קרובה לתחתית הדף. לאחר טעינת הפלח החדש של רשימת התמונות, סקריפט הגלריה יוצר רכיבים עבור התמונות ומוסיף אותם לרכיב הגלריה כדי להציג אותן.

הצגה של רשימת התמונות

שיטת התצוגה של רשימת התמונות היא בסיסית למדי. עובר ברשימת התמונות, יוצר רכיבי HTML ולחצני 1+. השלב הבא הוא להוסיף את פלח הרשימה שנוצר לאלמנט הראשי של הגלריה. ניתן לראות מספר מוסכמות של מהדר סגירה בקוד הבא, לשים לב להגדרות הסוגים בהערה של 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;
};

טיפול באירועי גלילה

כדי לראות מתי המבקר גלל את הדף לתחתית ואנחנו צריכים לטעון תמונות חדשות, הגלריה מתאימה לאירוע הגלילה של אובייקט החלון. כדי להבין את ההבדלים בהטמעות הדפדפן, השתמשנו בכמה פונקציות שימושיות שימושיות מספריית החסימות: 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 עם אובייקט של פרמטר שאילתה ופונקציית קריאה חוזרת (callback).

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

כפי שהוזכר למעלה, סקריפט הגלריה משתמש בהידור החסימות (Crunk) כדי הידור והקטנה של הקוד. מהדר החסימות שימושי גם מבחינת אכיפת ההקלדה הנכונה (צריך להשתמש בסימון JSDoc של @type foo בתגובות כדי להגדיר את סוג הנכס), והוא גם מודיע כשאין תגובות לשיטה מסוימת.

בדיקות של יחידות

היינו צריכים גם בדיקות יחידה לסקריפט הגלריה, לכן כדאי שספריית החסימות כוללת מסגרת לבדיקת יחידה. הוא פועל בהתאם למוסכמות של ה-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+ שתואמות ל- #hashtag. היא נבנתה באמצעות Go וספריית החסימות. הקצה העורפי פועל ב-App Engine. Gallery+ משמשת באתר של פרס הצילום של Google כדי להציג את גלריית ההגשות. במאמר הזה עברנו על החלקים העסיסיים של התסריט של ממשק הקצה. הקולגה שלי יוהאן אופרוזין מצוות קשרי המפתחים של App Engine כותב מאמר שני שעוסק באפליקציית הקצה העורפי. הקצה העורפי נכתב ב-Go, השפה החדשה של Google בצד השרת. אז אם אתם רוצים לראות דוגמה להפקה של קוד Go, כדאי לעקוב אחר העדכונים.

קובצי עזר