블로그 이미지
개발자로서 현장에서 일하면서 새로 접하는 기술들이나 알게된 정보 등을 정리하기 위한 블로그입니다. 운 좋게 미국에서 큰 회사들의 프로젝트에서 컬설턴트로 일하고 있어서 새로운 기술들을 접할 기회가 많이 있습니다. 미국의 IT 프로젝트에서 사용되는 툴들에 대해 많은 분들과 정보를 공유하고 싶습니다.
솔웅

최근에 받은 트랙백

글 보관함

calendar

          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31            

Sencha Touch 1.x 관련 메모

2013.10.01 11:35 | Posted by 솔웅


* 앱 시작하기


Ext.setup({
    tabletStartupScreen: 'tablet_startup.png',
    phoneStartupScreen: 'phone_startup.png',
    icon: 'icon.png',
    glossOnIcon: false,
    statusBarStyle: 'default',
 
onReady: function(){
new Ext.Panel(
{
    fullscreen: true,
    html: 'Hello Sencha Touch'
}
);
}
});



new Ext.Application({
launch: function() {
new Ext.Panel(
{
fullscreen: true,
html: ‘Hello Sencha Touch’
}
);
}
});


MVC - Ext.Application




* Theme : Sass (Ruby) - CSS를 프로그래밍하듯이 작성할 수 있음
  Sass 내장함수 : http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html
  > sass filename.scss filename.css
  > css2sass filename.css filename.scss
  > sass -style compressed filename.scss filename.css



* Compass : Sass 를 좀 더 편리하게 개발할 수 있도록 함 http://compass-style.org/



* 설치
  - Ruby 설치 http://www.ruby-lang.org/ko/downloads/
  - Sass & Compass 설치
    > gem install haml
    > gem install compass


* config.rb : 컴파일 옵션



* resources/themes/stylesheets/sencha-touch/default/_variables.scss
  $base-color : 앱의 기본 색상 정의
  $base-gradient : ‘matte’, ‘glossy’, ‘flat’, ‘bevel’, ‘recessed’ etc.
  > compass compile sencha-touch.scss
  > compass -watch



* Ext.Panel



Ext.setup({
onReady: function(){
new Ext.Panel({          Panel 객체 생성, <dev>요소로 DOM에 추가 됨
fullscreen: true,        전체 화면
style: "...",          CSS 지정
layout: { ... },         레이아웃 정보
items: [ { ... }, { ... }, ... ],         UI 아이템들 지정
dockedItems: [ { ... }, { ... }, ... ]         도킹 아이템 지정. Panel의 상하좌우에 밀착된 아이템
});
}
});



* dockedItems


dockedItems: [
{
dock: "top",
style: "background-color:red;",
html: "DockItem-Top"
},
{
dock: "left",
style: "background-color:gray;",
html: "DockItem-Left"
},
{
dock: "bottom",
style: "background-color:yellow;",
html: "DockItem-Bottom"
},
{
dock: "right",
style: "background-color:green;",
html: "DockItem-Right"
}
}



* layout


layout: {
type: 'vbox',
align: 'stretch',
pack: 'center'
},



* items : 객체 생성


items:[
new Ext.Button({ text: ‘button1’ })
]



* Ext.TabPanel


Ext.setup({
onReady: function(){
var item1 = {
title: 'Tab1',
style: "background-color: red;",
html: 'Item1'
};
var item2 = {
title: 'Tab2',
style: "background-color: gray;",
html: 'Item2'
};
var item3 = {
title: 'Tab3',
style: "background-color: yellow;",
html: 'Item3'
};
var panel = new Ext.TabPanel({
fullscreen: true,
ui: 'dark',
tabBarDock: 'top',
cardSwitchAnimation: 'slide',
items: [item1,item2,item3]
});
}
});



* Ext.Carousel


Ext.setup({
onReady: function(){
var item1 = {
style: "background-color:red;",
html: 'Item1'
};
var item2 = {
style: "background-color:gray;",
html: 'Item2'
};
var item3 = {
style: "background-color:yellow;",
html: 'Item3'
};
var panel = new Ext.Carousel({
fullscreen: true,
ui: 'dark',
indicator: true,
direction: 'horizontal',
items: [item1,item2,item3]
});
}
});


* Multi Carousel


Ext.setup({
onReady: function(){
var item1 = {
style: "background-color:red;",
html: 'Item1'
};
var item2 = {
style: "background-color:gray;",
html: 'Item2'
};
var item3 = {
style: "background-color:yellow;",
html: 'Item3'
};
var topCarousel = new Ext.Carousel({
items: [item1,item2,item3]
});
var middleCarousel = new Ext.Carousel({
items: [item1,item2,item3]
});
var bottomCarousel = new Ext.Carousel({
items: [item1,item2,item3]
});
var panel = new Ext.Panel({
fullscreen: true,
layout:{
type: 'vbox',
align: 'stretch'
},
defaults: {flex: 1},
items: [topCarousel, middleCarousel, bottomCarousel]
});
}
});



* Ext.Toolbar


Ext.setup({
onReady: function(){
var panel = new Ext.Panel({
fullscreen: true,
dockedItems: [{
xtype: 'toolbar',
dock: 'top',
title: 'My Toolbar',
defaults: {
xtype: 'button'
},
items: [
{ ui: 'decline-round', text:'decline-round' }
]
}]
});
}
});



* Ext.Button


Ext.setup({
onReady: function(){
new Ext.Panel({
fullscreen: true,
layout: {
type: 'vbox',
align: 'start'
},
defaults: {
layout: { type: 'hbox' },
flex:1,
defaults: {
xtype: 'button'
}
},
items:[
{items: [
{ ui: 'normal', text:'Normal',
handler: function() {
Ext.Msg.alert('Message', 'Hi~~', Ext.emptyFn);
}
},
{ui: 'round', text:'Round'},
{ui: 'small', text:'Small'}
]},
{items: [
{ui: 'decline', text:'Drastic'},
{ui: 'decline-round', text:'Round'},
{ui: 'decline-small', text:'Small'}
]},
{items: [
{ui: 'confirm', text:'Confirm'},
{ui: 'confirm-round', text:'Round'},
{ui: 'confirm-small', text:'Small'}
]},
{items: [
{ui: 'action', text:'Action'},
{ui: 'action-round', text:'Round'},
{ui: 'action-small', text:'Small'}
]},
{items: [
{ui: 'back', text:'back'},
{ui: 'Forward', text:'Forward'}
]}
]
});
}
});



* SegmentedButton


Ext.setup({
onReady: function(){
var segmentedButton = [{
xtype: 'segmentedbutton',
allowMultiple: false,
allowDepress: false,
items: [
{ text: 'Toggle 1' },
{ text: 'Toggle 2', pressed : true },
{ text: 'Toggle 3' }
],
listeners : {
toggle : function(container, button, active){
Ext.Msg.alert('Tap', button.text + " : " + (active ? 'on' : 'off'), Ext.emptyFn);
}
}
}];
new Ext.Panel({
fullscreen: true,
dockedItems:{
xtype: 'toolbar',
ui: 'light',
items: [segmentedButton]
}
});
}
});



* iconCls


Ext.setup({
onReady: function(){
var item1 = { title: 'info', iconCls: 'info', badgeText:'2',};
var item2 = { title: 'favorites', iconCls: 'favorites' };
var item3 = { title: 'settings', iconCls: 'settings' };
var item4 = { title: 'bookmarks', iconCls: 'bookmarks' };
var item5 = { title: 'download', iconCls: 'download' };
var item6 = { title: 'more', iconCls: 'more' };
var item7 = { title: 'search', iconCls: 'search' };
var item8 = { title: 'team', iconCls: 'team' };
var item9 = { title: 'time', iconCls: 'time' };
var item10 = { title: 'user', iconCls: 'user' };
var item11 = { title: 'action', iconCls: 'action' };
var item12 = { title: 'add', iconCls: 'add' };
var item13 = { title: 'arrow_up', iconCls: 'arrow_up' };
var item14 = { title: 'arrow_right', iconCls: 'arrow_right' };
var item15 = { title: 'arrow_down', iconCls: 'arrow_down' };
var item16 = { title: 'arrow_left', iconCls: 'arrow_left' };
var item17 = { title: 'compose', iconCls: 'compose' };
var item18 = { title: 'delete', iconCls: 'delete' };
var item19 = { title: 'refresh', iconCls: 'refresh' };
var item20 = { title: 'reply', iconCls: 'reply' };
var item21 = { title: 'star', iconCls: 'star' };
var item22 = { title: 'trash', iconCls: 'trash' };
var panel = new Ext.TabPanel({
fullscreen: true,
ui: 'light',
cardSwitchAnimation: 'slide',
tabBar: {
dock: 'bottom',
scroll: 'horizontal'
},
items:
[
item1,item2,item3,item4,item5,item6,item7,item8,item9,item10,
item11,item12,item13,item14,item15,item16,item17,item18,item19,item20,
item21,item22
]
});
}
});



* Overlay : HTML과 스크립트 모두 작업해야 함

* Ext.List

* Ext.MessageBox : Alert, Confirm, Prompt


* Ext.is
  width: Ext.is.Phone ? 260 : 400,
  height: Ext.is.Phone ? 220 : 400


* contentEl : html을 사용해서 레이아웃 구성



********************** 이상 UI 와 관련해서 ************************

* Ajax

* Ext.Template : HTML 태그 모형


var myTpl = new Ext.Template([
'<div>',
'<span> <b>{name}</b> is {value} </span>',
'</div>'
]);
Ext.setup({
onReady: function(){
var panel = new Ext.Panel({
fullscreen:true,
tpl: myTpl
});
panel.update(
{
name: "Sencha Touch",
value: "The First HTML5 Mobile Web App Framework"
}
);
}
});



* Ext.XTemplate : 배열 형태의 데이터를 쉽게 다룰 수 있음



* Ajax 호출 : Ext.Ajax
    • url: Ajax 통신 대상이 되는 원격지의 URL
    • timeout: 네트워크 통신이 항상 안정적이지는 않다. 따라서 적절한 타임아웃 설정이 필요하다. Ajax 통신 과정에서 타임아웃 시간을 초과하면 더는 기다리지 않고 실패로 처리하는 것이 좋다. 이 속성에 타임아웃 값을 밀리초 단위로 지정한다. 기본값은 30,000밀리초(약 30초)다.
    • success 함수: Ajax 통신이 성공하면 실행되는 함수
    • failure 함수: Ajax 통신이 실패하면 실행되는 함수
    • callback 함수: 성공/실패와 상관없이 Ajax 통신이 완료되면 실행되는 함수



* JSONP : 크로스도메인 접근을 가능하도록 함
 <script type=’text/javascript’ src=’http://anotherDomain/server.js’></script>



Ext.util.JSONP.callback(
{
"name": "Sencha Touch",
"value": "The First HTML5 Mobile Web App Framework"
}
)



