2011년 7월 7일 목요일

HP webOS 3.0

HP webOS 3.0
sjinpark@samsung.com
박성진


webOS는 리눅스 커널위에서 작동하는 mobile operation system 이름이다. 처음에는 Palm에 의해서 개발이 되었고 후에 HP에 들어갔다.

SDK를 설치하면 webOS PDK, HP webOS SDK 두 개의 디렉토리가 생성되었다.
JavaScript development 와 C/C++ development 를 할 수 있다고 하는데, PDK는 GCC compiler, SDL and OpenGL code libraries, project templates, sample source code, scripts, utilities, documents 를 포함한 Plug-in Development Kit 라고 한다.

초기 설정

RSS를 읽어들이는 간단한 애플리케이션을 제작해 본다.
FeedReader라는 디렉토리를 생성하여 시작한다. index.html이라는 파일을 만들어주는데 내용은 아래와 같다.








new MyApps.FeedReader().renderInto(document.body);




는 애플리케이션 이름과 함께 이라는 태그를 포함하고 있다. 태그는 Enyo 프레임워크 파일의 위치이다.
안에는 새로운 FeedReader object를 초기화하기 위한 호출이다. 애플리케이션을 시작하기 전에 생성해야 할 몇가지 다른 파일들이 있다. index.html에 더해서 JSON 포맷의 애플리케이션 프로퍼티의 리스트를 FeedReader 디렉토리가 가지고 있어야 한다. (appinfo.json). framework 설정 셋팅은 (framework_config.json) 그리고 애플리케이션을 구성하는 모든 파일들의 리스트는 (depends.js)

appinfo.json 의 내용은 아래와 같다.
{
"id": "com.palm.feedreader",
"version": "1.0.0",
"vendor": "HP",
"type": "web",
"main": "index.html",
"title": "Enyo FeedReader",
"uiRevision": "2"
}

uiRevision 프로퍼티를 2로 셋팅했기 때문에 우리의 앱은 풀스크린 해상도로 TouchPad Emulator와 실제 하드웨어에서 작동할 것이다. 아래것은 framework_config.json 이다.
{
"logLevel": 99
}

logLeven을 99로 설정함으로써. enyo.log에서 가장 높은 로그를 하라고 할 수 있는데, 이것이 애플리케이션 개발시에는 적당하다. 릴리즈 할 때는 logLevel이 0이 되는것이 좋다.
마지막으로 이것은 depends.js의 내용이다.
enyo.depends(
"source/FeedReader.js",
"css/FeedReader.css"
);

여기서 depends.js 는 상당히 여유분인것 같다. 메인 애플리케이션 오브젝트(source/FeedReader.js)로의 패스와 애플리케이션 주 스타일 시트(css/FeedReader.css)로의 패스만을 담고 있다. 우리가 애플리케이션에 파일을 추가하면서 depends.js에 추가를 해주어야 한다.
enyo.kind({
name: "MyApps.FeedReader",
kind: enyo.VFlexBox,
components: [
]
});

FeedReader.js 는 MyApps.FeedReader 라는 것을 정의한다. 이것은 새로운 FeedReader 오브젝트들을 생성하기 위한 안내를 담고 있다. 프로퍼티는 FeedReader 오브젝트가 enyo.VFlexBox 로부터 상속되는 view라는 것을 말해준다. componentsproperty 는 우리가 FeedReader를 새롭게 초기화할 때 보여주고 싶은 UI 요소들을 담고 있다.

기본 UI

우리의 애플리케이션에 FeedReader 생성자의 컴포넌트 프로퍼티를 만들어서 UI 를 추가해 보자. FeedReader.js에서 header(PageHeader의 일종) 와 text 입력 상자를 만들어보자.
enyo.kind({
name: "MyApps.FeedReader",
kind: enyo.VFlexBox,
components: [
{kind: "PageHeader", content: "Enyo FeedReader"},
{kind: "RowGroup", caption: "Feed URL", components: [
{kind: "Input", components: [
{kind: "Button", caption: "Get Feed", onclick: "btnClick"},
]}
]}
],
btnClick: function() {
// handle the button click
}
});

