들어가며

RealGrid에는 두 가지 제품 RealGridJS와 RealGrid+가 있고, 이 두 가지 제품에는 각기 다른 장단점들이 있습니다. RealGridJS는 HTML5기반으로 ActiveX없이 사용할 수 있어 모바일 웹 환경과 호환이 되는 장점이 있지만 HTML5를 지원하지 않는 브라우저에서는 사용하지 못한다는 제약이 존재합니다. 반대로 RealGrid+는 HTML5를 지원하지 않는 하위버전의 브라우저에서 사용이 가능하지만 Flash Player가 사전에 설치되어야 한다는 제약이 있습니다. 이런 두 제품을 병행 사용하여 서로의 단점을 보완하는 방법으로 개발할 수 있습니다. 이 강좌에서는 두가지 제품을 동시에 사용할 때 각각 별도의 코드로 구현하지 않고 하나의 코드로 손쉽게 구현할 수 있는 방법에 대해서 설명하도록 하겠습니다.

이론

RealGridJS와 RealGrid+는 거의 동일한 기능을 가지고 있지만, 개발 측면에서 몇가지 차이가 존재합니다.

  1. Grid를 화면에 표시하는 준비 과정이 다릅니다. RealGridJS는 document가 준비된 상태에서 바로 GridBase객체와 DataProvider객체를 생성하고 사용하면 됩니다. RealGrid+는 object element를 division하위에 추가하고 object element에 의해 Flash Player가 실행됩니다. Flash Player가 실행 되면 url로 지정한 RealGrid+ swf파일을 불러오고 RealGrid+는 정상적으로 실행되었다는 callback을 호출하게 됩니다. 이 때 사용되는 callback이 RealGrids.onload입니다. 이 callback함수에서 GridBase객체와 DataProvider객체를 생성 후 사용할 수 있습니다.
  2. 데이터를 불러올 때 사용하는 함수와 RealGrid 자체적인 통신유무가 다릅니다. RealGrid+의 loadData함수는 AJAX를 통해 데이터를 수신 하는 기능을 포함 하고 있습니다. RealGridJS는 데이터를 수신하는 기능이 없으며 별도로 AJAX를 통해 수신된 데이터를 fillJsonData등의 함수를 이용하여 DataProvider에 채워넣습니다. 따라서 RealGridJS에서는 AJAX 통신후 fillJsonData등을 호출하여 구현하는 방법으로 loadData함수 prototype을 구현하도록 합니다.
  3. RealGrid+의 일부 함수가 RealGridJS에서 이름과 형태가 변경되었습니다.
  4. 이미지가 자체적으로 존재하는지의 여부가 다릅니다. RealGrid+는 swf로 일종의 binary파일이므로 내부에서 사용하는 이미지를 binary에 포함시킬 수 있지만 RealGridJS는 순수 javascript로 구현된 제품이므로 필요한 이미지를 외부에 두어 그 경로를 지정해주어야 합니다.(RealGridJS.setRootContext 참고)

이 강좌에서는 RealGrid+와 RealGridJS를 한 페이지 안에서 조건에 따라 설치할 수 있도록 setup()이라는 이름의 함수를 하나 작성하고, RealGrid+와 RealGridJS에서 함수의 이름이 다르거나 없는 경우에 대비하여 객체의 prototype도 몇가지 조정 해보겠습니다.

실습