Ext.util.JSONP.request({ .......



**************************************



* Data package



* Ext.data.Proxy
  - ServerProxy : 원격지 서버에서 데이터 로딩
  - ClientProxy : 로컬 저장소로부터 데이터 로딩



* Ext.data.Reader : proxy로 얻어온 데이터 해석하고 데이터 모델 객체로 생성함
  - JsonReader
  - XmlReader

 

* Ext.data.Store : 데이터의 연산을 수행할 수 있도록 함
  - inline Data


var store = new Ext.data.Store({
data: [
{firstName: 'Julio', lastName: 'Benesh'},
{firstName: 'Julio', lastName: 'Minich'},
{firstName: 'Tania', lastName: 'Ricco'},
………
]


  - External Data


var store = new Ext.data.Store({
proxy: {
type: 'ajax',
url : 'server.html',
reader: {
type: 'json',
root: 'users'
}
}
});



* DataView : Store에 저장된 데이터를 UI에 표현하기 위한 컴포넌트 객체 (Ext.List가 이 클래스를 상속함)



* Ext.Template : 데이터의 레이아웃을 결정할 때 사용 (XTemplate)



* 원격지 서버의 리소스 호출 : Mode, Store, Proxy, Reader 이용
  - JsonReader 사용
  - XmlReader 사용



* 데이터 정렬과 필터링
  - Store 객체의 sort()와 filter() 함수


jsonStore.sort(‘firstName’,’DESC’); //firstName을 기준으로 내림차순 정렬
jsonStore.filter(‘firstName’, ‘JM’);. //firstName 값이 ‘JM’인 것만 조회



* Ext.data.ClientProxy 클래스를 상속받은 객체를 통해 로컬 자원에 액세스할 수 있음
  - Memory
  - Web Storage : HTML5에서 추가된 클라이언트 측 저장소
    : LocalStorage (영구저장소)
    : SessionStorage (임시저장소)
  - ClientProxy, WebStorageProxy 상속받아 LocalStorageProxy 객체나 SessionStorageProxy 객체 이용



* Ext.NestedList : 리스트를 계층 구조로 표현할 수 있음 : 타이틀바의 제목이나 뒤로가기 버튼이 자동으로 생성 됨



***********************************


* Event
  - handler 를 이용한 버튼 탭 이벤트 처리


new Ext.Button({
text: 'MyButton',
handler: function(btn, event){
Ext.Msg.alert('Title', btn.text, Ext.emptyFn);
}
})
 

  - onItemDisclosure 속성을 이용한 리스트 탭 이벤트 처리



new Ext.List({
itemTpl : '{firstName} {lastName}',
store: store,
onItemDisclosure: function(record, btn, index) {
Ext.Msg.alert('Message', record.get('firstName'), Ext.emptyFn);
}
}


* 표준 이벤트 처리
  - listeners를 이용한 인라인 이벤트 처리


new Ext.Button({
text: 'MyButton',
listeners: {
tap: function(btn, event){ console.log(event.type); },
click: {
element: 'el',
fn: function(event){ console.log(event.type); }
}
}
})



  - 0n() 함수를 이용한 이벤트 처리


btn.on({
'tap' : {
fn: function(event){ console.log('tap')},
delay: 2000
},
'click' : {
element: 'el',
fn: function(event){ console.log('click')},
}
});



* 리스트의 itemtap, itemswipe 이벤트



* Touch Event
  - 탭(Tap) 관련 이벤트 : tap, singletap , doubletap, taphold, tapcancel
  - 터치(Touch) 관련 이벤트 : touchstart, touchend, touchmove, touchdown
  - 스윕(Swipe) 이벤트 : swipe
  - 핀치(Pinch) 관련 이벤트 : pinch, pinchstart, pinchend
  - 드래그(Drag) 및 스크롤(Scroll) 관련 이벤트 : drag, dragstart, dragend, scroll, scrollstart, scrollend



*****************************



* Media 지원 : HTML5에 추가된 Video, Audio 사용. Ext.Video, Ext.Audio



* Ext.Media
  - autoPause  : 미디어가 비활성화(deactivate)될 때 자동으로 일시정지할지 결정한다(기본값: true).
  - autoResume : 미디어가 활성화(activate)될 때 자동으로 재생할지 결정한다(기본값: false)
  - enableControls : 미디어 재생을 제어하는 컨트롤 바의 표시 여부를 결정한다(기본값: true).
  - url : 미디어 소스를 지정한다
  - play() : 미디어를 재생한다.
  - pause() : 미디어 재생을 일지정지한다.
  - toggle() : 미디어 재생과 일시정지를 번갈아 처리하는 토글 기능을 제공한다.



* Ext.Video


Ext.setup({
onReady: function() {
var pnl = new Ext.Panel({
fullscreen: true,
layout: {
type: 'vbox',
pack: 'center'
},
items: [{
xtype: 'video',
url: 'space.mp4',
loop: true,
width: 500,
height: 400,
posterUrl: 'Screenshot.png'
}]
})
}
});



* Ext.Audio


Ext.setup({
onReady: function() {
var pnl = new Ext.Panel({
fullscreen: true,
layout: {
type: 'vbox',
pack: 'center'
},
items: [{
xtype: 'audio',
url: 'crash.mp3',
loop: true
}]
})
}
});



저작자 표시 비영리 동일 조건 변경 허락
신고


그동안 Eric Sarrion 이라는 분이 만든 http://the-jquerymobile-tutorial.org/ 라는 웹사이트에 있는 jQuery Mobile tutorial 로 공부 했었습니다.

Part 2 부분인 JavaScript Programming 부분을 완전히 끝냈는데요.

(관련 글들은 JQM tutorial 코너에 있습니다. 여기로 가시면  관련 글들을 보실 수 있습니다.


앞으로도 계속 jQuery Mobile 공부는 이어 가겠지만 이젠 Sencha Touch 쪽에 좀 더 신경을 쓸까 합니다.


Eric Sarrion 은 제가 알기로 프랑스 사람인 것으로 아는데요.

jQuery Mobile Tutorial 책을 저술 했고 관련 내용을 안드로이드와 iOS 애플리케이션으로 만들어서 보급하고 있습니다.


저는 그 중에서 jQuery Mobile 과 자바스크립트로 프로그래밍하는 부분을 다룬 Part 2 부분을 공부한 겁니다.


Eric Sarrion 에게 감사드리고 여러분들도 마켓에 있는 Eric Sarrion 의 jQuery Mobile Tutorial application 을 많이 사 보시기 바랍니다. 가격은 1불 입니다.


일단 목표했던 jQuery Mobile 튜토리얼도 2012년을 마감하면서 완료 됐네요.

2013년 새해도 기분 좋게 시작됐습니다.

새해 첫날 올린 글에 Daum View의 Best 글에도 선정되고...

대서양 해돋이를 보고 글과 그림을 올렸는데 2000분이 넘는 분들이 보셨네요.


이제는 Sencha Touch 를 공부해 보려고 하는데요.


너무 오랫동안 손을 놔서 다 잊어 버렸습니다.

그래서 일단 제가 예전에 올린 Sencha Touch 관련 글들을 보면서 공부하려고 합니다.


예전에 센차터치 관련해서 24개의 글을 올렸었네요.

그 글을 읽으면서 계속 이어서 할지 아니면 jQuery Mobile 에서 했던 것 처럼 어떤 Tutorial 싸이트를 찾아서 공부를 진행할지 결정할 생각입니다.


제 글을 보면서 공부하고 직접 실습도 하고 있는데요.

만든 소스 파일은 얼마전 웹 호스팅을 받은 서버에 올려놓고 있습니다.

도메인 주소도 받았는데요.



주소는  ...... (잠시 폐쇄했습니다. )입니다.

(Sencha Touch 는 구글 크롬 브라우저에서 실행해야 제일 잘 되더라구요.)


이 글을 쓰는 시점엔 http://coronasdk.tistory.com/88 글과 http://coronasdk.tistory.com/167 글에 있는 소스코드를 실습해 보고 올려 놨습니다.


그 사이에 http://coronasdk.tistory.com/157 부터 이어지는 4회에 걸친 Sencha Touch 로 앱 만들기 예제들이 있는데요.

그 글을 쓸 때도 잘 안됐는데 이번에도 잘 안되더라구요.

뭐가 잘 못 된건지..

혹시 그 글에 나온 소스코드 제대로 실행되시는 분들은 소스코드 좀 공유해 주시면 감사하겠습니다.


그러면 앞으로도 계속 이어지는 Sencha Touch 관련 글들과 또 다른 Cross Platform Mobile Application 개발에 필요한 여러 테크닉들 관련 글들에 많은 관심 부탁 드려요. ^^



저작자 표시 비영리 동일 조건 변경 허락
신고


Working with classes in Sencha Touch 2.0

1. Declaration (선언)

1.1. The Old Way (옛날 방법)


만약 여러분이 Sencha Touch 1.x 버전을 사용하신다면 분명 클래스를 생성하기 위한 Ext.extend 를 잘 알고 계실겁니다.

var MyPanel = Ext.extend(Object, { ... });

이것은 다른 클래스를 상속받는 새로운 클래스를 생성할 때 손쉽게 이용할 수 있는 방법입니다. 상속하는 것 말고는 다른 클래스 생성 관점에서 보면은 그렇게 크게 유용하지는 않습니다. configuration이라던지 statics, mixins 같은 것들 말이죠. 간단하게 이런 아이템들과 관련한 사항들을 살펴보죠.

아래 샘플을 한번 보세요.

My.cool.Panel = Ext.extend(Ext.Panel, { ... });

여기서는 새로운 클래스의 네임스페이스를 사용했고 Ext.Panel을 extend 했습니다. 이 예제에서는 두가지 알아두어야 할 것들이 있습니다.

1. My.cool은 프로퍼티로서 Panel을 할당하기 전에 객체로서 존재해 있어야 합니다.
2. Ext.Panel은 이것이 참조되기 전에 존재하고 또 로드 되어 있어야 합니다.

첫번째 아이템은 대개 Ext.namespace (Ext.ns)를 사용해서 해결 합니다. 이 메소드는 object/property를 통해 재귀적으로 참조 되고 그것이 존재하지 않으면 생성합니다. 좀 성가신 일은 여러분이 Ext.extend를 사용할 때는 항상 이것을 사용해야 한다는 것이죠. 아래 예제가 있습니다.

Ext.ns('My.cool');
My.cool.Panel = Ext.extend(Ext.Panel, { ... });


두번째 이슈는 이것을 address 하기가 쉽지 않다는 것입니다. 왜냐하면 Ext.Panel 은 다른 많은 클래스들을 depend on  할 것이기 때문이죠. 다른 클래스들을 직접적이든 간접적이든 상속하니까요. 그러면 그 상속할 대상이 되는 클래스는 반드시 존재해 있어야 하겠죠. 이러한 이유 때문에 Sencha Touch 2 이전에는 ext-all.js의 모든 라이브러리의 일부분만 사용해도 이것을 통재로 include 했었습니다.

1.2. The New Way (새로운 방법)

센차 터치 2 는 클래스를 생성할 때 오직 한가지 메소드만 생각하면 되도록 부가적인 것들은 전부 제거했습니다. 그것은 Ext.define 입니다. 아주 간단한 신택스죠.

Ext.define(className, members, onClassCreated);

이 신택스의 각 부분별로 한번 살펴 보겠습니다.

- className 은 클래스의 이름입니다.
- members는 key-value 조합의 클래스 멤버 collection을 나타내는 객체입니다.
- onClassCreated는 옵션입니다. 모든 관계된 클래스가 ready 되면 invoke 하기 위한 callback 함수이죠. 또한 이 클래스 자체가 완전히 생성되면 invoke 되기도 합니다. 클래서 생성의 새로운 비동기적인 성격으로 인해  이 callback은 다양한 경우에 아주 유용하게 사용될 수 있습니다. 이 내용은 Sencha Touch 2 튜토리얼 4장에서 다시 자세하게 다뤄질 겁니다.


Example

Ext.define('My.sample.Person', {
    name: 'Unknown',

    constructor: function(name) {
        if (name) {
            this.name = name;
        }
    },

    eat: function(foodType) {
        alert(this.name + " is eating: " + foodType);
    }
});

var aaron = Ext.create('My.sample.Person', 'Aaron');
    aaron.eat("Salad"); // alert("Aaron is eating: Salad");

   
Ext.create() 메소드를 사용해서 My.sample.Person의 새로운 인스턴스를 생성했습니다. 그러려면 아마 (new My.sample.Person())을 사용해야 했겠죠. 하지만 다이나믹 로딩의 장점을 이용하기 위해서 위의 방법대로 하실것을 권장합니다. 다이나믹 로딩의 좀 더 자세한 사항은 Getting Started guide를 살펴 보세요.

2. Configuration

센차터치 2에서는 클래스가 생성되기 전에 강력한 Ext.Class 전처리 장치에 의해 진행되는 config property를 제공하고 있습니다.
이것은 아래와 같은 내용들을 제공합니다.

- configurations는 다른 클래스 멤버로부터 완전하게 캡슐화 되어있습니다.
- 각 config property의 getter와 setter 메소드들은 클래스가 생성되는 동안 이 메소드들이 따로 정의되어 있지 않으면 class prototype에 자동적으로 생성합니다.
- 각 config property에 대해 apply 메소드도 또한 생성됩니다. 자동으로 생성된 setter 메소드는 value를 setting 하기 전에 내부적으로 apply 메소드를 call 합니다.
그 value 를 세팅하기 전에 custom logic 을 run 할 필요가 있으면 config property에 apply 메소드를 override 합니다. 만약 apply가 어떤 값도 return 하지 않으면 setter는 value를 set 하지 않습니다.

아래에 예제가 있습니다.

Ext.define('My.own.WindowBottomBar', {});

Ext.define('My.own.Window', {

    /** @readonly */
    isWindow: true,

    config: {
        title: 'Title Here',

        bottomBar: {
            enabled: true,
            height: 50,
            resizable: false
        }
    },

    constructor: function(config) {
        this.initConfig(config);
    },

    applyTitle: function(title) {
        if (!Ext.isString(title) || title.length === 0) {
            console.log('Error: Title must be a valid non-empty string');
        }
        else {
            return title;
        }
    },

    applyBottomBar: function(bottomBar) {
        if (bottomBar && bottomBar.enabled) {
            if (!this.bottomBar) {
                return Ext.create('My.own.WindowBottomBar', bottomBar);
            }
            else {
                this.bottomBar.setConfig(bottomBar);
            }
        }
    }
});


아래 예제는 이것을 어떻게 사용하는지를 보여줍니다.

var myWindow = Ext.create('My.own.Window', {
    title: 'Hello World',
    bottomBar: {
        height: 60
    }
});

console.log(myWindow.getTitle()); // logs "Hello World"

myWindow.setTitle('Something New');
console.log(myWindow.getTitle()); // logs "Something New"

myWindow.setTitle(null); // logs "Error: Title must be a valid non-empty string"

myWindow.setBottomBar({ height: 100 }); // Bottom bar's height is changed to 100



3. Statics

static 멤버들은 static config를 사용해서 정의될 수 있습니다.

Ext.define('Computer', {
    statics: {
        instanceCount: 0,
        factory: function(brand) {
            // 'this' in static methods refer to the class itself
            return new this({brand: brand});
        }
    },

    config: {
        brand: null
    },

    constructor: function(config) {
        this.initConfig(config);

        // the 'self' property of an instance refers to its class
        this.self.instanceCount ++;
    }
});

var dellComputer = Computer.factory('Dell');
var appleComputer = Computer.factory('Mac');

alert(appleComputer.getBrand()); // using the auto-generated getter to get the value of a config property. Alerts "Mac"

alert(Computer.instanceCount); // Alerts "2"



Error Handling and debugging (에러 처리와 디버깅)


센차터치 2에는 디버깅과 에러 처리를 도와주는 유용한 기능이 있습니다.

메소드의 display name을 얻으려면 Ext.getDisplayName()을 사용하실 수 있습니다. 이것은 description 안에 클래스 이름과 메소드 이름을 가진 에러를 throw 할 때 유용하게 사용하실 수 있습니다.

throw new Error('['+ Ext.getDisplayName(arguments.callee) +'] Some message here');

에러가 Ext.define()을 사용해 정의된 클래스의 메소드내에서 던져졌을 때 여러분의 브라우저가 크롬(Chrome)이나 사파리(Safari) 같이 WebKit 을 사용하는 브라우저라면 call stack에 그 메소드와 클래스 이름이 표시될 겁니다.

아래에 크롬에서 보실 수 있는 에러 메세지들이 있습니다.


저작자 표시 비영리 동일 조건 변경 허락
신고


큰 규모의 프로젝트를 진행하려면 OOP (객체지향) 개념을 사용하는 것이 좋습니다.

이 OOP를 사용하려면 클래스를 잘 이용해야 합니다.

이번글과 다음글은 Sencha Touch 2 에서 클래스를 어떻게 사용하는지에 대해 공부해 보겠습니다.

Sencha Touch2 에서는 나름의 class system을 제공하고 있네요.

클래스를 사용하는데 여러 간편한 방법을 제공하는 것 같습니다.


오늘은 여러 규칙과 개념 위주로 정리를 하고 다음 글에서 실제로 코딩하는 방법에 대해 주로 알아 보겠습니다.


먼저 이 글을 보시기 전에 아래 Sencha Touch 2 의 class system에 대해 설명하는 동영상을 보시면 도움이 되실 겁니다.




SenchaCon 2011: The Sencha Class System from Sencha on Vimeo.



How to use classes in Sencha Touch 2  - 1 -

센차터치 2는 Ext JS 4에서 개발된 art class system을 사용합니다. 이 시스템은 providing inheritance, dependency loading, mixins, 강력한 configuration 옵션들 그리고 더 다양한 것들을 위해 자바스크립트에서 새로운 클래스를 쉽게 생성하도록 해 줍니다.

이 시스템에서 클래스는 단지 어떤 함수의 객체일 뿐이고 거기에 프로퍼티들이 첨가 됩니다. 예를 들어 여기 animal 이란 클래스가 있습니다. 이 클래스의 이름과 함수는 아래와 같이 구성할 수 있습니다.

Ext.define('Animal', {
    config: {
        name: null
    },

    constructor: function(config) {
        this.initConfig(config);
    },

    speak: function() {
        alert('grunt');
    }
});


이제 Animal이라는 클래스가 생겼습니다. 이제 각각의 animal 객체를 만들 수 있고 그 객체들은 이름을 가질 수 있고 짖을 수 있습니다. animal의 새 instance를 만들기 위해 우리는 Ext.create 를 사용하면 됩니다.

var bob = Ext.create('Animal', {
    name: 'Bob'
});

bob.speak(); //alerts 'grunt'


여기 Bob이라는 Animal을 만들었습니다. 그리고 bob에게 말하라라고 시켰습니다. 우리가 클래스를 생성하고 그것을 초기화 했고 또 그것을 더 진화시킬 수도 있습니다. 여기까지는 우리는 아직 Bob이 어떤 종류인지를 모릅니다. 이제 우리가 Human 이라는 서브클래스가 있다고 해 봅시다.

Ext.define('Human', {
    extend: 'Animal',

    speak: function() {
        alert(this.getName());
    }
});


여기 Animal을 상속받은 새 클래스가 있습니다. 이 클래스는 Animal 클래스의 모든 함수와 configuration들을 사용할 수 있습니다. 이 Human 에서는 speak 함수를 overrode 했습니다. 왜냐하면 human은 똑똑하기 때문에 grunt 하는 대신에 자신의 이름을 말 할 수 있으니까요. Human은 grunt 하지 않고 자기 이름을 말하도록 하겠습니다.

var bob = Ext.create('Human', {
    name: 'Bob'
});

bob.speak(); //alerts 'Bob'


우리는 Human 서브클래스를 추가할 때 마법같은  함수를 사용했습니다. 느끼셨겠지만 Animal 클래스에서는 getName 함수를 정의하지 않았었습니다. 어디서 이게 왔을까요? 이 class system에서는 다음과 같은 것들을 자동으로 제공하고 있습니다.

- 현재 값을 return 하는 getter 함수, 위 경우에는 getName()
- 새 값을 세팅하는 setter 함수, 위 경우에는 setName()
- setter 에 의해 call 되는 applier 함수. 이 함수는 configuration 에 변경이 있을 때 함수를 실행할 수 있도록 해 줍니다. 위 역우에는 applyName()


getter와 setter 함수들은 자동으로 생성되고 클래스 안에서 데이터를 저장할 때 사용하시기에 좋을 겁니다. 센차터치의 모든 컴포넌트는 이 class system을 사용합니다. 그리고 이렇게 자동으로 함수들을 생성해 주니까 여러분이 config를 알면 자동으로 그 값의 get과 set을 어떻게 해야 하는지도 알게 되시는 겁니다.

이 기능은 여러분들의 코드를 더 간결하게 해 줄 겁니다. 예를 들어 만약에 유저가 Bob의 이름을 바꾸고 싶어한다면 여러분은 applyName 함수를 정의하시기만 하면 됩니다. 이 함수는 자동으로 call 되는 함수이니까요.

Ext.define('Human', {
    extend: 'Animal',

    applyName: function(newName, oldName) {
        return confirm('Are you sure you want to change name to ' + newName + '?')? newName : oldName;
    }
});


위 예제에서는 브라우저의 built in confirm 함수를 사용했습니다. 이 함수는 대화창을 보여주면서 Yes 와 No 를 선택하도록 할 겁니다. applier 함수에서 만약에 false를 return 하게 된다면 이름을 바꾸는 것을 cancel 하도록 할 수도 있습니다. 위 예제에서는 유저가 Yes나 No를 선택하게 되면 거기에 맞는 새 name 이나 기존 name 이 return 될 겁니다.

만약 No를 클릭하면 Bob의 이름은 바뀌지 않겠죠.

var bob = Ext.create('Person', {
    name: 'Bob'
});

bob.setName('Fred'); //opens a confirm box, but we click No

bob.speak(); //still alerts 'Bob'


여기까지 하면 아래와 같은 클래스에 대한 아주 중요한 부분을 이미 다 알게 되신겁니다.

- 여러분들이 만드는 클래스를 포함해 모든 클래스는 Ext.define 에서 정의합니다.
- 대부분의 클래스들은 extend 신택스를 이용해 다른 클래스를 extend 합니다.
- 클래스들은 Ext.create를 사용해서 생성됩니다. 예를 들어 Ext.create('클래스이름',{어떤:'configuration'}) 이런식입니다.
- 자동적으로 생성되는 getter와 setter를 사용하기 위해 config 신택스를 사용합니다. 그러면 코드도 한결 간결해 집니다.


이 정도 되면 여러분들은 여러분의 앱에 이미 클래스들을 생성할 수 있습니다. 그런데 class system에는 몇가지 더 주의해야 할 것들이 있습니다. class system의 유용한 다른 기능들을 사용할 수 있도록 도와주는 방법들이 몇개 더 있습니다.







Dependencies and Dynamic Loading

대부분 클래스들은 다른 클래스들에 depend on 합니다. Human 클래스는 위에서 Animal 클래스를 depend on 했죠. 왜냐하면 Human 클래스에서 Animal 클래스를 extend 했으니까요. Animal 이 Human을 정의할 수 있도록 도와 준 겁니다. 가끔 여러분들은 클래스 안에 클래스를 생성해야 될 때가 있을 겁니다. 이렇게 하려면 아래 신택스대로 하시면 됩니다.

Ext.define('Human', {
    extend: 'Animal',

    requires: 'Ext.MessageBox',

    speak: function() {
        Ext.Msg.alert(this.getName(), "Speaks...");
    }
});


이런 방법으로 클래스를 생성하면 센차 터치는 Ext.MessageBox가 이미 로드 됐는지 아닌지를 체크합니다. 그래서 로드되지 않았다면 즉시 그 파일을 AJAX를 이용해서 로드 합니다.

Ext.MessageBox는 그 안에 자신이 depend on 하는 클래스들을 가지고 있을 겁니다. 그것들은 background에서 자동적으로 로드될 겁니다. 일단 모든 required 클래스들이 로드 되면 Human 클래스는 정의된거고 여러분은 people를 instantiate 하기 위해 Ext.create를 사용하기 시작할 수 있습니다. 이것은 개발자 여러분이 개발 할 대 필요한 그런 클래스들을 직접 로드할 필요없이 다 자동적으로 문제없이 로드 된다는것을 의미합니다. 그런데 production 에서는 그렇지 않습니다. 왜냐하면 인터넷을 통해서 하나하나 로드되는것은 시간이 아주 길어질 수가 있으니까요.

production 에서는 단 1개의 JavaScript 파일만을 로드할 것을 원합니다. 단지 우리의 어플리케이션이 사용할 그 클래스만 포함되면 되죠. 이것은 JSBuilder 툴을 이용해 Sencha Touch2를 사용하면 아주 쉽게 할 수가 있습니다. 이 툴은 여러분의 앱을 분석해서 여러분이 만든 모든  클래스들과 오직 여러분의 앱이 실제로 사용하는 framework class 들만 모아서 single file build를 생성하거든요. JSBuilder를 어떻게 사용하는지 좀 더 자세하게 아시고 싶으면 Building guide를 보세요.

각각의 approach는 pros와 cons를 가집니다. 이 중에 나쁜것은 말고 그 둘의 좋은 부분만 가질 수 있을까요? 정답은 yes 입니다. Sencha Touch 2 에서는 그 solution을 implement 했습니다.

Naming Conventions

클래스와 네임스페이스 그리고 파일이름과 관련해 일관된 naming convention을 사용한다는 것은 여러분의 코드를 organize 하고 structure 하고 가독성있게 유지하는데 아주 유용합니다.

1) Classes


클래스 이름들은 오직 알파벳 글자만이 포함됩니다. 숫자도 가능하지만 그것이 어떤 전문 용어가 아닌 이상 대부분의 경우 권장되지는 않습니다. 밑줄이나 하이픈 또는 다른 알파벳이 아닌 특수문자들은 사용하지 마세요.

- MyCompany.useful_util.Debug_Toolbar 같은 형식은 추천하지 않습니다.
- MyCompany.util.Base64 같은 형식은 가능합니다.


클래스 이름들은 . (dot) 을 사용해서 관련된 것끼리 패키지를 만들어서 그룹화 해야 합니다. 최소한 거기에는 한개의 유니크한 top-level 네임스페이스가 있어야 하죠.

- MyCompany.data.CoolProxy
- MyCompany.Application


top-level 네임스페이스와 실제 클래스 이름은 CamelCase를 사용해야 합니다. 다른 것들은 소문자로 하시면 됩니다.

- MyCompany.form.action.AutoLoad

Sencha Touch에서 기본적으로 지원하는 클래스가 아니면 Ext 라는 top-level 네임스페이스를 절대 사용할 수 없습니다.

Acronyms(첫글자 이니셜)은 CamelCase convention 규칙을 따릅니다.

- Ext.data.JSONProxy 대신 Ext.data.JsonProxy 를 사용합니다.
- MyCompary.parser.HTMLParser 대신 MyCompany.util.HtmlParser 를 사용합니다.
- MyCompany.server.HTTP 대신 MyCompany.server.Http 를 사용합니다.


2) Source Files


클래스 맵의 이름은 그것이 저장돼 있는 곳의 file path를 가리킵니다. 그렇기 때문에 한 파일에 한개의 클래스만 있어야 합니다.

- Ext.mixin.Observable 는 path/to/src/Ext/mixin/Observable.js 에 저장돼 있습니다.
- Ext.form.action.Submit 는 path/to/src/Ext/form/action/Submit.js 에 저장돼 있습니다.
- MyCompany.chart.axis.Numeric 는 path/to/src/MyCompany/chart/axis/Numeric.js 에 저장돼 있습니다.


path/to/src는 여러분 애플리케이션의 클래스의 경로 입니다. 모든 클래스들은 이 경로 밑에 위치합니다. 그리고 개발과 운영,보수 등을 용이하게 하기 위해 이렇게 확실하게 네임스페이스를 따라 명명합니다.

3) 메소드(Methods)와 변수(Variables)


클래스 이름과 마찬가지로 메소드와 변수 이름들도 알파벳만을 사용합니다. 숫자를 사용할 수는 있지만 특정 용어가 아닌 이상은 권장하지 않습니다. 밑줄이나 하이픈 같은 다른 특수문자들은 사용하지 마세요.

메소드와 변수 이름들은 CamelCase를 사용합니다. acronyms 에도 마찬가지로 적용 됩니다.

아래 예제가 있습니다.

사용 가능한 메소드 이름들 :

- encodeUsingMd5()
- getHTML() 대신 getHtml() 를 사용합니다.
- getJSONResponse() 대신 getJsonResponse() 를 사용합니다.
- parseXMLContent() 대신 parseXmlContent() 를 사용합니다.


사용 가능한 변수 이름들 :

- var isGoodName
- var base64Encoder
- var xmlReader
- var httpServer


4) Properties

클래스 프로퍼티 이름은 그것이 static constant일 경우를 제외하고는 메소드나 변수이름과 같은 규칙을 따릅니다. static 클래스 프로퍼티는 모두 대분자로 표시되어야 합니다.

- Ext.MessageBox.YES = "Yes"
- Ext.MessageBox.NO = "No"
- MyCompany.alien.Math.PI = "4.13"

저작자 표시 비영리 동일 조건 변경 허락
신고

Sencha Touch 2 Tutorial - Layouts -

2012.04.24 01:47 | Posted by 솔웅


Using Layouts in Sencha Touch 2

Intro and HBox

Layout은 컴포넌트들의 positioning과 sizing 을 정하는 겁니다. 예를 들어 이메일 클라이언트는 왼쪽에 메세지 리스트가 위치해 있어야 할 것입니다. 이것이 1/3을 차지한다고 합시다. 그 나머지 공간엔 메세지를 볼 수 있는 panel이 위치해 있을 겁니다.

이 구도라면 우리는 hbox layout을 사용해서 구현할 수 있습니다. 그 안에 flex라는 아이템을 넣어서 조절할 수도 있구요. Flexing 의 의미는 각각의 child component의 flex에 근거해서 공간을 나누겠다는 것입니다. 위의 조건을 만족하기 위해서 우리는 flex를 아래와 같이 나눌 수 있습니다.




코드는 아주 간단합니다. 그냥 컨테이너에 hbox 레이아웃을 정해주고나서 각 child 컴포넌트들에 flex를 지정해 주면 됩니다. (이 경우에는 panel이 됩니다.)

Ext.create('Ext.Container', {
    fullscreen: true,
    layout: 'hbox',
    items: [
        {
            xtype: 'panel',
            html: 'message list',
            flex: 1
        },
        {
            xtype: 'panel',
            html: 'message preview',
            flex: 2
        }
    ]
});


이 예제대로 하면 먼저 화면을 가득 채우는 container가 생성됩니다. 그 안에 메세지 리스트 panel 과 preview panel이 들어가게 됩니다. 두개의 아이템이 있는데요. 하나는 flex: 1 이고 다른 하나는 flex:2 입니다. 이 의미는 메세지 리스트는 전체 화면의 1/3차지하고 message preview 는 나머지 2/3 를 차지한다는 것입니다. 만약 이 콘테이너가 300 픽셀이라면 첫번째 것은 100 픽셀을 차지하고 나머지는 200픽셀을 차지하게 될 겁니다.

hbox는 가장 많이 사용되는 레이아웃 중 하나입니다. 이 레이아웃은 수평적으로 컴포넌트들을 배치하는데 사용할 수 있습니다. 더 자세한 사항은 HBox 문서를 봐 주세요.

VBox Layout

VBox는 HBox와 거의 비슷합니다. 좌우로 나누어지는 것이 아니라 상하로 나누어 지는것만 다릅니다.



이 레이아웃을 구현하는 코드는 hbox와 거의 비슷합니다. 단지 layout을 hbox 대신 vbox로 해 주시면 됩니다.

Ext.create('Ext.Container', {
    fullscreen: true,
    layout: 'vbox',
    items: [
        {
            xtype: 'panel',
            html: 'message list',
            flex: 1
        },
        {
            xtype: 'panel',
            html: 'message preview',
            flex: 2
        }
    ]
});


이 컨테이너 위 아래가 300 픽셀이라면 첫번째 패널은 100픽셀을 그리고 두번째 패널은 200 픽셀의 높이를 차지할 겁니다. 좀 더 자세한 사항은 VBox 문서를 보세요.

Card Layout

어떤 경우는 여러개의 스크린으로 정보를 보여줄 필요가 있습니다. 하지만 이 일을 하기에는 디바이스의 스크린 크기가 너무 작습니다. TabPanel 과 Carousels 는 한 스크린에 많은 정보를 보여줄 때 유용합니다. 이 두가지는 모두 Card layout 과 같이 쓰여 집니다.

Card layout은 현재 활성화 된 아이템의 전체 Container 크기를 차지합니다. 나머지 아이템들은 그 밑에 숨겨집니다. 그 숨겨진 아이템들 중에 한가지를 visible 하도록 만들 수 있습니다. 이 때 한번에 한 아이템만 visible 하도록 만들 수 있습니다.



여기 gray box가 Container 입니다. 그 안의 blue box 가 현재 활성화된 card 입니다. 나머지 세개는 비활성화 되서 모이지 않습니다. 하지만 이후에 swap 되서 활성화 될 수 있습니다. card layout을 direct 하게 생성하는 것은 아주 일반적이지는 않습니다. 아래와 같이 생성하시면 됩니다.

var panel = Ext.create('Ext.Panel', {
    layout: 'card',
    items: [
        {
            html: "First Item"
        },
        {
            html: "Second Item"
        },
        {
            html: "Third Item"
        },
        {
            html: "Fourth Item"
        }
    ]
});

panel.setActiveItem(1);


여기서는 card layout을 panel 과 같이 생성했습니다. 이 경우는 두번째 아이템이 활성화 될 겁니다. 왜냐하면 setActiveItem(1)을 했기 때문이죠. 첫번째 아이템은 0 으로 지정하면 활성화 될 수 있습니다. 일반적으로 이 card layout은 Tap Panel 이나 Carousel 을 사용하시는것이 좋습니다.

Fit Layout

Fit Layout 은 가장 간단한 레이아웃일 겁니다. 이 레이아웃이 하는 일은 그냥 parent Container의 크기만큼 child component를 꽉 차게 만드는 것입니다.



예를 들어 parent Container가 200X200 픽셀이고 한개의 child component와 fit layout을 사용한다면 이 child component는 200X200 픽셀이 될 겁니다.

var panel = Ext.create('Ext.Panel', {
    width: 200,
    height: 200,
    layout: 'fit',

    items: {
        xtype: 'panel',
        html: 'Also 200px by 200px'
    }
});

Ext.Viewport.add(panel);


fit layout을 사용하면서 container에 1개 이상의 아이템을 넣으면 첫번째 아이템만이 보여질 겁니다. 여러개의 아이템을 보이기를 원한다면 Card layout을 사용해야 합니다.

Docking

모든 레이아웃은 아이템들을 그 안에 탑재할 수가 있습니다. Docking은 parent Container의 위,아래,오른쪽,왼쪽 화면에 추가적인 Component를 넣을 수 있도록 해 줍니다.

예를들어, 처음에 다뤘던 hbox layout으로 돌아가서 화면 위에 다른 component를 탑재하기를 원한다고 가정해 봅시다.





이것은 툴바나 배너들을 사용할 때 자주 이용됩니다. 아래는 이것을 단지 dock: 'top' configuration을 함으로서 간단히 구현한 예제입니다.

Ext.create('Ext.Container', {
    fullscreen: true,
    layout: 'hbox',
    items: [
        {
            dock: 'top',
            xtype: 'panel',
            height: 20,
            html: 'This is docked to the top'
        },
        {
            xtype: 'panel',
            html: 'message list',
            flex: 1
        },
        {
            xtype: 'panel',
            html: 'message preview',
            flex: 2
        }
    ]
});


child component에 이 dock configuration을 사용해서 원하는 만큼의 아이템들을 간단하게 dock 할 수 있습니다. 그리고 방향을 어느쪽이든지 위치 시킬 수 있습니다. 우리가 위에서 사용했든 vbox 예제를 예로 들면 아래와 같이 할 수도 있습니다.



이것은 dock: 'left' 를 사용해서 구현하겠습니다.

Ext.create('Ext.Container', {
    fullscreen: true,
    layout: 'vbox',
    items: [
        {
            dock: 'left',
            xtype: 'panel',
            width: 100,
            html: 'This is docked to the left'
        },
        {
            xtype: 'panel',
            html: 'message list',
            flex: 1
        },
        {
            xtype: 'panel',
            html: 'message preview',
            flex: 2
        }
    ]
});


여러분은 사방에 여러개의 docked item들을 추가할 수 있습니다. (예를 들어 bottom 포지션에 여러개의 docking을 사용할 수 있습니다.)


저작자 표시 비영리 동일 조건 변경 허락
신고


일단 센차터치에서 그동안 버전업이 됐길래 저는 최신버전으로 새로 받아서 작업하고 있습니다.


제가 받은 버전은 sencha-touch-2.0.0-gpl 입니다.


제가 센차터치 (Sencha Touch) 튜토리얼을 몇개 번역 했는데요.

Component 까지 번역을 했군요.


그런데 실습을 하지 않으면서 계속 번역만 하니까 제가 잘 이해를 못하겠더라구요.

제일 모르겠는건 어떻게 MVC 모델을 적용해서 앱을 만드는지 입니다.


그래서 지난 시간에 했었던 Component 글의 첫번째 예제를 MVC 모델을 적용해서 고쳐 보겠습니다.


먼저 원래 소스는 아래와 같습니다.


var panel = Ext.create('Ext.Panel', {
    layout: 'hbox',

    items: [
        {
            xtype: 'panel',
            flex: 1,
            html: 'Left Panel, 1/3rd of total size',
            style: 'background-color: #5E99CC;'
        },
        {
            xtype: 'panel',
            flex: 2,
            html: 'Right Panel, 2/3rds of total size',
            style: 'background-color: #759E60;'
        }
    ]
});

Ext.Viewport.add(panel);


이것을 app.js에 넣어서 직접 실행해 보려면 아래와 같이 하면 됩니다.


Ext.application({
    name: 'Panel',

    launch: function() {

        var panel = Ext.create('Ext.Panel', {
        layout: 'hbox',

        items: [
            {
                xtype: 'panel',
                flex: 1,
                html: 'Left Panel, 1/3rd of total size aaa ',
                style: 'background-color: #5E99CC;'
            },
            {
                xtype: 'panel',
                flex: 2,
                html: 'Right Panel, 2/3rds of total sizeaaa',
                style: 'background-color: #759E60;'
            }
            ]
        });

        Ext.Viewport.add(panel);
    }
});


보시면 아시겠지만 튜토리얼에 있는 코드를 복사해서 Ext.application 안의 launch:function() 안에 넣으시면 됩니다.


문제는 이 소스를 어떻게 MVC 모델에 맞게 수정하느냐 입니다.


이 소스는 view 만 있는 간단한 소스 입니다. 그러니까 폴더 구조를 아래와 같이 가져가겠습니다.


Panel 폴더가 프로젝트 폴더입니다.

그 안에 index.html과 app.js 가 있습니다. 그리고 sencha-touch.css 와 sencha-touch-all.js는 다운받은 Sencha Touch 소스 파일에서 복사해 넣습니다.


그리고 MVC 패턴을 적용하려면 app 폴더를 만들어야 하는 것 같습니다.

그 app 폴더에 view,controller,model,store 등의 폴더를 만드는데 이 소스에서는 view 만 있으면 되니까 view를 만들었습니다.

저 view 안에 panel.js 파일을 만들어서 그 안에 실제 view 를 담당하는 코드를 넣겠습니다.


일단 index.html 코드를 볼께요.


<!DOCTYPE html>
<html>
<head>
    <title>MVC Sample with Panel</title>
    <link rel="stylesheet" href="sencha-touch.css" type="text/css">
    <script type="text/javascript" src="sencha-touch-all.js"></script>
    <script type="text/javascript" src="app.js"></script>
</head>
<body></body>
</html>


소스는 간단합니다. html 파일에서는 단지 css와 js 파일을 불러왔습니다.

sencha-touch.css와 sencha-touch-all.js 파일을 Panel 폴더 밑에 복사해 넣었기 때문에 위와 같이 불러왔는데요. 서버에서 동작 시키려면 해당 경로를 써 주셔야 합니다.


그 다음은 app.js 파일을 보겠습니다.


Ext.application({
    name: 'Panel',


    launch: function() {
        Ext.create('Panel.view.panel');
    }
});


소스 코드가 아주 간단해 졌죠?

MVC 모델을 쓰는 이유가 이렇게 app.js를 간단하게 만들고 각 기능별로 폴더와 js 파일을 나눠서 코드를 관리하기 위해서 입니다.

여기서는 launch:function() 안에 Ext.create 함수를 사용해서 Panel.view.panel 을 불러왔습니다.

처음 Panel 은 위에서 설정한 이 애플리케이션의 name 이고 view는 app 폴더 밑에 있는 view 폴더를 가르킵니다. 그리고 맨 마지막 panel 은 view 폴더 밑에 있는 panel.js를 말합니다.


이렇게 하면 app/view/panel.js 파일을 불러올겁니다.


다 하고 나니까 이렇게 간단한데 몰랐을 때는 이것 맞추느라고 엄청 헤맸습니다.


다음은 panel.js 파일을 보겠습니다.


Ext.define('Panel.view.panel', {
    extend: 'Ext.Container',

    config: {
            fullscreen: true,
            layout: 'hbox',
            items: [
            {
                xtype: 'panel',
                flex: 1,
                html: 'Left Panel, 1/3rd of total size',
                style: 'background-color: #5E99CC;'
            },
            {
                xtype: 'panel',
                flex: 2,
                html: 'Right Panel, 2/3rds of total size',
                style: 'background-color: #759E60;'
            }
            ]
    }
});


우선 Ext.define 함수를 사용합니다. 곧바로 이 파일의 경로를 표시하구요.

다음으로는 Ext.Container 를 extend 합니다. 이 부분을 extend 하지 않으면 에러가 납니다.

Container 부분 튜토리얼을 번역 했는데 왜 그런지 아직 딱 떠오르지 않네요. 다시 한번 봐야겠습니다.


그 다음은 config 를 사용하고 그 안에서 화면을 full 로 해 준 다음에 원래 튜토리얼에 있던 소스를 위와 같이 해 주면 됩니다.


여기까지 하느라고 많이 헤맸습니다.


아래 제가 완료한 소스파일 올려 놓습니다. 혹시 저처럼 MVC 패턴 적용이 많이 헛갈리시는 분들은 이 소스가 도움이 되실겁니다.


Panel.zip


다음 글은 계속 센차터치의 튜토리얼을 다루겠습니다.

될 수 있는대로 소스코드를 이것 저것 변경해 보고 적용해 봐야 완전히 제것이 될 것 같군요.


다음에는 각 디바이스별로 화면을 달리 하는 Profile 을 한번 적용해 볼까 합니다.

저작자 표시 비영리 동일 조건 변경 허락
신고


Using Components in Sencha Touch 2

What is a Component?

센차터치(Sencha touch)에서 눈에 보이는 부분을 담당하는 대부분의 클래스들은 컴포넌트(Component) 입니다. 센차터치의 모든 컴포는트는 Ext.Component의 서브클래스 입니다. 그 의미는 아래와 같다는 겁니다.

- templet을 사용하는 페이지에 자신들을 Render  한다.
- 어느 때든지 자신들을 보이도록 하거나 안보이도록 한다.
- 스크린에 자신들을 중앙에 위치 시킨다.
- Enable 하거나 Disable 하도록 할 수 있다.


이 컴포넌트들은 좀 더 진보된 것들을 할 수 있습니다.

- 다른 컴포넌트 위에서 떠 있는다. (윈도우, 메세지 박스와 overlay들)
- 애니메이션을 위해 스크린에서 사이즈와 포지션을 변경한다.
- 자신 내부에 다른 컴포넌트를 위치시킨다. (툴바에서 유용하게 사용함)
- 다른 컴포넌트들에 맞춰 정열한다. 자신들이 drag 될 수 있도록 한다. 자신들의 content를 스크롤 가능하게 한다. 등등


What is a Container?


여러분이 생성한 모든 컴포넌트는 위에 열거한 기능들을 모두 가지고 있습니다. 애플리케이션들은 수많은 컴포넌트들로 만들어 집니다. 대개 다른 컴포넌트 안에 다른 컴포넌트가 있고.. 하는 식으로 사용됩니다. 컨테이너도 컴포넌트와 마찬가지입니다. render하고 그 안에 child Component들이 있고 하는 그런 기능들이 마찬가지로 있는데 그 규모가 좀 다릅니다. 예를들어 메일앱에서 Viewport 컨테이너의 두 children 은 메세지 리스트와 이메일 미리보기 pane 이 필요할 겁니다. 

컨테이너는 다음과 같은 추가 기능들이 있습니다.

- 초기화 단계와 runtime 에서 child Component들을 add 할 수 있다.
- child Components 들을 remove 할 수 있다.
- Layout 을 지정할 수 있다.


Layout은 스크린에 child Component들을 어떻게 배치할 것인가를 결정하는 겁니다. 메일 앱 예제에서 우리는 HBox layout을 사용했습니다. 그래서 우리는 왼쪽에 이메일 리스트를 위치시키고 스크린의 부분에 preview pane 을 위치시킬 수 있었습니다. 센차 터치 2에는 몇가지 레이아웃이 있습니다. 모두 앱의 화면 구성을 정해 주는 겁니다. 레이아웃에 대한 좀 더 자세한 내용은 Layout guide를 참조하세요.

Instantiating Components


컴포넌트는 센차터치의 다른 모든 클래스들과 같은 방법으로 생성됩니다. Ext.create 를 사용합니다. 여기 어떻게 Panel 을 생성할 수 있는지 예제가 있습니다.

var panel = Ext.create('Ext.Panel', {
    html: 'This is my panel'
});


이렇게 하면 Panel instance가 생성될 겁니다. 그리고 아주 기본적인 HTML 이 있습니다. Panel은 HTML을 render 할 수 있는 간단한 컴포넌트 입니다. 그리고 다른 아이템들을 포함합니다. 이 경우에 우리는 Panel instance를 생성했지만 아직 스크린에 그 판넬이 보이지는 않을겁니다. 왜냐하면 아이템들은 instantiated 됐다고 그 즉시 render 되는것이 아니기 때문이죠. 이렇게 함으로서 우리는 어떤 컴포넌트들을 생성하고 rendering 하기 전에 움직여서 배치할 수 있습니다. rendering 한 다음에 움직이는 것보다 훨씬 빠른 방법이죠.

이 패널을 스크린에 보이도록 하려면 이것을 global Viewport 에 add 하기만 하면 됩니다.

Ext.Viewport.add(panel);

패널도 또한 컨테이너 입니다. 그러므로 다른 컴포넌트들을 포함할 수 있습니다. 그리고 layout을 통해서 정열 시킬 수도 있죠. 위의 예제를 고쳐보겠습니다. 이번에는 패널을 두개의 child 컴포넌트와 hbox 레이아웃으로 만들어 보겠습니다.

var panel = Ext.create('Ext.Panel', {
    layout: 'hbox',

    items: [
        {
            xtype: 'panel',
            flex: 1,
            html: 'Left Panel, 1/3rd of total size',
            style: 'background-color: #5E99CC;'
        },
        {
            xtype: 'panel',
            flex: 2,
            html: 'Right Panel, 2/3rds of total size',
            style: 'background-color: #759E60;'
        }
    ]
});

Ext.Viewport.add(panel);


이 예제에서 우리는 3개의 panel을 생성했습니다. 첫번째는 저 위의 예제에서와 같이 생성했구요 그 안에 두개의 또 다른 패널들을 생성했습니다. 패널 안의 패널은 xtype을 사용해서 정의했습니다. Xtype은 Ext.create 를 사용하고 전체 클래스 이름을 넣어서 컴포넌트를 만들 필요 없이 간편하게 컴포넌트를 생성할 수 있는 방법입니다. 단지 그 객체 안의 클래스에서 xtype을 넣어주면 됩니다. 그러면 그 프레임워크가 여러분을 위해 컴포넌트를 만들어 드릴 겁니다.

또한 top level 패널에서 레이아웃을 설정할 수 있습니다. 위 예제의 경우는 hbox를 설정했습니다. hbox는 parent 패널의 horizontal 방향으로 화면을 나눕니다. 각 child 들은 flex로 그 크기가 규정됩니다. 예를 들어 parent 패널의 너비가 300픽셀이면 첫번째 child는 100px을 차지할 것이고 두번째 child는 200px을 차지할 겁니다. 왜냐하면 첫번째는 flex: 1 이라고 했고 두번째는 flex:2라고 했으니까요.

Configuring Components

새 컴포넌트를 생성할 때는 configuration option들을 pass 할 수 있습니다. Component에서 사용할 수 있는 configuration들은 해당 class docs page의 "Config options" 섹션에 그 리스트들이 있습니다. 여러분이 컴포넌트를 instantiate 할 때 이 configuration option들을 원하는대로 사용하실 수 있습니다. 그리고 그 이후에 어느때든지 필요하면 그 내용을 수정하실 수도 있습니다. 예를 들어 html content를 생성한 이후 그 내용을 쉽게 수정할 수 있습니다.

//we can configure the HTML when we instantiate the Component
var panel = Ext.create('Ext.Panel', {
    fullscreen: true,
    html: 'This is a Panel'
});

//we can update the HTML later using the setHtml method:
panel.setHtml('Some new HTML');

//we can retrieve the current HTML using the getHtml method:
alert(panel.getHtml()); //alerts "Some new HTML"


모든 config들은 getter 메소드와 setter 메소드가 있습니다. 이 메소드들은 자동적으로 generate 되고 항상 같은 패턴을 유지합니다. 예를 들어 html 이라는 config는 getHtml과 setHtml 메소드를 받습니다. degaultType이라는 config가 있다면 getDefaultType 과 setDefaultType 메소드를 받을 겁니다.

Using xtype

xtype은 full class name 을 사용하지 않고 Component를 생성할 수 있도록 합니다. 특히 child 컴포넌트들을 포함한 컨테이너를 생성할 때 효과적입니다. xtype은 컴포넌트를 specifying 하는 간단한 방법입니다. 예를들어 Ext.panel.Panel 을 타입하는 대신 xtype: 'panel" 이라고 타입 하시면 됩니다.

Sample usage:

Ext.create('Ext.Container', {
    fullscreen: true,
    layout: 'fit',

    items: [
        {
            xtype: 'panel',
            html: 'This panel is created by xtype'
        },
        {
            xtype: 'toolbar',
            title: 'So is the toolbar',
            dock: 'top'
        }
    ]
});


List of xtypes

아래 내용들은 센차 터치 2에서 가능한 모든 xtype들 입니다.

xtype                   Class
-----------------       ---------------------
actionsheet             Ext.ActionSheet
audio                   Ext.Audio
button                  Ext.Button
component               Ext.Component
container               Ext.Container
image                   Ext.Img
label                   Ext.Label
loadmask                Ext.LoadMask
map                     Ext.Map
mask                    Ext.Mask
media                   Ext.Media
panel                   Ext.Panel
segmentedbutton         Ext.SegmentedButton
sheet                   Ext.Sheet
spacer                  Ext.Spacer
title                   Ext.Title
titlebar                Ext.TitleBar
toolbar                 Ext.Toolbar
video                   Ext.Video
carousel                Ext.carousel.Carousel
carouselindicator       Ext.carousel.Indicator
navigationview          Ext.navigation.View
datepicker              Ext.picker.Date
picker                  Ext.picker.Picker
pickerslot              Ext.picker.Slot
slider                  Ext.slider.Slider
thumb                   Ext.slider.Thumb
tabbar                  Ext.tab.Bar
tabpanel                Ext.tab.Panel
tab                     Ext.tab.Tab
viewport                Ext.viewport.Default

DataView Components
---------------------------------------------
dataview                Ext.dataview.DataView
list                    Ext.dataview.List
listitemheader          Ext.dataview.ListItemHeader
nestedlist              Ext.dataview.NestedList
dataitem                Ext.dataview.component.DataItem

Form Components
---------------------------------------------
checkboxfield           Ext.field.Checkbox
datepickerfield         Ext.field.DatePicker
emailfield              Ext.field.Email
field                   Ext.field.Field
hiddenfield             Ext.field.Hidden
input                   Ext.field.Input
numberfield             Ext.field.Number
passwordfield           Ext.field.Password
radiofield              Ext.field.Radio
searchfield             Ext.field.Search
selectfield             Ext.field.Select
sliderfield             Ext.field.Slider
spinnerfield            Ext.field.Spinner
textfield               Ext.field.Text
textareafield           Ext.field.TextArea
textareainput           Ext.field.TextAreaInput
togglefield             Ext.field.Toggle
urlfield                Ext.field.Url
fieldset                Ext.form.FieldSet
formpanel               Ext.form.Panel



Adding Components to Containers

위에서 언급했듯이 컨테이너는 Layout으로 정렬되는 child 컴포넌트를 가질 수 있는 특별한 컴포넌트입니다. 위의 예제에서 이미 1개의 패널 안에 2개의 child 패널을 만드는 방법에 대해서 보여 드렸습니다. 그리고 이 코드를 run time 중에 일어나게 할 수도 있습니다.

//this is the Panel we'll be adding below
var aboutPanel = Ext.create('Ext.Panel', {
    html: 'About this app'
});

//this is the Panel we'll be adding to
var mainPanel = Ext.create('Ext.Panel', {
    fullscreen: true,

    layout: 'hbox',
    defaults: {
        flex: 1
    },

    items: {
        html: 'First Panel',
        style: 'background-color: #5E99CC;'
    }
});

//now we add the first panel inside the second
mainPanel.add(aboutPanel);


이 예제에서는 총 3개의 패널을 생성했습니다. 첫번째로 aboutPanel을 만들었습니다. 이것은 유저에게 앱에 대한 설명을 하는데 사용할 겁니다. 그 다음에 우리는 mainPanel을 생성했습니다. 그리고 마지막으로 이 mainPanel 안에 add 메소드를 사용해서 첫번째 패널인 aboutPanel에 넣었습니다.

이 경우 mainPane을 다른 hbox 레이아웃에 넣었습니다. 그리고 디폴트도 사용했습니다. 모든 아이템들은 패널에 추가 됐습니다. 그러므로 이 경우에는 mainPanel에 있는 모든 child가 flex: 1 configuration을 가지게 됩니다. 그 결과 스크린에 첫번째로 render 되는 것은 현재 mainPanel 입니다. 그러므로 그 child는 full width를 차지하게 될 겁니다. mainPanel.add 라인이 call 될 때 그 안에 aboutPanel 이 render 될 것입니다. 이것은 또한 flex 1이 될 겁니다.

이와 같은 방법으로 컨테이너로부터 아이템들을 remove 하는 것도 아주 쉽습니다.

mainPanel.remove(aboutPanel);

이 코드 이 후는 aboutPanel이 없어져서 display 될 겁니다. 첫번째 child panel이 mainPanel의 전체를 차지하게 되겠죠.

Showing and Hiding Components

센차터치의 모든 컴포넌트는 간단한 API를 사용해서 보이게 하거나 또는 안보이게 할 수 있습니다. 위의 mainPanel 예제를 계속 사용해서 어떻게 안보이게 할 수 있는지를 보여드리겠습니다.

mainPanel.hide();

이렇게 하면 mainPanel이 안 보이게 될 겁니다. 그러면 mainPanel의 안에 있는 모든 child 컴포넌트들도 안보이게 될 겁니다. 아 컴포넌트를 다시 보이게 하는 것도 아주 쉽습니다.

mainPanel.show();

이렇게 하면 다시 mainPanel이 보이게 될 겁니다. 물론 그 안에 있는 컴포넌트들도요.

Events


모든 컴포넌트들은 이벤트를 발생합니다. 여러분들은 이 이벤트를 listen 하고 그 이벤트가 일어나면 특정 action을 하도록 할 수 있습니다 예를 들어 Text field 가 type 될때마다 이 Text field 는 change 이벤트를 발생합니다. 여러분은 리스너의 config 부분을 설정함으로서 간단히 이 이벤트를 listen 할 수 있습니다.

Ext.create('Ext.form.Text', {
    label: 'Name',
    listeners: {
        change: function(field, newValue, oldValue) {
            myStore.filter('name', newValue);
        }
    }
});


이 text field 의 값이 바뀔 때마다 change 이벤트가 발생하고 그러면 지정한 함수를 call 하게 됩니다. 이 경우에는 name에 새로운 값을 저장하게 될 겁니다. 저장하는 것 말고 다른 원하는 동작들을 얼마든지 코딩해 넣을 수 있습니다.

센차터치 컴포넌트들은 수많은 이벤트들을 발생합니다. 여러분은 어플리케이션에서 구현할 필요가 있으면 언제든지 쉽게 그 이벤트들을 catch 해서 이를 핸들링 할 수 있습니다. 이러한 것들은 그 컴포넌트를 생성하고 난 이후에 설정할 수 있습니다.

예를 들어 실시간으로 업데이트 하는 여론조사용 dashboard 가 있다고 합시다. 그런데 이 dashboard가 visible 하지 않을 때는 이 여론조사를 하고 싶지가 않습니다. 여러분은 이 dashboard의 show 와 hide 이벤트를 catch 해서 원하는 기능을 넣으실 수 있을 겁니다.

dashboard.on({
    hide: MyApp.stopPolling,
    show: MyApp.startPolling
});


이렇게 어떤 이벤트를 catch (hook) 하는 방법은 아주 쉽습니다. 더 자세한 사항은 아래 링크를 클릭해서 보세요.


각 컴포넌트들의 class docs 안에 그들이 발생하는 모든 이벤트 리스트들이 있습니다.

Docking


센차터치에는 다른 컨테이너 안에 Component들을 dock 할 수 있는 기능이 있습니다. 예를 들어 hbox layout을 사용하고 윗부분에 banner를 단다고 합시다. 아것을 다른 컨테이너 안에 또 다른 nest 컨테이너를 사용하지 않고 아래와 같이 쉽게 구현할 수 있습니다.




Layout Guide 를 보시면 이 docking에 대한 자세한 설명이 있고 또 다른 layout option들에 대한 설명도 보실 수 있습니다.

Destroying Components

모바일 디바이스의 메모리는 제한된 메모리를 가지고 있습니다. 그래서 컴포넌트들을 더이상 필요로 하지 않게 될 경우 그 컴포넌트들을 destory 시킬 필요가 있습니다. 이 작업은 그 컴포넌트를 생성할 때 할 수 있는 작업은 아니죠. 나중에 기능들이 완료 되고 난 후 필요한 부분에서 예를 들어 앱을 최적화 하는 단계에서 구현하셔야 하는 기능입니다. 컴포넌트를 destroy 하는 방법은 쉽습니다.

mainPanel.destroy();

이렇게 하면 DOM으로부터 mainPanel이 remove 될 겁니다. 그리고 특정 DOM elements 에서 셋업된 이 컴포넌트의 이벤트 리스너들도 remove 될 겁니다. 그리고 이 패널이 내부적으로 사용한 인스턴스들도 destroy 할 겁니다. 그리고 또한 모든 child 컴포넌트들도 destroy 될 겁니다. 이 컴포넌트가 destroy 된 후 그 컴포넌트의 모든 children도 사라지게 되는 것이죠. 그것들은 더이상 DOM에 존재하지 않을겁니다. 그리고 더이상 그 컴포넌트들을 사용할 수 없게 됩니다.


저작자 표시 비영리 동일 조건 변경 허락
신고

Sencha Touch 2 Tutorial - MVC -

2012.04.14 20:44 | Posted by 솔웅


Managing Dependencies with MVC

센차터치 2 앱에서는 종속적인 관계가 정의될 수 있는 곳이 두 군데 있습니다. 한군데는 application 이고요 다른 곳은 application class 입니다. 이 가이드에서는 앱내에서 어디에 어떻게 종속적인 관계를 정의하는지에 대해 설명 드리겠습니다.

Application Dependencies

MVC 어플리케이션을 생성할 때 Ext.application 에 손쉽게 models, views, controllers, stores, profiles 를 지정할 수 있도록 해 줍니다. 아래 예제가 있습니다.

Ext.application({
    name: 'MyApp',

    views: ['Login'],
    models: ['User'],
    controllers: ['Users'],
    stores: ['Products'],
    profiles: ['Phone', 'Tablet']
});


위 예제에 있는 5개의 configuration option 들은 애플리케이션이 주로 models, views, controllers, stores, profiles 로 구성되는데요 그 각각을 별도의 파일로 만들고 그 만든 파일을 로드할 수 있도록 해 줍니다. 위 예제를 실행하면 센차터치는 아래 경로의 파일들을 로드하게 될 겁니다.

app/view/Login.js
app/model/User.js
app/controller/Users.js
app/store/Products.js
app/profile/Phone.js
app/profile/Tablet.js


위 예제는 아래처럼 Ext.requre를 해서 로드할 수도 있습니다.

Ext.require([
    'MyApp.view.Login',
    'MyApp.model.User',
    'MyApp.controller.Users',
    'MyApp.store.Products',
    'MyApp.profile.Phone',
    'MyApp.profile.Tablet'
]);


애플리케이션에 클래스가 추가되면 추가될 수록 처음의 configurations 방법이 전체 클래스이름을 쓸 필요 없이 파일 이름만 쓰면 되므로 점점 더 효율적이 될 겁니다. 이 방법은 단지 파일을 로딩하는 것 이외에 아래와 같은 일도 추가적으로 하게 됩니다.

* profiles - 각 프로파일들의 instantiate 과 active 여부 판별. active 여부가 판별 되면 해당 프로파일을 로드 함
* controllers - 로딩 후 각 Controller instantiate 함
* stores - 각 Store들 instantiate 하고 아무것도 지정된 것이 없으면 디폴트 store ID 를 세팅함


위 기능들을 이용하기 원하시면 첫번째 configuration options 를 사용하시면 좋습니다.





Profile-specific Dependencies

Device Profile을 사용한다면 특정 디바이스에서만 사용되는 clsss 들이 있을겁니다. 예를 들어 태블릿에서는 폰 보다는 좀 더 많은 기능이 들어갈 겁니다. 그 얘기는 곧 태블릿쪽이 좀 더 많은 클래스를 로드해야 한다는 것이죠. 이 필요한 클래스들은 각각의 프로파일 내에서 정의할 수 있습니다.

Ext.define('MyApp.profile.Tablet', {
    extend: 'Ext.app.Profile',

    config: {
        views: ['SpecialView'],
        controllers: ['Main'],
        models: ['MyApp.model.SuperUser']
    },

    isActive: function() {
        return Ext.os.is.Tablet;
    }
});


이렇게 하면 위 프로파일이 active 되면 config에서 설정한 클래스들이 로드되게 됩니다. 위 태블릿 프로파일이 아니라 폰 프로파일이 active 되면 이 클래스들은 로드되지 않고 폰 프로파일에서 설정한 config에 있는 클래스들이 로드 되겠죠.

이렇게 각 디바이스 별로 프로파일을 만들고 그 프로파일 내에서 필요한 클래스들을 로드 하도록 함으로서 여러분들이 만든 애플리케이션이 특정한 디바이스가 아니라 여러 디바이스에서 사용할 수 있게 됩니다. 앱이 시작할 때 각 프로파일을 체크하고 그 프로파일에 필요한 클래스를 로드하기 때문이죠.

만약 앱의 용량이 아주 클 경우에는 이 방법을 사용해서 처음 앱이 실행될 때 그 앱이 실행된 디바이스에 필요한 클래스들만 다운로드 하도록 만들어서 용량을 줄일 수도 있을겁니다.

Nested Dependencies

규모가 큰 앱에서는 models, views, controllers를 subfolder들로 분리해서 프로젝트를 organize 하는것이 일반적입니다. 특히 views 부분이 더 그런데요. 큰 규모의 앱에서는 수백개의 view 클래스가 있는 것이 흔한일입니다. 그래서 그 클래스들을 더 간단하게 관리할 수 있도록 폴더별로 관리하는 것 입니다.

subfolders로 관리하게 되면 코드상에서 그 경로를 . 로 연결해서 사용하면 됩니다.

Ext.application({
    name: 'MyApp',

    controllers: ['Users', 'nested.MyController'],
    views: ['products.Show', 'products.Edit', 'user.Login']
});


이 5개의 파일들은 아래와 같은 경로에서 로드될 겁니다.

app/controller/Users.js
app/controller/nested/MyController.js
app/view/products/Show.js
app/view/products/Edit.js
app/view/user/Login.js


여기서 각각의 configuration 에서 서로 혼합하거나 match 할 수 있습니다. model, view, controller, profile, store 에 대해 클래스 이름의 마지막 부분을 사용하거나(directory convention을 따르고 있다면) 또느 전체 클래스 이름을 사용할 수 있습니다.

External Dependencies

우리는 앱 외부에 있는 클래스를 로드할 수도 있습니다. 이를 위해 사용하는 일반적인 방법은 여러 어플리케이션 사이에서 authentication(인증) 로직을 공유하는 방법이 있습니다. 아마 여러분의 여러개의 앱이 공동의 유저 데이터베이스에 로그할 필요가 있을 수 있을겁니다. 그럴 경우 그 앱들 사이에서 공통으로 인증 로직을 사용해서 접근하면 됩니다. 이를 위한 쉬운 방법은 폴더를 앱 폴더와 따로 만드는 것입니다. 그리고 여러분의 앱에서 이 폴더에 dependency를 주시면 됩니다.

예를 들어 login controller,유저 모델과 로그인 폼 뷰를 포함한 로그인 코드의 경우를 봅시다.  이 모든것을 어플리케이션 안에서 사용하기를 원합니다.

Ext.Loader.setPath({
    'Auth': 'Auth'
});

Ext.application({
    views: ['Auth.view.LoginForm', 'Welcome'],
    controllers: ['Auth.controller.Sessions', 'Main'],
    models: ['Auth.model.User']
});


이럴 경우 아래 파일들을 로드할 겁니다.

Auth/view/LoginForm.js
Auth/controller/Sessions.js
Auth/model/User.js
app/view/Welcome.js
app/controller/Main.js


처음 나오는 세개의 파일이 어플리케이션 밖에 있는 파일을 로드하는 것입니다. 나머지 두개는 앱 내에 있는 파일이구요. 이렇게 함으로서 어플리케이션 파일과 외부의 독립된 파일을 서로 mix하고 match 해서 사용 할 수도 있습니다.

외부 파일을 로드할 수 있도록 하려면 그 파일이 어디에 있는지를 Loader 에게 알려주면 됩니다. 이것은 위 예제에서 Ext.Loader.setPath 에서 한 일입니다. 이 경우 Auth 폴더 안의 Auth namespace로 시작되는 모든 파일을 Loader에게 찾으라고 지시하게 되는 겁니다. 이렇게 함으로서 Auth 코드를 애플리케이션 외부에 따로 떨어뜨려서 관리할 수 있고 그렇게 함으로서 전체 framework이 모든 필요한 것들을 load 하게 됩니다.

Where Each Dependency Belongs


각각의 dependency를 정의하는 장소를 정할 때의 일반적인 룰은 여러분의 클래스들을 완전하게 self-contained 하도록 유지하는 것입니다. 예를 들어 몇개의 다른 뷰를 가지고 있는 한개의 뷰가 있다고 했을 때 그 dependency들은 어플리케이션이 아니라 그 뷰 클래스 안에서 정의해야 합니다.

Ext.define('MyApp.view.Main', {
    extend: 'Ext.Container',

    requires: [
        'MyApp.view.Navigation',
        'MyApp.view.MainList'
    ],

    config: {
        items: [
            {
                xtype: 'navigation'
            },
            {
                xtype: 'mainlist'
            }
        ]
    }
});


app.js 에서는 아래와 같이 해 줍니다.

Ext.application({
    views: ['Main']
});


두가지 이유 때문에 이렇게 dependency들을 정의하는것이 가장 좋은 방법이라고 할 수 있습니다. 첫번째는 MyApp.view.Main을 불러옴으로서 여러분의 app.js 는 간단 명료하게 유지할 수 있고 이러한 dependency들을 이미 다 만족하도록 만들었다는 것을 확인 할 수 있습니다.

//this is bad
Ext.application({
    views: ['Main', 'Navigation', 'MainList']
});


간단하게 생각하면 app.js 안에는 단지 top-level 뷰들만 넣으시면 됩니다. 만약 여러분 앱에 Ext.create('MyApp.view.SomeView')를 사용한다면 그 뷰는 top-level일 겁니다. 한 뷰가 다른 뷰의 sub-view라면 (위 예제에서 MyApp.view.Navigation 과 MyApp.view.MainList 같은) 이 뷰는 app.js에 있을 필요가 없습니다.

Changes since 1.x

센차터치 1 (Sencha Touch 1)에서는 Ext.application call 과 같이 콘트롤러 안에서도 사용되어졌었습니다. 이 방법이 편리한 측면이 있지만 Controller 가 너무 뷰,모델, 스토어들을 통제하는 면이 있기도 합니다. 1.x 에서는 아래와 같이 사용할 수 있습니다.

//1.x code, deprecated
Ext.regController('SomeController', {
    views: ['Login'],
    models: ['User'],
    stores: ['Products']
});

이 방법은 Ext.application에서 뷰,모델,스토어를 정의하는 것과 동일합니다. 콘트롤러에서 이 클래스들에 접근하는데에는 편리합니다. 1.x 는 getLoginView()와 getUsermodel() 두개의 함수를 generate 합니다. 그리고 이 콘트롤러에서 정의한 Stores 에 대한 reference를 return 하는 getStore() 함수를 노출시킵니다. 2.x 에서는 이러한 함수들을 사용하지 않습니다. 두가지를 동시에 사용할 수도 잇습니다.

//creating a view - 2.x uses the standardized Ext.create
this.getLoginView().create();
Ext.create('MyApp.view.Login');

//getting a Model - just type out the Model name (it's shorter and faster)
this.getUserModel();
MyApp.model.User;

//Ext.getStore can access any Store whereas the old this.getStore only
//accessed those Stores listed in your Controller
this.getStore('Products');
Ext.getStore('Products');


이 함수들을 없애면 앱을 처음 시작할 때 로딩 시간을 줄일 수 있습니다. 왜냐하면 framework 이 각 콘트롤러에서 정의한 각 모델,뷰에 대한 함수들을 generate 할 필요가 없기 때문입니다. 또한 이러한 함수들을 사용하지 않으면 MVC 모델에 더 부합하는 설계를 할 수가 있습니다.


저작자 표시 비영리 동일 조건 변경 허락
신고


Routing, Deep Linking and the Back Button

센차 터치 2는 history 와 deep-linking 을 지원합니다. 이것은 여러분의 애플리케이션에 두가지 큰 잇점을 드립니다.

- 앱 내에서 back 버튼을 사용할 수 있고 여기 저기 메뉴(화면)간 이동을 페이지 refresh 없이 정확하고 빠르게 할 수 있습니다.
- deep-linking은 유저가 앱 내의 어떤 부분이든 한번에 이동할 수 있도록 합니다.


이 기능은  native 앱에서 느낄 수 있는 효과를 줍니다. 특히 안드로이드 디바이스에서는 back button 이 있는데 이 기능을 완전히 지원합니다.





Setting up routes

history를 사용하는 방법은 아주 간단합니다. routes라는 개념을 염두에 두고 작업 하셔야 합니다. Route는 url과 콘트롤러 action들과의 간단한 매핑입니다. 해당 Controller action 중에  address bar 에서 특정 형식의 url을 감지하면 자동적으로 이 Route가 call 됩니다. 아래 Controller에 대한 간단한 예제를 살펴보겠습니다.

Ext.define('MyApp.controller.Products', {
    extend: 'Ext.app.Controller',

    config: {
        routes: {
            'products/:id': 'showProduct'
        }
    },

    showProduct: function(id) {
        console.log('showing product ' + id);
    }
});


위와 같이 route를 특정해 줌으로서 Main controller는 브라우저 url 이 #products/123 같은 형식이면 반응하게 됩니다. 예를 들어 여러분의 어플리케이션이 http://myapp.com 에 디플로이 됐다면 http://myapp.com/#products/123, http://myapp.com/#products/456, http://myapp.com/#products/abc 같은 형식의 url 이 되면 자동적으로 showProduct 함수가 call 됩니다.

showProduct 함수가 이렇게 call 되면 url을 제외한 id 토큰을 pass  합니다. route에 :가 포함돼 있으면 :id를 사용하게 됩니다. 그러면 url을 제외한 정보를 가져와서 함수에 전해주게 됩니다. 이 파싱된 토큰은 항상 string 입니다. (왜냐하면 url은 언제나 string 이기 때문입니다.) 그렇기 때문에 http://myapp.com/#products/456 를 입력하는 것은 showProduct('456')을 call 하는 것과 같습니다.

아래와 같이 여러개의 route를 가질수도 있고 route는 여러개의 토큰 가질 수도 있습니다.

Ext.define('MyApp.controller.Products', {
    extend: 'Ext.app.Controller',

    config: {
        routes: {
            'products/:id': 'showProduct',
            'products/:id/:format': 'showProductInFormat'
        }
    },

    showProduct: function(id) {
        console.log('showing product ' + id);
    },

    showProductInFormat: function(id, format) {
        console.log('showing product ' + id + ' in ' + format + ' format');
    }
});


두번째 route는 url이 #products/123/pdf 일 경우 showProductInFormat 함수를 통해서 콘솔에 showing product 123 in pdf 를 찍을 겁니다.

물론 실제로는 콘솔에 문자를 찍는 일은 필요 없겠죠. 그냥 여러분들이 필요한 기능을 이곳에 넣으시면 됩니다. 데이터를 수집한다던가 UI를 업데이트 한다던가 하는 일들을요.

Advanced Routes

디폴트로 라우트 안의 와일드 카드는 어떤 문자나 숫자가 와도 해당됩니다. 즉 products/:id/edit 는 #products/123/edit 과 매치 됩니다. 하지만 #products/a ,fd.sd/edit 와는 매치되지 않습니다. 두번째에 있는 스페이스나 쉼표 그리고 점은 정해 놓은 규칙에 맞지 않기 때문입니다.

경우에 따라서는 이러한 스페이스나 쉼표 점도 url에 매치될 필요가 있습니다. 예를 들어 url이 가져오고 전달할 파일 이름을 포함하고 있는 경우가 그것에 해당 될 겁니다. 이것을 구현하기 위해서는 route에 configuration 객체를 패스할 수 있습니다.

Ext.define('MyApp.controller.Products', {
    extend: 'Ext.app.Controller',

    config: {
        routes: {
            'file/:filename': {
                action: 'showFile',
                conditions: {
                    ':filename': "[0-9a-zA-Z\.]+"
                }
            }
        }
    },

    //opens a new window to show the file
    showFile: function(filename) {
        window.open(filename);
    }
});


action string 대신에 action 프로퍼티를 포함한 configuration 객체를 가지게 되었습니다. 여기에 conditions configuration도 추가했습니다. :filename 토큰에는 어떤 숫자나 문자 그리고 마침표가 올 수 있게 됬습니다. 이렇게 되면 http://myapp.com/#file/someFile.jpg 를 someFile.jpg 를 controller의 showFile 함수에 인자로서 패스하게 됩니다.

Restoring State

history나 deep linking을 지원하려면 유저가 deep link로 어떤 페이지에 가고자 했을 때 그 페이지의 UI 상태가 완전히 어딘가에 restore 돼 있을 필요가 있습니다. 그래야지 그 페이지를 신속하게 보여줄 수 있으니까요. 이것은 약간 트릭으로 보일 수 있지만 좀 더 간편한 애플리케이션 사용을 위해 필요한 사항 입니다.

http://myapp.com/#products/123 이런 식의 url 일 경우 product를 로딩하는 간단한 예제를 만들어 봅시다. 위 예제에서 Products Controller를 약간 수정하겠습니다.

Ext.define('MyApp.controller.Products', {
    extend: 'Ext.app.Controller',

    config: {
        refs: {
            main: '#mainView'
        },

        routes: {
            'products/:id': 'showProduct'
        }
    },

    /**
     * Endpoint for 'products/:id' routes. Adds a product details view (xtype = productview)
     * into the main view of the app then loads the Product into the view
     *
     */
    showProduct: function(id) {
        var view = this.getMain().add({
            xtype: 'productview'
        });

        MyApp.model.Product.load(id, {
            success: function(product) {
                view.setRecord(product);
            },
            failure: function() {
                Ext.Msg.alert('Could not load Product ' + id);
            }
        });
    }
});


끝이 products/:id 일 경우 앱의 메인 뷰(TabPanel 이나 다른 컨테이너 등)에 즉각 view가 추가 됩니다. 그리고 나서 서버로부터 Product를 구하기 위해 product model (MyApp.model.Product)를 사용합니다. 여기에 call back을 추가하고 새로 로드된 Product와 함께 product detail view를 보유 합니다. 즉시 UI를 렌더링 해서 유저에게 가능한한 빨리 비쥬얼한 화면을 보여주도록 합니다. (Product 가 로드돼 있다면 렌더링만 하는것 과 같은...)

deeply-linked view 에 대한 restoring state를 만들려면 각 앱마다 서로 다른 logic을 필요로 하게 될 겁니다. 예를 들어 Kitchen Sink는 NestedList 네비게이션 상태가 restore 될 필요가 있고 해당 url에 맞는 view를 렌더링 해야 할 겁니다. 이 기능이 Phone과 Tablet 프로파일들에서 구현되는지를 보시려면 Kitchen Sink 의  app/controller/phone/Main.js 와 app/controller/tablet/Main.js 파일들의 showView 함수를 살펴 보세요.

Sharing urls across Device Profiles

대부분 여러분은 Device profiles 사이에서 같은 route structure를 공유하기를 원할 겁니다. 이렇게 하면 유저가 폰에서 해당 url을 태블릿을 사용하는 친구에 보낼 수 있고 그 친구가 태블릿에서 제대로 된 정보를 같이 볼 수 있게 될 겁니다. 이렇게 하려면 Phone 과 Tablet-specific Controllers 의 수퍼클래스에 이 route configuration을 정의하는것이 가장 좋은 방법일 겁니다.

Ext.define('MyApp.controller.Products', {
    extend: 'Ext.app.Controller',

    config: {
        routes: {
            'products/:id': 'showProduct'
        }
    }
});


이제 Phone-specific subclass에서는 해당 product에 대한 Phone에 맞는 view를 보여주기 위해 이 showProduct  함수를 implement 할 수 있습니다.

Ext.define('MyApp.controller.phone.Products', {
    extend: 'MyApp.controller.Products',

    showProduct: function(id) {
        console.log('showing a phone-specific Product page for ' + id);
    }
});


Tablet-specific subclass 에서도 같은 작업을 해서 tablet에 맞는 view를 보여줄 수 있습니다.

Ext.define('MyApp.controller.tablet.Products', {
    extend: 'MyApp.controller.Products',

    showProduct: function(id) {
        console.log('showing a tablet-specific Product page for ' + id);
    }
});


이 규칙에는 몇가지 예외가 있습니다.  Kitchen Sink 예제에는 phone 과 tablet 에 맞는 view들이 있습니다. 두 프로파일에서 우리는 NestedList를 사용해서 navigation을 합니다. 하지만 Tablet 에서만 스크린의 왼쪽에 NestedList 가 옵니다. Phone에서는 화면에 꽉 찰겁니다. Phone에서 back 버튼이 원하는대로 작동하게 하려면 매번 NestedList 안에서 navigate 할 때마다 새로운 url을 history에 push 해야 합니다. 이 말은 Phone-specific controller는 추가적인 route를 가진다는 의미입니다. 이 경우를 보시려면 app/controller/phone/Main.js 를 살펴 보시기 바랍니다.


저작자 표시 비영리 동일 조건 변경 허락
신고


Sharing sub views

전 시간 마지막에 다뤘던 예제도 유용하지만 서로 다른 프로파일별로 부분적인 특정 view를 공유하고 또 그 부분적인 view들을 서로 합치고 하는 방법도 일반적으로 사용됩니다. 예를 들어 태블릿 UI의 이메일 앱은 왼쪽에 메세지 리스트가 있고 오른쪽에 현재의 메세지가 나타나도록 스크린을 나눈다고 가정해 봅시다. Phone 버전에서는 똑 같은 message list 와 비슷한 message 뷰가 사용됩니다. 하지만 이 경우 스크린이 태블릿과 비교해서 많이 작기 때문에 card layout에 두가지를 한꺼번에 나타내기가 힘듭니다.

이 문제를 해결하기 위해 우리는 두 개의 서로 공유되는 sub view들을 생성하겠습니다. 메세지 리스트와 메세지 뷰어를 따로따로 sub view로 만들게 되겠죠. 어떤 경우든 class config를 구성해야 합니다.

Ext.define('Main.view.MessageList', {
    extend: 'Ext.List',
    xtype: 'messagelist',

    //config goes here...
});


And the Message Viewer:

Ext.define('Main.view.MessageViewer', {
    extend: 'Ext.Panel',
    xtype: 'messageviewer',

    //config goes here...
});


이제 태블릿 main view 에서는 아래와 같은 식으로 이 sub view를 이용하게 될 겁니다. :

Ext.define('Main.view.tablet.Main', {
    extend: 'Ext.Container',

    config: {
        layout: 'fit',
        items: [
            {
                xtype: 'messagelist',
                width: 200,
                docked: 'left'
            },
            {
                xtype: 'messageviewer'
            }
        ]
    }
});


이렇게 하면 200 픽셀크기의 메세지 리스트가 왼쪽에 나타날거구요 나머지 공간에 메세지 뷰어가 나타날 겁니다. 그럼 이제 phone 의 경우는 어떻게 될지 살펴 보죠.

Ext.define('Main.view.phone.Main', {
    extend: 'Ext.Container',

    config: {
        layout: 'card',
        items: [
            {
                xtype: 'messagelist'
            },
            {
                xtype: 'messageviewer'
            }
        ]
    }
});


이 경우 card layout과 함께 Container를 사용하면 됩니다. (이렇게 되면 한번에 한 아이템만 화면에 나타납니다.) 그리고 나서 리스트와 뷰어를 이 콘테이너에 집어 넣으면 됩니다. 이제 리스트 안의 어떤 메세지를 유저가 tap 했을 경우 메세지 뷰어를 보여주는 기능만 만들면 됩니다 . 이렇듯이  우리는 로드된 프로파일을 근거로 서로 다른 configuration만 함으로서 두개의 sub view를 쉽게 재 사용할 수 있습니다.




Specializing Controllers

View와 마찬가지로 많은 어플리케이션들은 여러 Profile 에서 공유될 수 있는 많은 Controller 로직들이 있습니다. Profile들 사이에서 달라져야 할 점은 대개 workflow와 관련된 것들입니다. 예를 들어 앱의 태블릿 프로파일은 한 페이지로 해결 될 수 있는 workflow가 Phone에서는 여러 화면으로 나누어야 되는 경우가 많습니다.

아래 간단한 Phone 프로파일이 있습니다. 여기서는 Main이라는 view와 Message라는 Controller 를 로드 합니다. 이전에 살펴 봤듯이 이럴경우 실제로는 app/view/phone/Main.js와 app/controller/phone/Messages.js 를 로드할 겁니다.

Ext.define('Mail.profile.Phone', {
    extend: 'Ext.app.Profile',

    config: {
        views: ['Main'],
        controllers: ['Messages']
    },

    launch: function() {
        Ext.create('Mail.view.phone.Main');
    }
});


여기서 폰과 태블릿에서는 대부분 controller들을 공유합니다. 그래서 우선 app/controller/Messages.js 에 controller 수퍼클래스를 생성하겠습니다.

Ext.define('Mail.controller.Messages', {
    extend: 'Ext.app.Controller',

    config: {
        refs: {
            viewer: 'messageviewer',
            messageList: 'messagelist'
        },
        control: {
            messageList: {
                itemtap: 'loadMessage'
            }
        }
    },

    loadMessage: function(item) {
        this.getViewer().load(item);
    }
});


이 Controller는 3가지로 구성돼 있습니다.

- 우리가 다룰 view와 관련해서 refs를 셋업합니다.
- Message List item의 tap 이벤트를 Listening 합니다. tap 되면 loadMessage 함수를 call 합니다.
- loadMessage 함수에서는 선택된 메세지 아이템을 Viewer 에 로드합니다.


이제 Phone 에 맞는 controller 작업을 하겠습니다.

Ext.define('Mail.controller.phone.Messages', {
    extend: 'Mail.controller.Messages',

    config: {
        refs: {
            main: '#mainPanel'
        }
    },

    loadMessage: function(item) {
        this.callParent(arguments);
        this.getMain().setActiveItem(1);
    }
});


보시면 Messages 수퍼클래스 controller를 extend 했습니다. 그리고 두개의 기능을 정의했습니다.

We add another ref for the phone UI's main panel

Phone UI의 panel에 맞게 하기 위해 수퍼클래스와는 다른 ref를 add 했습니다. 원래의 로직을 수행하기 위해 loadMessage 함수를 extend 했고 main 패널의 active 아이템을 message viewer에 셋업합니다. 수퍼클래스의 모든 configuration은 subclass에 의해 inherite 됐습니다. refs 같이 duplicated config가 있을 경우 이 config는 통합됩니다. 그래서 phone Message controller 클래스에는 3개의 refs 가 있게 됩니다. (main,viewer, messageList) 이와 같이 수퍼클래스를 extend 해서 사용할 경우 superclass의 config를 사용할 수 있게 됩니다.

Mail.controller.Messages 수퍼클래스는 애플리케이션이나 프로파일에 의해 사용되거나 변경되지 않았습니다. 이렇게 되면 자동적으로 로드되게 됩니다. Mail.controller.phone.Message controller 가 이것을 extends 했기 때문입니다.

What to Share

위 예제에서 refs 중 일부를 공유할 수 있습니다. 또한 Controller의 control config에 대해 Listen 하는 이벤트를 공유할 수도 있습니다. 일반적으로 프로파일별로 이보다 더 그 로직들이 나눠집니다. refs와 control config 들 중 좀 더 적은 수만이 공유될 겁니다.

모든 프로파일에 걸쳐 공유되어야 할 Controller config는 route 입니다. 이 map url은 콘트롤러 action을 위한 그리고  back button 지원과 좀 더 깊게 menu link 를 하기 위한 겁니다. 수퍼클래스에 route를 가지고 있게 만드는 것은 중요합니다. 왜냐하면 device에 상관 없이 같은 url은 같은 content에 map 되어야 하기 때문입니다.

예를 들어 여러분의 친구가 여러분 앱의 phone version을 사용한다면 그리고 여러분에게 그 친구가 현재 사용하고 있는 앱의 현재 페이지 링크를 send 한다면 태블릿을 사용하고 있는 여러분도 그 링크를 tab 함으로서 같은 내용을 볼 수 있어야 하기 때문입니다. 모든 route들을 수퍼클래스에서 관리하는것은 device에 상관없이 url structure의 consistent를 유지 가능하게 해 줄 겁니다.

Specializing Models

Model 은 Controller와 View에 비해 profile 별로 다른 기능이 적용되어야 하는 경우가 적습니다. 그래서 대개 subclass가 필요하지 않습니다. 이 에제에서는 model을 모든 device에 같이 적용되도록 하나의 클래스로 관리합니다.

Ext.define('Mail.profile.Tablet', {
    extend: 'Ext.app.Profile',

    config: {
        models: ['Mail.model.Group']
    }
});


저작자 표시 비영리 동일 조건 변경 허락
신고
이전 1 2 3 다음