이번 포스팅은 Ext JS의 Grid와 Store 에 대한 구조적 원리에 대해서 심도있게 분석한 글로 Ext.data 패키지를 구성하고 있는 기본적인 Store 요소들이 Grid와 어떻게 동작하는 지에 대해서 자세히 설명합니다.


Structure
Ext JS의 Grid 애플리케이션을 만드는 것은 매우 간단하지만 프레임워크가 동작하는 내부 로직은 그렇게 단순하지가 않다.  그래서 각 구성요소들의 연결구조와 동작 방식을 이해하고 고급 웹 애플리케이션을 설계할 수 있도록 알아보자.

Grid 애플리케이션의 기본 컴포넌트인 Ext.grid.GridPanel은 다양한 구성옵션을 가지고 있다.  그중 그리드에 표현될 컬럼과 나열될 데이터 구조체는 필수 옵션이기 때문에 이 두 가지의 역할에 대해서도 잘 이해하고 넘어가야 한다.

 


GridPanel에 데이터를 표시하기 위해서는 일단 Store에 대한 지식이 선행되어야 한다.  

Tip
Ext JS는 GridPanel과 같이 Visual Component Library이나 Store와 같은 구조체를 JavaScript 객체지향 설계를 통하여 4GL(4th Generation Language) 형태로 잘 설계하였다.  이것은 언어(Language)라기 보다는 도구(Tool)의 성격을 가지고 있어 문제해결 방법을 일일이 기술하지 않고 원하는 작업만 명시함으로 자체적으로 필요한 문제를 해결한다.

몇 가지 용어 정의
Data Source : 가공되지 않는 데이터 리소스(JSON, XML, Array) 를 말한다.
Field Set : Store 에서 Data Source를 접근하기 위한 recodeType을 말하며 맵핑 테이블과 같다.
Record : Data Source를 캡슐화한 Ext.data.Record의 인스턴스를 말한다.


Basic Source

//Store 생성
var dataSource = new Ext.data.Store({
//HTTP 를 이용한 데이터 로드
/*url: '/twitter/api/status/user_timeline', //static file*/
proxy : new Ext.data.HttpProxy({  //proxy 클래스를 이용한 데이터 로드
        url		: '/twitter/api/status/public_timeline',
        method	: 'GET'
        }),
reader: new Ext.data.JsonReader( {
idProperty	: 'id',
        root		: 'status',
        totalProperty	: 'total'
} , [ Field Set ] )
});

//그리드 패널 생성
var grid = new Ext.grid.GridPanel({
configuration options,
store		: dataSource,
columns	: [
   { header: 'All Friends', width: 400, dataIndex: 'name' }/*,
   { header: 'Location', width : 200, dataIndex: 'location' }*/
]
});


Store
Ext JS 에서는 JSON, XML과 같은 데이터 구조를 손쉽게 접근하고 제어할 수 있도록 설계되었으며 이러한 데이터를 필요로 하는 다양한 컴포넌트들과 유연하게 동작하기 위해 매우 잘 설계되어있다.

이 클래스는 GridPanel, ComboBox, DataView와 같은 컴포넌트에 삽입할 Data Source 객체의 클라이언트 측 캐시를 캡슐화하는 기본 클래스이다.  캡슐화 한다는 것은 Data Source를 런타임 시에 지정하거나 수동으로 지정할 수 있을 뿐 아니라 서버로부터 변환된 데이터를 사용할 수도 있다.



Data Source를 Store 클래스에 지정하는 3가지 방법
Proxy 구성 옵션을 통한 데이터 소스 지정
var dataSource = new Ext.data.Store({
		url : ‘/server_side.php’	//1. 서버 사이드 호출
		url : ‘data.xml’		//2. Static한 데이터 파일 지정
		proxy : new Ext.data.HttpProxy({	//3. DataProxy를 사용
			url : ‘/server_side.php’
		}),
		reader : new Ext.data.XmlReader({ … })
	});
원격지의 데이터 요소를 로드하려고 할 때 url을 지정하거나 proxy 객체를 이용할 수 있다. 이것은 proxy 객체의 활용방법에서 좀더 자세히 알아보기로 하고 여기에서는 Store에서 원격지 데이터를 클라이언트 측으로 캐시하는 방법을 간단히 소스로 알아본다.


