Google 사진상 갤러리 만들기
최근 Google 사진상 사이트에 갤러리 섹션이 출시되었습니다. 갤러리에는 Google+에서 가져온 사진의 무한 스크롤 목록이 표시됩니다. 갤러리의 사진 목록을 검토하는 데 사용하는 AppEngine 앱에서 사진 목록을 가져옵니다. 또한 갤러리 앱을 Google Code에서 오픈소스 프로젝트로 출시했습니다.
갤러리의 백엔드는 Google+ API를 사용하여 Google 사진상 해시태그 (예: #megpp 및 #travelgpp) 중 하나가 포함된 게시물을 검색하는 AppEngine 앱입니다. 그러면 앱에서 이러한 게시물을 검토되지 않은 사진 목록에 추가합니다. Google의 콘텐츠팀은 일주일에 한 번 검토되지 않은 사진 목록을 검토하고 Google 콘텐츠 가이드라인을 위반하는 사진을 신고합니다. '검토' 버튼을 누르면 신고되지 않은 사진이 갤러리 페이지에 표시되는 사진 목록에 추가됩니다.
갤러리 프런트엔드
갤러리 프런트엔드는 Google Closure 라이브러리를 사용하여 빌드됩니다. 갤러리 위젯 자체는 폐쇄 구성요소입니다. 소스 파일 상단에서 이 파일이 photographyPrize.Gallery
라는 구성요소를 제공하고 앱에서 사용하는 Closure 라이브러리의 일부가 필요하다고 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');
갤러리 페이지에는 JSONP를 사용하여 AppEngine 앱에서 사진 목록을 가져오는 JavaScript가 있습니다. JSONP는 jsonpcallback("responseValue")
와 같은 스크립트를 삽입하는 간단한 교차 출처 JavaScript 해킹입니다. JSONP 항목을 처리하기 위해 Closure 라이브러리의 goog.net.Jsonp
구성요소를 사용합니다.
갤러리 스크립트는 사진 목록을 살펴보고 갤러리 페이지에 표시할 HTML 요소를 생성합니다. 무한 스크롤은 창 스크롤 이벤트에 연결하고 창 스크롤이 페이지 하단에 가까워지면 새 사진 배치를 로드하는 방식으로 작동합니다. 새 사진 목록 세그먼트를 로드한 후 갤러리 스크립트는 사진의 요소를 만들고 갤러리 요소에 추가하여 표시합니다.
이미지 목록 표시
이미지 목록 표시 메서드는 매우 기본적인 내용입니다. 이미지 목록을 살펴보고 HTML 요소와 좋아요 버튼을 생성합니다. 다음 단계는 생성된 목록 세그먼트를 갤러리의 기본 갤러리 요소에 추가하는 것입니다. 아래 코드에서 일부 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 규칙을 따르므로 쉽게 시작할 수 있습니다.
단위 테스트를 작성하는 데 도움이 되도록 JavaScript 파일을 파싱하고 갤러리 구성요소의 각 메서드와 속성에 대해 실패하는 단위 테스트를 생성하는 작은 Ruby 스크립트를 작성했습니다. 다음과 같은 스크립트가 주어집니다.
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 개발자 관계팀의 동료인 요하네스 에우프로시네가 백엔드 앱에 관한 두 번째 도움말을 작성하고 있습니다. 백엔드는 Google의 새로운 서버 측 언어인 Go로 작성됩니다. Go 코드의 프로덕션 예시를 확인하고 싶다면 계속 지켜봐 주세요.
참조
- Google 사진상
- Gallery+ 프로젝트 페이지
- 종료 라이브러리
- 종료 컴파일러