들어가며
본 포스팅은 사내(넥스트리소프트) 솔루션 개발시 가이드작성했던 문서를 다시 정리하여 올린것입니다.
기본개념
기본적인 Grid 사용법및 DataStore 및 RowSelectionModel에 대해 알아본다.
GridPanel을 이용하여 데이타스토어에 저장된 데이타를 가져오는 법과 기본적인 그리드 렌더링 방법, 각각의 Row에 대한 Model에서 Data를 축출하는 법에 대해서 기술한다.
아래소스다운로드
Step 1. Basic Array Grid
basicGrid.html
<html>
<head>
<title>Basic Grid</title>
<link rel="stylesheet" type="text/css" href="http://techbug.tistory.com/resources/css/ext-all.css" />
<script type="text/javascript" src="../../adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="../../ext-all.js"></script>
<script type="text/javascript" src="basicGrid.js"></script>
</head>
<body id="basicGridBody">
<div id="DIV_GRID_HERE"></div> <!-- Grid를 렌더링 할 위치 -->
</body>
</html>
basicGrid.js
BasicGridClass = function(){
return {
init: function(){
//데이타스토어에 사용할 데이타
var myData = [
['3m Co',71.72,0.02,0.03,'9/1 12:00am'],
['Alcoa Inc',29.01,0.42,1.47,'9/1 12:00am'],
['Altria Group Inc',83.81,0.28,0.34,'9/1 12:00am'],
['American Express Company',52.55,0.01,0.02,'9/1 12:00am'],
['American International Group, Inc.',64.13,0.31,0.49,'9/1 12:00am'],
['AT&T Inc.',31.61,-0.48,-1.54,'9/1 12:00am'],
['Boeing Co.',75.43,0.53,0.71,'9/1 12:00am'],
['Caterpillar Inc.',67.27,0.92,1.39,'9/1 12:00am'],
['Citigroup, Inc.',49.37,0.02,0.04,'9/1 12:00am'],
['E.I. du Pont de Nemours and Company',40.48,0.51,1.28,'9/1 12:00am'],
['Exxon Mobil Corp',68.1,-0.43,-0.64,'9/1 12:00am'],
['General Electric Company',34.14,-0.08,-0.23,'9/1 12:00am'],
['General Motors Corporation',30.27,1.09,3.74,'9/1 12:00am'],
['Hewlett-Packard Co.',36.53,-0.03,-0.08,'9/1 12:00am'],
['Honeywell Intl Inc',38.77,0.05,0.13,'9/1 12:00am'],
['Intel Corporation',19.88,0.31,1.58,'9/1 12:00am'],
['International Business Machines',81.41,0.44,0.54,'9/1 12:00am'],
['Johnson & Johnson',64.72,0.06,0.09,'9/1 12:00am'],
['JP Morgan & Chase & Co',45.73,0.07,0.15,'9/1 12:00am'],
['McDonald\'s Corporation',36.76,0.86,2.40,'9/1 12:00am'],
['Merck & Co., Inc.',40.96,0.41,1.01,'9/1 12:00am'],
['Microsoft Corporation',25.84,0.14,0.54,'9/1 12:00am'],
['Pfizer Inc',27.96,0.4,1.45,'9/1 12:00am'],
['The Coca-Cola Company',45.07,0.26,0.58,'9/1 12:00am'],
['The Home Depot, Inc.',34.64,0.35,1.02,'9/1 12:00am'],
['The Procter & Gamble Company',61.91,0.01,0.02,'9/1 12:00am'],
['United Technologies Corporation',63.26,0.55,0.88,'9/1 12:00am'],
['Verizon Communications',35.57,0.39,1.11,'9/1 12:00am'],
['Wal-Mart Stores, Inc.',45.45,0.73,1.63,'9/1 12:00am']
];
this.store = new Ext.data.SimpleStore({
fields: [{
name: 'company'
}, //1번째 컬럼을 'company'로 정의
{
name: 'price',
type: 'float'
},//2번째 컬럼을 'price'로 정의하며 데이타 타입은 float으로 정의
{
name: 'change',
type: 'float'
}, {
name: 'pctChange',
type: 'float'
}, {
name: 'lastChange',
type: 'date',
dateFormat: 'n/j h:ia'
}]
});
//데이타 스토어 로드하기
this.store.loadData(myData);
//그리드 그리기
this.grid = new Ext.grid.GridPanel({
store: this.store, //그리드에 사용될 데이타 스토어 정의
columns: [ //그리드 헤드 및 데이타 정제(ColumnModel 정의)
{
id: 'company',
header: "Company",
width: 160,
sortable: true,
dataIndex: 'company'
}, {
header: "Price",
width: 75,
sortable: true,
renderer: 'usMoney',
dataIndex: 'price'
}, {
header: "Change",
width: 75,
sortable: true,
dataIndex: 'change'
}, {
header: "% Change",
width: 75,
sortable: true,
dataIndex: 'pctChange'
}, {
header: "Last Updated",
width: 85,
sortable: true,
renderer: Ext.util.Format.dateRenderer('m/d/Y'),
dataIndex: 'lastChange'
}],
stripeRows: true, //Row마다 CSS class를 적용하고 싶을때 처리
autoExpandColumn: 'company', //정의된 ColumnModel에서 자동으로 늘리고 싶은 컬럼
loadMask: {
msg: '데이타 로드중'
}, //그리드 로드시 화면로딩 indicator설정
//그리드 선택모델(selectionModel)정의 : RowSelectionMode -> Row별로 처리하겠다.
sm: new Ext.grid.RowSelectionModel({
singleSelect: true //Row 하나만 선택가능하게 하기
}),
viewConfig: { //그리드의 Dataview 설정
forceFit: true //가로에 그리드의 크기를 맞춘다.
},
height: 350,
width: 800,
title: '기본기리드'
});
//그리드를 DIV_GRID_HERE 라는 ID값을 가진 객체에 렌더링한다.
this.grid.render('DIV_GRID_HERE');
//그리드 로드시 첫번째 Row를 자동으로 선택되게 한다.
this.grid.getSelectionModel().selectFirstRow();
}
}
}
();
Ext.EventManager.onDocumentReady(BasicGridClass.init, BasicGridClass, true);
Step 2. How to use HttpProxy & ScriptTagProxy
Step1에서 배열데이타를 사용할때(Ext.data.SimpleStore)와 직접 통신하여 사용할때(Ext.data.Store)를 비교하면서 아래 소스를 보기 바란다.
Simplestore 는 기본적으로 네트웍을 사용하지 않으므로 Proxy를 사용하지 않는다. 그러나 Store, JsonStore, GroupingStore의 경우 Proxy를 사용하여 데이타의 위치를 지정할수 있다.
SimpleStore vs. (Json, Grouping) Store
| SimpleStore
| (Json, Grouping) Store
|
Proxy
| 없음(없을시 HttpProxy사용)
| HttpProxy, ScriptTagProxy, MemoryProxy
|
Reader
| ArrayReader
| JsonReader, XmlReader, ArrayReader
|
Load Method
| store.loadData(data);
| store.load()
|
Extend
| Ext.data.Store
| Ext.data.Store
|
basicGrid.js
BasicGridClass = function(){
return {
init: function(){
////DataStore를 정의한다. Ext.data.Store
this.store = new Ext.data.Store({
// Data를 가져올 Proxy설정
proxy: new Ext.data.HttpProxy({
url: 'basicGrid.json',
method: 'POST'
}),
// Client쪽에서 Sort할 경우 소트할 항목
sortInfo: {
field: 'price',
direction: "DESC"
},
reader: new Ext.data.JsonReader({
root: 'testData'
},
[{
name: 'company'
}, {
name: 'price',
type: 'float'
}, {
name: 'change',
type: 'float'
}, {
name: 'pctChange',
type: 'float'
}, {
name: 'lastChange',
type: 'date',
dateFormat: 'n/j h:ia'
}])
});
this.grid = new Ext.grid.GridPanel({
store: this.store,
columns: [{
id: 'company',
header: "Company",
width: 160,
sortable: true,
dataIndex: 'company'
}, {
header: "Price",
width: 75,
sortable: true,
renderer: 'usMoney',
dataIndex: 'price'
}, {
header: "Change",
width: 75,
sortable: true,
dataIndex: 'change'
}, {
header: "% Change",
width: 75,
sortable: true,
dataIndex: 'pctChange'
}, {
header: "Last Updated",
width: 85,
sortable: true,
renderer: Ext.util.Format.dateRenderer('m/d/Y'),
dataIndex: 'lastChange'
}],
stripeRows: true,
autoExpandColumn: 'company',
loadMask: {
msg: '데이타 로드중'
},
sm: new Ext.grid.RowSelectionModel({
singleSelect: true
}),
viewConfig: {
forceFit: true
},
height: 350,
width: 800,
title: '기본 그리드'
});
this.grid.render('DIV_GRID_HERE');
this.grid.getSelectionModel().selectFirstRow();
this.store.load(); //데이타를 로드한다.
}
}
}
();
Ext.EventManager.onDocumentReady(BasicGridClass.init, BasicGridClass, true);
HttpProxy나 ScriptTagProxy로 서버 통신을 한다. ScriptTagProxy에 대한 고찰(?) 을 참조하라.
- HttpProxy : 같은 도에인일 경우
- ScriptTagProxy: 다른 도메인일 경우
- MemoryProxy: 메모리에 있는 데이타
basicGrid.json
{
testData: [{
'company': '3m Co',
'price': 71.72,
'change': 0.02,
'pctChange': 0.03,
'lastChange': '9/1 12:00am'
}, {
'company': 'Alcoa Inc',
'price': 29.01,
'change': 0.42,
'pctChange': 1.47,
'lastChange': '9/1 12:00am'
}, {
'company': 'Altria Group Inc',
'price': 83.81,
'change': 0.28,
'pctChange': 0.34,
'lastChange': '9/1 12:00am'
}, {
'company': 'American Express Company',
'price': 52.55,
'change': 0.01,
'pctChange': 0.02,
'lastChange': '9/1 12:00am'
}, {
'company': 'American International Group, Inc.',
'price': 64.13,
'change': 0.31,
'pctChange': 0.49,
'lastChange': '9/1 12:00am'
}, {
'company': 'AT&T Inc.',
'price': 31.61,
'change': -0.48,
'pctChange': -1.54,
'lastChange': '9/1 12:00am'
}, {
'company': 'Boeing Co.',
'price': 75.43,
'change': 0.53,
'pctChange': 0.71,
'lastChange': '9/1 12:00am'
}, {
'company': 'Caterpillar Inc.',
'price': 67.27,
'change': 0.92,
'pctChange': 1.39,
'lastChange': '9/1 12:00am'
}, {
'company': 'Citigroup,Inc.',
'price': 49.37,
'change': 0.02,
'pctChange': 0.04,
'lastChange': '9/1 12:00am'
}, {
'company': 'E.I. du Pont de Nemours and Company',
'price': 40.48,
'change': 0.51,
'pctChange': 1.28,
'lastChange': '9/1 12:00am'
}, {
'company': 'Exxon Mobil Corp',
'price': 68.1,
'change': -0.43,
'pctChange': -0.64,
'lastChange': '9/1 12:00am'
}, {
'company': 'General Electric Company',
'price': 34.14,
'change': -0.08,
'pctChange': -0.23,
'lastChange': '9/1 12:00am'
}, {
'company': 'General Motors Corporation',
'price': 30.27,
'change': 1.09,
'pctChange': 3.74,
'lastChange': '9/1 12:00am'
}, {
'company': 'Hewlett-Packard Co.',
'price': 36.53,
'change': -0.03,
'pctChange': -0.08,
'lastChange': '9/1 12:00am'
}, {
'company': 'Honeywell Intl Inc',
'price': 38.77,
'change': 0.05,
'pctChange': 0.13,
'lastChange': '9/1 12:00am'
}, {
'company': 'Intel Corporation',
'price': 19.88,
'change': 0.31,
'pctChange': 1.58,
'lastChange': '9/1 12:00am'
}, {
'company': 'International Business Machines',
'price': 81.41,
'change': 0.44,
'pctChange': 0.54,
'lastChange': '9/1 12:00am'
}, {
'company': 'Johnson & Johnson',
'price': 64.72,
'change': 0.06,
'pctChange': 0.09,
'lastChange': '9/1 12:00am'
}, {
'company': 'JP Morgan & Chase & Co',
'price': 45.73,
'change': 0.07,
'pctChange': 0.15,
'lastChange': '9/1 12:00am'
}, {
'company': 'McDonald\'s Corporation',
'price': 36.76,
'change': 0.86,
'pctChange': 2.40,
'lastChange': '9/1 12:00am'
}, {
'company': 'Merck & Co., Inc.',
'price': 40.96,
'change': 0.41,
'pctChange': 1.01,
'lastChange': '9/1 12:00am'
}, {
'company': 'Microsoft Corporation',
'price': 25.84,
'change': 0.14,
'pctChange': 0.54,
'lastChange': '9/1 12:00am'
}, {
'company': 'Pfizer Inc',
'price': 27.96,
'change': 0.4,
'pctChange': 1.45,
'lastChange': '9/1 12:00am'
}, {
'company': 'The Coca-Cola Company',
'price': 45.07,
'change': 0.26,
'pctChange': 0.58,
'lastChange': '9/1 12:00am'
}, {
'company': 'The Home Depot,Inc.',
'price': 34.64,
'change': 0.35,
'pctChange': 1.02,
'lastChange': '9/1 12:00am'
}, {
'company': 'The Procter & Gamble Company',
'price': 61.91,
'change': 0.01,
'pctChange': 0.02,
'lastChange': '9/1 12:00am'
}, {
'company': 'United Technologies Corporation',
'price': 63.26,
'change': 0.55,
'pctChange': 0.88,
'lastChange': '9/1 12:00am'
}, {
'company': 'Verizon Communications',
'price': 35.57,
'change': 0.39,
'pctChange': 1.11,
'lastChange': '9/1 12:00am'
}, {
'company': 'Wal-Mart Stores, Inc.',
'price': 45.45,
'change': 0.73,
'pctChange': 1.63,
'lastChange': '9/1 12:00am'
}]
}
Step 3. Data-Mapping and Renderer
BasicGridClass = function(){
return {
init: function(){
//데이타 스토어 정의
this.store = new Ext.data.Store({
proxy: new Ext.data.HttpProxy({
url: 'basicGrid.json',
method: 'POST'
}),
sortInfo: {
field: 'price',
direction: "DESC"
},
reader: new Ext.data.JsonReader({
root: 'testData'
}, [{
name: 'company',
type: 'string',
mapping: 'company',
convert: this.convertCompany
}, //응답받은 JSON의 company와 맵핑된다. 맵핑정보 변경을 converting한다.
{
name: 'price',
type: 'float',
mapping: 'price'
}, {
name: 'change',
type: 'float',
mapping: 'change'
}, {
name: 'pctChange',
type: 'float',
mapping: 'pctChange'
}, {
name: 'lastChange',
type: 'date',
dateFormat: 'n/j h:ia',
mapping: 'lastChange'
}])
});
this.grid = new Ext.grid.GridPanel({
store: this.store,
columns: [{
id: 'company',
header: "Company",
width: 160,
sortable: true,
dataIndex: 'company'
}, // 맵핑된 company의 name을 dataIndex로 잡는다.
{
header: "Price",
width: 75,
sortable: true,
renderer: 'usMoney',
dataIndex: 'price'
}, {
header: "Change",
width: 75,
sortable: true,
dataIndex: 'change'
}, {
header: "% Change",
width: 75,
sortable: true,
renderer: this.pctChange,
dataIndex: 'pctChange'
}, // 화면에 렌더링할때 renderer를 이용해 칼라를 바꿔준다.
{
header: "Last Updated",
width: 85,
sortable: true,
renderer: Ext.util.Format.dateRenderer('m/d/Y'),
dataIndex: 'lastChange'
}],
stripeRows: true,
autoExpandColumn: 'company',
loadMask: {
msg: '데이타 로드중'
},
sm: new Ext.grid.RowSelectionModel({
singleSelect: true
}),
viewConfig: {
forceFit: true
},
height: 350,
width: 800,
title: '기본 그리드'
});
this.grid.render('DIV_GRID_HERE');
this.grid.getSelectionModel().selectFirstRow();
this.store.load();
},
//화면에 렌더링할때 renderer에 의해서 칼라를 바꿔주느 메쏘드
pctChange: function(val){
if (val > 0) {
return '<span>' + val + '%</span>';
}
else
if (val < 0) {
return '<span>' + val + '%</span>';
}
return val;
},
// 데이타 맵핑할때 자식노드가 있을 경우 해당 자식노드로 변환하여 반환한다.
convertCompany: function(value, p, record){
return (value.name != undefined) ? value.name : value;
}
}
}
();
Ext.EventManager.onDocumentReady(BasicGridClass.init, BasicGridClass, true);
위의 convert 와 renderer 옵션을 이용하여 여러가지 다양한 형태의 grid 생성이 가능하다. 또한 JsonReader의 root 노드또한 Json 표기 형식의 . (dot) 연산자로 해당 자식 노드를 가져올수 있다.
추가적으로 extjs의 API Doc에는 rederer에 대해서 다음과 같이 기술하고 있다.
rederer의 파라미터값들
* value : Object - 셀에 들어가는 데이타
* metadata : Object - 정의한 객체 메타데이타
* css : String : 테이블테그에서 td에 먹이는 css
* attr : String : HTML 애트리뷰트 정의
* record : Ext.data.record : 데이타가 압축된 레코드
* rowIndex : Number
* colIndex : Number
* store : Ext.data.Store
Step 4. DataStore Load & Loadexception Handling
데이타스토어가 모두 로드가 완료됐을 경우 혹은 서버이상으로 로드가 되지 않았을 경우를 예를 들어 설명한다.
BasicGridClass = function(){
return {
init: function(){
//데이타 스토어 정의
this.store = new Ext.data.Store({
proxy: new Ext.data.HttpProxy({
url: 'basicGrid.json'
}),
sortInfo: {
field: 'price',
direction: "DESC"
},
reader: new Ext.data.JsonReader({
root: 'testData'
}, [{
name: 'company',
type: 'string',
mapping: 'company'
}, {
name: 'price',
type: 'float',
mapping: 'price'
}, {
name: 'change',
type: 'float',
mapping: 'change'
}, {
name: 'pctChange',
type: 'float',
mapping: 'pctChange'
}, {
name: 'lastChange',
type: 'date',
dateFormat: 'n/j h:ia',
mapping: 'lastChange'
}])
});
this.grid = new Ext.grid.GridPanel({
store: this.store,
columns: [{
id: 'company',
header: "Company",
width: 160,
sortable: true,
dataIndex: 'company'
}, {
header: "Price",
width: 75,
sortable: true,
renderer: 'usMoney',
dataIndex: 'price'
}, {
header: "Change",
width: 75,
sortable: true,
dataIndex: 'change'
}, {
header: "% Change",
width: 75,
sortable: true,
dataIndex: 'pctChange'
}, {
header: "Last Updated",
width: 85,
sortable: true,
renderer: Ext.util.Format.dateRenderer('m/d/Y'),
dataIndex: 'lastChange'
}],
stripeRows: true,
autoExpandColumn: 'company',
loadMask: {
msg: '데이타 로드중'
},
sm: new Ext.grid.RowSelectionModel({
singleSelect: true
}),
view: new Ext.grid.GridView({
forceFit: true,
enableRowBody: true,
ignoreAdd: true,
emptyText: 'No Record found'
}),
height: 350,
width: 800,
title: '기본 그리드'
});
this.grid.render('DIV_GRID_HERE');
//this.grid.getSelectionModel().selectFirstRow();
this.store.load();
this.grid.on('rowcontextmenu', function(grid, index, e){
alert('오른쪽 버튼 클릭');
}, this);
// Row에서 마우스 오른쪽 클릭시
this.grid.on('rowclick', function(grid, index, e){
alert('클릭');
}, this);
// Row 클릭시
this.grid.on('rowdblclick', function(grid, index, e){
alert('더블클릭');
}, this);
// Row 더블클릭시
this.gsm = this.grid.getSelectionModel();
// 데이타 스토어에서 데이타 로드 완료시 첫번째 Row 선택되게하기
this.store.on('load', this.gsm.selectFirstRow, this.gsm);
this.store.on('load', function(store, records, options){
alert('데이타로드완료')
}, this);
// 데이타 스토어에서 데이타 로드실패시 exception throw
this.store.on('loadexception', function(a, conn, resp){
//alert(resp.status.toString() +'\n'+ resp.statusText);
this.grid.emptyText = 'data load error';
}, this);
// Ext.data.Dataproxy 통신 에러시
this.store.proxy.on('loadexception', function(proxy, dataObj, callbackArgs, e){
if (Ext.gecko)
console.log(e);
});
}
}
}
();
Ext.EventManager.onDocumentReady(BasicGridClass.init, BasicGridClass, true);
Step 5. 선택된 Row에서 값 추출하기
BasicGridClass = function(){
return {
init: function(){
//데이타 스토어 정의
this.store = new Ext.data.Store({
proxy: new Ext.data.HttpProxy({
url: 'basicGrid.json'
}),
sortInfo: {
field: 'price',
direction: "DESC"
},
reader: new Ext.data.JsonReader({
root: 'testData'
}, [{
name: 'company',
type: 'string',
mapping: 'company'
}, {
name: 'price',
type: 'float',
mapping: 'price'
}, {
name: 'change',
type: 'float',
mapping: 'change'
}, {
name: 'pctChange',
type: 'float',
mapping: 'pctChange'
}, {
name: 'lastChange',
type: 'date',
dateFormat: 'n/j h:ia',
mapping: 'lastChange'
}])
});
this.grid = new Ext.grid.GridPanel({
store: this.store,
columns: [{
id: 'company',
header: "Company",
width: 160,
sortable: true,
dataIndex: 'company'
}, // DataIndex정의
{
header: "Price",
width: 75,
sortable: true,
renderer: 'usMoney',
dataIndex: 'price'
}, {
header: "Change",
width: 75,
sortable: true,
dataIndex: 'change'
}, {
header: "% Change",
width: 75,
sortable: true,
dataIndex: 'pctChange'
}, {
header: "Last Updated",
width: 85,
sortable: true,
renderer: Ext.util.Format.dateRenderer('m/d/Y'),
dataIndex: 'lastChange'
}],
stripeRows: true,
autoExpandColumn: 'company',
loadMask: {
msg: '데이타 로드중'
},
//RowSelectionModel 이거나 CellSelectionModel일 경우만 데이타 가져오기
sm: new Ext.grid.RowSelectionModel({
singleSelect: true
}),
view: new Ext.grid.GridView({
forceFit: true,
enableRowBody: true,
emptyText: 'No Record found'
}),
height: 350,
width: 800,
title: '기본 그리드'
});
this.grid.render('DIV_GRID_HERE');
this.store.load();
this.gsm = this.grid.getSelectionModel();
this.store.on('load', this.gsm.selectFirstRow, this.gsm);
// ① Row에서 오른쪽 마우스 클릭했을 경우
this.grid.on('rowcontextmenu', this.getRowData, this);
// ① Row 클릭했을 경우
this.grid.on('rowclick', this.getRowData, this);
// ① Row 더블클랙했을 경우
this.grid.on('rowdblclick', this.getRowData, this);
// ① Row가 선택됐을 경우
this.gsm.on('rowselect', this.rowSelect, this);
},
getRowData: function(thisGrid, rowIndex, eventObject){
// ② 클릭,컨텍스트,더블클릭시 해당 이벤트가 일어난 Row의 Record가져오기
var record = this.store.getAt(rowIndex) || this.gsm.getSelected();
// ③ 선택된 레코드에서 데이타 가져오기
alert(record.data.company + '\n' + record.get('company'));
},
// ④ rowSelectionModel에서 선택된 Row의 데이타 가져오기
rowSelect: function(selectionModel, index, record){
alert(record.data.company + '\n' + record.get('company'));
}
}
}
();
Ext.EventManager.onDocumentReady(BasicGridClass.init, BasicGridClass, true);