Enyo의 좋은 점은 애플리케이션 개발 프로세스가 모두 웹브라우저 안에서 일어나도록 해준다는 것이다. 예를 들어, index.html 파일을 WebKit기반 브라우저에서 열어봄으로써 애플리케이션의 모양을 확인할 수 있다. 이것은 애플리케이션을 실제 기기나 에뮬레이터에 올려보지 않고서도 할 수 있는 것이다.
시큐리티 제한 때문에 튜토리얼 앱을 크롬에서 개발하기로 했다면 "--allow-file-access-from-files” 라는 커맨드 라인 스위치를 해서 브라우저를 런치해야 한다.
윈도우에서 chrome.exe로의 바로가기를 만들어서 이것을 할 수 있고 바로가기의 Target property에 스위치를 추가한다. 그리고 나서 브라우저를 띄울 때 마다 바로가기를 사용한다. Mac이나 Linux에서도 비슷하다.
여기에 현재 우리의 애플리케이션 상태가 보인다.
소스 코드를 보면 텍스트 입력 상자가 입력 오브젝트이고 Button 오브젝트를 컴포넌트로 가진다는 것을 볼 수 있다. RowGroup 안에 입력을 가져서 FEED URL 텍스트와 함께 좋은 테두리를 보여준다.
실행시에 사용자가 URL을 입력하고 Get Feed button을 입력하면 우리는 특정한 행동이 실행되기를 바란다. Enyo에서 button을 누르는 것은 클릭 이벤트를 만들고 우리가 이벤트를 다루기 위한 기능을 정의해야 한다. 기능의 이름은 Buttonobject의 “onclick”에 명시되어 있고 기능 자체는 이름, 종류, 컴포넌트등과 같은 레벨로 FeedReader의 프로퍼티로 정의되어 있다.
이제 실제 feed date를 받도록 해보자.

서비스 사용하기

RSS데이터를 원격에 있는 서버로부터 가져오기 위해서 우리는 우리의 고유 서비스를 정의하겠다. webOS에서 서비스는 비동기적인 요청을 로컬이나 원격 서버에 보내기 위해 만들어진다.
샘플 애플리케이션에서 우리는 WebService를 사용할 것이다. 우리는 우리의 서비스를 FeedReader.js의 컴포넌트 블럭으로 선언한다.
components: [
{name: "getFeed", kind: "WebService",
onSuccess: "gotFeed",
onFailure: "gotFeedFailure"},
{kind: "PageHeader", content: "Enyo FeedReader"},
{kind: "RowGroup", caption: "Feed URL", components: [
{kind: "Input",
components: [
{kind: "Button", caption: "Get Feed", onclick: "btnClick"}
],
value: "http://feeds.bbci.co.uk/news/rss.xml"
}
]}
],
선언은 서비스의 이름을 포함하고 종류, 메서드 이름 등 불릴것이 선언되어 있어서 우리의 비동기적인 요청이 성공하거나 실패한다.
우리가 Get Feed 버튼을 누르기 원해서 비동기 요청을 보내고 요청은 btnClick 안에 코드가 있을것이다.
btnClick: function() {
var url = "http://query.yahooapis.com/v1/public/yql?q=select"
+ "%20title%2C%20description%20from%20rss%20where%20url%3D%22"
+ this.$.input.getValue() + "%22&format=json&callback=";
this.$.getFeed.setUrl(url);
this.$.getFeed.call();
},
getFeed WebService 는 URL 프로퍼티를 셋팅해야 한다. 이 케이스에서 URL값은 실제로 공개 Yahoo API의 위치이고 RSS 서버를 쿼리하고 타이틀과 데이터를 JSON 포맷으로 반환한다.
달러 사인은 여기있는 모든 컴포넌트들의 해쉬를 나타낸다. 각 컴포넌트는 이름으로 접근할 수 있다.
텍스트 입력 상자는 theInput의 이름을 할당하지 않아도 this.$.input 으로 접근할 수 있다. 입력값은 Enyo에 의해서 자동으로 생성이된다. “mySuperInput” 이라는 입력에 대한 이름을 주면 this.$.mySuperInput 으로 가용해진다.
여기서 우리의 비동기 요청을 보내기 위한 것을 추가한다. 궁극적으로 물론 반환된 데이터를 파싱하고 보여주어야 한다. 그러나 요청이 성공했는지 실패했는지 검사한다.
우리가 우리의 입력에 값 프로퍼티를 추가했다는 것을 보라.
value: "http://feeds.bbci.co.uk/news/rss.xml"