실습은 이 강좌의 맨 아래 링크에 첨부된 realgridutil.js파일에 포함된 코드를 영역별로 설명한 내용입니다.

  1. setup 함수 선언
    setup함수는 id, width, height 매개변수와 RealGrid에 대한 설정 정보인 gridObj 매개변수를 전달받습니다.

    id: grid가 생성될 division의 id
    width: 생성될 그리드의 넓이 (숫자 또는 비율)
    height: 생성될 그리드의 높이 (숫자 또는 비율)

    gridObj에 담겨지는 속성은 다음과 같습니다.

    type: 그리드의 종류 (“grid” || “tree”)
    fields: DataProvider.setFields에 전달될 값, array of DataField
    columns: GridBase.setColumns에 전달될 값, array of DataColumn
    options: GridBase.setOptions에 전달될 값, GridOptions
    onCompleted: Grid설정이 완료된 후 호출될 callback

    함수 선언은 다음과 같습니다.

     var RealGridsUtil = {
         setup: function(id, width, height, gridObj) {
         }
     }
  2. 공통 부분 함수 초입부분에서 고려해야 할 부분은 gridObj가 null이거나 undefined일 때 Object로 생성을 해줘야 합니다.

     if (!gridObj)
         gridObj = {};

    또한, 이후 callback등에서 식별이 용이하도록 하기위해 division id를 gridObj의 속성으로 저장해줍니다.

         gridObj.id = id;

    gridObj의 type속성이 지정되지 않았을 경우를 위해 기본값으로 “grid”로 설정합니다.

     if (!gridObj.type)
         gridObj.type = "grid";

    이론에서도 설명했다시피 RealGrid+는 callback이 호출된 후에 View와 DataProvider를 생성할 수 있다는 점입니다. 문제는 callback이 비동기 호출이므로 반드시 순서대로 호출된다는 보장이 없습니다. 따라서 gridObj를 Array등에 저장하여 놓고 나중에 빼서 쓸수 있는 형태의 알고리즘이 필요합니다. RealGridJS의 경우도 추후 loadData구현시 필요하므로 공통 처리하였습니다. Array보다는 Object형태가 찾아쓰기 편한점이 있어 이 강좌에서는 Object의 속성으로 저장해놓도록 하였습니다.

     var __grids = {};
    
     var RealGridsUtil = {
         setup: function(id, width, height, gridObj) {
             __grids[id] = gridObj;
         }
     }

    브라우저에 따라 RealGridJS와 RealGrid+로 분기하기 위한 로직이 필요합니다. 이 강좌에서는 Internet Explorer의 버전만 체크하도록 하엿습니다.

     function checkHtml5Browser() {
         var agent = navigator.userAgent;
         return (agent.indexOf("MSIE") < 0 || agent.indexOf("compatible") > 0);
     }
    
     var RealGridsUtil = {
         setup: function(id, width, height, gridObj) {
             ...
    
             var html5supported = checkHtml5Browser();
    
             if (html5supported) { 
                 // RealGridJS setup
             } else {
                 // RealGrid+ setup
             }
         }
     }
  3. RealGridJS setup RealGridJS에서는 Canvas의 크기가 상위 division의 크기에 의해 결정되기 때문에 Grid객체 생성하기 전에 division의 크기를 설정해야 합니다.

     if (!isNaN(width)) //숫자이면
         width = width + "px";
     if (!isNaN(height)) //숫자이면
         height = height + "px";
        
     $("#" + id).css({ width: width, height: height });

    또한 RealGridJS내부에서 필요한 이미지의 경로를 지정해야 합니다.

     RealGridJS.setRootContext("./images"); //RealGridJS assets 폴더의 위치

    GridView 또는 TreeView와 그에 맞는 DataProvider를 생성합니다.

     if (gridObj.type == "grid") {
         gridObj.dataProvider = new RealGridJS.LocalDataProvider();
         gridObj.gridView = new RealGridJS.GridView(id);
     } else {
         gridObj.dataProvider = new RealGridJS.LocalTreeDataProvider();
         gridObj.gridView = new RealGridJS.TreeView(id);             
     }
     gridObj.gridView.setDataSource(gridObj.dataProvider);

    모든 생성이 끝났으면 fields, columns, options 정보에 대해 처리할 ready함수를 호출합니다.

     RealGridsUtil.ready(gridObj);
  4. RealGrid+ setup 이제 division하위에 object element를 추가해야 하는데 여기에서는 swfObject를 사용하였습니다.

     var flashvars = {
         id: id
     };
    
     var pars = {
         quality: "high",
         wmode: "opaque",
         allowscriptaccess: "sameDomain",
         allowfullscreen: false,
         seamlesstabbing: false
     };
    
     var attrs = {
         id: id,
         name: id,
         align: "middle"
     };
    
     /* swfobject.js required */
     var swfUrl = "/objects/RealGridWeb.swf"; // RealGrid+ swf path
     if (location.href.indexOf("http://localhost") == 0) {
         swfUrl = swfUrl + "?" + new Date().getTime();
     }
    
     swfobject.embedSWF(swfUrl, id, width, height, "11.1.0", "objects/expressInstall.swf", flashvars, pars, attrs);

    RealGrid+가 load된 후 처리할 callback함수를 작성합니다.

     RealGrids.onload = function (id) {
         if (__grids[id]) {
             if (__grids[id].type == "grid") {
                 __grids[id].dataProvider = new RealGrids.LocalDataProvider();
                 __grids[id].gridView = new RealGrids.GridView(id);
             } else {
                 __grids[id].dataProvider = new RealGrids.TreeDataProvider();
                 __grids[id].gridView = new RealGrids.TreeView(id);
             }
             __grids[id].gridView.setDataProvider(__grids[id].dataProvider);
            
             RealGridsUtil.ready(__grids[id]);
         }
     }
  5. RealGridsUtil.ready 함수 ready함수에서는 grdObj의 fields, columns, options 값이 있을 경우 각각 그 처리를 하고, callback이 있으면 callback을 호출하도록 구현합니다.

     var RealGridsUtil = {
         ...
    
         ready: function(gridObj) {
             if (gridObj.fields) 
                 gridObj.dataProvider.setFields(gridObj.fields);
    
             if (gridObj.columns) 
                 gridObj.gridView.setColumns(gridObj.columns);
                
             if (gridObj.options)
                 gridObj.gridView.setOptions(gridObj.options);
                
             if (gridObj.onCompleted) 
                 gridObj.onCompleted(gridObj);       
         }
     }
  6. loadData prototype LocalDataProvider와 loadData함수를 다음과 같이 prototype선언후 내부에서 사용할 변수 선언과 options.progress 기본값 설정을 합니다.

     RealGridJS.LocalDataProvider.prototype.loadData = function (options, onSuccess, onFailed, onComplete) {
         var provider = this;
         var xhrcallback = undefined;
         options.progress = (options.progress == null || options.progress == undefined) ? true : options.progress; //미지정시 true로 설정
     }

    이 함수에서는 ajax통신, progress bar 표시, provider에 값을 채우는 역할을 합니다. progress bar를 표시하기 위해서 gridView객체를 알아야 하는데 GridView에서 DataProvider를 알아올 수는 있지만, 반대로 알아오는 방법을 없습니다. 따라서 전역변수 __grids에서 dataProvider로 gridView객체를 알아오는 기능을 구현해야 합니다.

     function findGridView(provider) {
         for (var i in __grids) {
             if (__grids[i].dataProvider == provider) {
                 return __grids[i].gridView;
             }
         }
         return null;
     }
    
     RealGridJS.LocalDataProvider.prototype.loadData = function (options, onSuccess, onFailed, onComplete) {
         ...
            
         var gridView = findGridView(provider);            
     }

    jQuery ajax함수를 호출하여 통신을 하며 xhr callback을 통하여 progress bar를 구현합니다.

     RealGridJS.LocalDataProvider.prototype.loadData = function (options, onSuccess, onFailed, onComplete) {
         ...
    
         if (options.progress && gridView) {
             xhrcallback = function () {
                 var xhr = new window.XMLHttpRequest();
                 //Download progress
                 xhr.addEventListener("progress", function (evt) {
                     if (evt.lengthComputable) {
                         gridView.setProgress(0, evt.total, evt.loaded);
                     }
                 }, false);
                 return xhr;
             }
                
             gridView.showProgress();
         }
    
         $.ajax({
             type: options.method ? options.method : "GET",
             url: options.url,
             dataType: "text",
             data: options.params,
             success: function (data) {
                 if (options.filters) {
                     provider.setFilters(provider.filters);
                 };
    
                 if (options.type == "json") {
                     provider.fillJsonData(data, options);
                 } else if (options.type == "xml") {
                     provider.fillXmlData(data, options);
                 } else if (options.type == "csv") {
                     provider.fillCsvData(data, options);
                 };
    
                 if (onSuccess)
                     onSuccess(provider);
             },
             error: function (xhr, status, error) {
                 if (onFailed)
                     onFailed(provider, xhr + ', ' + status + ', ' + error);
             },
             complete: function (data) {
                 if (options.progress && gridView)
                     gridView.closeProgress();
    
                 if (onComplete)
                     onComplete();
             },
             xhr: xhrcallback
         });
     };

    LocalTreeDataProvider도 동일하게 구현합니다.

     RealGridJS.LocalTreeDataProvider.prototype.loadData = function (options, onSuccess, onFailed, onComplete) {
         var provider = this;
         var xhrcallback = undefined;
         options.progress = (options.progress == null || options.progress == undefined) ? true : options.progress;
            
         var gridView = findGridView(provider);
         if (options.progress && gridView) {
             xhrcallback = function (evt) {
                 var xhr = new window.XMLHttpRequest();
                 //Download progress
                 xhr.addEventListener("progress", function (evt) {
                     if (evt.lengthComputable) {
                         gridView.setProgress(0, evt.total, evt.loaded);
                     }
                 }, false);
                 return xhr;
             }
                
             gridView.showProgress();
         }
            
         $.ajax({
             type: options.method ? opt.method : "GET",
             url: options.url,
             data: options.params,
             success: function (data) {
                 if (options.filters) {
                     provider.setFilters(provider.filters);
                 };
    
                 if (options.type == "json") {
                     provider.fillJsonData(data, options);
                 } else if (options.type == "xml") {
                     provider.fillXmlData(data, options);
                 } else if (options.type == "csv") {
                     provider.fillCsvData(data, options);
                 };
    
                 if (onComplete)
                     onComplete(provider);
             },
             error: function (xhr, status, error) {
                 if (onFailed)
                     onFailed(provider, xhr + ', ' + status + ', ' + error);
             },
             complete: function (data) {
                 if (options.progress && gridView)
                     gridView.closeProgress();
    
                 if (onComplete)
                     onComplete();
             },
             xhr: xhrcallback
         });
     };
  7. addImageList prototype RealGrid+의 addImageList함수가 RealGridJs에서는 registerImageList로 다소 달라진 내용이 있어 다음과 같이 각각 prototype선언합니다

     RealGridJS.GridView.prototype.addImageList = function (images) {
         var imgs = new RealGridJS.ImageList(images.name, images.rootUrl);
         imgs.addUrls(images.images);
         this.registerImageList(imgs);
     };
     RealGridJS.TreeView.prototype.addImageList = function (images) {
         var imgs = new RealGridJS.ImageList(images.name, images.rootUrl);
         imgs.addUrls(images.images);
         this.registerImageList(imgs);
     };
  8. RealGridUtils 사용예 처음 화면이 보여질때 RealGridJS가 보여지고, 버튼을 클릭하면 RealGridJS와 RealGrid+가 토글되어 보여지도록 코드를 작성해 보겠습니다.

     RealGridsUtil.setup("realgrid", "100%", "200", "js", gridObj);
    
     $("#btnJsShow").click(function() {
         $("#realgrid").replaceWith("<div id=\"realgrid\"></div>");
         RealGridsUtil.setup("realgrid", "100%", "200", "js", gridObj);
     });
    
     $("#btnPlusShow").click(function() {
         $("#realgrid").replaceWith("<div id=\"realgrid\"></div>");
         RealGridsUtil.setup("realgrid", "100%", "200", "plus", gridObj);
     });

