개요
jqGrid 는 jQuery 를 사용하여 활용 가능한 Grid 를 제공하는 오픈소스이다.
우리는 jqGrid 를 사용하면서 여러가지 기능의 확장을 꾀하고 싶은 경우가 생긴다. 이 때, jqGrid는 오픈소스 답게 코드를 확장할 수 있는 포인트들을 제공해 주고 있다. jqGrid 가 제공하는 확장 포인트들을 알아보고 이를 활용하는 방법에 대해 얘기하고자 한다.
jqGrid 는 크게 두 가지 확장 방법을 제공한다.
Callback Function
Event Listener
이제부터 jqGrid를 사용하면서 기능을 확장하기 위해 시도한 방법을 정리하겠다.
1. Callback Function
$("GridID").jqGrid({
url:'/com/sds/cchs/conffarefareManagementList.grid',
datatype: 'JSON',
mtype: 'GET',
colNames:[
'Version No'
, 'Fare No'
, 'Fare'
],
jsonReader : {
root: "fareManagementDVOList",
repeatitems: false,
id: 'id'
},
colModel :[
{ name:'vrsnNo' , index:'vrsnNo' , sortable:false}
, { name:'fareNum' , index:'fareNum' , sortable:false}
, { name:'fareAmt' , index:'fareAmt' , sortable:false}
],
pager: '#pagerList',
rowNum:10,
height: 'auto',
width: tableWidth,
ondblClickRow: function(rowid,iRow,iCol,e){
// write your code
},
loadComplete: function(data){
// write your code
},
beforeRequest: function() {
// write your code
fn_jqgridCommon_beforeRequest(this); // <-- @
}
});
위 코드에서 ondblClickRow, loadComplete, beforeRequest 등이 jqGrid 에서 제공하는 Callback Function 이다.
이렇게 jqGrid 에서 제공하는 Callback Function 은 이 외에도 많다.
이 부분에 적절한 코드를 입력해서 필요한 기능을 추가 할 수 있다.
페이지에서 include 하는 jqgridCommon.js 파일에 다음과 같은 함수들을 제공할 수 있다.
function fn_jqgridCommon_beforeRequest(jqgrid) {
jqgrid.p.postData.pageIndex = parseInt(jqgrid.p.page) > parseInt(jqgrid.p.lastpage) ? jqgrid.p.lastpage : jqgrid.p.page;
jqgrid.p.postData.pageSize = jqgrid.p.rowNum;
};
여기서 주의점은 jqGrid 의 내부 정보에 접근하기 위하여 jqGrid 를 넘겨받아야 한다.
위 코드는 Callback Function 내부에서만 사용할 수 있고 다음과 같이 써야한다. (<-- @ 부분이다.)
fn_jqgridCommon_beforeRequest(this);
this 를 넘기는 것은 조금 위험한 행위일 수 있다. 물론, 정해진 용도로만 사용한다면 크게 문제될 것은 없지만 이 부분이 꺼림직 하다면 다음과 같이 수정할 수 있다.
function fn_jqgridCommon_beforeRequest(selector) {
var postdata = $(selector).getGridParam("postData");
$(selector).setGridParam({
postData: {
pageIndex : parseInt(postdata .page) > parseInt(postdata.lastpage) ? postdata.lastpage : postdata.page,
pageSize : postdata.rowNum
}
});
};
fn_jqgridCommon_beforeRequest("#GridID");
selector 를 통해 접근하려면 jqGrid 가 제공한 메소드를 이용해야 한다. 따라서 코드가 조금 복잡해지는 것은 감수해야 한다.
참고로 이후에 "껴들기" 를 사용할 경우엔 this 보다 좀 더 안전한 방법으로 접근이 가능하므로 구지 selector 를 사용하지 않아도 된다.
장점
구현이 간단하고 Side Effect 의 여지가 적다.
각 기능들을 함수로 제공하고 목적에 맞는 Callback Function 에 원하는 함수를 적으면 된다.
따라서, 원하는 기능들을 선택적으로 적용할 수 있으며 이는 개발 시점에서 결정할 수 있다.
단점
하지만, 개발 시점에 결정해야 할 사항들이 늘어나면 그만큼 반복 작업이 늘어나는 것이고 의도치 않은 버그가 발생할 가능성이 늘어나는 것이기도 하다.
2. Event Listener
Event Listener 를 이용하면 1. Callback Function 방식에서 유발될 수 있는 반복 작업들을 줄일 수 있다.
jqGrid 는 여러가지 Event 들을 jQuery 의 trigger 를 이용하여 발생시키고 있다.
var bfr = $(ts).triggerHandler("jqGridBeforeRequest");
if (bfr === false || bfr === 'stop') { return; }
if ($.isFunction(ts.p.datatype)) { ts.p.datatype.call(ts,ts.p.postData,"load_"+ts.p.id); return;}
else if($.isFunction(ts.p.beforeRequest)) {
bfr = ts.p.beforeRequest.call(ts);
if(bfr === undefined) { bfr = true; }
if ( bfr === false ) { return; }
}
위 코드는 jqGrid 의 코드 일부이다. 위 부분은 beforeRequest 라는 Callback Function 을 실행하기 앞서 jqGridBeforeRequest 라는 이벤트를 던지고 있다. 이 처럼 jqGrid 에서 발생하는 이벤트들은 Callback Function 만큼이나 많으며 이 이벤트의 Listener 를 등록하여 원하는 기능을 껴 넣을 수 있다.
var fn_jqgridCommon_binder;
/* Closer */
(function() {
function init(jqgrid) {
// init code
}
function setPageInfo(p) {
// page info code
};
fn_jqgridCommon_binder = ( selector ) {
$(selector).bind("jqGridBeforeRequest", function() {
setPageInfo(obj[0].p);
});
$(selector).bind("jqGridLoadComplete", function() {
init(obj.selector);
});
}
}
Closer 안에 필요한 기능을 모두 구현한 후에 제공할 binder 함수를 통해 Event Listener 를 등록한다. 이렇게 제공된 binder 는 다음과 같이 사용한다.
fn_jqgridCommon_binder("#GridID");
Event Listener 를 사용하면 이렇게 미리 결정한 기능을 한번에 적용할 수 있다.
이렇게 하면 각 그리드 마다 특화된 부분은 Callback Function 을 통해 개발 시점에 구현하고, Event Listener 에는 공통 기능을 모아두는 것으로 구분하여 그 성격 별로 코드를 분리할 수 있다. 이로써 반복작업은 줄어들고 좀 더 의미있는 코드만 남길 수 있다.
장점
1. Callback Function 방식에 단점들을 극복할 수 있다.
또한 유의한 기준으로 코드가 분리되어 관리가 편해진다.
단점
아직 개발 시점에서 해야할 일이 사라진 것은 아니다.
3. 껴들기
공통으로 정의하거나 jqGrid 의 기능 확장을 하는데 있어서 개발 시점에서 관여하지 않도록 하는 것이 개발 시점에 수고를 덜 수 있다.
물론 2. Event Listener 를 사용해서 한번에 그 작업을 처리해 줄 수도 있지만 다음에 소개하는 "껴들기" 를 시도하면 최소한의 개발 코드 조차 없앨 수 있다.
/* Closer */
(function() {
var oriJqGrid = $.fn.jqGrid;
function init(jqgrid) {
// init code
};
function setPageInfo(p) {
// page info code
};
function checkChangeGrid(selector) {
// check something changed
};
var binder = function( pin ) {
$.fn.jqGrid = oriJqGrid;
var obj = $.fn.jqGrid.apply(this, arguments);
$.fn.jqGrid = binder;
if (typeof pin !== 'string') {
obj.bind("jqGridBeforeRequest", function() {
setPageInfo(obj[0].p);
});
obj.bind("jqGridLoadComplete", function() {
init(obj.selector);
});
$(window).bind("beforeunload", function () {
if (g_jqgridObj[obj.selector] != null && checkChangeGrid(obj.selector)) {
return "You did not save the modified data.";
}
});
}
return obj;
}
$.fn.jqGrid = binder;
}());
껴들기 의 기본은 위와 같다. oriJqGrid 에 껴들고 싶은 함수($.fn.jqGrid) 의 레퍼런스를 저장해 두고 기존의 함수는 유도함수로 연결해 둔다. ($.fn.jqGrid = binder;)
그리고 이 유도함수에서는 oriJqGrid 에 저장해 둔 원본 레퍼런스를 다시 지정하고 제 기능을 하도록 실행(apply) 한 후, 다시 유도함수로 치환해 다음 접근 시 다시 유도함수로 접근하도록 해 둔다. 이렇게 그 위치를 회복해 줌으로써 원본 함수에서 사용하는 this 나 변수들의 scope 에 혼란을 일으키지 않고 원래의 제 기능대로 움직이게 할 수 있다. (함수 빌려쓰기를 할 경우 매우 주의해야할 부분이다.)
이후 결과를 return 하기 전에 우리가 원하는 작업을 마음껏 하면 된다. 이 코드에서는 기존에 Event Listener 를 등록하는 코드를 옮겨 두었다. 그리고 가공이 끝난 결과를 return 하면 된다.
이렇게 사용하면 개발 시점에 jqGrid를 선언 시점에 껴들어서 원하는 작업을 해주기 때문에 마치 jqGrid 자체 기능을 확장한 것 같은 효과를 줄 수 있다.
장점
개발 시점의 코드를 완벽히 제거하였다.
단점
Javascript 의 특성 상 this 관리나 변수의 scope 등을 잘 고려해야 Side effect 를 줄일 수 있다.
4. jqGrid 확장하기
앞서 설명한 "껴들기" 를 비롯하여 실제 jqGrid 의 구조에 적합한 확장법을 설명하고자 한다. 다음 코드는 jqGrid 의 선언부이다. jQuery 를 기반으로 돌아가는 jqGrid 는 $.fn 에 그 선언함수를 두고 있으며 "껴들기" 의 대상이 되는 함수이다.
$.fn.jqGrid = function( pin ) {
if (typeof pin == 'string') {
var fn = $.jgrid.getAccessor($.fn.jqGrid,pin);
if (!fn) {
throw ("jqGrid - No such method: " + pin);
}
var args = $.makeArray(arguments).slice(1);
return fn.apply(this,args);
}
return this.each( function() { ... } )
}
이를 살펴보면 pin 이라는 argument 를 받아서 type 이 string 일 경우와 아닐 경우로 분리되서 진행 된다. 이 문법은 jqGrid 의 메소드들의 이름을 첫번째 인자로 받아 실행해 주는 내용이지만 "껴들기" 를 준비하는데에는 그 의미는 중요하지 않다. 단지 jqGrid 의 생성함수가 두개의 흐름으로 나뉘는 것에만 유의하면 된다.
다시 3. 껴들기 에 있는 코드를 보면 역시 같은 if 문을 통해 흐름을 나누고 있다. 껴드는 코드에 성격에 따라 어느 한쪽 흐름에만 적용될 필요가 있다면 이처럼 같은 방식의 분기로 필요 시점에만 적용되게끔 코딩하도록 한다.
만약 jqGrid 에 추가적인 임이의 메소드를 부여하고 싶다면 $.jgrid 에 extend 해야 한다. jqGrid 는 기본적으로 내부에서 사용되는 Callback Function 은 $.fn.jqGrid 가 반환하는 객체에서 처리 되지만 jQuery 의 selector 로 수집된 jqGrid 의 메소드는 $.jgrid 에 등록된다.
/* Closer */
(function() {
$.jgrid.extend({
updateRowData:
function (rowid, data) {
var IDs = $(this).getDataIDs();
var index = 0;
for (index in IDs) {
if (IDs[index] == rowid) break;
}
var origin = g_jqgridObj["#"+$(this).get($(this).length-1).id].orgData[index].data;
for (var key in data) {
if (origin[key] != undefined && origin[key].trim() != data[key].trim() ) {
$(this).jqGrid("setRowData", rowid, data, {color:"blue"});
return;
}
}
$(this).jqGrid("setRowData", rowid, data, {color:""});
},
insertRowData:
function (rowid, data, pos) {
$(this).jqGrid("addRowData", rowid, data, pos);
$(this).jqGrid("setRowData", rowid, data, {color:"blue"});
}
});
}());
이렇게 추가 메소드를 구현하여 공통으로 사용할 수 있다.