 |
|
블로그 편집기
XUL 개발 환경을 준비했으니 이제 XUL을 사용해 간단한 응용 프로그램을 개발할 차례다. 여기서는 블로그 게시물을 작성하고 미리 살펴보는 블로그 편집기를 구현한다. 게시물을 로컬 시스템에 저장했다가 나중에 불러오는 기능도 구현한다. 사용자 인터페이스는 XUL로 구현하고, 나머지 기능은 자바스크립트로 구현한다. 우선, 사용자 인터페이스부터 만들어보겠다.
블로그 편집기 사용자 인터페이스
대다수 응용 프로그램 개발자가 아주 싫어하는 작업이 인터페이스 구현이다. 흔히 사용자 인터페이스를 생성하는 작업은 지루하고 따분하지만, XUL을 사용하면 여러모로 수월하다. XUL은 위젯을 생성하고 레이아웃을 지정하는 기능이 뛰어나다. Listing 4는 아주 간단한 UI를 정의한다.
Listing 4. XUL로 정의한 UI(/chrome/xulblogger/home.xul)
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<xul:window id="xulblogger" title="Create Blog Entry" orient="horizontal"
align="start" xmlns="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<xul:script src="blog.js"/>
<xul:script src="json.js"/>
<xul:vbox>
<xul:hbox>
<xul:label value="Name of entry"/>
<xul:textbox id="name" multiline="false" cols="70"/>
</xul:hbox>
<xul:textbox id="entry" multiline="true" rows="20" cols="80"/>
<xul:hbox>
<xul:label value="Tags"/>
<xul:textbox id="tags" cols="80" multiline="false"/>
<xul:button id="saveBtn" class="btnClass" label="Save" />
<xul:button id="previewBtn" label="Preview" onclick="preview()"/>
</xul:hbox>
<div id="preview"></div>
</xul:vbox>
<xul:script>read();</xul:script>
</xul:window>
|
아주 간단한 UI다. XUL vbox와 hbox 컴포넌트를 사용하면 레이아웃을 잡기 쉽다. vbox는 컴포넌트를 수직으로 쌓고, hbox는 컴포넌트를 (왼쪽에서 오른쪽으로) 수평으로 쌓는다. 위 UI는 레이블 몇 개, 텍스트 상자 세 개(이 중 하나는 여러 행을 포함한다), 버튼 몇 개로 이루어진다. 실행한 모습은 그림 3과 같다.
그림 3. 블로그 편집기 UI
Listing 4에 preview라는 HTML div가 있다는 사실을 눈치챘을지도 모르겠다. 이것이 블로그 게시물을 미리 보는 HTML 영역이다. 사용자가 일반 HTML로 내용을 입력한 후 Preview 버튼을 클릭하면 최종 모양새가 표시된다. 그림 4는 미리 보기를 수행한 예다.
그림 4. 블로그 게시물 미리보기
편집기에 입력한 내용을 preview 영역에 HTML로 뿌리는 방법은 무엇일까? Listing 4에 있는 XUL 코드를 살펴보자. Preview 버튼을 클릭하면 preview() 함수가 호출된다. Listing 5는 blog.js 파일에 정의된 preview() 함수다.
Listing 5. preview() 함수
function preview(){
document.getElementById("preview").innerHTML =
document.getElementById("entry").value;
}
|
HTML/자바스크립트 프로그래밍 경험이 있다면 코드가 낯설지 않으리라. 특히 Ajax 개발 경험이 있다면 코드를 이해하기 한결 수월할 것이다. 이제까지 써오던 자바스크립트 방식 그대로다. ID로 엘리먼트를 찾은 다음, HTML 엘리먼트의 innerHTML 속성을 이용하여 HTML에다 삽입한다.
이름 공간
눈치챘을지도 모르지만, Listing 4는 이름 공간을 두 개 정의한다. 하나는 xul 이름 공간으로, XUL 파일에서 UI 컨트롤을 생성할 때 사용한다. 또 하나는 HTML 스키마를 가리키는 기본 이름 공간이다. 이는 대다수 XUL 파일에서 사용하는 방식과 반대다. 대개는 XUL 이름 공간이 기본이고 HTML 요소에 접두어를 붙인다. 하지만 여기서는 블로그 편집기가 HTML 코드도 입력을 받으므로 HTML 이름 공간을 기본으로 설정했다. 이름 공간을 반대로 설정한 후, 미리보기 영역에 뿌릴 내용을 (뿌리기 직전에) 분석하여 태그 앞에 html 접두어를 (혹은 원하는 접두어를) 붙여도 상관 없다.
응용 프로그램 스킨 만들기
현재는 블로그 편집기가 아주 평범하지만, 모양새를 멋지게 바꾸기는 아주 쉽다. 웹 페이지처럼 CSS만 정의하면 충분하다. 위젯에 class나 ID를 넣은 후 이를 사용하는 CSS selector를 작성하면 된다. 웹 페이지를 꾸밀 때와 똑같다.
블로그 게시물 저장하기
이제 로컬 파일 시스템에 블로그 게시물을 저장하겠다. 로컬 I/O를 수행하려면 자바스크립트를 사용한다. 그런데 자바스크립트에 익숙하다면 자바스크립트 자체에 이런 기능이 없다는 사실을 알 것이다. 바로 여기서 XPCOM을 사용한다. Listing 6을 살펴보자.
Listing 6. 자바스크립트로 로컬 I/O 수행하기
function save() {
try {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
} catch (e) {
alert("Permission to save file was denied.");
}
var file = Components.classes["@mozilla.org/file/local;1"]
.createInstance(Components.interfaces.nsILocalFile);
file.initWithPath( savefile );
if ( file.exists() == false ) {
file.create( Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 420 );
}
var outputStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
.createInstance( Components.interfaces.nsIFileOutputStream );
/* Open 플래그
#define PR_RDONLY 0x01
#define PR_WRONLY 0x02
#define PR_RDWR 0x04
#define PR_CREATE_FILE 0x08
#define PR_APPEND 0x10
#define PR_TRUNCATE 0x20
#define PR_SYNC 0x40
#define PR_EXCL 0x80
*/
/*
** 파일 모드 ....
**
** 주의: 'mode'는 현재 유닉스 플랫폼에만 적용이 가능하다.
** 다른 플랫폼에서 PR_Open에 넘어가는 'mode' 인수는 무시될지도 모른다.
**
** 00400 소유주 읽기
** 00200 소유주 쓰기
** 00100 소유수 수행(디렉터리라면 탐색)
** 00040 그룹 읽기
** 00020 그룹 쓰기
** 00010 그룹 수행
** 00004 다른 사람 읽기
** 00002 다른 사람 쓰기
** 00001 다른 사람 수행
**
*/
outputStream.init( file, 0x04 | 0x08 | 0x20, 420, 0 );
//var output = document.getElementById('blog').value;
var output = serialize();
var result = outputStream.write( output, output.length );
outputStream.close();
}
|
가장 먼저, XPConnect를 활성화한다. 그러면 XPConnect를 통해 XPCOM 컴포넌트를 사용할 수 있다. 이 경우 우리는 모질라의 org.file.local 클래스를 사용한다. 다음으로, 객체가 로컬에 있는 듯이 객체 메서드를 호출한다. 이 과정에서 serialize() 메서드를 호출한다는 사실에 주목한다. 이 메서드는 블로그 편집기에 입력한 문자열을 JSON 문자열로 직렬화한다. 코드는 Listing 7을 참조한다.
Listing 7. 자료 직렬화
function serialize(){
var name = document.getElementById("name").value;
var entry = document.getElementById("entry").value;
var tags = document.getElementById("tags").value;
var obj = { "name" : name, "entry" : entry, "tags" : tags"};
var str = obj.toJSONString();
return str;
}
|
여기서도 자바스크립트 DOM 기능을 사용하여 여러분이 만든 폼에서 정보를 추출한다. 그런 다음, 블로그 게시물로 저장할 세 가지 정보를 묶어 자바스크립트 객체를 만든다. json.org에서 제공하는 JSON 라이브러리를 사용하여 자바스크립트 객체를 JSON 문자열로 변환한다. 파일에는 이 문자열을 저장한다. 그렇다면 어느 파일에 저장할까? Listing 8은 저장할 파일을 결정하는 코드다.
Listing 8. 저장할 파일을 결정하는 코드
var savefile = "blogentry.txt";
try {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
} catch (e) {
alert("Permission to save file was denied.");
}
// 사용자 홈 (프로파일) 디렉터리에 대한 경로를 얻는다.
const DIR_SERVICE = new Components.Constructor("@mozilla.org/file/
directory_service;1","nsIProperties");
try {
path=(new DIR_SERVICE()).get("ProfD", Components.interfaces.nsIFile).path;
} catch (e) {
alert("error");
}
// 파일 구분자 결정
if (path.search(/\\/) != -1) {
path = path + "\\";
} else {
path = path + "/";
}
savefile = path+savefile;
|
위 코드는 사용자 홈 디렉터리를 찾아낸다. 따라서 블로그 편집기에서 저장하는 정보는 무조건 ~/blogentry.txt로 저장된다. 여기서도 마찬가지로 XPCOM을 통해 XUL 프레임워크에서 제공하는 풍부한 기능 중 몇 가지를 사용한다. 위 코드에서는 자료 저장 과정에서 잘못된 경로 때문에 일어나는 문제점을 피하기 위해 org.file.directory_service 클래스로 경로를 검증한다.
이제 정보를 디스크에 쓸 수 있다. 그렇다면 다시 읽어오는 방법은 무엇일까? Listing 4에서 눈치챘겠지만, 블로그 편집기를 시작하면 자바스크립트 함수인 read()가 호출된다. Listing 9는 read() 함수 소스 코드다.
Listing 9. read() 함수
function read() {
try {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
} catch (e) {
alert("Permission to read file was denied.");
}
var file = Components.classes["@mozilla.org/file/local;1"]
.createInstance(Components.interfaces.nsILocalFile);
file.initWithPath( savefile );
if ( file.exists() == false ) {
alert("File does not exist");
}
var is = Components.classes["@mozilla.org/network/file-input-stream;1"]
.createInstance( Components.interfaces.nsIFileInputStream );
is.init( file,0x01, 00004, null);
var sis = Components.classes["@mozilla.org/scriptableinputstream;1"]
.createInstance( Components.interfaces.nsIScriptableInputStream );
sis.init( is );
var output = sis.read( sis.available() );
deserialize(output);
}
|
마찬가지로 XPCConnect를 통해 XPCOM 컴포넌트인 org.file.local 클래스를 사용했다. 여기서는 컴포넌트가 제공하는 read API를 사용하여 Listing 6에서 저장했던 파일을 읽어들였다. 이번에는 deserialize() 메서드를 호출했는데, 소스 코드는 Listing 10을 참조한다.
Listing 10. deserialize() 함수
function deserialize(input){
var obj = input.parseJSON();
document.getElementById("name").value = obj.name;
document.getElementById("entry").value = obj.entry;
document.getElementById("tags").value = obj.tags;
}
|
이 함수 역시 JSON 라이브러리를 사용한다. 이번에는 파일에서 읽은 문자열을 자바스크립트 객체로 변환한다. 그런 다음, 객체 속성 값을 UI 컨트롤 값으로 설정한다.
블로그 게시물 게시하기
이제 우리 프로그램은 게시물을 로컬 디스크에 임시로 저장했다가 읽어들이고, 블로그 기사를 HTML 마크업으로 미리 보는 기능까지 제공한다. 다음 단계로 웹 서비스와 연결하여 블로그 게시물을 온라인에 게시하는 일이 남았다. 이를 위해 XPConnect와 XPCOM을 통해 XUL 프레임워크가 제공하는 네트워크 API를 사용한다. 만약 응용 프로그램이 전적으로 브라우저 안에서 돌아간다면 XMLHttpRequest를 사용해도 무방하다. 브라우저에서 돌아가는 자바스크립트라면 항상 XMLHttpRequest를 사용할 수 있다. 마찬가지로, XUL에서 돌아가는 자바스크립트도 항상 XMLHttpRequest를 사용할 수 있다. 구체적인 방법은 Listing 11을 참조한다.
Listing 11. XMLHttpRequest() 사용
var xhr = new XMLHttpRequest();
function publish(){
var url = "http://some.blogService.com/sendBlog"; // 당연히 변경해야 한다
var serializedEntry = serialize();
xhr.onreadystatechange = processResponse;
xhr.open("POST", url, true);
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
// 인증 헤더와 매개변수 설정
// 요청 전송
xhr.send(serializedEntry);
}
function processResponse(){
// check for readyState == 'loaded'
if (xhr.readyState == 4){
if (xhr.status == 200){
alert("Your blog entry was published!");
} else {
alert("Sorry there was an error");
}
}
}
|
Listing 11은 웹 페이지에서 Ajax를 호출하는 코드와 비슷하다는 사실에 주목한다. 가장 큰 차이점이라면, 여기서는 (IE, 사파리, 오페라, 파이어폭스 등) 브라우저 종류나 버전을 걱정할 필요가 없다는 점이다. 이 덕분에 XMLHttpRequest를 사용하기가 훨씬 수월하다.
기타 XUL 개발
지금까지 XUL을 사용하여 간단한 데스크톱 응용 프로그램을 작성했다. XUL은 웹 응용 프로그램 개발자에게 가능성이 무궁무진한 세상을 열어준다. 앞서 살펴본 데스크톱 응용 프로그램 외에도 좀 더 전문화된 개발 분야가 존재한다. 그 중 하나가 파이어폭스나 기타 모질라 기반 브라우저에 추가할 확장 기능을 구현하는 분야다.
파이어폭스 확장 기능 개발
가장 인기 있는 XUL 개발 분야 중 하나는 파이어폭스 확장 기능이다. XUL 데스크톱 응용 프로그램 개발과 파이어폭스 확장 기능 개발은 몇 가지 커다란 차이가 존재한다. 가장 중요한 차이점이라면, 확장 기능을 개발할 때는 설치 manifest 파일을 install.rdf라 부르고 응용 프로그램 디렉터리 루트에 두어야 한다는 점이다. 이 파일은 확장 기능과 관련하여 중요한 메타 정보를 저장한다. 확장 기능에 아이콘이 필요하다면, 아이콘을 생성한 후 /chrome/icons/default/ 아래 복사한다. 윈도우용 파일은 확장자가 .ico이고, 리눅스용 파일은 .xpm이다. 파일 이름은 메인 윈도우의 ID와 똑같아야 한다. 즉, 메인 윈도우 ID가 "xulBloggerMain"이라면 xulBloggerMain.ico와 xulBloggerMain.xpm이 필요하다.
XPCOM 개발
XUL 개발에 어느 정도 익숙해지면, XPCOM 컴포넌트를 직접 만들겠다는 욕심이 생길지도 모르겠다. XPCOM 컴포넌트를 만들려면 게코 SDK가 필요하다. 게코 SDK는 이진 파일로 내려받아도 좋고 소스를 내려받아 직접 빌드해도 좋다. SDK는 기본 게코 XPCOM 컴포넌트의 C++ 헤더 파일과 IDL 파일과 더불어 독자적인 XPCOM 컴포넌트를 만드는 명령행 도구를 제공한다.
|