Store에 Configuration option으로 지정
var dataSource = new Ext.data.Store({
		data : [ 
		  { name : ‘rhio’, age : 20 },
		  { name : ‘edina’, age : 21 },
		  { name : ‘chan’, age : 21 }
		],
		reader : new Ext.data.ArrayReader({ … })
	});

Store에 수동으로 데이터 소스 지정
var data = [ 
	{ name : ‘rhio’, age : 20 },
	{ name : ‘edina’, age : 21 },
	{ name : ‘chan’, age : 21 }
];

var dataSource = new Ext.data.Store({
… ,
	reader : new Ext.data.ArrayReader({ … })
});
store.loadData(data);

Store객체에 Data Source 를 위의 3가지 방식을 이용하여 지정하였다면 이 Data Source를 요구에 맞게 판독할 수 있는 Reader클래스 인스턴스를 생성하여 Store의 구성옵션으로 지정하여야 한다.



Reader
Ext JS 에서는 웹 애플리케이션 개발 시 자주 사용하게 되는 Array, JSON, XML 전용 Reader 클래스 ArrayReader, JsonReader, XmlReader를 Ext.data 패키지에 포함시켜 두었습니다.  

이 Reader 클래스들의 예시를 살펴보자.

ArrayReader
//Array 포맷 데이터
var data = [
		[1, ‘rhio’, 20, ‘male’],
		[2, ‘edina’, 21, ‘female’],
		[3, ‘chan’, 31, ‘female’]
];
//레코드 객체 생성을 위한 맵핑 테이블 지정
var friends = Ext.data.Record.create({
		{ name: ‘name’, mapping: 1 },	// 1번째 인덱스의 값을 name Field로 맵핑
		{ name: ‘age’, mapping: 2 },	// 2번째 인덱스의 값을 age Field로 맵핑
		{ name: ‘gender’, mapping: 3 }	// 3번째 인덱스의 값을 gender Field로 맵핑
});
//Array 리더 클래스 생성
var arrReader = new Ext.data.ArrayReader({
		idIndex: 0
}, friends);

JsonReader 
//JSON 포맷 데이터
var data = {
		total : 2000,
		rows : [
			{ id : 1, name : ‘rhio’, age : 20, gender : ‘male’ },
			{ id : 2, name : ‘edina’, age : 21, gender : ‘female’ },
			{ id : 3, name : ‘chan’, age : 30, gender : ‘female’ }
		]
};

var jsonReader = new Ext.data.JsonReader({
		idProperty: ‘id’,
		root: ‘rows’,
		totalProperty: ‘total’,
		[
			id,	//key 가 id인 값을 id Field로 맵핑
			{ name: ‘name’, mapping: ‘name’ },	// key가 name인 값을 name Field로 맵핑
			{ name: ‘age’, mapping: ‘age’ },	// key가 age인 값을 age Field로 맵핑
			{ name: ‘gender’, mapping: ‘gender’ } // key가 gender인 값을 gender Field로 맵핑
		]
]
}, friends);

XMLReader 
var data = ‘
		2000
		
		  1rhio20male


1rhio20male


1rhio20male



var xmlReader = new Ext.data.XmlReader({
		idProperty: ‘id’,
		root: ‘row’,
		totalProperty: ‘total’,
		[
			id, 	//key 가 id인 값을 id Field로 맵핑
			{ name: ‘name’, mapping: ‘name’ },	// key가 name인 값을 name Field로 맵핑
			{ name: ‘age’, mapping: ‘age’ },// key가 age인 값을 age Field로 맵핑
			{ name: ‘gender’, mapping: ‘gender’ }// key가 gender인 값을 gender Field로 맵핑
		]
]
}, friends);

위의 예시에서도 알 수 있듯이 Reader 객체의 생성 방식은 매우 유사하다.  Reader의 경우 이런 일관된 생성과 동작을 위해서 Ext.data.DataReader 추상 클래스(abstract class)를 만들고 이를 확장하여 세가지의 클래스를 구현해 놓았다.  
만약 특수한 데이터 구조를 위해 사용자 Reader 클래스가 필요할 경우에는 이 DataReader 클래스를 상속받는 클래스로 만들어야 할 것이다.

