Thư viện giải thưởng ảnh của Google

Ilmari Heikkinen

Trang web Giải thưởng nhiếp ảnh của Google

Gần đây, chúng tôi đã ra mắt mục Thư viện trên trang web Giải thưởng ảnh của Google. Thư viện hiển thị danh sách cuộn vô hạn các ảnh được tìm nạp từ Google+. Thư viện nhận danh sách ảnh từ ứng dụng AppEngine mà chúng tôi sử dụng để kiểm duyệt danh sách ảnh trong thư viện. Chúng tôi cũng đã phát hành ứng dụng thư viện dưới dạng một dự án nguồn mở trên Google Code.

Trang thư viện

Phần phụ trợ của thư viện là một ứng dụng AppEngine sử dụng API Google+ để tìm kiếm các bài đăng có một trong các hashtag Giải thưởng nhiếp ảnh của Google trên đó (ví dụ: #megpp và #travelgpp). Sau đó, ứng dụng sẽ thêm các bài đăng đó vào danh sách ảnh chưa được kiểm duyệt. Mỗi tuần một lần, nhóm nội dung của chúng tôi sẽ kiểm tra danh sách những ảnh chưa được kiểm duyệt và gắn cờ những ảnh vi phạm nguyên tắc về nội dung. Sau khi nhấn nút Kiểm duyệt, các ảnh không bị gắn cờ sẽ được thêm vào danh sách ảnh hiển thị trên trang thư viện.

Phần phụ trợ kiểm duyệt

Giao diện người dùng của Thư viện được xây dựng bằng thư viện Google Đóng. Bản thân tiện ích Thư viện là một thành phần đóng. Ở đầu tệp nguồn, chúng ta cho Đóng biết rằng tệp này cung cấp một thành phần có tên là photographyPrize.Gallery và yêu cầu các phần của thư viện Đóng mà ứng dụng dùng:

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

Trang thư viện có một số JavaScript sử dụng JSONP để truy xuất danh sách ảnh từ ứng dụng AppEngine. JSONP là một cuộc tấn công JavaScript đa nguồn gốc đơn giản chèn một tập lệnh giống như jsonpcallback("responseValue"). Để xử lý nội dung JSONP, chúng ta sẽ sử dụng thành phần goog.net.Jsonp trong thư viện đóng.

Tập lệnh thư viện kiểm tra danh sách ảnh và tạo các phần tử HTML để hiển thị chúng trên trang thư viện. Tính năng cuộn vô hạn hoạt động bằng cách liên kết với sự kiện cuộn cửa sổ và tải một loạt ảnh mới khi cửa sổ cuộn gần cuối trang. Sau khi tải phân đoạn danh sách ảnh mới, tập lệnh thư viện sẽ tạo các phần tử cho ảnh và thêm các phần tử đó vào phần tử thư viện để hiển thị các phần tử đó.

Đang hiển thị danh sách hình ảnh

Phương thức hiển thị danh sách hình ảnh là một nội dung khá cơ bản. Công cụ này đi qua danh sách hình ảnh, tạo các phần tử HTML và nút +1. Bước tiếp theo là thêm phân đoạn danh sách đã tạo vào phần tử thư viện chính của thư viện. Bạn có thể thấy một số quy ước của trình biên dịch Đóng trong mã bên dưới, lưu ý định nghĩa loại trong nhận xét JSDoc và chế độ hiển thị @private. Các phương thức riêng tư có dấu gạch dưới (_) sau tên.

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

Xử lý sự kiện cuộn

Để biết thời điểm khách truy cập cuộn trang xuống dưới cùng và chúng ta cần tải hình ảnh mới, thư viện sẽ nối với sự kiện cuộn của đối tượng cửa sổ. Để so sánh sự khác biệt trong quá trình triển khai trình duyệt, chúng ta đang sử dụng một số hàm hiệu dụng tiện dụng từ thư viện đóng: goog.dom.getDocumentScroll() trả về đối tượng {x, y} có vị trí cuộn tài liệu hiện tại, goog.dom.getViewportSize() trả về kích thước cửa sổ và goog.dom.getDocumentHeight() chiều cao của tài liệu 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() {
  // ...
};

Tải hình ảnh

Để tải hình ảnh từ máy chủ, chúng ta sẽ sử dụng thành phần goog.net.Jsonp. Cần có goog.Uri để truy vấn. Sau khi tạo, bạn có thể gửi truy vấn đến nhà cung cấp Jsonp bằng đối tượng tham số truy vấn và hàm callback thành công.

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

Như đã đề cập ở trên, tập lệnh thư viện sẽ sử dụng trình biên dịch đóng để biên dịch và rút gọn mã. Trình biên dịch đóng cũng hữu ích trong việc thực thi việc nhập chính xác (bạn sử dụng ký hiệu JSDoc @type foo trong nhận xét để đặt loại thuộc tính) và cũng cho bạn biết khi nào bạn không có nhận xét cho một phương thức.

Kiểm thử đơn vị

Chúng tôi cũng cần kiểm thử đơn vị cho tập lệnh thư viện. Vì vậy, rất thuận tiện khi thư viện lock được tích hợp sẵn khung kiểm thử đơn vị. API này tuân theo các quy ước jsUnit, vì vậy, rất dễ dàng để bắt đầu.

Để giúp tôi viết mã kiểm thử đơn vị, tôi đã viết một tập lệnh Ruby nhỏ phân tích cú pháp tệp JavaScript và tạo một kiểm thử đơn vị không thành công cho từng phương thức và thuộc tính trong thành phần thư viện. Cho sẵn một tập lệnh như:

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

Trình tạo kiểm thử sẽ tạo một kiểm thử trống cho từng thuộc tính:

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

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

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

Các chương trình kiểm thử được tạo tự động này đã giúp tôi dễ dàng viết chương trình kiểm thử cho đoạn mã, và tất cả phương thức cũng như thuộc tính đều được sử dụng theo mặc định. Những bài kiểm thử không đạt sẽ tạo ra một hiệu ứng tâm lý tốt, trong đó tôi phải trải qua từng bài kiểm tra và viết những bài kiểm thử phù hợp. Được kết hợp với bộ đo mức độ sử dụng mã, đây là một trò chơi thú vị giúp tăng cường hiệu quả kiểm thử và mức độ sử dụng.

Tóm tắt

Gallery+ là dự án nguồn mở giúp hiển thị danh sách ảnh trên Google+ đã kiểm duyệt phù hợp với #hashtag. API này được xây dựng bằng Go và thư viện Đóng. Phần phụ trợ chạy trên App Engine. Gallery+ được dùng trên trang web Giải thưởng nhiếp ảnh của Google để hiển thị thư viện ảnh gửi. Trong bài viết này, chúng tôi đã tìm hiểu các đoạn hấp dẫn của tập lệnh giao diện người dùng. Đồng nghiệp của tôi là Johan Euphrosine thuộc nhóm Quan hệ với nhà phát triển App Engine đang viết một bài viết thứ hai nói về ứng dụng phụ trợ. Phần phụ trợ được viết bằng Go, ngôn ngữ mới của Google được phía máy chủ. Vì vậy, nếu bạn muốn xem ví dụ về đoạn mã Go trong quá trình sản xuất, hãy chú ý theo dõi!

Tài liệu tham khảo