이것은 BBC로부터 뉴스를 가져옴으로써 우리의 테스트를 URL과 함께 처리한다. 우리는 여기서 빠른 WebService 호출의 성공 실패를 defininggotFeed 와 gotFeedFailure를 통해서 볼 수 있다.
gotFeed: function(inSender, inResponse) {
this.$.button.setCaption("Success");
},
gotFeedFailure: function(inSender, inResponse) {
this.$.button.setCaption("Failure");
}

캡션은 Button 종류의 공개된 프로퍼티이므로 Enyo는 자동으로 getter와 setter 메서드를 그것을 위해 생성한다. 그래서 우리는 setCaption을 메서드 선언 없이 부를수 있다. 이제 service 호출이 완료되었을때 Get Feed 버튼의 텍스트는 "성공"이나 "실패"를 읽기 위해 업데이트된다. 당신은 브라우저에서 요청이 성공했는지 볼 수 있다. 여기서 우리의 FeedReader가 반환된 값을 보여주기 위한 UI가 없어서 그것이 우리의 다음 주제이다.

데이터 보여주기

RSS 요청을 보여주기 위해서 반환된 데이터를 두기 위한 곳이 필요하다. FeedReader.js에 생성 메서드를 두어 VFlexBox에서 상속된 createmethod의 동작을 오버라이드 한다.
create: function() {
this.inherited(arguments);
this.results = [];
}
this.inherited(arguments) 호출은 우리 superkind, VFlexBox의 메서드를 생성한다. 우리는 여기서 결과를 담을 비어있는 array를 생성한다. 생성자 메서드가 오브젝트가 초기화 될 때마다 호출 되어서 new FeedReader 오브젝트의 this.result 에 접근할수 있다. 이제 gotFeed를 변경하여 this.result에 실제 결과 데이터를 담도록 한다. gotFeedFailure 를 변경하여 실패했을때 사려깊게 행동하도록 한다.
gotFeed: function(inSender, inResponse) {
this.results = inResponse.query.results.item;
},
gotFeedFailure: function(inSender, inResponse) {
enyo.log("got failure from getFeed");
}

실제 데이터의 보여주기는 새로운 UI 컴포넌트에 의한다. FeedReader의 컴포넌트 블럭에 이것을 추가하고 이것과 같을 것이다.
components: [
{name: "getFeed", kind: "WebService",
onSuccess: "gotFeed",
onFailure: "gotFeedFailure"},
{kind: "PageHeader", content: "Enyo FeedReader"},
{kind: "RowGroup", caption: "Feed URL", components: [
{kind: "Input",
components: [
{kind: "Button", caption: "Get Feed", onclick: "btnClick"}
],
value: "http://feeds.bbci.co.uk/news/rss.xml"
}
]},
{kind: "Scroller", flex: 1, components: [
{name: "list", kind: "VirtualRepeater", onSetupRow: "getListItem",
components: [
{kind: "Item", layoutKind: "VFlexLayout", components: [
{name: "title", kind: "Divider"},
{name: "description"}
]}
]
}
]}
]
우리 새 Scroller 컨트롤의 핵심은 VirtualRepeater이고 이것은 Itemobjects의 값을 가지고 있다. 우리가 데이터를 보일때 Item 정의는 간단한 포맷으로 보여진다. 새 Item 오브젝트가 VirtualRepeater에 추가될 때 newgetListItem을 호출할 수 있다. 이것은 타이틀과 값의 묘사를 채운다.
getListItem: function(inSender, inIndex) {
var r = this.results[inIndex];
if (r) {
this.$.title.setCaption(r.title);
this.$.description.setContent(r.description);
return true;
}
}