그럼 Reader 클래스의 동작 원리를 살펴보자.


Reader 의 동작 원리

Reader 클래스는 시스템이 데이터 구조를 이해할 수 있도록 판독하는 클래스이기 때문에 Data Source와 구성옵션를 기본적으로 필요로 한다.  위의 소스에서 알 수 있듯이 data와 생성자 함수에 인자로 넘겨주는 구성옵션을 말한다. 

Reader 클래스는 Ext JS의 시스템이 데이터 구조를 이해할 수 있도록 판독하는 클래스이기 때문에 meta 정보와 recordType 두 가지를 필요로 한다.

특히나 meta 정보는 프레임워크 내의 데이터에 쉽고 유연하게 동작하기 위해서 Data Source의 일부 정보를 명시하게 된다.  단 Array 는 JSON과 XML과 같이 key, value의 한쌍으로 이루어진 것이 아닌 index 구조이기 때문에 구성옵션이 조금 차이가 있다.

var reader = new Ext.data.JsonReader( {
idProperty	: 'id',
        root		: 'status',
        totalProperty	: 'total'
} , [ Field Set ] );

예를 들어 데이터가 많아서 페이지네이션을 위해 Ext.PagingToolbar 컴포넌트를 이용할 수 있는데 이때 필요한 정보는 전체 개수와 블록개수이다.  그리고 GridPanel에서는 복잡하게 정의된 Data Source에서 표시해야 할 레코드 영역을 필요로 한다. 
  이렇게 구성옵션에 정의한 ‘root’나 ‘totalProperty’등의 메타정보가 프레임워크 내에서 데이터를 참조하고 유연하게 동작할 수 있는 역할을 한다.

그리고 또 하나의 중요한 것은 recordType 을 지정하는 것이다.  recordType 은 데이터베이스에서 Field 처럼 Data Source에서 Field를 정의하는 객체 배열이다.

 



Record

이것은 Store 객체에서 클라이언트(즉 브라우저)에 캐시된 레코드에 접근하기 위해서 레코드 정보를 정의한 것과 Data Source를 캡슐화한 클래스이다.

Ext.data.Record는 Reader 객체에 전달된 recordType 을 인자로 받아 생성자가 만들어 지는데 이 것들은 구조화 되지 않는 데이터 객체들을 처리가 발생하는 시점에서 오직 Ext.data.Reader의 구현부에 의해서만 인스턴스가 생성되어진다.

예시
// Record 생성자 함수 생성
var FriendRecord = Ext.data.Record.create([ //Ext.data.Record의 서브 클래스 생성
{ id : 1, name : ‘rhio’, age : 20, gender : ‘male’ },
{ id : 2, name : ‘edina’, age : 21, gender : ‘female’ },
{ id : 3, name : ‘chan’, age : 30, gender : ‘female’ }
]);

//Record 인스턴스 생성
var myNewFriend = new FriendRecord (
    {
        id : 4,
        name: 'hans.ryu',
        gender: ‘male’
    }
);
dataSource.add(myNewFriend);

Data Source를 캡슐화한 Record 인스턴스는 데이터베이스의 레코드 객체와 유사하게 제공되어 GridPanel, ComboBox, DataView 등과 같은 컴포넌트에서 요구되는 데이터 구조로 직렬화(serialize)하여 사용하거나 수정, 삭제 등의 처리를 다양한 포맷에 있어서 일관된 기능을 제공한다.



ColumnModel

이렇게 Store의 Proxy를 통해 클라이언트측 캐시에 로드되고 Record를 통해 구조화된 Data Source는 GridPanel에 표시되기 위해서 Ext.grid.ColumnModel 클래스를 구현해 사용하고 있다.

Grid의 ColumnModel 의 각각의 컬럼이 Store의 각 Record에 어떻게 인덱싱 할 수 있는지 보여주기 위하여 dataIndex를 설정하게 된다.

다음 예시는 ColumnModel  클래스가 초기화하는 2가지 방법이다.

