본문 바로가기

Sencha Touch 2

9일차 : Sencha Touch 2 Model Store Proxy

Intro 

본격적으로, 서버와의 통신을 위한 지식들을 블로깅 하도록 하겠다.


9일차와 10일차는 model, proxy, store 을 활용해서 어떻게 화면에 보이게 되는지에 대해서 공부를 하게 될 것이다.


먼저 model, proxy, store 이 뭔지 부터 알아보자!


1. Definition

Model : model 은 센차를 이용해서 만들어진 app 이 다루게 될, 혹은 나타내게 될 모든 entity 를 이른다. 가령 우리가 옷을 파는 쇼핑몰 app 을 만들 경우에 옷에 대한 정보를 Product 라고 한다면 이 안에는 size, price, count, color 등등의 다양한 정보들을 가지고 있을 것이다. 이런 하나 하나 들이 모두 model 로 표현이 된다.


Store : model 들의 실제 instance 들을 가지고 있는 공간이다. 대개, store 은 model 의 instance 들이 배열 형태로 배치되있지만, (sorting, filtering, grouping 같은) 다양한 기능들도 제공한다.


Proxy : 실제로 data 들을 load 하고 해당 data 들을 store 에 저장하는 역할을 수행한다. 



2. Model

(오늘은 model 의 가장 기본적인 형태만 살펴보도록 하겠다. 내일은 model 간의 연관 관계를 정의하는 법과, model 의 field 들 별로 가지고 있는 값들이 유효한 값을 가질 수 있도록 조건을 거는 방법을 배울 것이다.)

앞에서도 설명 했듯이 model 은 app 이 관리하는 모든 entity 들을 표현할 떄 사용된다. 가령 Users, Students, Schools, Players, Cities 등등이 될 수 있을 것이다.


2.1 Model define

Model 을 정의(define) 하는 방법은, 우리가 이전에 Ext.define 을 사용했던 것과 거의 일맥 상통한다.

먼저 아래 코드를 보자.

Ext.define('User', {
    extend: 'Ext.data.Model',

    config: {
        fields: [
            {name: 'name',  type: 'string'},
            {name: 'age',   type: 'int'},
            {name: 'phone', type: 'string'},
            {name: 'alive', type: 'boolean', defaultValue: true}
        ]
    },

    changeName: function() {
        var oldName = this.get('name'),
            newName = oldName + " The Barbarian";

        this.set('name', newName);
    }
});

'User' 라는 이름의 model 을 define 하고 있다. 해당 model 은 name, age, phone, alive 라는 field 를 가지도록 선언했다. 위에서 보는 것처럼 model 이 field 를 가지게 하기 위해선 config 속성 안에서 fileds 라는 속성에 배열 형태로 주게 된다. 각 field 별로 name 을 줄 수 있고 해당 field 의 type 을 줄 수 있으며, 또한 해당 field 의 defaultValue 값을 줄 수 있다.

fields 속성 안에서 각 field 들을 정의할 때, type 이 가질 수 있는 값은 string, int, boolean, float 이렇게 총 네 가지 이다. (fields 배열은 MixedCollection 으로 자동적으로 변환이 되는데, MixedCollection 이란 key - value 의 형태로 값을 가지는 것을 말한다.) 