기술적인 용어로 Scroller는 높이가 고정된 viewpoint를 가지고 넓은 범위를 보여준다. 실제적으로 말하면 Scroller는 당신이 예상하는 것을 한다. Scroller 콘트롤러의 스크린 높이에 너무 많은 것을 나타낼때는 ourVirtualRepeater에 스크롤을 할 수 있게 한다. flexproperty는 FeedReaderVFlexBox의 아래까지 가능한 공간을 Scroller가 채워야 하는지 보여준다.
우리의 UI에 결과 데이터를 보여주기 위해 마지막으로 해야할것있다. theVirtualRepeater를 모든 아이템을 거기 추가한 후에 다시 그린다. 여기서 해야 할 것은 gotFeed아래에 새로운 한줄의 코드추가 이다.
gotFeed: function(inSender, inResponse) {
this.results = inResponse.query.results.item;
this.$.list.render();
}

이제 우리가 해온 것을 본다.

여러개의 View 로 일하기

오늘날 전형적인 RSS 피드 스토리는 전체 기사를 읽기 위한 헤드라인이나 한 두줄 짜리 기사만 담고 있어서 웹사이트를 방문해야 한다. 여기서 원래의 전체 기사를 읽게 하기 위하여 사용자가 클릭 가능하게 함으로써 우리의 애플리케이션을 더 유용하게 만들어보자. 이것을 하기 위해 두번째 view를 사용한다. 우리의 이미 존재하는 view에 이어 원래 기사를 보여주는 view. 그리고 검색 상자와 검색 결과도 보여준다. 우리는 또한 이 기회를 빌어 검색 관련 코드를 FeedReader.js에서 리팩터링한다. 이 리팩터링은 우리의 코드를 읽고 이해하기 쉽게 할 뿐만아니라 Enyo 의 디자인 골을 구현한다. 그것은 데이터의 인캡슐레이션이다. 인캡슐레이션에 의해서 애플리케이션 오브젝트가 처리해야 할 데이터의 자세한 것을 알 필요없이 데이터 처리 오브젝트의 코디네이터로 작동하게 한다.
Search.js

Here are the contents of the new file, Search.js:
enyo.kind({
name: "MyApps.Search",
kind: enyo.VFlexBox,
events: {
onLinkClick: "",
onSelect: ""
},
components: [
{name: "getFeed", kind: "WebService",
onSuccess: "gotFeed",
onFailure: "gotFeedFailure"},
{kind: "RowGroup", caption: "Feed URL", components: [
{kind: "Input",
components: [
{kind: "Button", caption: "Get Feed", onclick: "btnClick"}
],
value: "http://feeds.bbci.co.uk/news/rss.xml"
}
]},
{kind: "Scroller", flex: 1, components: [
{name: "list", kind: "VirtualRepeater", onSetupRow: "getListItem",
components: [
{kind: "Item", layoutKind: "VFlexLayout",
components: [
{name: "title", kind: "Divider"},
{name: "description", kind: "HtmlContent",
onLinkClick: "doLinkClick"}
],
onclick: "listItemClick"
}
]
}
]}
],
create: function() {
this.inherited(arguments);
this.results = [];
},
btnClick: function() {
var url = "http://query.yahooapis.com/v1/public/yql?q=select%20"
+ "title%2C%20description%2C%20link%20from%20rss%20where%20url%3D%22"
+ this.$.input.getValue() + "%22&format=json&callback=";
this.$.getFeed.setUrl(url);
this.$.getFeed.call();
},
getListItem: function(inSender, inIndex) {
var r = this.results[inIndex];
if (r) {
this.$.title.setCaption(r.title);
this.$.description.setContent(r.description);
return true;
}
},
gotFeed: function(inSender, inResponse) {
this.results = inResponse.query.results.item;
this.$.list.render();
},
gotFeedFailure: function(inSender, inResponse) {
enyo.log("got failure from getFeed");
},
listItemClick: function(inSender, inEvent) {
var feed = this.results[inEvent.rowIndex];
this.doSelect(feed);
}
});

