1. 모델클래스(Ext.data.Model)
Model클래스는 우리가 ExtJS에서 사용하는 데이터의 최소 단위라 할 수 있다. Model은 데이터를 표현하기 위한 틀로서 데이터베이스의 Table과 일대일로 매핑된다고 볼수 있다. 우리가 데이터베이스의 어떤 테이블을 읽어올지 관심을 갖듯 ExtJS에서 표현되는 데이터는 어떤 모델을 참조해야할지 관심을 가져야한다. 이는 하나의 엔티티로 애기할 수 있다. 모델은 필드, 검증, 관계 등을 설정할 수 있고 데이터를 읽어오거나 , 수정하거나, 삭제할수 있으며, 입력할수 있다. 하나의 데이터베이스 테이블에서 처리해야할 데이터를 모두 다룰 수 있는 클래스이다.
1-1. 모델의 정의
모델을 정의하기 위해 Ext.data.Model클래스를 상속 받았다. fields설정에 데이터를 표현할 name과 type을 명시하였다.
#1. name은 데이터베이스의 필드명과 동일하게 취급한다. 서버에서 전달한 데이터에 표기한 이름과 동일해야 한다.
#2. type는 해당 데이터의 형식을 말한다. int 뿐만 아니라 string, boolean, date 등이 있다.
Ext.define('Board', {
extend : 'Ext.data.Model',
fields : [ { // #1
name : 'sequence', // #2
type : 'int' // #3
}, {
name : 'title',
type : 'string'
}, {
name : 'userName',
type : 'string'
}, {
name : 'role',
type : 'string'
}, {
name : 'content',
type : 'string'
}, {
name : 'createDate',
type : 'date',
dateFormat : 'Y.m.d'
}, {
name : 'updateDate',
type : 'date',
dateFormat : 'Y.m.d'
}, {
name : 'readCnt',
type : 'int'
}, {
name : 'deleteYn',
type : 'boolean',
defaultValue: false
} ]
});
1-2. 모델의 검증
Model클래스는 데이터를 담는 그릇 역할을 하므로 그릇에 담을 적절한 데이터인지 검증할 수 있도록 validations 설정을 지원한다. 기존 코드에 validations설정을 추가하자.
validations: [
{type: 'presence', field: 'title'}, // #1
{type: 'length', field: 'content', min: 2, max:10}, // #2
{type: 'inclusion', field: 'deleteYn', list: [true, false]}, // #3
{type: 'exclusion', field: 'role', list: ['Admin', 'Manager']}, // #4
{type: 'format', field: 'userName', matcher: /^[ㄱ-힣"'\\{\\}\s]+$/} // #5
]
Model클래스가 지원하는 validations은 위의 코드에서 보듯 5가지이다.
#1. presence : 값이 채워져 있어야 한다.
#2. length : 길이를 통해 검증한다. min은 최소, max는 최대 값으로 이 사이에 있어야 한다.
#3. inclusion : 포함하는지 검증한다. list속성 내부에 포함해야 할 값을 여러 개 입력할 수 있다.
#4. exclusion : 포함하지 않는지 검증한다. list속성 내부에 포함하면 안 되는 값을 여러 개 입력할 수 있다.
#5. format : 정규식을 사용하여 한글만 입력되도록 했다.
모델을 생성하고 각 필드에 데이터를 기입한다.
var board = Ext.create('Board', {
sequence : 1,
title : '안녕하세요^^',
userName : '홍길동2',
content: '게시물 내용을 입력합니다.',
role : 'Admin',
readCnt : 300,
deleteYn: false
});
생성된 모델을 validata메소드를 통해 검증한다. 아래 코드는 검증결과 통과하지 못한 필드가 몇 개인지 알아보기 위해 items.length를 출력해 보았다.
console.log('검증 훌 발견된 오류수 :', board.validate().items.length);
검증에 통과하지 못한 내용을 확인하는 코드이다.
Ext.each(board.validate().items, function(item){
console.log(item)
});
코드를 실행하면 아래와 같이 검증 결과를 확인할 수 있다.
검증 결과 3개의 오류가 발견되었고 각각의 오류를 Ext.each문으로 확인하였다.
발견된 3개의 오류를 수정해 보자. Model클래스는 내용을 확인할 때 get메소드, 수정 할때 set메소드를 사용한다. 이를 이용하여 board 모델 객체의 내용을 수정하여 오류를 정정해 보도록 하자.
board.set('content', '게시물내용 입력'); // #1
board.set('role', 'User'); // #2
board.set('userName', '홍길동'); // #3
console.log('---- 수정 된 내용 확인 ----')
console.log('content : ', board.get('content'));
console.log('role : ', board.get('role'));
console.log('userName : ', board.get('userName'));
var errors = board.validate();
console.log('검증 후 발견된 오류수 :', errors.length, '검증 통과여부 : ', errors.isValid());
Ext.each(errors.items, function(item){
console.log(item)
});
#1. content필드는 길이 검증에 실패하였으므로 최소2, 최대10 안에 들도록 길이를 줄였다.
#2. role필드는 포함되지 않는 조건검증에 실패하였다. ‘Admin’,’Manager’에 포함되지 않도록 ‘User’로 변경하였다.
#3. userName필드는 오직 한글만 입력 받도록 하는 조건 검증에 실패하였으므로 이전 이름에 숫자를 제거하였다.
실행 결과 검증을 모두 통과하고 validations에 아무것도 검색되지 않는 것을 확인했다.
1-3. 프록시를 이용한 입력, 수정하기
모델은 데이터의 최전방에 있는 클래스로 자기 스스로 데이터를 관리할 수 있도록 서버와 통신 할 수 있는 프록시 설정을 가지고 있다. 모델은 데이터베이스의 테이블 정보와 매핑되므로 하나의 모델이 데이터베이스 내부의 하나의 테이블의 데이터를 입력, 수정, 삭제, 읽기 등의 행위를 할 수 있도록 구현된 것이다.
기존 Board모델에 프록시 설정을 추가하자. 프록시는 Ext.data.proxy.Proxy클래스로 데이터 입출력을 담당하며 각종 통신 (Ajax, Rest, JsonP, Direct)을 위한 클래스 이다.
proxy : {
type : 'ajax',
actionMethods : { // #1
read : 'GET', // #2
create : 'POST', // #3
update : 'POST', // #4
destroy : 'POST' // #5
},
api : { // #6
read : 'boards.json?read', // #7
create : 'boards.json?create', // #8
update : 'boards.json?update', // #9
destroy : 'boards.json?destroy' // #10
},
reader : { // #11
type : 'json',
root : 'entitys'
}
}
#1. actionMethods는 CRUD(create, read, update, destroy) 처리 시 통신방식을 설정한다. 통신방식은 GET, POST, PUT, DELETE가 있고 이러한 방식은 주로 RESTFUL통신시 각 액션에 맞도록 설정할 수 있도록 한다.
#2. 데이터를 로딩할 때는 GET방식을 사용한다.
#3,4. 데이터를 입력,수정 처리 할 때는 POST방식을 사용한다.
#5. 데이터를 삭제할 때는 DELETE를 사용한다.
#6. api는 CRUD 처리 시 필요한 서버주소를 각기 명시한다.
#7~10. 데이터를 처리시 필요한 서버의 처리주소다. 주소와 ?뒤의 파라메터는 개발자가 임의로 지정한 것이다.
#11. reader는 api의 read액션 처리 시 필요한 설정으로 읽어오는 데이터의 type과 읽어온 데이터가 복수 개 일 경우 root설정을 추가할 수 있다.
api에 의해 CRUD를 처리할 서버페이지를 작성한다. 아래 코드는 서버가 요청을 처리한 이후 클라이언트에서 응답할 메시지를 출력할 뿐이다.(서버의 처리 로직은 생략한다.)
// boards.json
{entitys: [
{
"sequence": 33,
"title": "ExtJS에 대한 문의",
"content": "ExtJS Model클래스의 Proxy설정에 대해 알아봅니다.",
"userName": "홍길동",
"role": 'User',
"createDate": '2013-12-03',
"updateDate": '2013-12-04',
"readCnt": 230,
"deleteYn": false
}],
success: false
}
이제 모델객체를 생성하고 해당 모델을 저장해 보도록 하자 이때의 저장은 생성 된 모델객체의 데이터를 서버에 전달하는 과정을 의미한다.
var board = Ext.create('Board', {
sequence : 1,
title : '안녕하세요^^',
userName : '홍길동',
content: '게시물 내용을 입력합니다.',
role : 'User',
deleteYn: false
});
저장을 위해 모델클래스의 save()메소드를 호출한다.
board.save({
success: function (record, operation) { // #1
console.log('읽어온 데이터 레코드는 : ', record.data)
},
failure : function(record,options){ // #2
console.log('저장실패');
},
callback: function(){ // #3
console.log('callback 처리 ');
}
});
#1. 서버와 통신이 성공할 경우 호출된다. 첫 번째 인자는 입력된 모델객체를 리턴하는데 서버에서 응답해준 데이터가 있다면(boards.json) 응답한 데이터를 모델객체로 리턴하고 서버에서 전달해준 데이터가 없다면(boards.json내용을 모두 삭제) ExtJS생성한 모델객체를 그대로 리턴한다.
#2. 서버와 통신이 실패한 경우 또는 저장처리 시 사용자에 의해 인위적으로 실패된 경우이다. 인위적인 실패에 경우 boards.json파일 내부의 success: true를 false로 변경할 경우 이 함수가 호출된다.
#3. callback함수의 구현은 ajax통신이 비동기 통신이므로 순차적인 실행이 어려우므로 callback함수를 구현한다. 저장 작업 이후 처리 로직을 구현한다.
아래 그림은 개발자 도구의 Network탭을 통해 서버와 통신내역을 확인 한 것이다.
save메소들 실행한 이후 서버와 통신한 결과를 확인하자. api설정의 4가지(CRUD) 중 create를 실행하였고(boards.json?create) 이후 서버에 모델데이터를 전달하였다. 그렇다면 save메소드는 api 중 create에 해당하는 것일까? 꼭 그렇지만은 않다 save()메소드는 update api에서도 사용한다.
이 때 필요한 속성이 idProperty이다. 이 속성은 데이터를 구분하는 유일한 필드를 설정하는 것으로 데이터베이스에 테이블의 Primary Key와 동일한 개념이다. save()메소드는 idProperty속성이 존재하지 않거나 존재하더라도 모델에 해당 필드의 값이 비어 있을 경우 api의 create를 실행하게 된다. 이와 반대로 idProperty필드에 데이터가 포함되어 있을 경우 update api를 실행시키게 된다.
Board 모델의 유일성을 보장하는 필드는 sequence이다. idProperty속성으로 아래와 같이 지정하자.
Ext.define('Board', {
extend : 'Ext.data.Model',
idProperty: 'sequence',
이제 코드를 실행하자. 아래 그림과 같이 서버에 요청한 url은 boards.json?update..로 api 중 update가 실행 된 것을 확인 할 수 있다. idProperty 설정에 따라 입력과 수정이 결정 됨을 명심하자.
참고 : update 실행 시에는 서버에서 응답한 결과를 create와 달리 반환하지 않음을 알아두자.
1-4. 프록시를 이용한 데이터 읽기, 삭제하기
입력과 수정을 알아보았으니 데이터를 읽고 삭제하는 기능을 구현해 보자.
api에 설정된 read 액션으로 아래 코드와 같이 Ext.ModelMgr클래스의 getModel()메소드로 모델 객체를 생성한다.
var board = Ext.ModelMgr.getModel('Board');
load() 메소드로 idProperty인 sequence값을 전달한다.
board.load(33, {
success: function (record, operation) { // #1
console.log('읽어온 데이터 레코드는 : ', record.data); // #2
}
});
#1. 서버의 요청이 성공하면 success 구문이 실행된다.
#2. 서버에서 응답한(boards.json) 데이터를 출력한다.
읽어오기(load)가 성공하면 success메소드가 실행되고 서버에서 전달해준 데이터를 인자로 전달한다. 전달되는 객체는 모델 객체로 이 객체를 이용하여 다시 서버에 update요청을 할 수 있고 destroy작업을 요청할 수도 있다. 읽기 작업 이후 서버에서 전달받은 모델객체를 통해 삭제작업(destroy)을 진행 해 보자.
success: function (record, operation) {
console.log('읽어온 데이터 레코드는 : ', record.data)
record.destroy({
success: function (record, operation) {
console.log('삭제 후 서버에서 전달한 결과는 : ', record.data)
}
});
}
삭제가 성공하면 읽기와 동일하게 success메소드가 호출되고 서버의 응답이 있다면 모델객체로 반환한다.
이렇듯 하나의 데이터를 읽어오고 삭제하는 작업이 간단히 처리되었다. 모델을 통해 서버에 데이터를 입력처리하고 입력된 데이터를 읽어와 수정하고 삭제 처리가 가능하다.
1-5. 모델간의 관계 설정을 통한 손쉬운 데이터로딩
모델클래스는 데이터베이스 테이블에 비유하였다. RDBMS는 각 테이블 간의 관계를 설정하여 데이터를 표현하고 관리한다. 클라이언트에서 다루는 데이터 또한 데이터베이스의 데이터와 동일하므로 이러한 데이터베이스의 관계설정을 클라이언트에서도 활용할 수 있도록 ExtJS에서도 관계의 설정을 지원하고 있다.
아래 그림과 같이 데이터베이스가 구성되었다고 가정하자. 이러한 데이터베이스 관계를
ExtJS 모델을 통해 구현하도록 한다.
우선 아래 데이터베이스 구조에 대한 간략한 관계정보를 정리해 보자.
테이블명 |
설명 |
Primary Key |
Foreign Key |
|
MemberMaster |
회원정보 |
email |
|
|
MemberDetail |
회원상세정보 |
|
email >> MemberMaster.email |
|
BoardMaster |
게시판 관리 |
boardId |
|
|
BoardContent |
게시물 저장 |
contentId |
boardId >> BoardMaster.boardId createEmail >> MemeberMaster.email |
|
BoardReply |
댓글저장 |
replyId |
contentId >> BoardContent.contentId createEmail >> MemeberMaster.email |
|
ExtJS에서는 모델간의 관계 설정을 위해 아래와 같은 3가지 Type을 지원한다.
- 일 대 다 (Ext.data.HasManyAssociation)
- 다 대 일 (Ext.data.BelongsToAssociation)
- 일 대 일 (Ext.data.association.HasOne)
아래 표는 데이터베이스 테이블간의 관계를 모델의 관계로 풀어서 표현한 것이다. 각 테이블간의 관계는 테이블의 컬럼 정보를 Primary Key와 Foreign Key로 설정하였고 이러한 컬럼 정보는 모델클래스의 field로 전이 되어 각 모델간의 관계를 hasMany, belongsTo, HasOne 등의 3가지로 표현 할 수 있게 된다.
모델관계
|
MemberMaster
|
MemberDetail
|
BoardMaster
|
BoardContent
|
BoardReply
|
hasMany
|
email
|
|
|
createEmail
|
|
hasMany
|
email
|
|
|
|
createEmail
|
hasMany
belongsTo
|
|
|
boardId
|
boardId
|
|
hasMany
belongsTo
|
|
|
|
contentId
|
contentId
|
hasOne
|
email
|
email
|
|
|
|
예제를 통해 3가지 모델관계에 대해 알아보자.
첫 번째는 일대다(Ext.data.HasManyAssociation)관계이다. BoardMaster모델과 BoardContent모델을 구현해 보고 이 두 모델을 일대다(hasMany)관계로 설정하는 코드를 작성한다.
BoardMaster모델 클래스를 작성하자. 위의 ER Diagram을 보고 필드를 추가한다.
Ext.define('MyApp.model.BoardMaster', {
extend: 'Ext.data.Model',
fields: [
{
name: 'boardId',
type: 'int'
},
{
name: 'boardName',
type: 'string'
},
{
name: 'openYn',
type: 'string'
},
{
name: 'createDate',
type: 'date',
dateFormat: 'Y.m.d'
}
]
});
BoardMaster테이블 프라이머리키를 idProperty설정을 통해 설정한다.
Ext.define('MyApp.model.BoardMaster', {
extend: 'Ext.data.Model',
idProperty : ‘boardId’
fields: [
모델 스스로 CRUD가 가능하도록 프록시를 설정한다.
proxy: {
type: 'ajax',
actionMethods: {
read: 'GET',
create: 'POST',
update: 'POST',
destroy: 'POST'
},
api: {
read: 'data/boardMaster.json?read',
create: 'data/boardMaster.json?create',
update: 'data/boardMaster.json?update',
destroy: 'data/boardMaster.json?destroy'
},
reader: {
type: 'json',
root: 'data'
}
}
BoardMaster모델이 스스로 데이터를 읽어올 때 데이터를 전달할 boardMaster.json파일을 아래와 같이 코딩한다. 이 파일에는 BoartMaster테이블과 일대다로 연결되어 있는 BoardContent테이블의 데이터도 함께 제공 해주고 있다.
{
success: true,
data: [
{
"boardId": 100,
"boardName": "ExtJS Q&A",
"openYn": "Y",
"createDate": '2013-12-03',
"contents": [
{
"contentId": 1,
"subject": "ExtJS에 대한 1번 질문입니다.",
"content":"Model에 대한 궁금증.",
"createEmail":"extjs1@mail.com",
"createDate": '2013-12-03'
},
{
"contentId": 2,
"subject": "ExtJS에 대한 2번 질문입니다.",
"content":"Model에 대한 궁금증.",
"createEmail":"extjs1@mail.com",
"createDate": '2013-12-13'
},
{
"contentId": 3,
"subject": "ExtJS에 대한 3번 질문입니다.",
"content":"Model에 대한 궁금증.",
"createEmail":"extjs1@mail.com",
"createDate": '2013-12-02'
}
]
}
]
}
BoardMaster모델을 통해 위의 json파일을 호출해 보도록 하자. 아래 코드는 load메소드를 통해 boardId가 11번에 해당하는 BoardMaster테이블의 데이터를 요청하는 코드이다.
MyApp.model.BoardMaster.load(11, {
success: function (record, operation) {
console.log(record.data)
}
});
위의 코드를 실행하고 결과를 확인하자. boardMaster.json파일 내부의 정보를 읽어와 보여주고 있다. 그러나 하위 테이블인 BoardContent(게시물)데이터는 표시하지 않고 있다. 이는 BoardMaster모델에 boardMaster.json파일 내부의 하위 데이터 필드 contents를 모델의 필드로 갖고 있지 않아서 이다.
BoardMaster모델의 필드에 아래와 같이 contents필드를 추가하고 결과를 확인하자.
Ext.define('MyApp.model.BoardMaster', {
…
{
name : 'contents'
}
],
boardMaster.json내부의 하위 게시물 정보까지 출력되는 것을 확인했다.
위의 경우는 단순 데이터로서 처리한 것으로 모델의 관계설정과는 관련이 없다는 것을 알 수 있다. 이 경우 처럼 하위 데이터를 한번에 상위 데이터와 묶어서 전달해주면 쉽겠지만 각기 다른 데이터베이스 테이블의 데이터를 묶어서 전달하는 일은 서버의 부하와 구현상의 문제로 쉽게 생각할 부분은 아닌 것이다. 해서 각 모델은 독립적으로 데이터를 핸드링 할 수 있고 서로 관계를 설정하여 상호 작용할 수 있어야 하겠다.
이제 BoardContent(게시물)모델을 작성하고 BoardMaster모델과 관계를 설정하도록 한다.
ER Diagram을 보고 BoardContent모델을 정의하자.
Ext.define('MyApp.model.BoardContent', {
extend: 'Ext.data.Model',
idProperty: 'contentId',
fields: [
{
name: 'boardId',
type: 'int'
},
{
name: 'contentId',
type: 'int'
},
{
name: 'subject',
type: 'string'
},
{
name: 'createEmail',
type: 'string'
},
{
name: 'content',
type: 'string'
},
{
name: 'createDate',
type: 'date',
dateFormat: 'Y.m.d'
},
{
name: 'updateDate',
type: 'date',
dateFormat: 'Y.m.d'
},
{
name: 'removeDate',
type: 'date',
dateFormat: 'Y.m.d'
},
{
name: 'readCount',
type: 'int'
},
{
name: 'deleteYn',
type: 'boolean',
defaultValue: false
}
]
});
BoardMaster모델에 아래와 같이 일대다(hasMany)관계설정을 추가한다.
associations: [
{
type: 'hasMany', #1
model: 'MyApp.model.BoardContent', #2
name : 'contents' #3
}
],
#1. 일대다 관계를 의미한다.
#2. associations설정을 가진 BoardMaster모델(일)과 관계(다)를 맺을 모델클래스
#3. name에 설정된 contents라는 이름으로 store객체를 리턴하게 된다. BoardContent모델은 1개 이상의 데이터를 갖고 있어야 하므로 contents는 store를 의미한다.
참고 : Store에 대해서는 이후에 자세히 다루도록 하자. 여기서는 여러 개의 모델 데이터를 표현하기 위해서 모델집합을 갖는 Store클래스를 사용한다는 점만 이해하자.
위의 설정을 통해 모델간의 관계가 설정되어지고 아래 코드와 같이 모델을 통한 관계정보에 접근 할 수 있다. 기존 실행 코드를 아래와 같이 수정하자.
MyApp.model.BoardMaster.load(11, {
success: function (record, operation) {
// record >> MyApp.model.Board모델 객체.(단건의 데이터)
console.log(' BoardMaster >>');
console.log(' boardId : ', record.get('boardId'));
console.log(' boardName : ', record.get('boardName'));
console.log(' BoardContent >>');
// Case1
var associationData = record.getAssociatedData(), idx=0; // #1
Ext.each(associationData.contents, function (content) { // #2
idx++;
console.log(' No(',idx,') >>');
console.log(' contentId : ', content.contentId); // #3
console.log(' subject : ', content.subject); // #4
console.log(' createEmail : ', content.createEmail);
});
// Case2
var associationStore = record.contents(), idx=0; // #5
associationStore.each(function (content) { // #6
idx++;
console.log(' No(',idx,') >>');
console.log(' contentId : ', content.get('contentId')); // #7
console.log(' subject : ', content.get('subject'));
console.log(' createEmail : ', content.get('createEmail'));
});
}
});
#1. BoardMaster모델을 로드하고 이를 통해 관계설정 정보를 변수에 전달한다.
#2. 관계설정 시 사용한 ‘name’으로 접근하면 json파일에서 전달한 하위 데이터에 접근할 수 있다. associationData.content는 배열이다.
#3,4. 하위 데이터 정보를 출력한다.
#5. record.content()메소드는 Store객체를 반환한다. 이는 관계설정시 ‘contents’라는 이름을 사용하였고 ExtJs에서는 이 이름으로 접근할 경우 복수의 모델정보를 담고 있는 Store정보를 리턴하도록 해준다.
#6. Store내부 모델 정보를 each문으로 하나씩 접근하고 #7과 같이 get메소드를 통해 내용을 확인한다.
Case1과 Case2의 결과는 동일하다. getAssociatedData메소드는 다수의 관계설정 정보에 대한 Array데이터 집합을 가진다.
아래 그림을 통해 실행결과를 확인하면 boardMaster.json에서 전달한 두 개의 테이블 정보를 모두 출력하는 것을 볼 수 있다. 이는 모델간의 관계설정으로 인해 두 개의 모델이 연결되었다는 증거이다.
load메소드를 실행 할 경우 아래 그림과 같이 Ajax호출이 이루어지고 전달한 11이라는 id값이 전달되는 것을 볼 수 있다. 이 id값은 모델클래스에 설정된 idProperty:’boardId’를 의미하나 요청 전달 시 boardId가 아니라 id로 전달되는 것을 명심하자.
참고 : 필자는 위와 같은 경우 id라는 이름의 파라메터를 사용하지 않고 idProperty값인 boardId가 전달되도록 하기 위해 override코드를 사용한다. 이 책 실무 코딩 부분에서 코드를 설명하기로 하자.
이번에는 Store를 이용하는 방법을 알아보자. 아직 Store에 대해 배우진 않았지만 Store는 복수개의 모델객체를 담을 수 있는 클래스로 Grid, Tree, View등에서 여러 개의 반복적인 데이터를 표현할 클래스에서 Store를 이용해 데이터를 전달받는다.
BoardMaster모델클래스를 이용하는 Store를 생성한다.
var store = Ext.create('Ext.data.Store', { // #1
model: 'MyApp.model.BoardMaster', // #2
autoLoad: true, // #3
proxy: {
type: 'ajax',
url: 'data/boardMaster.json', // #4
reader: {
type: 'json',
root: 'data'
}
}
});
#1. Store의 클래스명은 Ext.data.Store이다.
#2. 적재할 모델을 명시한다.
#3. autoLoad:true는 생성과 함께 서버에 #4에 해당하는 주소로 요청을 보낸다.
#4. 복수개의 BoardMaster정보를 리턴한다.
autoLoad설정이 true로 생성과 함께 요청을 보내게 하였다. 이러한 과정을 Store가 load한다고 한다. 이 때 load이벤트가 발생하고 이를 리스닝하여 서버에서 전달받은 결과를 해석하고 처리할 수 있다. 아래 코드는 load이벤트를 리스너에 추가하고 반환된 결과를 이전에 배웠던 코드와 동일하게 코딩하였다.
store.on('load', function () { // #1
store.each(function (record) { // #2
console.log(' BoardMaster >>');
console.log(' boardId : ', record.get('boardId'));
console.log(' boardName : ', record.get('boardName'));
console.log(' BoardContent >>');
var associationData = record.getAssociatedData(), idx=0; // Array로 리턴.
Ext.each(associationData.contents, function (content) {
idx++;
console.log(' No(',idx,') >>');
console.log(' contentId : ', content.contentId);
console.log(' subject : ', content.subject);
console.log(' createEmail : ', content.createEmail);
});
var associationStore = record.contents(), idx=0; // Author Store
associationStore.each(function (content) {
idx++;
console.log(' No(',idx,') >>');
console.log(' contentId : ', content.get('contentId'));
console.log(' subject : ', content.get('subject'));
console.log(' createEmail : ', content.get('createEmail'));
});
});
});
#1. load이벤트를 리스닝 한다.
#2. boardMaster.json파일에서 반환한 다수의 결과를 BoardMaster모델로 반환하고 이를 each문으로 풀어낸다.
코드를 실행하면 이전에 보았던 탐색결과와 동일한 결과를 얻을 수 있다. 만약 boardMaster.json파일 내부에 BoardMaster테이블의 정보가 하나만 존재하지만 이를 복사해 2개 이상으로 늘릴 경우 탐색결과 또한 반복적으로 늘어날 것이다.
여기까지는 boardMaster.json파일에서 BoardMaster와 BoardContent 두개의 테이블에 데이터를 한꺼번에 제공해주는 경우를 예로 들었다. 모든 일대다의 관계에 있는 테이블 정보를 한꺼번에 반환하는 것은 서버의 부하나 구현상의 어려움이 있으므로 실무에서는 각 모델이 독립적으로 Proxy를 구현하도록 하여 필요에 따라 상위 모델과 하위모델의 관계설정에 의해 그때 그때 데이터를 로드 할 수 있도록 하는 것이 좋겠다.
아래와 같이 BoardContent(하위 모델)모델이 독립적으로 데이터를 로드 할 수 있도록 프록시를 설정하자.
Ext.define('MyApp.model.BoardContent', {
…
proxy: {
type: 'ajax',
actionMethods: {
read: 'GET',
create: 'POST',
update: 'POST',
destroy: 'POST'
},
api: {
read: 'data/boardContent.json?read',
create: 'data/boardContent.json?create',
update: 'data/boardContent.json?update',
destroy: 'data/boardContent.json?destroy'
},
reader: {
type: 'json',
root: 'data'
}
}
});
게시물 데이터를 제공 할 boartContent.json파일을 준비하자.
{
success: true,
data: [
{
"boardId": 11,
"contentId": 100,
"subject": "ExtJS에 대한 1번 질문입니다.",
"content": "Model에 대한 궁금증.",
"createEmail": "extjs1@mail.com",
"createDate": '2013-12-03'
},
{
"boardId": 11,
"contentId": 200,
"subject": "ExtJS에 대한 2번 질문입니다.",
"content": "Model에 대한 궁금증.",
"createEmail": "extjs1@mail.com",
"createDate": '2013-12-13'
},
{
"boardId": 11,
"contentId": 300,
"subject": "ExtJS에 대한 3번 질문입니다.",
"content": "Model에 대한 궁금증.",
"createEmail": "extjs1@mail.com",
"createDate": '2013-12-02'
}
]
}
이제 BoardContent모델도 스스로 데이터를 로드 할 수 있게 되었다. BoardMaster모델에 설정된 관계를 통해 하위 모델에 접근하는 코드를 만들어 보자.
MyApp.model.BoardMaster.load(11, { // #1
success: function (boardMasterRecord, operation) { // #2
var boardContent = boardMasterRecord.contents(), // #3
boardId = boardMasterRecord.get('boardId'); // #4
console.log(‘상위모델:’, boardMasterRecord.data);
boardContent.load({ // #5
callback: function (boardContentRecord, operation) {// #6
console.log(‘하위모델:’, boardContentRecord.data); // #7
},
failure: function () { // #8
console.log('실패..');
}
});
}
});
#1. BoardMaster모델을 통해 게시판 마스터 정보를 로드한다.
#2. 로드가 성공하는 경우 서버에서 전달받은 데이터를 모델객체로 변환 전달 받는다.
#3. 전달받은 모델객체를 통해 관계설정 중 content라는 이름을 찾고 이를 contents()메소드로 접근하고 있다. 이는 Store객체를 반환하게 된다. 일대다의 관계는 한 개의 모델에 복수개의 모델객체를 담고 있는 Store를 통해 표현 된다. 즉 boardContent변수는 Store객체로 복수개의 모델을 탑재하고 있는 것이다.
#4. 로드 된 모델 객체에서 boardId값을 변수에 전달한다.
#5. boardContent.load는 Store.load()와 동일하다. 즉 Store를 통해 서버에 데이터를 요청하고 있다. 이는 모델에 설정된 프록시 정보를 이용하여 서버와 통신한다.
#6. 데이터로드가 끝나면 필수로 실행된다.
#7. 데이터로드가 실패 시 실행된다.
코드를 실행하고 개발자 도구의 Network탭을 열고 하단 필터를 XHR로 설정하면 2개의 Ajax콜이 이루어진 것을 확인할 수 있다. 첫 번째는 BoardMaster모델에서 한 개의 모델객체를 로드한 것이고 두 번째는 BoardMaster에 설정된 BoardContent모델간의 관계 설정에 의해 Store를 통해 로드한 결과 이다.
개발자 도구의 Console탭을 열고 실행코드에서 원하는 값을 출력하고 있는지 확인하자.
실행 결과를 확인한 결과 이상한 점이 있다. 분명 Ajax전송은 2번에 걸쳐 BoardMaster와 BoardContent 데이터를 가져오는 것으로 확인했다. 그러나 실제 코드에서는 BoardMaster모델이 로드한 결과만 출력 할 뿐 Store를 통해 로드한 boardContent.load()에 대한 callback이 실행되지 않은 것이다. 해답은 BoardMaster모델의 관계설정에 있다. 아래와 같이 BoardMaster모델의 관계설정에 primaryKey를 추가하자. 여기서 추가한 primaryKey는 일대다 관계에서 상위모델의 idProperty 필드를 설정한다.
associations: [
{
type: 'hasMany',
model: 'MyApp.model.BoardContent',
name : 'contents',
// primaryKey는 contents를 통해 ajax load할 경우 필수다.
primaryKey: 'boardId'
}
],
코드를 다시 실행하면 아래 그림과 같이 상위, 하위 데이터를 모두 출력하는 것을 볼 수 있다.
다시 개발자 도구의 Network탭을 확인하자. 2번의 Ajax전송에 차이가 있다. 첫 번째 호출은 id값을 전달하지만 두 번째 호출은 id값을 전달하지 않고 있다. 테스트를 위해 정해진 json파일을 이용하므로 데이터를 반환 받게 되지만 실제 서버로직을 구현하고 DBMS로 하위 테이블(BoardContent)에 대한 쿼리실행에 boardId의 값이 있어야 적절한 데이터를 전달 받을 수 있게 된다.
boardContent데이터를 로드하는 코드를 아래와 같이 수정하자. Store는 load메소드 내부에서 params속성을 통해 서버에 특정 파라메터를 전달 할 수 있도록 한다.
boardContent.load({
params : {
boardId : boardId
},
다시 실행하면 그림과 같이 params에 설정 한 boardId가 서버에 전달된 것을 확인할 수 있다.
이렇듯 일대다 모델간의 관계를 이용하여 상위 모델을 통해 하위모델데이터에 접근할 수 있게되었다.
모델의 관계설정 두 번째로 다대일(Ext.data.BelongsToAssociation) 관계에 대해 알아보자.
BoardMaster와 BoardContent는 일대다 관계를 맺고 있다. 즉 BoardMaster기준으로 다수의 BoardContent데이터에 접근하는 것이다. 이와는 반대로 다수의 BoardContent데이터 기준에서 한 개의 BoardMaster데이터에 접근 해야 하는 경우가 발생한다. 이 경우 모델의 관계설정이 belongsTo 이다. 이 설정은 BoardContent모델에 필요하다.
아래 코드와 같이 모델내부에 관계설정을 정의하자.
Ext.define('MyApp.model.BoardContent', {
….
associations: [
{
type: 'belongsTo', // #1
model: 'MyApp.model.BoardMaster', // #2
foreignKey: 'boardId', // #3
getterName: 'getBoardMaster', // #4
setterName: 'setBoardMaster' // #5
}
]
});
#1. 관계설정 타입을 belongsTo로 설정한다.
#2. 다대일 관계에서 ‘일’에 해당하는 모델의 클래스명을 설정한다.
#3. foreignKey는 관계를 설정 할 모델의 idProperty필드로 BoardMaster모델의 boardId를 설정한다.
#4. getterName은 관계로 설정된 모델에 접근할 때 사용한다. 즉 BoardMaster데이터에 접근할 때 이 이름으로 접근하고 데이터를 얻어온다.
#5. setterName은 BoardContent모델이 가지는 BoardMaster의 idProperty값을 변경할 때 사용한다. 이 부분은 실행코드에서 자세히 설명하기로 하자.
모델에 관계설정이 왼료되었다. 이제 실행코드를 작성하여 모델관계가 잘 작동하는지 확인하도록 하자.
MyApp.model.BoardContent.load(1, { // #1
success: function (record, operation) { // #2
// 실제 ajax콜이 이루어진다.
// book_id를 99로 변경해 서버에 요청한다.
record.getBoardMaster({ // #3
callback: function (record, operation) { // #4
console.log('callback:', record.data);
},
success: function (record, operation) { // #5
console.log('success:', record.data)
},
failure: function (record, operation) { // #6
console.log('failure:', record)
}
});
}
});
#1. BoardContent 모델객체를 로드하자. id값으로 1을 전달한다.
#2. 로드가 성공하는 경우다.
#3. ExtJS는 관계설정 시 사용한 getterName인 getBoardMaster라는 이름으로 메소드를 제공한다. 이 메소드를 호출하여 BoardMaster객체를 반환하게 된다.
#4. callback메소드는 데이터 로드가 완료되면 무조건 실행된다.
#5. success메소드는 데이터 로드가 성공하면 실행된다.
#6. failure메소드는 데이터 로드에 실패할 경우 실행된다.
코드를 실행하면 callback메소드와 success메소드가 실행될 것이다.
아래 그림을 통해 실행결과를 확인하자.
두 개의 모델이 관계설정으로 인해 Ajax호출은 어떻게 되는지 개발자 도구의 Network탭을 확인하도록 하자.
BoardContent모델의 프락시 설정에 의해 boardContent.json파일이 데이터를 제공했다. 이 때 제공한 데이터에 boardId값은 11이다. 이 값은 관계 설정에 의해 BoardMaster모델이 데이터 로드 시 id값으로 제공되게 된다. 즉 관계설정에 의해 2개의 모델이 각각 연관성 있는 데이터를 호출하도록 지원하고 있다.
모델 관계설정의 마지막인 일대일(Ext.data.association.HasOne )에 대해 알아보자. hasOne은 하나의 모델데이터와 다른 하나의 모델데이터가 관계를 맺게 된다.
MemberMaster(회원 마스터)와 MemberDetail(회원 상세)의 관계를 생각하자. 회원의 기본정보를 MemberMaster테이블에 저장하고 아주 상세한 내용의 데이터를 MemberDetail테이블에 저장한다면 이해가 쉽겠다. 두개의 테이블은 email필드를 Primary Key로 사용하므로 동일한 email은 존재하지 않는다.
두개의 테이블에 대응하는 모델클래스를 구현하자.
Ext.define('MyApp.model.MemberMaster', {
extend: 'Ext.data.Model',
idProperty: 'email', // #1
fields: [ // #2
{
name: 'email',
type: 'string'
},
{
name: 'name',
type: 'string'
},
{
name: 'gender',
type: 'string'
},
{
name: 'createDate',
type: 'date',
dateFormat: 'Y.m.d'
}
],
// 관계를 설정
associations: [ // #3
{
type: 'hasOne', // #4
model: 'MyApp.model.MemberDetail', // #5
foreignKey: 'email', // #6. 없다면 id값이 넘어가지 않음.
getterName : 'getMemberDetail' // #6
}
],
// CRUD를 설정한다.
proxy: { // #7
type: 'ajax',
url: 'data/memberMaster.json?read',
reader: {
type: 'json'
}
}
});
위의 코드는 MemberMaster모델 클래스이다.
#1. idProperty로 사용할 필드는 유일한 키인 email필드이다.
#2. fields내부에 필드를 정의한다.
#3. 관계를 설정한다.
#4. hasOne은 일대일 관계로 하나의 모델객체에 다른 하나의 모델객체가 관계를 맺는다.
#5. hasOne관계를 맺을 모델클래스를 명시한다.
#6. 외래키를 설정한다. 이 외래키는 MemberDetail모델에서 주키로 사용할 MemberMaster모델 필드를 의미하므로 명시 된 email필드는 MemberMaster모델의 필드다.
#7. 프록시 정보를 설정한다.
MemberMaster모델이 데이터를 로드 할 때 필요한 memberMaster.json 파일을 작성하자.
{
"success": true,
"email":'master@extjs.com',
"name":"홍길동",
"gender":"M",
"createDate": '2013.01.10'
}
이제 MemberDetail모델클래스를 정의하자. 관계설정(hasOne)으로 MemberMaster를 설정하여 MemberDetail모델객체를 통해 MemberMaster모델을 로드 할 수 있도록 한다.
Ext.define('MyApp.model.MemberDetail', {
extend: 'Ext.data.Model',
idProperty: 'email', // #1
fields: [ // #2
{
name: 'email',
type: 'string'
},
{
name: 'address',
type: 'string'
},
{
name: 'age',
type: 'int'
},
{
name: 'job',
type: 'string'
}
],
// 관계를 설정
associations: [ // #3
{
type: 'hasOne', // #4
model: 'MyApp.model.MemberMaster', // #5
foreignKey: 'email', // #6
getterName : 'getMemberMaster' // #7
}
],
// CRUD를 설정한다.
proxy: {
type: 'ajax',
url: 'data/memberDetail.json?read',
reader: {
type: 'json'
}
}
});
#1. MemberDetail 모델도 MemberMaster모델과 동일한 email필드를 idProperty로 사용한다.
#2. field를 정의한다.
#3. 관계를 설정한다.
#4. 일대일(hasOne)으로 관계타입을 정한다.
#5. 일대일 관계를 맺을 클래스로 MyApp.model.MemberMaster모델을 설정한다.
#6. MemberMaster모델이 참조 할 MemberDetail모델의 필드를 설정한다.
#7. MemberMaster모델에 접근 할 getterName을 설정한다.
MemberDetail모델이 데이터를 로드 할 때 필요한 memberDetail.json 파일을 작성하자.
{
"success": true,
"email":'master@extjs.com',
"address":"서울시 강남구 역상동",
"age":20,
"job":"프로그래머"
}
이제 두 모델 클래스가 완성되었고 서로 일대일(hasOne)관계가 교차되어 맺어졌다. 실행 코드를 작성하여 모델간의 관계설정이 정상적인지 확인하자.
모델 클래스의 load메소드를 실행하여 한 개의 MemberMaster객체를 로드하자.
MyApp.model.MemberMaster.load('master@extjs.com', {
success: function(master){
console.log('master::', master.data);
}
});
success메소드는 로드가 완료된 후 MemberMaster모델객체를 반환한다. 반환된 모델객체를 통해 관계설정의 getterName으로 MemberDetail모델객체를 로드하는 코드를 추가한다.
MyApp.model.MemberMaster.load('master@extjs.com', {
success: function(master){
console.log('master::', master.data)
master.getMemberDetail(function (detail, operation) {
console.log('detail ::', detail.data);
});
}
});
코드를 실행하면 MemberMaster모델 데이터가 로드되고 이후 MemberDetail데이터가 로드 된 것을 확인할 수 있다.
개발자 도구의 Network탭을 열면 두 번의 Ajax호출이 이뤄진 것을 알 수 있다. 각각의 통신은 get방식으로 id파라메터에 master@extjs.com이라는 값을 전달하고 있다. 서버에서는 이 값을 이용해 DBMS를 검색하고 결과를 반환하면 되는 것이다.
이번에는 MemberDetail모델을 통해 MemberMaster모델에 접근해 보도록 하자. MemberMaster의 관계설정 처럼 MemberDetail모델에도 MemberMaster에 대한 관계설정 코드를 구현한 것을 기억 할 것이다.
MyApp.model.MemberDetail.load('master@extjs.com', { // #1
success: function(detail){ // #2
console.log('detail::', detail.data) // #3
detail.getMemberMaster(function (master, operation) { // #4
console.log('master ::', master.data); // #5
});
}
});
#1. load메소드를 통해 MemberDetail모델 데이터를 로드한다.
#2. 데이터 로드가 성공하면 success메소드가 호출된다.
#3. 로드 된 데이터의 내용을 출력한다.
#4. #2에서 반환된 MemberDetail모델 객체를 통해 일대일 관계의 MemberMaster모델 객체를 로드한다.
#5. #4번의 결과 MemberMaster데이터가 로드되었고 데이터 내용을 출력하고 있다.
아래 그림들은 실행결과를 보여준다. 이전 MemberMaster를 통해 MemberDetail모델에 접근한 것과 반대로 실행되는 것을 확인할 수 있겠다.
참고 : 모델을 통해 데이터를 로드 할 때 복수개의 데이터를 전달하더라도 첫 번째 데이터만 인식하고 반환한다. 편의상 json파일이 복수개의 데이터가 제공하더라도 이를 알고 있도록 하자.
관계설정 시 hasMany는 Store객체를 , belongsTo, hasOne는 모델객체를 반환한다. 모델객체는 단일 데이터만 표현하고 복수개의 데이터를 표현 할 경우에는 Store를 사용해야 하기 때문이다.
2. 스토어(Ext.data.Store)
...