(http://docs.sencha.com/touch/2-0/#!/api/Ext.util.MixedCollection)

field 값 뿐 아니라 method 도 정의 할 수 있다. (method 가 정의된 위치를 잘 보면 config 속성 '밖' 인걸 알 수 있다. 프로그래머가 직접 메소드를 정의하고 싶을 경우, model 뿐 만 아니라 다른 class 들의 경우에도, config 속성 밖에서 정의를 해 주면 된다.)


2.2 Model instace 만들기

위에서 정의한 Model 을 바탕으로 instance 를 만드는 코드는 아래와 같다.

var user = Ext.create('User', {
    name : 'Conan',
    age  : 24,
    phone: '555-555-5555'
});

user.changeName();
user.get('name'); //returns "Conan The Barbarian"

지금까지 우리가 해왔던 것처럼, 여기서도 동일하게 Ext.create 를 사용해서 만든다. 이 때 첫 번째 전달인자는, 어떤 Model 의 타입으로 만들 것인지를 결정해주기 위해 model 이름을 넘겨주고, 두 번째 는 해당 model 의 각각의 field 의 값을 무엇으로 줄 것인지를 객체 형태로 전달한다.

보통, 직접 Model instace 를 create 할 일은 잘 없다. 그 보다 이제 배울 store 과 함께 model 이 쓰이게 된다.



3. Store

위에서 배운 model 은 거의 대부분이 store 과 함께 사용된다. 앞에서 설명 한 것 처럼, store 의 주 목적은 model 의 실제 instance 들을 가지고 있는 것이다. store 는 proxy 와 함께 사용된다. proxy 는 데이터를 주고 받는 것이 어디며 어떻게 하는지를 알고 있기 때문에 store 은 proxy 를 통해 데이터를 읽어와서, 읽어온 데이터를 나타낼 수 있는 model 의 instance 형태로 저장한다.

아래 코드를 보자.

// Set up a model to use in our Store
Ext.define('User', {
    extend: 'Ext.data.Model',
    config: {
        fields: [
            {name: 'id', type: 'int'},
            {name: 'name',  type: 'string'},
            {name: 'age',       type: 'int'},
            {name: 'gender',  type: 'string'}
        ]
    }
});

var myStore = Ext.create('Ext.data.Store', {
    model: 'User',
    proxy: {
        type: 'ajax',
        url : './data/persons.jsp',
        reader: {
            type: 'json',
            rootProperty: 'users'
        }
    },
    autoLoad: true
});

store 내부에 model 이라는 속성이 있는데, 여기에는 이 전에 우리가 정의한 User 라는 Model 을 사용하겠다고 명시한다. 이렇게 함으로써, 지금 우리가 만드는 store 는 'User' model 의 instance 들을 저장하게끔 한다. 

proxy 는 앞에서도 설명했듯이, 어디와 어떻게 작업할 것인지에 대한 정보를 알고 있어야 한다. 그러므로 proxy 속성에서 type 을 ajax 로 줘서, asynchronous(비동기적) 하게 데이터를 주고 받게 된다는 것과, 그 위치는 ./data/persons.jsp 라고 명시를 해 준다.

reader 는 proxy 가 데이터를 읽을 때 필요한 정보를 제공해 준다. 위의 코드에 의하면, proxy 가 읽어들일 데이터의 타입은 json 이며, rootProperty, 즉, 최상위 필드가 persons 라는 의미이다. reader 의 type 과 rootProperty 속성을 이렇게 주는 것은, 센차에서 정한 것이 아니라, app 을 개발하면서 서버쪽 개발자와 서로 협의 하에 '데이터를 이러이러하게 넘겨줄께!' 하고 결정되고 나면 그에 따라서 type 과 rootProperty 를 주게 되는 것이다.

그러므로 ./data/persons.jsp 파일을 열어보면 아래와 같다.

<%@ page contentType="text/xml;charset=utf-8"  pageEncoding="euc-kr" %>
{
	"persons": [
		{
			"id":	1,
			"name": "ㄴ하하하",
			"age": 27,
			"gender": "male"
		},
		{
			"id":	2,
			"name": "만수무강",
			"age": 127,
			"gender": "male"
		}
	]
}

autoLoad: true 는 store 이 만들어 지자 말자 데이터를 load 하겠다는 뜻이다.

위에서는 그렇게 하지 않았지만, 서버에서 json 형태로 데이터를 줄 때, 아래와 같은 뼈대로 값을 줘야 한다.

{
    success: true,
    users: [
        { id: 1, name: 'Ed' },
        { id: 2, name: 'Tommy' }
    ]
}

즉, success 속성을 줘서, 제대로 데이터를 리턴할 경우에는 true, 그게 아니라 데이터 access 에 문제가 생겼을 때는 false 를 리턴하도록 해야 한다.

그리고 그 밑에, users 속성을 주고, 거기서 부터 전달하려는 데이터를 주면 된다.



3.1 Inline data

다른 곳에서 뭔가를 읽어와서 store 에 data 를 저장하는 것이 아니라, 바로 store 에 데이터를 넣어 줄 수 있다. 아래 코드를 보자.

Ext.create('Ext.data.Store', {
    model: 'User',
    data : [
        {firstName: 'Ed',    lastName: 'Spencer'},
        {firstName: 'Tommy', lastName: 'Maintz'},
        {firstName: 'Aaron', lastName: 'Conran'},
        {firstName: 'Jamie', lastName: 'Avins'}
    ]
});



3.2 storeManager 에 store 등록하기

store 을 만들 때 storeId 속성에 값을 주게 되면 storeManager 에 등록하게 된다. 일단 등록되면, 같은 store 을 계속해서 재사용 할 수 있다.

아래 코드는 storeId 로 어떻게 재사용 할 수 있는지를 보여준다.

//this store can be used several times
Ext.create('Ext.data.Store', {
    model: 'User',
    storeId: 'usersStore'
});

new Ext.List({
    store: 'usersStore',

    //other config goes here
});

new Ext.view.View({
    store: 'usersStore',

    //other config goes here
});

위의 방법 말고도, 

Ext.getStore() 을 써서도 storeId 값을 가지고 해당 store 을 찾을 수 있다. 

(http://docs.sencha.com/touch/2-0/#!/api/Ext-method-getStore)



4. Proxy

proxy 는 크게 client 와 server, 이렇게 두 개의 타입으로 나뉜다. 

각 타입을 확인하기에 앞서서 proxy 는  Model 에 포함해서 쓸 수 있다. 이렇게 했을 때 장점은, 모델에 proxy 가 정의되어 있기 때문에 여러 store이 같은 model 을 쓰게 될 경우 따로 중복해서 proxy 를 지정하지 않아도 된다. 또 다른 장점은, store 을 선언하지 않고도 바로 load 와 save 를 호출해서 서버와 데이터를 주고 받을 수 있다는 점이다. (그러나 model 에 proxy 를 선언할 것인지, 아니면 store 에 쓸 것인지는 일장일단 이기 때문에 그 때 상황에 맞추어 잘 사용하길 바란다)

아래 예제는 Model 에서 바로 proxy 를 선언한 예이다.

Ext.define('User', {
    extend: 'Ext.data.Model',

    config: {
        fields: ['id', 'name', 'age', 'gender'],
        proxy: {
            type: 'rest',
            url : '/data/users.json',
            reader: {
                type: 'json',
                root: 'users'
            }
        }
    }
});

// Uses the User Model's Proxy
Ext.create('Ext.data.Store', {
    model: 'User'
});


아래 예제 코드는 store 없이 Model 에서 바로 데이터를 받는 예제를 보여준다.

// Gives us a reference to the User class
var User = Ext.ModelMgr.getModel('User');

var ed = Ext.create('User', {
    name: 'Ed Spencer',
    age : 25
});

// We can save Ed directly without having to add him to a Store first because we
// configured a RestProxy this will automatically send a POST request to the url /users
ed.save({
    success: function(ed) {
        console.log("Saved Ed! His ID is "+ ed.getId());
    }
});

// Load User 1 and do something with it (performs a GET request to /users/1)
User.load(1, {
    success: function(user) {
        console.log("Loaded user 1: " + user.get('name'));
    }
});


4.1 client

4.1.1 memory proxy : 웹 브라우저의 메모리를 활용한다. 

memory proxy 는 웹 브라우저 메모리 공간을 잠시 활용 한다. 휘발성이기 때문에 이 때 이 proxy 가 사용하는 data 들은 페이지가 바뀔 때 사라진다.

inline data 를 쓰고 싶은데 딱 model 에 정확히 mapping 되지 않는 데이터의 경우 memory proxy 를 써서 사용한다.

아래 코드를 보자.

//this is the model we will be using in the store
Ext.define('User', {
    extend: 'Ext.data.Model',
    config: {
        fields: [
            {name: 'id',    type: 'int'},
            {name: 'name',  type: 'string'},
            {name: 'phone', type: 'string', mapping: 'phoneNumber'}
        ]
    }
});

//this data does not line up to our model fields - the phone field is called phoneNumber
var data = {
    users: [
        {
            id: 1,
            name: 'Ed Spencer',
            phoneNumber: '555 1234'
        },
        {
            id: 2,
            name: 'Abe Elias',
            phoneNumber: '666 1234'
        }
    ]
};

//note how we set the 'root' in the reader to match the data structure above
var store = Ext.create('Ext.data.Store', {
    autoLoad: true,
    model: 'User',
    data : data,
    proxy: {
        type: 'memory',
        reader: {
            type: 'json',
            root: 'users'
        }
    }
});

위의 코드에서 Model 의 field 들과 data 의 field 들이 다르다. 이 때는 memory 공간에 data 를 올리고, fields 에서 mapping 이라는 것을 사용해서 데이터를 넣어주도록 한다. 

inline data 예제 코드에서도 data 가 사용됐고, 바로 위 코드에서도 data 속성을 사용했는데, data 속성에 들어갈 수 있는 값은 Model instance 들의 배열이거나 아니면 지역적으로 load 되야 하는 data objects 들 이어야 한다.


4.1.2 LocalStorageProxy : client browser 에 저장하기 위한 용도로 사용. 

LocalStorage 는 HTML5 에서 새로 나온 localStorage API 를  사용한다. local storage 는 사용자의 browser 에 저장하는데, key-value 형태로 저장을 한다. (그러므로 JSON 같은 복잡한 형태의 데이터 형태로는 저장할 수 없기 때문에, localStorage 는 데이터를 저장하거나 다시 뽑아 올 때, 각각 serialize 와 deserialize 를 시켜준다.)

아래 예제 코드를 보자. 아래의 예제는 트위터에서 이전에 어떤 것을 검색했는지 정보를 저장해서, 나중에 다시 같은 내용으로 검색할 때 쉽게 검색할 수 있도록 해주기 위한 간단한 예제 코드다.


먼저 Model 을 정의하자.

Ext.define('Search', {
    extend: 'Ext.data.Model',
    config: {
        fields: ['id', 'query'],
        proxy: {
            type: 'localstorage',
            id  : 'twitter-Searches'
        }
    }
});

Search model 에서 field 는 id 와 query, 두 개가 있다.

proxy 의 type은 localstorage 이고, id 에 값을 줬다. 여기서 id 가 매우 중요한데, 현재 이 proxy 에 있는 Model data 를 다른 것들과 구분 짓기 위함이다. localStorage API 는 하나의 공유되는  namespace 에 모든 데이터를 저장하기 때문에 id 를 줘야 localStorage 에 있는 다른 데이터들과 구분할 수 있다.

localStorage 에 데이터를 저장하는 방법은 아래와 같다.

//our Store automatically picks up the LocalStorageProxy defined on the Search model
var store = Ext.create('Ext.data.Store', {
    model: "Search"
});

//loads any existing Search data from localStorage
store.load();

//now add some Searches
store.add({query: 'Sencha Touch'});
store.add({query: 'Ext JS'});

//finally, save our Search data to localStorage
store.sync();

add 할 때 검색 관련 데이터를 넣는데 (예를 들어 {query: 'Ext JS'}), Model 에서 id 라는 filed 가 있는데 id 는 주지 않고, query 값만 준 이유는, store.sync() 를 호출하는 순간 id 값을 자동적으로 주기 때문이다. 


4.2 client

4.1.1 AjaxProxy : 서버로 부터 데이터를 요구하기 위해 AJAX request 를 사용.

실제로 해당 proxy 를 사용하는 것은 어렵지 않다.

아래 코드를 보자.

Ext.define('User', {
    extend: 'Ext.data.Model',
    config: {
        fields: ['id', 'name', 'email']
    }
});

// The Store contains the AjaxProxy as an inline configuration
var store = Ext.create('Ext.data.Store', {
    model: 'User',
    proxy: {
        type: 'ajax',
        url : 'users.json'
    }
});

store.load();

User 라는 model 을 정의한 뒤, fields 값에 id, name, email 이라고 줬다. 그리고 store 을 만들어서, User 라는 model 타입의 instance 를 저장을 할 것이라고 알려 주고, proxy 의 type 에는 ajax 를, url 에는 users.json 으로 부터 데이터를 읽어 와야 한다고 명시를 했다. 그리고 store.load() 를 해서 데이터를 실제로 load 하고 있다.  

proxy 에 전달된 속성들({type: 'ajax', url: 'users.json'}) 은 센차에서 자동적으로 Ext.data.proxy.Ajax 의 instance 로 만들어 준다. 그래서 사실 위에서 proxy 를 만드는 과정은 실제로 아래 코드를 실행하는 것과 같다.

Ext.create('Ext.data.proxy.Ajax', {
    config: {
        url: 'users.json',
        model: 'User',
        reader: 'json'
    }
});

여기서 갑자기 model 과 reader 가 나왔는데, 이는 Store 에서, 자기에게 전달된 proxy 속성을 가지고 ajax proxy 를 만들 때 default 로 넣어주는 값이다. Store 에 이미 Model 이 전달되 있기 때문에 자동적으로 proxy 에도 model 이 추가가 된 것이고, reader 같은 경우에는 따로 명시를 하지 않으면, JsonReader 가 default 가 된다. (XmlReader 라는 것도 있다.)


이제 다시 그 위 코드로 가서 store.load() 가 실행되면, 전달된 url 주소로 가서 데이터를 읽어온다. store 에서 데이터를 읽거나 쓸 때, 기본적으로 정해진 방식이 있다. 데이터를 읽는 경우에는 GET request 를 보내게 되고, 쓰는 경우에는 POST request 를 보내게 된다.


4.1.2 JsonP 라는 것이 있는데 그건 나중에..

4.1.3 Resf 라는 것이 있는데 그것도 나중에..