익순한 사람은 이것이 말그대로 이전 버전의 FeedReader.js에서 온 것임을 알것이다. 새로운 기능을 위해서 바뀐 부분을 살펴본다.
추가적인 feed데이터를 가져오기 위해 변경이 되었다. btnClick에서 Yahoo API 쿼리를 확장하였다.
둘째로 이벤트 관련 코드를 추가하였다. Item 오브젝트를 예로 들면 onclick 핸들러가 있다. Item과 클릭 이벤트가 일어나면 listItemClick 메서드를 부른다.
다음으로 Item 컴포넌트 블럭에서 몇가지 변경을 RSS피드가 늘어나고 하이퍼링크를 내장하기 때문에 바뀌었다. 이제 그 기술은 HtmlContent의 오브젝트로 정의되고 onLinkClick이벤트에 핸들러에 할당하였다.
onLinkClick 문장 : "doLinkClick”은 onLinkClick을 HtmlContent의 주인에게 포워딩하는 빠른 길이다.
마지막으로 onLinkClick과 onSelect가 이벤트 블럭에 정의되어 있다. onSelect이벤트는 this.doSelect 으로 쏘아진다.
FeedReader.js
New event-related code can also be found in FeedReader.js, which now looks like this:
enyo.kind({
name: "MyApps.FeedReader",
kind: enyo.VFlexBox,
components: [
{kind: "PageHeader", components: [
{kind: enyo.VFlexBox, content: "Enyo FeedReader", flex: 1},
{name: "backButton", kind: "Button", content: "Back", onclick: "goBack"}
]},
{name: "pane", kind: "Pane", flex: 1, onSelectView: "viewSelected",
components: [
{name: "search", className: "enyo-bg", kind: "MyApps.Search",
onSelect: "feedSelected", onLinkClick: "linkClicked"},
{name: "detail", className: "enyo-bg", kind: "Scroller",
components: [
{name: "webView", kind: "WebView", className: "enyo-view"}
]
}
]
}
],
create: function() {
this.inherited(arguments);
this.$.pane.selectViewByName("search");
},
feedSelected: function(inSender, inFeed) {
this.$.pane.selectViewByName("detail");
this.$.webView.setUrl(inFeed.link);
},
linkClicked: function(inSender, inUrl) {
this.$.webView.setUrl(inUrl);
this.$.pane.selectViewByName("detail");
},
viewSelected: function(inSender, inView) {
if (inView == this.$.search) {
this.$.webView.setUrl("");
this.$.backButton.hide();
} else if (inView == this.$.detail) {
this.$.backButton.show();
}
},
goBack: function(inSender, inEvent) {
this.$.pane.back(inEvent);
}
});
검색 view는 이제 Pane 오브젝트의 view가 되었다. 우리의 두번째 view 같은 pane의 컴포넌트로 정의되었다. CSS 클래스 “enyo-bg” 안의 view와 styled는 어떤 것이 현재 보이는지 this.$.pane.selectViewByName 으로 조정된다. FeedReader가 초기화 되었을 때 검색 view가 골라진다. 사용자가 이야기를 클릭했을 때 자세한 view가 선택된다. 사용자가 자세한 view에서 검색 view로 옮기도록 하기위해 Backbutton을 추가하였다. 이것이 눌려졌을때 goBack을 호출한다.
FeedReader.js에 있는 많은 코드가 Search.js로 옮겨졌다. FeedReader가 view 관리를 위해 새로운 네가지 메서드를 얻었다.
The goBack method, as we've just seen, responds to clicks of the Back button.
The feedSelected method is called when FeedReader receives an onSelect event from the search view. The overall sequence of events is as follows:
In the search view, a list item (feed story) is clicked, triggering a call to Search.listItemClick.
Search.listItemClick generates an onSelect event.
FeedReader detects the onSelect event and calls FeedReader.feedSelected.
FeedReader.feedSelected makes the detail view active and sets the URL for the WebView (named"webView") inside the detail view.
Similarly, the linkClicked method sets the URL for webView in response to onLinkClick events. (With respect to encapsulation, note that while FeedReader receives object data from both onSelect andonLinkClick events, those objects are not processed here, but are passed right back to the detail view.)
The viewSelected method is the handler for onSelectView events generated by this.$.pane. If we're navigating to the search view, viewSelected clears the contents of webView in preparation for the next time the detail view is displayed. It also hides the Back button, since that button isn't needed in this view. If we're navigating to the detail view, viewSelected makes the Back button visible