Data Source가 배열인 경우의 초기화 방법
var colModel = new Ext.grid.ColumnModel([
   { header : ‘Company’, width : 200, sortable : true },
   { header : ‘Price’, width : 50, sortable : true },
   { header : ‘Change’, width : 50, sortable : true }
   { header : ‘% Change’, width : 50, sortable : true },
   { header : ‘Last Updated’, width : 60, sortable : true }
]);

 

ColumnModel은 Grid에서 컬럼의 첫번째 레이아웃/디스플레이를 정의하기 위해 Ext.grid.Column의 컬럼 설정 개체 배열로 초기화 된다.  Ext.grid.Column 컬럼 구성 객체에는 다양한 속성이 있다.  그 중 구성객체에 hidden 으로 컬럼을 숨기거나 ColumnModel에 포함되지 않은 모든 필드는 모두 표시되지 않을 것이다.

Grid의 각 컬럼이 Store의 Record 와 맵핑되어 데이터를 Grid에 표시하는 것은 dataIndex를 통해서 설정된다. 만약 위의 예시와 같이 명시적인 dataIndex가 정의되지 않았다면 컬럼 모델에 지정한 index를 그대로 적용한다.



Data Source가 오브젝트인 경우의 초기화 방법
컬럼 구성객체 배열은 columns 구성 속성에서 지정 되어지고 defaults 구성 속성은 모든 컬럼에 기본적으로 적용될 속성을 지정한다.

 

var colModel = new Ext.grid.ColumnModel({
   columns : [ 
     { header : ‘Friend’, dataIndex : ‘name’ },
     { header : ‘Age’, dataIndex : ‘age’, width : 30, sortable : true },
     { header : ‘Gender’, dataIndex : ‘gender’, width : 50, sortable : true }
   ],
   defaults : { 
     sortable : true,
     menuDisabled : true,
     width : 100
   }
});


GridPanel 요약

GridPanel에 데이터를 표시하기 위해서 살펴보았다.  사실 GridPanel을 살펴 보았다기 보다 Store 객체에 대해 자세히 살펴보았는데 내용에서도 알 수 있듯이 Store 객체의 구성옵션인 data와 reader에 대해서 알아 보았고 reader에 대해서는 좀더 자세한 동작원리에 대해서 살펴보았다.

요약하자면 GridPanel에 데이터를 표시하기 위해서는 수동이나 런타임에 Data Source를 지정하거나 Proxy를 이용한 원격 데이터를 로드합니다.  이렇게 로드된 Data Source는 GridPanel 이 사용할 수 있도록 규격화가 되어있지 않기 때문에 Array, JSON, XML 형태의 Data Source를 판독하는 Reader 클래스를 중간에 두어 레코드 형태의 규격화를 하도록 합니다.  

규격화를 위해 데이터의 메타 정보(meta)와 레코드 형태(recordType)를 정의하는 객체배열을 인자로 받게 되는데 recordType 은 Ext.data.Record.create에 인자로 전달되어 레코드 형태를 정의한 객체 배열과 Data Source의 해당 레코드 값을 캡슐화한 Record의 서브 클래스가 만들어지고 Store 객체를 통하여 GridPanel의 column 구성옵션에서 지정한 ‘dataIndex’와 reader의 ‘mapping’이 서로 매칭되어 GridPanel에 데이터를 아래와 같이 표시하게 된다.


 



함께 읽어 보세요.
Ext JS 를 이용한 고급 웹 애플리케이션 UI 설계 – Viewport 편 - http://rhio.tistory.com/349
Advanced Application Design in Ext JS – http://rhio.tistory.com/303
VCL(Visual Component Library) in Ext JS – http://rhio.tistory.com/345

Extjs GridPanel 및 데이터 축출하기 - http://techbug.tistory.com/36
Building a Simple GridPanel in Ext JS - http://www.devx.com/webdev/Article/42975
Grid PHP SQL Part1 - http://www.extjs.com/learn/Tutorial:Grid_PHP_SQL_Part1


예시 및 데모
http://www.extjs.com/deploy/dev/examples/#sample-3

신고
Posted by Rhio.kim


티스토리 툴바