การสร้างแกลเลอรี Google Photography Prize
เมื่อเร็วๆ นี้เราได้เปิดตัวส่วนแกลเลอรีในเว็บไซต์ Google Photography Prize แกลเลอรีจะแสดงรายการรูปภาพที่เลื่อนได้ไม่รู้จบซึ่งดึงมาจาก Google+ โดยจะดึงรายการรูปภาพจากแอป AppEngine ที่เราใช้เพื่อตรวจสอบรายการรูปภาพในแกลเลอรี นอกจากนี้ เรายังเปิดตัวแอปแกลเลอรีเป็นโปรเจ็กต์โอเพนซอร์สใน Google Code ด้วย
แบ็กเอนด์ของแกลเลอรีคือแอป AppEngine ที่ใช้ Google+ API เพื่อค้นหาโพสต์ที่มีแฮชแท็ก 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") เราใช้คอมโพเนนต์ goog.net.Jsonp ในไลบรารี Closure เพื่อจัดการกับ JSONP
สคริปต์แกลเลอรีจะตรวจสอบรายการรูปภาพและสร้างองค์ประกอบ 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 ได้ด้วยออบเจ็กต์พารามิเตอร์การค้นหาและฟังก์ชัน 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);
};
ตามที่กล่าวไว้ข้างต้น สคริปต์แกลเลอรีใช้คอมไพเลอร์ Closure เพื่อคอมไพล์และลดขนาดโค้ด นอกจากนี้ คอมไพเลอร์ Closure ยังมีประโยชน์ในการบังคับใช้การพิมพ์ที่ถูกต้อง (คุณใช้สัญกรณ์ JSDoc @type foo ในความคิดเห็นเพื่อตั้งค่าประเภทของพร็อพเพอร์ตี้) และยังบอกคุณเมื่อคุณไม่มีความคิดเห็นสำหรับเมธอดด้วย
Unit Test
นอกจากนี้ เรายังต้องการการทำ Unit Test สำหรับสคริปต์แกลเลอรีด้วย ดังนั้นจึงเป็นเรื่องดีที่ไลบรารี Closure มีเฟรมเวิร์กการทำ Unit Test ในตัว ซึ่งเป็นไปตามรูปแบบ jsUnit จึงเริ่มต้นใช้งานได้ง่าย
เพื่อช่วยฉันเขียน Unit Test ฉันจึงเขียนสคริปต์ Ruby ขนาดเล็กที่แยกวิเคราะห์ไฟล์ JavaScript และสร้าง Unit Test ที่ไม่สำเร็จสำหรับแต่ละเมธอดและพร็อพเพอร์ตี้ในคอมโพเนนต์แกลเลอรี เมื่อมีสคริปต์ดังนี้
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 Photography Prize เพื่อแสดงแกลเลอรีที่ส่งเข้ามา ในบทความนี้ เราได้อธิบายส่วนสำคัญของสคริปต์ฟรอนท์เอนด์ไปแล้ว Johan Euphrosine เพื่อนร่วมงานของฉันจากทีมความสัมพันธ์กับนักพัฒนาแอป App Engine กำลังเขียนบทความที่ 2 เกี่ยวกับแอปแบ็กเอนด์ โดยแบ็กเอนด์เขียนด้วย Go ซึ่งเป็นภาษาฝั่งเซิร์ฟเวอร์ใหม่ของ Google ดังนั้นหากคุณสนใจดูตัวอย่างการใช้งานจริงของโค้ด Go โปรดติดตามตอนต่อไป
ข้อมูลอ้างอิง
- Google Photography Prize
- หน้าโปรเจ็กต์ Gallery+
- Closure library
- Closure compiler