depends.js
There's one more thing we need to do to get this all to work--update depends.js to reflect the presence of the new Search.js file:
enyo.depends(
"source/FeedReader.js",
"source/Search.js",
"css/FeedReader.css"
);







Enyo Platform
Enyo의 기본이 되는 오브젝트는 Control이다. 이것은 DOM 노드처럼 동작한다. 사실은 각 control은 노드들로 바로 변환된다.
작은 control을 만들고 문서 바디에 뿌려주는 코드이다.
enyo.create({
content: "Hello World"
}).renderInto(document.body);

이것은 아래 HTML을 생성한다.
Hello World

control에 소스 작업을 하는것이 HTML을 다루는 것보다 이득이다. 더 간단하고 직관적임.
enyo.create({
components: [
// button with custom graphics
{kind: "Button"},
// input box with special features like hinting and graphic fx
{kind: "FancyInput"},
// one-of-many selector with custom graphics
{kind: "RadioGroup", components: [
{label: "Alpha"},
{label: "Beta"},
{label: "Gamma"}
]}
]
}).renderInto(document.body);

기본적인 자바 스크립트 사용예다.
// an object constructor
MyObject = function() {
this.data = [];
};

MyObject.prototype.toString = function() {
return this.data.join(", ");
};

// another object constructor, built on the first one
MySpecialObject = function() {
MyObject.apply(this, arguments);
};

MySpecialObject.prototype = new MyObject();

MySpecialObject.prototype.toNumber = function() {
return this.data.length;
};

// Make an instance
mso = new MySpecialObject();

객체 지향에 맞추기 위해 Enyo는 constructor를 만드는 메서드를 제공한다. constructor는 어떤 특별한 kinds 라는 것을 가지고 있다. kind를 생성하기 위한 메서드는 enyo.kind이다. 여기 enyo.kind의 예이다.

// a kind
enyo.kind({
name: "MyKind",
constructor: function() {
this.data = [];
},
toString: function() {
return this.data.join(", ");
}
});

// another kind, built on the first one
enyo.kind({
name: "MySpecialKind",
kind: "MyKind",
toNumber: function() {
return this.data.length;
}
});

// Make an instance
msk = new MySpecialKind();

이 예제에서 주의할 몇 가지이다.
The name of the kind is specified inside of the property block. This name will become a global variable that references the kind. Putting the name inside the block gives you an easy way to use namespacing. For example, say you write:
enyo.kind({name: "Super.Special.Kind"});

The namespaces Super and Super.Special will be created for you, and Super.Special.Kind will reference the new constructor.
Initialization code is placed in a special method called constructor. This is very similar to the body of theMyObject function in the first example. The main difference is that the constructor method is not called when inheriting from a kind (if you look closely at the first example, you can see that MyObject is called to create the prototype for MySpecialObject, which ends up creating an extraneous data array in theMySpecialObject prototype.)
To make a new kind that inherits from an old one, specify the old one's name in the new one's kindproperty. In the example, MySpecialKind is based on MyKind.

All these kinds may start to sound confusing, but it all boils down to one simple idea: whenever we make something, whether a constructor or an instance, we say what kind it's based on. When creating an instance, for example, we might do this:
enyo.create({kind: "aKind"});

(Note: The input for enyo.create is a JavaScript object that describes the object to create. This kind of input is sometimes called a "property block" or "property bag".)
Similarly, to make a new kind based on an existing kind, we could do this:
enyo.kind({kind: "aKind"});

This consistency makes the syntax easy to remember. It's turtles all the way down.

댓글 없음:

댓글 쓰기