실행화면

실행화면에서 사용한 스크립트는 버튼을 통하여 Toggle처리를 하기 위해 본 강좌에서 사용한 소스를 변경하여 사용하였습니다.

버튼을 눌러 RealGridJS가 화면에 보여지는지 확인하세요.

버튼을 눌러 RealGrid+가 화면에 보여지는지 확인하세요.

전체소스코드

SCRIPT
<link rel="stylesheet" href="/css/bootstrap.css">
<script type="text/javascript" src="/script/jquery-1.1.12.min.js"></script>
<script type="text/javascript" src="/script/bootstrap.min.js"></script>
<!--RealGridJS-->
<script type="text/javascript" src="/script/realgridjs-lic.js"></script>
<script type="text/javascript" src="/script/realgridjs_eval.1.0.14.min.js"></script>
<script type="text/javascript" src="/script/realgridjs-api.1.0.14.js"></script>

<!--RealGrid+-->
<script type="text/javascript" src="/script/realgridlic.js"></script>
<script type="text/javascript" src="/script/realgridplus.js"></script>
<script type="text/javascript" src="/script/swfobject.js"></script>

<script type="text/javascript" src="/script/realgridutil.js"></script>

<script>
/****************************************
* sample.html script
*****************************************/

var gridObj = {
    type: "grid",
    fields: 
        [{
            fieldName: "userid"
        }, {
            fieldName: "first_name"
        }, {
            fieldName: "last_name"
        }],
    columns:
        [{
            fieldName: "userid",
            width:100,
            header: {text:"user id"}
        }, {
            fieldName: "first_name",
            width:200,
            header: {text:"first name"}
        }, {
            fieldName: "last_name",
            width:200,
            header: {text:"last name"}
        }], 
    options: 
        {
            panel: {visible: false},
            footer: {visible: false},
            edit: {
                appendable: true,
                insertable: true
            }
        },
    onCompleted: function (obj) {
            obj.dataProvider.loadData({
                type: "json",
                url: "http://dev.demo.realgrid.net/DemoData/defaultloaddata.json?__time__=" + new Date().getTime(),
                progress: true
            });
    }
};

$(function () {
    RealGridsUtil.setup("realgrid", "100%", "200", "js", gridObj);

    $("#btnJsShow").click(function() {
        $("#realgrid").replaceWith("<div id=\"realgrid\"></div>");
        RealGridsUtil.setup("realgrid", "100%", "200", "js", gridObj);
    });

    $("#btnPlusShow").click(function() {
        $("#realgrid").replaceWith("<div id=\"realgrid\"></div>");
        RealGridsUtil.setup("realgrid", "100%", "200", "plus", gridObj);
    });
});
</script>
HTML
<button class="btn btn-primary btn-xs" id="btnJsShow">Show RealGridJS</button>
<button class="btn btn-primary btn-xs" id="btnPlusShow">Show RealGrid+</button>

<div id="realgrid"></div>

소스 코드 다운로드