BI Dashboard 인 만큼 엑셀의 그리드 형태의 표현은 필수적이다. 오픈소스중에서 가장 많이 사용하고 있는 jqGrid를 Angular.js에 포팅하고 RESTful API를 호출하여 표현하는 방법을 알아보자.
1. jqGrid 설치하기
- 최신버전 v4.5.4
- jqGrid만을 MIT 라이센스하에서 사용을 할 수 있다.
// bower 통하여 설치하기
$ bower install jqgrid --save
// index.html 에 설정하기
<!-- build:css(.tmp) styles/main.css -->
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css">
<link rel="stylesheet" href="bower_components/jqgrid/css/ui.jqgrid.css">
<link rel="stylesheet" href="styles/main.css">
<!-- endbuild -->
... 중략 ...
<!-- build:js scripts/plugins.js -->
<script src="bower_components/bootstrap/dist/js/bootstrap.js"></script>
<script src="bower_components/respond/respond.src.js"></script>
<script src="bower_components/jqgrid/js/i18n/grid.locale-en.js"></script>
<script src="bower_components/jqgrid/js/jquery.jqGrid.js"></script>
<!-- endbuild -->
- jqgrid의 theme을 위하여 jquery-ui 의 theme을 입힌다
// bowe 통하여 설치하기
$ bower install jquery-ui --save
지금까지의 bower components 목록 버전 의존성
$ bower list
BIDashboard
├── angular#1.2.1 (1.2.2 available)
.. 중략 ..
│ └── angular#1.2.1
├─┬ bootstrap#3.0.2
│ └── jquery#1.9.1 (2.0.3 available)
├── console-shim#05b957a4b1
├── es5-shim#2.0.12 (latest is 2.1.0)
├── jqgrid#4.5.4
├── jquery#1.9.1 (latest is 2.0.3)
├── jquery-migrate#1.2.1
├─┬ jquery-ui#1.10.3
│ └── jquery#1.9.1 (2.0.3 available)
├── json3#3.2.5 (3.2.6 available)
├── modernizr#2.7.0
└── respond#1.3.0
// index.html 에 설정하기
<!-- build:css(.tmp) styles/main.css -->
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css">
<link rel="stylesheet" href="bower_components/jquery-ui/themes/smoothness/jquery-ui.css">
<link rel="stylesheet" href="bower_components/jqgrid/css/ui.jqgrid.css">
<link rel="stylesheet" href="styles/main.css">
<!-- endbuild -->
... 중략 ...
<!-- build:js scripts/plugins.js -->
<script src="bower_components/bootstrap/dist/js/bootstrap.js"></script>
<script src="bower_components/respond/respond.src.js"></script>
<script src="bower_components/jquery-ui/ui/jquery-ui.js"></script>
<script src="bower_components/jqgrid/js/i18n/grid.locale-en.js"></script>
<script src="bower_components/jqgrid/js/jquery.jqGrid.js"></script>
<!-- endbuild -->
2. jqGrid 테스트 메뉴 추가하기
- 기존 프로그램에서 jqGrid 테스트 메뉴를 추가한다
+ step-1 : views 폴더 밑으로 jqGridTest.html 파일 생성
+ step-2 : scripts 폴더 밑으로 biz 폴더 만들고 (여기선 sales) jqGridBiz.js 파일 생성 (화면 : 비즈니스모듈 = 1:1)
jqGridBiz.js 의 모듈 명칭, 컨트롤러 명칭을 정의한다
+ step-3 : Main Application (DashboardApp.js) 안에 신규 Biz 모듈 설정 및 Routing 환경 설정
+ step-4 : index.html 안에 jqGridBiz.js 스크립트 태그 추가
+ step-5 : index.html 안의 메뉴설정에 jqGridTest.html이 보일 수 있도록 routing url 설정
// step-1
// views/jqGrid.html 파일 내역
jqGrid Test Page
// step-2
'use strict';
var RestTestBiz = angular.module('DasbhoardApp.JqGridBiz', ['DasbhoardApp.RestfulSvc']);
RestTestBiz.controller('JqGridBiz.salesCtrl', ['$scope', 'RestfulSvcApi', function ($scope, RestfulSvcApi) {
}]);
// step-3
// DashboardApp.js 변경 내역
'use strict';
var DashboardApp = angular.module('DasbhoardApp', [
'ngRoute',
'ngCookies',
'ngResource',
'ngSanitize',
'DasbhoardApp.CommonCtrl',
'DasbhoardApp.RestTestBiz',
'DasbhoardApp.JqGridBiz',
'DasbhoardApp.RestfulSvc'
]);
DashboardApp.config(['$routeProvider', function ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/main.html'
})
.when('/resttest', {
templateUrl: 'views/restTest.html',
controller: 'RestTestBiz.personCtrl'
})
.when('/jqgridtest', {
templateUrl: 'views/jqGridTest.html',
controller: 'JqGridBiz.salesCtrl'
})
.otherwise({
redirectTo: '/'
});
}]);
// step-4
// index.html 에 추가
<!-- build:js scripts/dashboard-spa.js -->
<script src="scripts/DashboardApp.js"></script>
<script src="scripts/common/controllers/CommonCtrl.js"></script>
<script src="scripts/common/services/RestfulSvc.js"></script>
<script src="scripts/person/RestTestBiz.js"></script>
<script src="scripts/sales/JqGridBiz.js"></script>
<!-- endbuild -->
// step-5
// index.html 의 메뉴 부분에 추가
<ul class="nav navbar-nav">
<li data-ng-class="activeWhen(path()=='/')">
<a href="" data-ng-click="setRoute('/')">Home</a>
</li>
<li data-ng-class="activeWhen(path()=='/resttest')">
<a href="" data-ng-click="setRoute('/resttest')">RESTTest</a>
</li>
<li data-ng-class="activeWhen(path()=='/jqgridtest')">
<a href="" data-ng-click="setRoute('/jqgridtest')">jqGridTest</a>
</li>
</ul>
// final
// 디렉토리 구조
// http://localhost:8080/#/jqgridtest 호출 결과
3. jqGrid 의 Angular.js 적용전략 - Directive 만들기
- jqGridTest.html partial view html 안에 일반적인 jqGrid 표현 내역을 입력하면 화면에 아무것도 출력되지 않는다
즉, jqGrid 사용하기 와 같은 <table> 태그와 <script> 태그를 jqGridTest.html 넣으면 화면출력이 되지 않는다는 것이다.
이는 Angular.js router를 통하여 partial html이 보여질 때 DOMP (DOM Parser)는 html만을 파싱하여 add 할 뿐이고,
<script> 태그는 수행되지 않기 때문이다 (참조)
- jqGrid에 대한 Common Directive 만들기 : JqGridDrtv.js
// Directive 모듈 JqGridDrtv.js 개발
'use strict';
var jqGridDrtv = angular.module('DashboardApp.JqGridDrtv', [ 'DasbhoardApp.RestfulSvc' ]);
/**
* mc is Mobile Convergence of MobiConSoft
* @use : <mc-jq-grid config="config" data="data"></mc-jq-grid>
* config and data is used in Controller of Angular.js
*/
jqGridDrtv.directive('mcJqGrid', [ 'RestfulSvcApi', function(RestfulSvcApi) {
return {
restrict : 'E',
scope : {
config : '=',
data : '=',
},
link : function(scope, element, attrs) {
var table;
// config attribute 값의 변경을 체크하여 반영한다
scope.$watch('config', function(newValue) {
element.children().empty();
table = angular.element('<table></table>');
element.append(table);
$(table).jqGrid(newValue);
});
// data attribute 값의 변경을 체크하여 반영한다
scope.$watch('data', function(newValue, oldValue) {
var i;
for (i = oldValue.length - 1; i >= 0; i--) {
$(table).jqGrid('delRowData', i);
}
for (i = 0; i < newValue.length; i++) {
$(table).jqGrid('addRowData', i, newValue[i]);
}
});
}
};
} ]);
- 메뉴에서 동작하도록 적용하기
+ step-1 : Controller에 JqGridDrtv.js의 config, data attribute에 대한 샘플 데이터 설정을 한다
+ step-2 : DashboardApp 안에 JqGridDrtv 모듈 의존관계를 설정한다
+ step-3 : index.html 안에 JqGridDrtv.js <script> 태그에 포함시킨다
+ step-4 : jqGridTest.html 에 <mc-jq-grid> 태그를 포함시킨다
// step-1 : JqGridBiz.js 에서 config, data 값 설정
'use strict';
var JqGridBiz = angular.module('DasbhoardApp.JqGridBiz', ['DasbhoardApp.RestfulSvc']);
JqGridBiz.controller('JqGridBiz.salesCtrl', ['$scope', 'RestfulSvcApi', function ($scope, RestfulSvcApi) {
$scope.config = {
datatype: "local",
height: 150,
colNames:['Inv No','Date', 'Client', 'Amount','Tax','Total','Notes'],
colModel:[
{name:'id',index:'id', width:60, sorttype:"int"},
{name:'invdate',index:'invdate', width:90, sorttype:"date"},
{name:'name',index:'name', width:100},
{name:'amount',index:'amount', width:80, align:"right",sorttype:"float"},
{name:'tax',index:'tax', width:80, align:"right",sorttype:"float"},
{name:'total',index:'total', width:80,align:"right",sorttype:"float"},
{name:'note',index:'note', width:150, sortable:false}
],
multiselect: true,
caption: "Manipulating Array Data"
};
$scope.data = [
{id:"1",invdate:"2007-10-01",name:"test",note:"note",amount:"200.00",tax:"10.00",total:"210.00"},
{id:"2",invdate:"2007-10-02",name:"test2",note:"note2",amount:"300.00",tax:"20.00",total:"320.00"},
{id:"3",invdate:"2007-09-01",name:"test3",note:"note3",amount:"400.00",tax:"30.00",total:"430.00"},
{id:"4",invdate:"2007-10-04",name:"test",note:"note",amount:"200.00",tax:"10.00",total:"210.00"},
];
}]);
// step-2 : DashboardApp.js에서 의존성 설정
var DashboardApp = angular.module('DasbhoardApp', [
'ngRoute',
'ngCookies',
'ngResource',
'ngSanitize',
'DasbhoardApp.CommonCtrl',
'DashboardApp.JqGridDrtv',
'DasbhoardApp.RestTestBiz',
'DasbhoardApp.JqGridBiz',
'DasbhoardApp.RestfulSvc'
]);
// step-3 : index.html 에서 <script> 태그 추가
<!-- build:js scripts/dashboard-4.js -->
<script src="scripts/DashboardApp.js"></script>
<script src="scripts/common/controllers/CommonCtrl.js"></script>
<script src="scripts/common/directives/JqGridDrtv.js"></script>
<script src="scripts/common/services/RestfulSvc.js"></script>
<script src="scripts/person/RestTestBiz.js"></script>
<script src="scripts/sales/JqGridBiz.js"></script>
<!-- endbuild -->
// step-4 : jqGridTest.html 안에 <mc-jq-grid> 태그를 넣는다
<div id="gridtest" class="container">
<div class="row">
<div class="col-md-8 alert alert-info">
<i class="icon-hand-right"></i> This is jqGrid Directive for AngularJS
</div>
</div>
<mc-jq-grid config="config" data="data"></mc-jq-grid>
</div>
// 최종결과
// 디렉토리 구조
4. jqGrid에 Bootstrp CSS 적용하기
- jqGrid는 기본적으로 jquery-ui theme을 사용한다. Bootstrap으로 기본적인 Layout과 controller를 사용하면 Grid 또한 같은 사용자 경험을 주어야 한다
- jqGrid에 Bootstrap과 유사한 CSS 값이 적용된 화면 참조 (소스)
- 적용 순서
+ styles/main.css 안에 jgGrid.bootstrap.css 내역을 복사하여 넣는다
+ main.css 가 맨밑에 있으므로 jqgrid의 css를 다시 쓰는 역할이다
<!-- build:css(.tmp) styles/main.css -->
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css">
<link rel="stylesheet" href="bower_components/jquery-ui/themes/smoothness/jquery-ui.css">
<link rel="stylesheet" href="bower_components/jqgrid/css/ui.jqgrid.css">
<link rel="stylesheet" href="styles/main.css">
<!-- endbuild -->
- 결과
* jQuery UI를 Angular.js에 적용하는 방법
- Bootstrap 스타일의 CSS를 적용했다면 그다음은 Directive로 만든 jqGrid의 column sorting을 시도해 보자
- column우측을 클릭하면 contents 영역에서 "loading..." 메세지만 나오고 sorting이 되지않을 것이다. 해결해 보자
- jQuery Plugins을 Directive로 만드는 이유 (참조)
// 하기와 같은 코드가 있을 경우 controller scope에서 동작을 하지 않으며, model의 변경도 반영되지 않는다
$('.datepicker').datepicker();
// 안될 경우 Angular.js 의 Directive 생성하여 동작토록 만들어야 한다
// datepicker를 wrapping한 directive 소스
var directives = angular.module('directives', []);
directives.directive('datepicker', function() {
return function(scope, element, attrs) {
element.datepicker({
inline: true,
dateFormat: 'dd.mm.yy',
onSelect: function(dateText) {
// input field의 jQuery Object
var modelPath = $(this).attr('ng-model');
// angular.js $parse와 동일한 역할
// modelPath = a.b.c , scope = {}, dateText = 2013.11.27 이면 리턴값은 {a : { b : { c: '2013.11.27 }}} 됨
putObject(modelPath, scope, dateText);
scope.$apply();
}
});
}
});
// html 태그에서 datepicker attribute 로 사용한다
<input type="text" datepicker ng-model="myobj.myvalue" />
- Directive를 만들어주는 function의 파라미터 값의 의미
+ scope : controller가 관장하는 scope
+ element : jQuery object
+ attrs : 태그의 attribute 속성 key/value
- datepicker의 callback function을 정의
+ onSelect : 사용자가 date을 선택했을 때 수행할 datepicker의 callback 이다
+ $apply : 해당 controller scope에 변경값을 적용하기 위하여 scope.$apply를 호출해야 한다 (참조)
- 데이터가 Object.properties 형태로 관리될 때 좀 더 우아하게 처리하는 방법은 $parse를 이용한다 (참조)
+ step-1 : 선택한 값은 controller의 scope안에 반영시키기 위하여 attrs.ngModel로 가져와서 $parse 파싱한다
+ step-2 : 날짜의 값을 선택하여 해당 scope의 ngModel에 assign 반영한다
myApp.directive('datepicker', function ($parse) {
return function (scope, element, attrs, controller) {
// step-1
var ngModel = $parse(attrs.ngModel);
$(function(){
element.datepicker({
...
onSelect:function (dateText, inst) {
scope.$apply(function(scope){
// step-2 Change binded variable
ngModel.assign(scope, dateText);
});
}
});
});
}
});
function MyCtrl($scope) {
$scope.userInfo = {
person: {
mDate: '1967-10-07'
}
};
}
* 다른 jQuery UI 들의 Directive 소스
* 소스 : https://github.com/ysyun/SPA_Angular_SpringFramework_Eclipse_ENV/tree/feature_jqgrid_angular
새로운 Directive IE8 에 적용하기
- IE8에서는 새로운 directive를 만들어 element 태그로 사용할 경우
- IE8 이하일 경우 DOM을 만들어준다 (참조, 호환성가이드)
<head>
<!--[if lte IE 8]>
<script>
document.createElement('mc-jq-grid');
</script>
<![endif]-->
</head>
<참조>
- Partial View html에서 javascript코드가 적용안되는 이유
- jqGrid Directives 만들기
- jqGrid에 Bootstrap CSS 적용하기
- jQuery UI를 datepicker를 Angular.js Directive로 적용하기
- jQuery UI를 Angular.js Directive로 만든 소스(GitHub)
- $parse를 이용하여 a.b.c 으로 시작하는 것을 json object 형태로 바꾸어주는 방법