/// start - module/common
/*
 * Module URI: module/common
 * SRC: module/common.module.js
 *
 * This file was generated by eXbuilder6 compiler, Don't edit manually.
 */
(function(){
	cpr.core.Module.define("module/common", function(exports, globals, module){
		/************************************************
		 * 각 화면에 대한 메뉴정보, 사용자정보 및 필수적인 공통 함수들을 제공
		 * 각 사이트별 커스터마이징하여 사용 가능
		 * version 2.0
		 ************************************************/
		function AppKit() {
			var extension = cpr.core.Module.require("module/extension");
			
			this._activeLoadMask = null;
			this._activeSubmission = [];
			
			this.Validator = new Validator(this);
			
			this.Auth = new AppAuthKit(this); 
			this.Msg = new extension.MsgKit(this);
			this.Dialog = new extension.DialogKit(this);
			this.Group = new extension.GroupKit(this);
			this.Control = new extension.ControlKit(this);
			this.SelectCtl = new extension.SelectKit(this);
			this.Tree = new extension.TreeKit(this);
			this.Tab = new extension.TabKit(this);
			this.DataSet = new extension.DataSetKit(this);
			this.DataMap = new extension.DataMapKit(this);
			this.Grid = new extension.GridKit(this);
			this.FreeForm = new extension.FreeFormKit(this);
			this.Submit = new extension.SubmissionKit(this);
			this.EmbApp = new extension.EmbeddedAppKit(this);
			this.MDI = new extension.MDIKit(this);
			this.Header = new extension.HeaderKit(this);
			this.ComUdcBtn = new extension.ComUdcBtnKit(this);
			
		};
		
		/**
		 * App 화면의 Layout에 맞게 컨트롤 배치 조건 래핑.
		 * - 사이트별 Customizing 필요
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {cpr.controls.layouts.Constraint}constraint 래핑할 배치조건
		 * @param {cpr.core.AppInstance} poApp 앱인스턴스
		 * @returns 래핑된 배치조건
		 */
		AppKit.prototype.wrapConstraints = function(app, constraint, poApp) {
			var isPopup = false;
			if(app.getHost() && app.getHost().modal === true){
				isPopup = true;
			}
			
			var layout;
			var container = null;
			if(poApp == null){
				container = isPopup ? app.getContainer() : app.getRootAppInstance().getContainer();
				poApp = isPopup ? app : app.getRootAppInstance();
			}else{
				container = poApp.getContainer();
			}
			layout = container.getLayout();
			
			if (layout instanceof cpr.controls.layouts.ResponsiveXYLayout) {
				var positionConstraints = [];
				var allMedia = poApp.allSupportedMedias;
				allMedia.forEach(function(media) {
					var newConst = _.clone(constraint);
					newConst["media"] = media;
					positionConstraints[positionConstraints.length] = newConst;
				});
				return {
					"positions" : positionConstraints
				};
			}
			
			return constraint;
		};
		
		
		/**
		 * 화면에 LoadMask 출력
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {String} maskType
		 */
		AppKit.prototype.showLoadMask = function(app, maskType) {
			var isPopup = false;
			if(app.getHost() && app.getHost().modal === true){
				isPopup = true;
			}
			
			this.hideLoadMask(app);
			
			var showConstraint = {
					"position" : "absolute",
					"top" : "0",
					"bottom" : "0",
					"left" : "0",
					"right" : "0"
			};
			showConstraint = this.wrapConstraints(app, showConstraint);
			
			var container = isPopup ? app.getContainer() : app.getRootAppInstance().getContainer();
			var layout = container.getLayout();
			var loadmask = null;
			if(maskType == "pro") {
				loadmask = container.getAppInstance().lookup("__loadmask_pro__");
				if(loadmask) {
					container.replaceConstraint(loadmask, showConstraint);
				} else {
					loadmask = new udc.cmn.ProgressLoader("__loadmask_pro__");
					container.addChild(loadmask, showConstraint);
					container.getAppInstance().register(loadmask);
				}
				loadmask.module.start();
			} else {
				loadmask = container.getAppInstance().lookup("__loadmask__");
				try{
					if(loadmask) {
						if(layout instanceof cpr.controls.layouts.FormLayout){
							app.floatControl(loadmask, showConstraint);
						}else{
							container.replaceConstraint(loadmask, showConstraint);
						}
					} else {
						loadmask = new udc.cmn.Loader("__loadmask__");
						
						if(layout instanceof cpr.controls.layouts.FormLayout){
							app.floatControl(loadmask, showConstraint);
						}else{
							container.addChild(loadmask, showConstraint);
						}
						container.getAppInstance().register(loadmask);
					}
				}catch(ex){showConstraint = null;}
			}
			
			this._activeLoadMask = loadmask;
		};
		
		/**
		 * LoadMask를 감춤
		 * - 사이트별 Customizing 필요
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 */
		AppKit.prototype.hideLoadMask = function(app) {
			if(this._activeLoadMask) {
				if(this._activeLoadMask.module && this._activeLoadMask.module.end) {
					this._activeLoadMask.module.end();
				}
				var hideConstraint = {
						"position" : "absolute",
						"top" : "-1px",
						"left" : "-1px",
						"width" : "1px",
						"height" : "1px"
				};
				
				//앱 객체가 사라진 경우... ROOT앱을 기본으로 하여 처리
				if(app == null || app.getRootAppInstance() == null){
					app = this._getRootApp();
				}
				
				var isPopup = false;
				if(app.getHost() && app.getHost().modal === true){
					isPopup = true;
				}
				
				var container = isPopup ? app.getContainer() : app.getRootAppInstance().getContainer();
				
				try{
					var layout = container.getLayout();
					if(layout instanceof cpr.controls.layouts.FormLayout){
						app.removeFloatingControl(this._activeLoadMask);
						/*2020.06.03 csj  */
						container.removeChild(this._activeLoadMask);
					}else{
						hideConstraint = this.wrapConstraints(app, hideConstraint);			
						//container.removeChild(this._activeLoadMask);
						container.replaceConstraint(this._activeLoadMask, hideConstraint);
					}
					if(this._activeLoadMask){
						this._activeLoadMask.module.count(0);
						this._activeLoadMask.module.hide();
					}
				}catch(ex){hideConstraint = null;}
				
				this._activeLoadMask = null;
			}
		};
		
		/**
		 * 최상위 루트 AppInstance를 반환한다.
		 * - 사이트별 Customizing 필요
		 * @private
		 */
		AppKit.prototype._getRootApp = function() {
			return cpr.core.Platform.INSTANCE.lookup("app/com/inc/main").getInstances()[0];
		};
		
		/**
		 * 메인 앱에 대한 인스턴스를 반환한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @returns 
		 */
		AppKit.prototype.getMainApp = function(app) {
			if(app.isRootAppInstance()) {
				return app;
			}else{
				if(app.getHostAppInstance().isRootAppInstance()) return app;
				else return this.getMainApp(app.getHostAppInstance());
			}
		};
		
		/**
		 * 모바일 접속여부를 반환한다.
		 */
		AppKit.prototype.isAccessMobile = function() {
			var info = cpr.utils.Util.detectBrowser();
			if(info.mobile || info.os.indexOf("Android") > -1) return true;
			return false;
		};
		
		/**
		 * 메인 화면에 데이터 변경사항이 있는지 여부를 체크한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {String} psAftMsg (Optional) 메시지구분
		 * @returns {Boolean} 데이터 변경여부
		 */
		AppKit.prototype.isAppModified = function(app, psAftMsg) {
			var mainApp = this.getMainApp(app);
			if(mainApp == null) return false;
			
			var vaDataCtrls = new Array();
			var container = mainApp.getContainer();
			function getChildRecursive(poContainer){
			    var vaChildCtrls = poContainer.getAllRecursiveChildren();
			    for (var i=0, len=vaChildCtrls.length; i<len; i++) {
			        if (vaChildCtrls[i].type == "grid") {
			        	vaDataCtrls.push(vaChildCtrls[i]);
			        }else if (vaChildCtrls[i] instanceof cpr.controls.Container && vaChildCtrls[i].style.getClasses().indexOf("form-box") != -1) {
			        	vaDataCtrls.push(vaChildCtrls[i]);
			        }else if(vaChildCtrls[i] instanceof cpr.controls.UDCBase){
			        	var voUdcApp = vaChildCtrls[i].getEmbeddedAppInstance();
			        	if(voUdcApp) getChildRecursive(voUdcApp.getContainer());
			        }else if(vaChildCtrls[i] instanceof cpr.controls.EmbeddedApp){
			        	var voEmbApp = vaChildCtrls[i].getEmbeddedAppInstance();
			        	if(voEmbApp) getChildRecursive(voEmbApp.getContainer());
			        }
			    }
			}
			getChildRecursive(container);
			
			var modify = false;
			var ctrl = null;
			var vsFieldLabel = "";
			for(var i=0, len=vaDataCtrls.length; i<len; i++){
				ctrl = vaDataCtrls[i];
				if(ctrl.type == "grid"){
					if(ctrl.userAttr("ignoreModify") === "Y" || ctrl.dataSet == null) continue;
					if(ctrl.dataSet.isModified()){
						modify = true;
						vsFieldLabel = ctrl.fieldLabel;
						break;
					}
				}else{
					var dataSet = this.Group.getBindDataSet(ctrl.getAppInstance(), ctrl);
					if(dataSet != null && dataSet.isModified()) {
						modify = true;
						vsFieldLabel = ctrl.fieldLabel;
						break;
					}
				}
			}
			if(modify && psAftMsg != null && psAftMsg.toUpperCase() == "CRM"){//변경사항이 반영되지 않았습니다. 계속 하시겠습니까? confirm
				if(!this.Msg.confirm("CRM-M003", [vsFieldLabel])) return true;
				else return false;
			}
			return modify;
		};
		
		/**
		 * 메인 화면에 데이터 변경사항이 있는지 여부를 체크한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @returns {Object Array} 변경된 데이터셋 객체 배열
		 */
		AppKit.prototype.getAllAppModifiedDataSet = function(app) {
			var mainApp = this.getMainApp(app);
			if(mainApp == null) return false;
			
			var vaDataCtrls = new Array();
			var vaDataSets = new Array();
			var container = mainApp.getContainer();
			function getChildRecursive(poContainer){
			    var vaChildCtrls = poContainer.getAllRecursiveChildren();
			    for (var i=0, len=vaChildCtrls.length; i<len; i++) {
			        if (vaChildCtrls[i].type == "grid") {
			        	vaDataCtrls.push(vaChildCtrls[i]);
			        }else if (vaChildCtrls[i] instanceof cpr.controls.Container && vaChildCtrls[i].style.getClasses().indexOf("form-box") != -1) {
			        	vaDataCtrls.push(vaChildCtrls[i]);
			        }else if(vaChildCtrls[i] instanceof cpr.controls.UDCBase){
			        	var voUdcApp = vaChildCtrls[i].getEmbeddedAppInstance();
			        	if(voUdcApp) getChildRecursive(voUdcApp.getContainer());
			        }else if(vaChildCtrls[i] instanceof cpr.controls.EmbeddedApp){
			        	var voEmbApp = vaChildCtrls[i].getEmbeddedAppInstance();
			        	if(voEmbApp) getChildRecursive(voEmbApp.getContainer());
			        }
			    }
			}
			getChildRecursive(container);
			
			var ctrl = null;
			for(var i=0, len=vaDataCtrls.length; i<len; i++){
				ctrl = vaDataCtrls[i];
				if(ctrl.type == "grid"){
					vaDataSets.push(ctrl.dataSet);
				}else{
					var dataSet = this.Group.getBindDataSet(ctrl.getAppInstance(), ctrl);
					if(dataSet == null) continue;
					vaDataSets.push(dataSet);
				}
			}
			
			return vaDataSets;
		};
		
		/**
		 * 메인화면에 막(Cover)를 쒸운다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 */
		AppKit.prototype.coverPage = function(app) {
			var coverCtl = new cpr.controls.Container("comPageCover");
			coverCtl.style.css({"background-color":"#ededed", "opacity":"0.5"});
			coverCtl.setLayout(new cpr.controls.layouts.XYLayout());
			var mainApp = this.getMainApp(app);
			
			var container = mainApp.getContainer();
			var layout = container.getLayout();
			if(layout instanceof cpr.controls.layouts.ResponsiveXYLayout){
				container.addChild(coverCtl, {
						positions: [{
								"media": "all and (min-width: 1320px)",
								"top": "0px",
								"right": "0px",
								"bottom": "0px",
								"left": "0px"
							},
							{
								"media": "all and (min-width: 1024px) and (max-width: 1319px)",
								"top": "0px",
								"right": "0px",
								"bottom": "0px",
								"left": "0px"
							},
							{
								"media": "all and (min-width: 500px) and (max-width: 1023px)",
								"hidden": false,
								"top": "0px",
								"right": "0px",
								"bottom": "0px",
								"left": "0px"
							}, 
							{
								"media": "all and (max-width: 499px)",
								"hidden": false,
								"top": "0px",
								"right": "0px",
								"bottom": "0px",
								"left": "0px"
							}
				]});
			}else if(layout instanceof cpr.controls.layouts.FormLayout){
				app.floatControl(coverCtl, {
					"top": "0px",
					"right": "0px",
					"bottom": "0px",
					"left": "0px"
				});
			}else{
				container.addChild(coverCtl, {
					"top": "0px",
					"right": "0px",
					"bottom": "0px",
					"left": "0px"
				});
			}
		};
		
		/**
		 * 컨트롤(그룹) 또는 Grid의 내의 입력 값에 대한 유효성 체크를 수행한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#uicontrol | Array} paCtlId 컨트롤 ID
		 * @param {String} dataScope (all:그리드의 전체 데이터, modify:변경된 전체 Row, current:현재  Row)
		 * @returns {Boolean} Valid true, Invalid false.
		 */
		AppKit.prototype.validate = function(app, paCtlId, dataScope) {
			dataScope = dataScope != null ? dataScope : "upd";
			if(!(paCtlId instanceof Array)){
				paCtlId = [paCtlId];
			}
			
			var valid = true;
			for(var i=0, len=paCtlId.length; i<len; i++) {
				var ctrlId = paCtlId[i];
				var ctrl = app.lookup(paCtlId[i]);
				if(ctrl instanceof cpr.controls.Grid){
					valid = this._validateGrid(ctrl, dataScope);
				}else if(ctrl instanceof cpr.controls.Container){
					/** @type cpr.bind.BindContext */
					var voBindContext = this.Group.getBindContext(app, ctrl);
					if(voBindContext){
						/**@type cpr.data.DataSet */
						var voDs = voBindContext.grid ? voBindContext.grid.dataSet : voBindContext.dataSet;
						var rowIndex = voBindContext.grid ? voBindContext.grid.getSelectedRowIndex() : voBindContext.rowIndex;
						//프리폼의 상태가 삭제상태이면... 유효성 체크에서 제외함
						if(voDs.getRowState(rowIndex) == cpr.data.tabledata.RowState.DELETED) continue;
					}
					
					valid = this._validateControl(ctrl);
				}else{
					valid = this._validateControl(ctrl);
				}
				
				if(valid == false) {
					return false;
				}
			}
			
			return true;
		};
		
		/**
		 * @private
		 * 일반 컨트롤에 대한 Validation 체크
		 * @param {cpr.controls.UIControl} ctrl
		 * @param {cpr.controls.UIControl} poParentCtl
		 */
		AppKit.prototype._validateControl = function(ctrl, poParentCtl) {
			if(!ctrl) return true;
			
			var valid = true;
			var _this = this;
			if(ctrl instanceof cpr.controls.Container) { // Group 일 경우 체크
				var children = this._getChildren(ctrl);
				var child;
				for(var i=0, len=children.length; i<len; i++){
					child = children[i];
					// 컨트롤별 Validation Check
					if(this._validateControl(child, ctrl) == false) {
						valid = false;
						break;
					}
				}
				return valid;
			} else if(ctrl instanceof cpr.controls.UDCBase){ //UDC인 경우
				var embApp = ctrl.getEmbeddedAppInstance();
				var children = embApp.getContainer().getAllRecursiveChildren();
				var child;
				for(var i=0, len=children.length; i<len; i++){
					child = children[i];
					// 컨트롤별 Validation Check
					if(this._validateControl(child, ctrl) == false) {
						valid = false;
						break;
					}
				}
				return valid;
			} else {
				valid = this.Validator.validate(ctrl, ctrl.value, poParentCtl);
				if(valid == false) {
					//탭내에 컨트롤이 존재하는 경우... 해당 탭페이지 포커싱
					this._focusToTabItem(ctrl);
					ctrl.focus();
				}
				return valid;
			}
		};
		
		/**
		 * @private
		 * Grid의 변경된 전체 데이터에 대한 Validation 체크
		 * - 사이트별 Customizing 필요
		 * 가능한 한 Validation 체크시 validate 메소드를 사용
		 * @param {cpr.controls.Grid} poGrid 체크할 Grid
		 * @param {String} dataScope (all:그리드의 전체 데이터, modify:변경된 전체 Row, current:현재  Row)
		 * @returns {Boolean}
		 */
		AppKit.prototype._validateGrid = function(poGrid, dataScope) {
			dataScope = dataScope != null ? dataScope : "modify";
			/** @type cpr.controls.Grid */
			var grd = poGrid;
			if(!grd) return false;
			
			var vsDataBindCtxId = grd.userAttr("bindDataFormId");
			
			var _this = this;
			/**
			 * @type cpr.controls.gridpart.GridBand
			 */
			var detailBand = grd.detail;
			var cellCnt = detailBand.cellCount;
			
			/**
			 * @type cpr.data.DataSet
			 */
			var dataSet = grd.dataSet;
			var rowIndexs = null;
			if(dataScope == "all"){
				rowIndexs = dataSet.getRowStatedIndices(cpr.data.tabledata.RowState.INSERTED | cpr.data.tabledata.RowState.UPDATED | cpr.data.tabledata.RowState.DELETED | cpr.data.tabledata.RowState.UNCHANGED);
			}else{
				rowIndexs = dataSet.getRowStatedIndices(cpr.data.tabledata.RowState.INSERTED | cpr.data.tabledata.RowState.UPDATED);
			}
			var _this = this;
			var invalid = rowIndexs.some(function(idx) {
				var row = dataSet.getRow(idx);
				var col = null;
				for(var i = 0; i < cellCnt; i++) {
					/**  @type cpr.controls.gridpart.GridColumn */
					col = detailBand.getColumn(i);
					//컬럼 매핑노드가 없으면... SKIP
					if(col.columnName == null || col.columnName == "") continue;
					if(col.columnType == "checkbox" || col.columnType == "rowindex") continue;
					//컬럼 유형이 output이면... SKIP
					if(col.controlType == null || col.controlType == "output" || col.controlType == "button" || col.controlType == "img") continue;
					//신규행  PK 체크 무시... SKIP
					if(row.getState() == cpr.data.tabledata.RowState.INSERTED && (col.control && col.control.userAttr("ignorePk") == "Y")) continue;
					
					// 컨트롤별 Validation Check
					if(_this.Validator.validate(col.control, row.getValue(col.columnName), grd, idx, i) == false) {
						//유효성 체크로 인해 selection-change 발생여부 셋팅 
						grd.userAttr("selectionChangeByValidation", "true");
						//탭내에 컨트롤이 존재하는 경우... 해당 탭페이지 포커싱
						_this._focusToTabItem(grd);
						if(ValueUtil.isNull(vsDataBindCtxId)){
							grd.setEditRowIndex(idx, true);
							grd.focusCell(idx, i);
							//포커싱할 컬럼이 UDC인 경우에...
							var dctrl = grd.detail.getColumn(i).control;
							if(dctrl instanceof cpr.controls.UDCBase){
								var empApp = dctrl.getEmbeddedAppInstance();
								dctrl = AppUtil.getUDCBindValueControl(dctrl);
								if(dctrl) empApp.focus(dctrl.id);
							}
						}else{
							grd.selectRows(idx);
							var cctrl = _this.Group.getDataBindedControl(dataSet.getAppInstance(), vsDataBindCtxId, col.columnName);
							if(cctrl) _this.Control.setFocus(cctrl.getAppInstance(), cctrl.id);
						}
						
						return true;
					}
				}
				return false;
			});
			if(invalid == true) {
				return false;
			}
			
			return true;
		};
		
		
		/**
		 * @private
		 * Validation 체크시 컨트롤이 속한 탭폴더 선택용
		 * @param {cpr.controls.UIControl} ctrl - 컨트롤 객체
		 */
		AppKit.prototype._focusToTabItem = function(ctrl) {
			/**@type cpr.controls.TabFolder */
			var tab = null;
			ctrl.findParent(function(pctrl){
				if(pctrl instanceof cpr.controls.TabFolder){
					tab = pctrl;
					return true;
				}
				return false;
			});
			if(tab){
				var tabItem = null;
				var tabItems = tab.getTabItems();
				ctrl.findParent(function(pctrl){
					tabItems.some(function(each){
						if( each.content == pctrl){
							tabItem = each;
							return true;
						}
						return false;
					});
					return tabItem != null;
				});
				if(tabItem && tabItem != tab.getSelectedTabItem()){
					tab.setSelectedTabItem(tabItem);
				}
			}
		};
		
		/**
		 * @private
		 * 그룹 컨트롤내의 자식 컨트롤 목록을 반환한다.
		 * @param {cpr.controls.Container} pcGroup - 그룹컨트롤
		 */
		AppKit.prototype._getChildren = function(pcGroup) {
			var children = pcGroup.getAllRecursiveChildren();
			function getNextControls(each,children){
				var order = [each];
				var next = each;
				while(next != null){
					next = next.getNextControl();
					if(next != null && children.indexOf(next) > -1 && order.indexOf(next) == -1) order.push(next);
					else next = null;
				}
				return order;
			} 
			
			var orderCtrls = [];
			children.forEach(function(each){
				if(children.indexOf(each.getPrevControl()) ==-1 && each.getNextControl() != null){
					orderCtrls = getNextControls(each,children);
				}
			});
			
			var etcCtrls = [];
			children.forEach(function(each){
				if(orderCtrls.indexOf(each) == -1){
					etcCtrls.push(each);
				}
			});
			
			return orderCtrls.concat(etcCtrls);
		};
		
		/**
		 * 디폴트 언어키 취득
		 * - 사이트별 Customizing 필요 getDefaultLocale 메소드 필요.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @returns {String} 디폴트 언어키
		 */
		AppKit.prototype.getDefaultLocale = function(app) {
			var rootApp = app.getRootAppInstance();
			if(rootApp.hasAppMethod("getDefaultLocale")){
				return rootApp.callAppMethod("getDefaultLocale");
			}
		};
		
		
		function AppAuthKit(appKit){
			this._appKit = appKit;
		};
		
		/**
		 * 로그인 사용자의 정보를 취득
		 * - 사이트별 Customizing 필요 
		 *   Root App에 getUserInfo 생성 필요.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {String} psUserInfoType (Optional) 사용자정보 TYPE 세션정보 참고
		 * 					   또는 
		 * 					   var mapUserInfo = util.getUserInfo();
		 * 					   mapUserInfo.get("USER_ID");
		 * 	USER_ID 		: 사용자 ID
		 *  USER_NM			: 사용자명
		 * 	USER_DIV_CD		: 사용자구분코드(STAFF:직원/PROF:교원/STUD:학생/GRAD_STUD:대학원생)
		 *  STAFF_NO		: 교직원번호(교원원인에 한함)
		 *  STUD_NO			: 학번(학생/대학원생에 한함)
		 *  UNIV_CD			: 소속대학코드(학생/대학원생에 한함)
			ASGN_DEPT_CD	: 소속부서코드
			ASGN_DEPT_NM	: 소속부서명
			CUR_RCD_NM		: 학점은행제 교육과정명
		 * @returns {String | cpr.data.DataMap} psUserInfoType 미지정시 Map 형태의 사용자 정보 리턴 
		 */
		AppAuthKit.prototype.getUserInfo = function(app, psUserInfoType) {
			var rootApp = app.getRootAppInstance();
			if(rootApp.hasAppMethod("getUserInfo")){
				if(ValueUtil.isNull(psUserInfoType)){
					return rootApp.callAppMethod("getUserInfo");
				}else{
					return rootApp.callAppMethod("getUserInfo", [psUserInfoType]);
				}
			}
		};
		
		/**
		 * 메뉴를 바로 오픈할 경우 부모페이지에서 전달한 파라미터를 꺼내는 함수 (JSON 형태로 리턴됨)
		 * 
		 * [ 메뉴 파람 담는 법 및 메뉴 다이렉트 오픈 예제 ]
		 * var voParam = { SUBJ_NO : util.Grid.getCellValue(app, "grdMain", "SUBJ_NO")};
		 * util.MDI.open(app, "rptCSubjMstMng", voParam);
		 * 
		 * [ 사용법 ]
		 * var voMenuParam = util.Auth.getMenuParam(app);
		 * var vsSubjNo = voMenuParam.SUBJ_NO;
		 * 
		 * - 사이트별 Customizing 필요 
		 *    Root App에 getMenuParam 메소드 생성 필요
		 * @param {cpr.core.AppInstance} app
		 * 
		 */
		AppAuthKit.prototype.getMenuParam = function(app) {
		    var rootApp = app.getRootAppInstance();
		    if(rootApp.hasAppMethod("getMenuParam")){
		        return JSON.parse(rootApp.callAppMethod("getMenuParam"));
		    }
		};
		
		/**
		 * 메뉴 정보 취득
		 * - 사이트별 Customizing 필요
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {String} (Optional) psMenuType  메뉴 정보 TYPE
		 *                 생략시 메뉴 정보 MAP 리턴
		 * @returns psMenuType 생략시 
		 *          {cpr.utils.ObjectMap} 
		 * 			getMenuInfo.get("OPRT_ROLE_ID");		//업무역할ID
					getMenuInfo.get("MENU_ID");				//메뉴ID
					getMenuInfo.get("PGM_ID");				//프로그램ID
					getMenuInfo.get("MENU_NM");				//메뉴명
					getMenuInfo.get("USE_AUTH_RCD"); 	//메뉴권한구분코드[CMN045]
					getMenuInfo.get("DEPT_AUTH_RCD"); 	 	//권한볌위코드[CMN035]
					getMenuInfo.get("UNIT_SYS_RCD");		//단위시스템코드[CMN003]
					getMenuInfo.get("CALL_PAGE");			//호출페이지
					getMenuInfo.get("TOP_MENU_ID");			//최상위 메뉴ID
					getMenuInfo.get("DOWNLD_YN");			//파일다운로드 여부
		 */
		AppAuthKit.prototype.getMenuInfo = function(app, psMenuType){
			var voMap = new cpr.utils.ObjectMap();
			
			var _mainApp = this._appKit.getMainApp(app);
			var vsData = null;
			if(_mainApp.__menuInfo != null){
				vsData = _mainApp.__menuInfo;
			}else{
				var rootApp = app.getRootAppInstance();
				/** @type cpr.controls.MDIFolder */
				var vcMdi = rootApp.lookup("mdiMainContent");
				if(vcMdi){
					var vcTabItem = vcMdi.getSelectedTabItem();
					if(vcTabItem != null){
						vsData = vcTabItem.userAttr("__menuInfo");
						_mainApp.__menuInfo = vsData;
					}
				}
			}
			if(!ValueUtil.isNull(vsData)){
				var voData = JSON.parse(vsData);
				if(psMenuType != null){
					return ValueUtil.fixNull(voData[psMenuType]);
				}else{
					for(var key in voData){
						voMap.put(key, ValueUtil.fixNull(voData[key]));
					}
					return voMap;
				}
			}else{
				return voMap;
			}
		};
		
		/**
		 * 해당 화면이 소속권한 화면인지 여부(true/false) 반환한다.
		 * - 사이트별 Customizing 필요
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 */
		AppAuthKit.prototype.isAsgnDeptAuth = function(app){
			var vsDeptAuthRcd = this._appKit.Auth.getMenuInfo(app, "DEPT_AUTH_RCD");
			// 소속부서 or 소속관리부서
			return vsDeptAuthRcd == "CMN035.0003" || vsDeptAuthRcd == "CMN035.0007" ? true : false;
		};
		
		/**
		 * 해당 화면이 개인권한 화면인지 여부(true/false) 반환한다.
		 * - 사이트별 Customizing 필요
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 */
		AppAuthKit.prototype.isPrivateAuth = function(app){
			var vsDeptAuthRcd = this._appKit.Auth.getMenuInfo(app, "DEPT_AUTH_RCD");
			return vsDeptAuthRcd == "CMN035.0009" ? true : false;
		};
		
		/**
		 * 해당 화면이 학과장 권한으로 할당되었는지 여부(true/false) 반환한다.
		 * - 사이트별 Customizing 필요
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 */
		AppAuthKit.prototype.isDepartMentDeanAuth = function(app){
			var vsOprtRoleId = this._appKit.Auth.getMenuInfo(app, "OPRT_ROLE_ID");
			return vsOprtRoleId == "USER_CSR_POS" ? true : false;
		};
		
		/**
		 * 해당 화면이 관리권한 화면인지 여부(true/false) 반환한다.
		 * - 사이트별 Customizing 필요
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 */
		AppAuthKit.prototype.isMenuMngAuth = function(app){
			/**@type cpr.core.AppInstance */
			var vsMenuAuthRcd = this._appKit.Auth.getMenuInfo(app, "USE_AUTH_RCD");
			return vsMenuAuthRcd == "CMN045.0003" ? true : false;
		};
		
		/**
		 * 해당 화면이 조회전용 화면인지 여부(true/false) 반환한다.
		 * - 사이트별 Customizing 필요
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 */
		AppAuthKit.prototype.isReadUseAuth = function(app){
			/**@type cpr.core.AppInstance */
			var vsUseAuthRcd = this._appKit.Auth.getMenuInfo(app, "USE_AUTH_RCD");
			return vsUseAuthRcd == "CMN045.0002" ? true : false;
		};
		
		/**
		 * 사용자 권한에 따라 핸들링되어야 하는 UI 컨트롤 목록을 지정한다.
		 * 기본적으로 공통처리되며, 추가 예외적으로 적용되어야 하는 컨트롤들만을 지정하면 된다.
		 * - 사이트별 Customizing 필요
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#uicontrol | Array} paCtrls - 대상 컨트롤 ID 또는 ID 배열
		 */
		AppAuthKit.prototype.setAuthCtrls = function(app, paCtrls){
			/**@type cpr.core.AppInstance */
			var _app = this._appKit.getMainApp(app);
			
			if(!(paCtrls instanceof Array)){
				paCtrls = [paCtrls];
			}
			if(_app.__AUTH_TARGET_CTRLS == null || _app.__AUTH_TARGET_CTRLS == undefined){
				_app.__AUTH_TARGET_CTRLS = [];
			}
			_app.__AUTH_TARGET_CTRLS.concat(paCtrls);
		};
		
		/**
		 * 화면 권한에 따른 컨트롤 제어를 수행한다.
		 * - 사이트별 Customizing 필요
		 * ex) 조회권한인 경우 권한 컨트롤 비활성화처리
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {String} psMenuAuthDivRcd - 메뉴권한코드[CMN045]
		 * @param {String} psSearchBoxId - (Optional) 검색조건 그룹ID
		 */
		AppAuthKit.prototype.applyAuthForCtrls = function(app, psMenuAuthDivRcd, psSearchBoxId){
			if(app.isRootAppInstance()) return;
			
			var getParameterByName = function(name, url) {
			    name = name.replace(/[\[\]]/g, "\\$&");
			    var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)");
			    var results = regex.exec(url);
			    return results == null ? "" : results[2];
			}
			
			//새창으로 띄웠는지 여부 Flag, 새창으로 띄운 경우... 무조건 조회용임
			var vsFlag = getParameterByName("flag", top.location.href); 
			
			if(vsFlag !== "R" && psMenuAuthDivRcd != "CMN045.0002") return;
			
			var vaSearchBoxIds = ValueUtil.split((!ValueUtil.isNull(psSearchBoxId) ? psSearchBoxId : ""), ",");
			
			var vaTargetCtrls = new Array();
			var container = app.getContainer();
			function getChildRecursive(poContainer){
			    var vaChildCtrls = poContainer ? poContainer.getAllRecursiveChildren() : container.getAllRecursiveChildren();
			    for (var i=0, len=vaChildCtrls.length; i<len; i++) {
			        if (vaChildCtrls[i].type == "udc.com.comButton"
			        	|| vaChildCtrls[i].type == "udc.com.comButtonAddDel"
			        	|| vaChildCtrls[i].type == "grid"
			        	|| vaChildCtrls[i].type == "button"
			        	|| vaChildCtrls[i].type == "container" && vaSearchBoxIds.indexOf(vaChildCtrls[i].id) == -1 && (vaChildCtrls[i].getLayout() instanceof cpr.controls.layouts.FormLayout)) {
			        	vaTargetCtrls.push(vaChildCtrls[i]);
			        }else if (vaChildCtrls[i] instanceof cpr.controls.Container ) {
			        	getChildRecursive(vaChildCtrls[i]);
			        }
			    }
			    vaChildCtrls = null;
			}
			
			//하위의 대상 컨트롤들을 모두 취합
			getChildRecursive(container);
			
			//추가적으로 화면단에 대한 넘겨준 대상 컨트롤이 있는 경우 
			if(!ValueUtil.isNull(app.__AUTH_TARGET_CTRLS)){
				vaTargetCtrls.concat(app.__AUTH_TARGET_CTRLS);
			}
			
			var header = this._appKit.Group.getAllChildrenByType(app, "udc.com.appHeader");
			if(header != null && header.length > 0){
				var vcCtrl = header[0].getEmbeddedAppInstance().lookup("grpButtons");
				if(vcCtrl){
					vcCtrl.visible = false;
				}
			}
			
			vaTargetCtrls.forEach(function(ctrl){
				//공통 작업버튼 UDC인 경우
				if(ctrl.type == "udc.com.comButton" || ctrl.type == "udc.com.comButtonAddDel"){
					ctrl.visible = false;
				//그리드인 경우
				}else if(ctrl.type == "grid"){
					if(ctrl.readOnly !== true) ctrl.readOnly = true;
				//프리폼인 경우
				}else if(ctrl.type == "container"){
					if(ctrl.style && ctrl.style.getClasses().join(";").indexOf("form-box") != -1 && ctrl.getBindContext() != null) ctrl.enabled = false;
				//버튼인 경우
				}else if(ctrl.type == "button"){
					var vsStyle = ctrl.style ? ctrl.style.getClasses().join(";") : "";
					//커밋 버튼, 신규 버튼, 삭제 버튼, 프리폼 삭제 버튼, 저장 버튼
					if(vsStyle.indexOf("btn-commit") != -1 || vsStyle.indexOf("btn-new") != -1 || vsStyle.indexOf("btn-delete") != -1 || vsStyle.indexOf("btn-delete-save") != -1 || vsStyle.indexOf("btn-save") != -1){
						ctrl.visible = false;
					}
				}
			});
		};
		
		// 모든 selection-change 이벤트에시 그리드에 대한  필터만 추가.
		//- 사이트별 Customizing 필요
		cpr.events.EventBus.INSTANCE.addFilter("selection-change", function(e) {
		    // 이벤트를 발생 시킨 컨트롤
		    var control = e.control;
		    /** @type cpr.core.AppInstance */
		    var _app = control.getAppInstance();
		    
		    // 이벤트 발송자가 그리드 이고.
		    if (control instanceof cpr.controls.Grid) {
		    	/** @type cpr.controls.Grid */
		    	var grid = control;
		    	if(grid.selectionUnit == "cell" && grid.getSelectedIndices()[0] == null){
		    		 e.stopPropagation();
		    	}else{
		    		var rowIndex = grid.selectionUnit != "cell" ? grid.getSelectedRowIndex() : grid.getSelectedIndices()[0]["rowIndex"];
			        // 그리드 선택 ROW가 -1이라면...
			        if (rowIndex < 0) {
			            // 이벤트 전파를 차단시킵니다.
			            e.stopPropagation();
			        }
		    	}
		    }
		});
		
		
		
		// 모든 before-selection-change 이벤트에시 그리드에 대한  필터만 추가.
		//- 사이트별 Customizing 필요
		cpr.events.EventBus.INSTANCE.addFilter("before-selection-change", function(e) {
		    // 이벤트를 발생 시킨 컨트롤
		    var control = e.control;
		    /** @type cpr.core.AppInstance */
		    var _app = control.getAppInstance();
		    
		    // 이벤트 발송자가 그리드 이고.
		    if (control instanceof cpr.controls.Grid) {
		    	if(e.newSelection[0] == null || e.newSelection[0] == undefined){
		    		// 이벤트 전파를 차단시킵니다.
		            e.stopPropagation();
				}
		    }
		});
		
		//모든 before-value-change 이벤트에시 인풋박스에 대한 대소문자 자동변환.
		//- 사이트별 Customizing 필요
		cpr.events.EventBus.INSTANCE.addFilter("before-value-change", function(e) {
		    // 이벤트를 발생 시킨 컨트롤
		    var control = e.control;
		    /** @type cpr.core.AppInstance */
		    
		    // 이벤트 발송자가 인풋박스이면.
		    if (control.type === "inputbox") {
		    	var inputLetter = control.userAttr("inputLetter");
				if (inputLetter == "uppercase") {
					if (/[a-z]/g.test(e.newValue)) {
						var newValue = e.newValue.toUpperCase();
						control.value = newValue;
						e.preventDefault();
						e.stopPropagation();
					}
				} else if (inputLetter == "lowercase") {
					if (/[A-Z]/g.test(e.newValue)) {
						var newValue = e.newValue.toLowerCase();
						control.value = newValue;
						e.preventDefault();
						e.stopPropagation();
					}
				}
		    }
		});
		
		//cpr.core.Platform.INSTANCE.onerror = function(report){
		//	console.log(JSON.stringify(report.stack, null, "  "));
		//};
		
		exports.AppKit = AppKit;
		
		
		globals.createModel = function(){
			return new AppKit();
		}
		
		globals.createCommonUtil = function(){
			var vsMainAppId = "app/com/inc/main"; //메인 APP-ID를 지정
		//	try {
		//		/**
		//		 * @type cpr.core.AppInstance
		//		 */
		//		var rootApp = cpr.core.Platform.INSTANCE.lookup(vsMainAppId).getInstances()[0];
		//		return rootApp.callAppMethod("getCommonUtil");
		//	} catch (ex){
				return new AppKit();
		//	}
		};
		
		
		//round 함수
		cpr.expression.ExpressionEngine.INSTANCE.registerFunction("getRound", function(value, position) {
			if(isNaN(value)) return 0;
			else if(value == Infinity || value == -Infinity) return 0;
			if(position == undefined || position == null) return Math.round(value);
			else return Math.round(value * (10 * position))/(10 * position);
		});
		//floor 함수
		cpr.expression.ExpressionEngine.INSTANCE.registerFunction("getFloor", function(value, position) {
			if(isNaN(value)) return 0;
			else if(value == Infinity || value == -Infinity) return 0;
			if(position == undefined || position == null) return Math.floor(value);
			else return Math.floor(value * (10 * position))/(10 * position);
		});
		//숫자 천단위 콤마 포맷
		cpr.expression.ExpressionEngine.INSTANCE.registerFunction("formatToNumber", function(value) {
			if(isNaN(value)) return value;
			return new String(value).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
		});
		
		cpr.expression.ExpressionEngine.INSTANCE.registerFunction("getRpad", function(str, padLen, padStr) {
			if(str == undefined) return;
			
			var parts = str.toString().split(".");
			
			padStr += "";
			var pointVal = "";
			if(parts[1] != null){
				pointVal = parts[1];
			}
			
			while(pointVal.length < padLen) pointVal = pointVal + padStr;
			
			pointVal = pointVal.length >= padLen ? pointVal.substring(0, padLen) : pointVal;
			return parts[0] + "." + pointVal;
		});
	});
})();
/// end - module/common
/// start - module/createAppAndLayout
/*
 * Module URI: module/createAppAndLayout
 * SRC: module/createAppAndLayout.module.js
 *
 * This file was generated by eXbuilder6 compiler, Don't edit manually.
 */
(function(){
	cpr.core.Module.define("module/createAppAndLayout", function(exports, globals, module){
		/************************************************
		 * createAppAndLayout.module.js
		 * Created at 2020. 7. 02. 오후 4:49:07.
		 *
		 * @author csj
		 ************************************************/
		
		
		/**
		 * 본 모듈은 데이터를 통해 화면 레이아웃을 동적으로 그려줍니다.
		 */
		
		
		/************************************************
		 * 전역 변수 선언
		 ************************************************/
		
		/**
		 * 탭 설정
		 */
		var vaDataTab = null; //탭 개수 
		/**
		 * 바로가기(링크)
		 */
		var vaDataLink = null; //링크 개수
		
		/**
		 * 탭 데이터셋 컬럼 타입
		 */
		var msRelateSrvGb = "RELATE_SRVCGB";
		
		/**
		 * 탭 관련된 컬럼 타입
		 */
		var msRelateSrvId = "RELATE_SRVCID";
		
		/**
		 * 팝업관련 컬럼 이름
		 */
		var msPopupAtc = "PRINCIPLE_ATC";
		
		/**
		 * 검색 데이터맵 이름
		 */
		
		var msSearchData = "dmSearchData";
		
		/**
		 * 서비스 아이디 이름
		 */
		
		var msStrSvrId = "strSvrId";
		
		/**
		 * 라벨 컬럼 이름(탭의 한글이름)
		 */
		
		var msLabelNm = "LABEL_NM";
		var msLabel = "LABEL";
		/**
		 * 최상위 루트 App
		 */
		var mcRootApp = null;
		
		/**
		 * 새롭게 생성될 탭 App
		 */
		var mcTabApp = null;
		
		/**
		 * mdi 탭 컨트롤
		 */
		var mcTabControl = null;
		
		/**
		 * 그리드 디테일그룹 ID
		 */
		var msGrpInfoNm = "grpDetail";
		
		/**
		 * 그리드 & 차트 영역 ID
		 */
		var msGrpGridAndCharNm = "grpGridAndChart";
		
		/**
		 * 자기자신을 가져올 변수
		 */
		var _this = null;
		
		/**
		 * 통계변수
		 */
		var msAnalyNm = "ANALYSIS_FUNCNM";
		var msAnalyID = "ANALYSIS_FUNCID";
		
		/**
		 * 검색박스 아이디
		 */
		var grpSearchBox = "grpSearchBox";
		
		
		/************************************************
		 * css관련 변수
		 ************************************************/
		
		/**
		 * 탭인스턴스 안에 mdi폴더 css
		 */
		var msMdiHeader = "tabHeader";
		
		/**
		 * 팝업 css
		 */
		var msNoticePopup = "NoticePopup";
		
		/**
		 * 타이틀 css
		 */
		var titleOutPut = "titleOutput";
		
		/**
		 * 검색 토글버튼 
		 */
		var msBtnArrowU = "btn-arrow-up";
		var msBtnArrowD = "btn-arrow-down";
		
		/**
		 * 링크버튼 css
		 */
		var msLinkBtn = "btn-0001";
		
		// 의존 모듈 선언.
		module.depends("module/common");
		
		function CreateAppLayout(invisibleKit) {
			this._invisibleKit = invisibleKit;
			_this = this;
		};
		
		/**
		 * 화면의 최소넓이를 세팅해줍니다.
		 * @param {cpr.core.AppInstance} app
		 * @param {Number} width
		 */
		CreateAppLayout.prototype.setMinWidth = function(app, width) {
			
			var vcLayout = app.getContainer().getLayout();
			
			vcLayout.setColumnMinWidth(0, width);
			
		}
		
		
		
		/**
		 * 앱을 생성해줍니다.
		 * @param {cpr.core.AppInstance} app
		 * @param {{
		 * 		appId : String ,
		 * 		path : String,
		 * 		label : String
		 * 	}} initValue
		 * @return {cpr.core.App} voApp;
		 */
		CreateAppLayout.prototype.createApp = function(app, initValue) {
			
			
			mcRootApp = app;
			
			var vsAppId = initValue["appId"];
			
			var voApp = new cpr.core.App(vsAppId, {
				onCreate: function(app, exports) {
					var container = app.getContainer();
					container.style.css({
						"width": "100%",
						"top": "0px",
						"height": "100%",
						"left": "0px"
					});
				}
			});
			return voApp;
			
		}
		
		/**
		 * 앱을 삭제해줍니다.
		 * @param {cpr.core.AppInstance} app
		 */
		CreateAppLayout.prototype.disposeApp = function(app){
			
			if(app){
				app.getContainer().getAllRecursiveChildren().forEach(function(each){
					if(each.type == "embeddedapp") {
						if(each.getEmbeddedAppInstance()){
							each.getEmbeddedAppInstance().dispose();
						}
						
					}
				});
				
				app.dispose();
			}
		}
		
		/**
		 * 타겟 임베디드에 동적으로 생성한 앱을 넣어준다.
		 * @param {cpr.core.App} app 동적으로 만든 앱
		 * @param {cpr.controls.EmbeddedApp} emb 타겟 임베디드 객체
		 */
		CreateAppLayout.prototype.setAppEmb = function(app, emb) {
			
			app.createNewInstance();
			
			app.getInstances()[0].run(emb, function() {});	
			
			return app.getInstances()[0];
			
		}
		
		/**
		 * 레이아웃을 설정한다.(xy레이아웃, form레이아웃, vertical레이아웃)
		 * <pre><code> var layoutObject = {
		 * type : "FormLayout || XYLayout || VerticalLayout",
		 * setting : {
		 *	rowSize : ["64px","50px","40px","1fr"],
		 *	column : ["1fr"],
		 *  margin : ["10px","10px","10px","10px"],
		 *	horizontalSpacing : "0px",
		 *	verticalSpacing : "10px"
		 * }
		 *};
		 * </code></pre>
		 * @param {cpr.core.AppInstance} app
		 * @param {
		 *   type : String ,
		 *   setting : {  
		 * 			rowSize : Array,
		 * 			column : Array,
		 * 			margin : Array,
		 * 			horizontalSpacing : String,
		 * 			verticalSpacing : String,
		 * 			spacing : Number
		 * 		}
		 * } option 레이아웃 설정
		 * 
		 */
		CreateAppLayout.prototype.settingLayout = function(app, option) {
			
			
			var container = app.getContainer();
			
			if(ValueUtil.isNull(option)){
				option.type = "XYLayout";
			}
			
			switch (option.type) {
				case "XYLayout":
					
					break;
				case "FormLayout":
					var FormLayout = new cpr.controls.layouts.FormLayout();
					FormLayout.topMargin = option.setting.margin[0];
					FormLayout.rightMargin = option.setting.margin[1];
					FormLayout.bottomMargin = option.setting.margin[2];
					FormLayout.leftMargin = option.setting.margin[3];
					FormLayout.horizontalSpacing = option.setting.horizontalSpacing;
					FormLayout.verticalSpacing = option.setting.verticalSpacing;
					FormLayout.setColumns(option.setting.column);
					FormLayout.setRows(option.setting.rowSize);
					option.setting.rowSize.forEach(function(each, index) {
						FormLayout.setRowAutoSizing(index, true);
					});
					FormLayout.setRowAutoSizing(0, true);
					container.setLayout(FormLayout);
					container.style.addClass(option.style);
					break;
					
				case "VerticalLayout":
					var VerticalLayout = new cpr.controls.layouts.VerticalLayout();
					VerticalLayout.topMargin = option.setting.margin[0];
					VerticalLayout.rightMargin = option.setting.margin[1];
					VerticalLayout.bottomMargin = option.setting.margin[2];
					VerticalLayout.leftMargin = option.setting.margin[3];
					VerticalLayout.spacing = option.setting.spacing;
					container.setLayout(VerticalLayout);
					break;
					
				default:
					break;
			}
			
		}
		
		/**
		 * 서브미션에서 내려온 데이터를 기준으로 화면을 구성한다.
		 * @param {cpr.core.AppInstance} app
		 * @param {cpr.data.DataSet} dsLinkList
		 * @param {cpr.data.DataSet} dsGridInfo
		 * @param {{
		 * 		appId : String ,
		 * 		path : String,
		 * 		label : String
		 * 	}} initValue
		 * @return {cpr.core.AppInstance} mcTabApp
		 */
		CreateAppLayout.prototype.setLayout = function(app, dsLinkList, dsGridInfo, initValue) {
			
			vaDataTab = []; //탭 개수
			vaDataLink = []; //링크 개수
			
			setLinkList(dsLinkList);
			
			var container = app.getContainer();
			
			/*헤더 영역을 그려준다 */
			var vcGrpHeader = new cpr.controls.Container("grpHeader");
			
			var vcFormHear = new cpr.controls.layouts.FormLayout();
			vcFormHear.horizontalSpacing = "0px";
			vcFormHear.verticalSpacing = "0px";
			vcFormHear.setColumns(["1fr"]);
			vcFormHear.setRows(["28px", "36px"]);
			vcGrpHeader.setLayout(vcFormHear);
			
			var vcHeaderTop = new cpr.controls.Container("grpHeaderTop");
			
			var vcTFormLayout = new cpr.controls.layouts.FormLayout();
			vcTFormLayout.setColumns(["1fr", "200px"]);
			vcTFormLayout.setColumnAutoSizing(1, true);
			vcTFormLayout.setRows(["1fr"]);
			vcHeaderTop.setLayout(vcTFormLayout);
			
			/*이동경로 udc를 그려준다 */
			var vcPathUdc = new udc.cmn.Breadcrumb();	
			vcPathUdc.class = "arrow";	
			vcPathUdc.values = initValue["path"];
			
			vcHeaderTop.addChild(vcPathUdc, {
				"colIndex": 1,
				"rowIndex": 0
			});
			vcGrpHeader.addChild(vcHeaderTop, {
				"colIndex": 0,
				"rowIndex": 0,
				"rightSpacing": 0
			});
			
			/*앱 타이틀 영역을 그려준다 */
			var vcHeaderTitle = new cpr.controls.Container("grpHeaderTitle");
			
			var vcFormHTitle = new cpr.controls.layouts.FormLayout();
			vcFormHTitle.setColumns(["1fr","10px", "70px"]);
			vcFormHTitle.setColumnAutoSizing(0, true);
			vcFormHTitle.setColumnAutoSizing(1, true);
			vcFormHTitle.setRows(["1fr"]);
			vcHeaderTitle.setLayout(vcFormHTitle);
			
			var vcTitltOutput = new cpr.controls.Output("TitleOutput");
			vcTitltOutput.value =  initValue["label"];
			vcTitltOutput.style.addClass("h3"); //titleOutput
			
			vcHeaderTitle.addChild(vcTitltOutput, {
				"colIndex": 0,
				"rowIndex": 0
			});
			
			/* 미승인 날짜 텍스트 추가*/
			var voRootAppIns = app.getRootAppInstance();
			var vcDsListAppDt = voRootAppIns.lookup("dsListAppDt")
			if(vcDsListAppDt) {
				var vsDisableDate = vcDsListAppDt.getColumnData("TMPV5")[0];
				if(vsDisableDate != null && vsDisableDate != "") {
					var vsFormatDate = moment(vsDisableDate).format("YY.MM.DD");
					var vcOptDisableDate = new cpr.controls.Output();
					vcOptDisableDate.value = "( '"+ vsFormatDate +" ) 미승인된 데이터";
					vcOptDisableDate.style.addClass("title-disapproval");
					
					vcHeaderTitle.addChild(vcOptDisableDate, {
						"colIndex": 1,
						"rowIndex": 0,
						"horizontalAlign": "fill",
						"verticalAlign": "center"
					});
				}
			}
			
			/*유의사항을 그려준다.*/
			var vsLangGb = voRootAppIns.lookup("dmSearchData").getValue("language_gb");
			var vcNoticeBtn = new cpr.controls.Button("Notice-Btn");
			vcNoticeBtn.value = vsLangGb != "KOR" ? "Note" : "유의사항";
			vcNoticeBtn.style.addClass("btn-notice");
			
			vcNoticeBtn.addEventListener("click", function(e) { 
				var vcBtnApp = e.control.getAppInstance();
				
				/** @type cpr.core.App */
				var vcPopupApp = createPopUpApp();
				
				var initValue = {
					"NoticeValue": dsGridInfo.getValue(0, msPopupAtc)
				}
				if (vcPopupApp) {
					app.dialogManager.openDialog(vcPopupApp.id, "", {width:480 ,height:350}, function(dialog){
						//2020-12-23 추가 언어코드별 유의사항 헤더타이틀 수정
						try{
							var vsLangGb = cpr.core.Platform.INSTANCE.getParameter("language_gb");
							
							if(vsLangGb) {
								if(vsLangGb == "ENG"){
									dialog.headerTitle = "Page Description";
								} else {
									dialog.headerTitle = "화면상세설명";
								}
							} else {
								throw new Error("파라미터 language_gb를 찾을 수 없습니다");
							}
						} catch(err){console.log(err)}
		//				dialog.headerTitle = "화면상세설명";
						dialog.initValue = initValue;
						
						dialog.addEventListener("close", function(e){
							vcNoticeBtn.focus(); // 웹접근성 추가(2020.09.07)
						});
					});
					
				}
				
			});
			
			vcHeaderTitle.addChild(vcNoticeBtn, {
				"colIndex": 2,
				"rowIndex": 0,
				"horizontalAlign": "fill",
				"verticalAlign": "center"
			});
			
			vcGrpHeader.addChild(vcHeaderTitle, {
				"colIndex": 0,
				"rowIndex": 1,
				"rightSpacing": 0
			});
			
			container.addChild(vcGrpHeader, {
				"colIndex": 0,
				"rowIndex": 0
			});
			
			
			/**
			 * vaDataTab에 따라서 탭을 만들어준다 데이터가 1개일때는 tab을 visual 처리를 해준다.
			 */	
			var vnTabLen = vaDataTab.length;
			
			var vcMdiFolder = new cpr.controls.MDIFolder("mdi1");
			
			mcTabControl = vcMdiFolder;	
			
			if (vnTabLen > 1) {
				vcMdiFolder.hideHeader = false;
				vcMdiFolder.style.removeClass(msMdiHeader);
			} else if(vnTabLen == 1){
				vcMdiFolder.hideHeader = true;
				vcMdiFolder.style.addClass(msMdiHeader);
			}else{	
				return null;
			}
			
			
			vaDataTab.forEach(function( each, index) {
				var vsLabelNm = each[msLabelNm];
				var vsLabel = each[msLabel];
				var tabItem = (function(tabFolder) {
					var tabItem = new cpr.controls.TabItem();
					tabItem.text = vsLabelNm;
					tabItem.id = index + 1;
					// TODO 엑셀 or PDF 익스포트할시 파일이름을 세팅하는 곳입니다.
					tabItem.userAttr("Name", initValue["label"] );
					return tabItem;
				})(vcMdiFolder);
				vcMdiFolder.addTabItem(tabItem);
			});
			
			/*무조건 첫번째를 클릭되어야되기 때문에 첫번째 아이템을 클릭한다. */
			vcMdiFolder.setSelectedTabItem(vcMdiFolder.getTabItems()[0]);
			
			/*탭폴더를 클릭했을 시 만약 한번이라도 클릭안했을 시에는 메인화면에서 서브미션을 통해서 화면을 새롭게 그려준다. 그렇지 않을시에는 화면을 다시 리로드 해준다.*/
			vcMdiFolder.addEventListener("selection-change", function(e) {		
				if (e.control.getSelectedTabItem().content) {
					e.control.getSelectedTabItem().content.redraw();
				} else {
					mcTabControl = e.control;
					mcRootApp.lookup(msSearchData).setValue(msStrSvrId, vaDataTab[e.control.getSelectedTabItem().id - 1].RELATE_SRVCID);
					mcRootApp.callAppMethod("setBtn", null);
				}
			});
			
			//탭폴더를 넣어줌
			container.addChild(vcMdiFolder, {
				"colIndex": 0,
				"rowIndex": 1
			});
			
			
			return true;
			
		}
		
		/**
		 * 메뉴클릭이 아닌 임베디드안에 탭을 클릭했을시 
		 * @param {cpr.core.AppInstance} app
		 */
		CreateAppLayout.prototype.setToLayout = function(app) {
			
			if (mcTabControl.getTabItems().length > 0) {
				var initValue = {
					appId: mcTabControl.getSelectedTabItem().text
				}
			}else{
				return null;
			}
			
			mcTabApp = this.createApp(app, initValue);
			
			var embeddedApp = new cpr.controls.EmbeddedApp(mcTabControl.getSelectedTabItem().id);
			
			embeddedApp.userAttr("Name", mcTabControl.getSelectedTabItem().userAttr("Name"));
			
			this.setAppEmb(mcTabApp, embeddedApp);	
			
			mcTabControl.getSelectedTabItem().content = embeddedApp;
			
			return embeddedApp.getEmbeddedAppInstance();
			
		};
		
		/**
		 * 탭 레이아웃 화면을 그려줍니다.
		 * @param {cpr.core.AppInstance} app
		 * @param {cpr.data.DataSet} pcAnalysis
		 */
		CreateAppLayout.prototype.setTabLayout = function(app, pcAnalysis) {
			
			if (!app) {
				return null;
			}
			var vcDsAnaly = pcAnalysis;
			
			var vcContainer = app.getContainer();
			
			/**
			 * 검색박스 
			 */
			var vcGrpSearch = new cpr.controls.Container("grpSearch");
		
			var vcFormSearch = new cpr.controls.layouts.FormLayout();
			vcFormSearch.horizontalSpacing = "0px";
			vcFormSearch.verticalSpacing = "0px";
			vcFormSearch.setColumns(["1fr"]);
			vcFormSearch.setRows(["1fr", "16px"]);
			vcGrpSearch.setLayout(vcFormSearch);
			
			var grpToggle = new cpr.controls.Container("grpToggle");
			
			var formLayout_7 = new cpr.controls.layouts.FormLayout();
			formLayout_7.setColumns(["1fr"]);
			formLayout_7.setRows(["16px"]);
			grpToggle.setLayout(formLayout_7);
			
			var _id = grpSearchBox;
			
			var group_3 = new cpr.controls.Container(_id);
			
			vcGrpSearch.addChild(group_3, {
				"colIndex": 0,
				"rowIndex": 0
			});
			
			vcContainer.addChild(vcGrpSearch, {
				"colIndex": 0,
				"rowIndex": 0
			});
			
			vcGrpSearch.addChild(grpToggle, {
				"colIndex": 0,
				"rowIndex": 1
			});
			
			/**
			 * 바로가기 버튼 및 분석통계 담을 컨테이너를 만들어준다.
			 */
			var vcQuickCtn = new cpr.controls.Container();
			vcQuickCtn.style.setClasses("border", "rounded", "bg-gray-100");
			
			
			var vcQuickFlowLayout = new cpr.controls.layouts.FlowLayout();
			vcQuickFlowLayout.scrollable = false;
			vcQuickFlowLayout.verticalAlign = "top";
			vcQuickFlowLayout.spacing = 0;
			vcQuickFlowLayout.topMargin = 8;
			vcQuickFlowLayout.leftMargin = 10;
			vcQuickFlowLayout.spacing = 10;
			vcQuickCtn.setLayout(vcQuickFlowLayout);
			
			/**
			 * 분석통계 화면을 만들어줍니다.
			 */
			
			var vcDsAnalyCount = vcDsAnaly.getRowCount();
			if(vcDsAnalyCount > 0){
				var output = new cpr.controls.Output("opt1");
				output.value = "분석통계";
				output.style.addClass("label");
				
				var comboBox = new cpr.controls.ComboBox("cmbAnaly");
				comboBox.setItemSet(vcDsAnaly, {
					label : msAnalyNm,
					value : msAnalyID
				});
				comboBox.placeholder = "선택하세요";
				
				vcQuickCtn.addChild(output, {
					"width": "65px",
					"height": "24px"
				});
				
				vcQuickCtn.addChild(comboBox, {
					"width": "130px",
					"height": "24px"
				});
				comboBox.addEventListener("selection-change", function(e){
					/** @type cpr.controls.Grid */
					var vcGrids  = app.lookup("grd");
					setAnalysisColumnVisible(vcGrids, e.newSelection[0].label);
				});
			}
			
			/**
			 * 링크 버튼을 만들어 준다.
			 */
			if (vaDataLink.length > 0) { // || 분석통계 부분 
				vaDataLink.forEach(function(each) {
					var vsLabelNm = each["LABEL"];			
					var btnNm = new cpr.controls.Button(vsLabelNm + "-btn");
					btnNm.style.addClass(msLinkBtn);
					btnNm.value = vsLabelNm;
					btnNm.userAttr("RELATE_SRVCID", each[msRelateSrvId]);
					
					btnNm.addEventListener("click", function(e) {				
						app.getHost().getAppInstance().dispose();
						mcRootApp.lookup(msSearchData).setValue(msStrSvrId, e.control.userAttr("RELATE_SRVCID"));
						mcRootApp.callAppMethod("setMenuClick",each[msRelateSrvId]);
						
					});
					vcQuickCtn.addChild(btnNm, {
						"width": "75px",
						"height": "24px",
						"autoSize": "width"
					});
				});
			} 
				
			if(vcDsAnalyCount < 1 && vaDataLink.length < 1)	{
					app.getContainer().getLayout().setRowVisible(1, false);		
			}else{	
				vcContainer.addChild(vcQuickCtn, {
					"colIndex": 0,
					"rowIndex": 1
				});
			}
			
		}
		
		/**
		 * 검색그룹에서 토글버튼을 만들어줍니다.
		 * @param {any} app
		 * @param {String} targetGrpId
		 */
		CreateAppLayout.prototype.setSearchToggleBtn = function(app, targetGrpId) {
			
			var vcGrp = app.lookup(targetGrpId);
			
			var btnToggle = new cpr.controls.Button("ToggleBtn");
			btnToggle.style.addClass(msBtnArrowU);
			btnToggle.userAttr({
				"isClick": "false"
			});
			
			
			btnToggle.addEventListener("click", function(e) {
				
				//검색박스를 나중에 넣어주기 때문에 getLastChild() 사용
				var vcParentLayout = e.control.getParent().getParent();
				var formLayout = vcParentLayout.getFirstChild().getLayout();
				
				if (e.control.userAttr("isClick") == "true") {
					setFormVisible(formLayout, true);
					btnToggle.style.removeClass(msBtnArrowD);
					btnToggle.style.addClass(msBtnArrowU);
					e.control.userAttr("isClick", "false");
				} else {
					setFormVisible(formLayout, false);
					btnToggle.style.removeClass(msBtnArrowU);
					btnToggle.style.addClass(msBtnArrowD);
					e.control.userAttr("isClick", "true");
				}
				
				/** @type cpr.controls.layouts.FormLayout */
				var layout = e.control.getAppInstance().getContainer().getLayout();
				
				
				// TODO 특정 컨트롤을 redraw() 해야할때 추가로 작성하세요. 		
				var control = layout.findControls({
					colIndex  : 0,
					rowIndex : 3,
					colSpan : 1,
					rowSpan : 1
				});
								
				control[0].redraw();
				
			});
			
			vcGrp.addChild(btnToggle, {
				"colIndex": 0,
				"rowIndex": 0,
				"horizontalAlign": "center"
			});
			
		}
		
		/**
		 * 검색영역에서 버튼클릭했을시 
		 * @param {cpr.controls.layouts.FormLayout} layout
		 * @param {Boolean} isBool
		 */
		function setFormVisible(layout, isBool) {
			for (var i = 1; i < layout.getRows().length; i++) {
				layout.setRowVisible(i, isBool);
			}
		}
		
		/**
		 * 타겟컨트롤을 원하는 위치에 세팅합니다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {cpr.controls.Control} targetCtrl 특정 위치에 세팅할 컨트롤 or 그룹
		 * @param {
		 *   {
		 *     xyLayout : {
		 *       top : number,
		 *       right : number,
		 *       bottom : number,
		 *       left : number
		 *     },
		 *     formLayout : {
		 *       rowIndex : number,
		 *       colIndex :number,
		 *       rowSpan : number,
		 *       colSpan : number       
		 *     },
		 *     verticalLayout : {
		 *       index : number
		 *     }   
		 *   }
		 * } pos 레이아웃형태에 따른 위치값
		 * @param {Boolean} default : true isVisible
		 */
		CreateAppLayout.prototype.setPosCtrl = function(app, targetCtrl, pos, isVisible){
			
			var container = app.getContainer();
			
			if(app.getContainer().getLayout() instanceof cpr.controls.layouts.XYLayout){		
				container.addChild(targetCtrl, {
					top : pos.xyLayout.top,
					right : pos.xyLayout.right,
					bottom : pos.xyLayout.bottom,
					left : pos.xyLayout.left
					
				});
			}else if(app.getContainer().getLayout() instanceof cpr.controls.layouts.FormLayout){
				container.addChild(targetCtrl, {
					colIndex : pos.formLayout.colIndex,
					rowIndex : pos.formLayout.rowIndex,
					colSpan : pos.formLayout.colSpan,
					rowSpan : pos.formLayout.rowSpan
				});
				container.getLayout().setRowVisible(pos.formLayout.rowIndex, ValueUtil.isNull(isVisible) ? true : isVisible);
				
			}else if(app.getContainer().getLayout() instanceof cpr.controls.layouts.VerticalLayout){
				container.insertChild(pos.verticalLayout.index, targetCtrl);
			}
				
			
			
		}
		
		
		/**
		 * 
		 * @param {cpr.core.AppInstance} app
		 * @param {Boolean} isBool
		 */
		CreateAppLayout.prototype.setVisibleSearchBox = function(app, isBool) {
			
			app.getContainer().getLayout().setRowVisible(0, isBool);
		};
		
		
		/**
		 * 버튼 클릭시 그리드 or 차트 숨김
		 * @param {cpr.controls.Container} voGrpLt 레이아웃
		 * @param {String} pValue 타입값
		 */
		CreateAppLayout.prototype.DetailVisible = function(voGrpLt, pValue) {
			var layout = voGrpLt.getParent().getLayout();
			
			switch (pValue) {
				case "chart":
					layout.setRowVisible(4, true);
					layout.setRowVisible(3, false);
					break;
				case "grid":
					layout.setRowVisible(3, true);
					layout.setRowVisible(4, false);
					break;
				default:
					layout.setRowVisible(4, true);
					layout.setRowVisible(3, true);
					break;
			}
		}
		
		
		
		/**
		 * 팝업을 만들어줍니다.
		 * @return {cpr.core.App} vcPopup
		 */
		function createPopUpApp() {
			
			var vcPopup = new cpr.core.App("appID", {
				
				onPrepare: function(loader) {},
				
				onCreate: function( /* cpr.core.AppInstance */ app, exports) {
					var container = app.getContainer();
							
					container.style.css({
						"width": "100%",
						"top": "0px",
						"height": "100%",
						"left": "0px"
					});
					
					var voVerticalLayout = new cpr.controls.layouts.VerticalLayout();
					voVerticalLayout.topMargin = 10;
					voVerticalLayout.leftMargin = 17;
					voVerticalLayout.rightMargin = 17;
					voVerticalLayout.bottomMargin = 10;
					container.setLayout(voVerticalLayout);
					
					
					var hTMLSnippet = new cpr.controls.HTMLSnippet("hspt1");
					
					//hTMLSnippet.style.addClass(msNoticePopup);
		
					container.addChild(hTMLSnippet, {
						"width":"100px",
						"height":"100px",
						"autoSize":"both"
					});
					
					
					app.addEventListener("load", function() {
						var vsNoticeValue = app.getHost().initValue["NoticeValue"];
						var vsReplaceBefore = vsNoticeValue.toString();
						var vsReplacedStr = "";
							vsReplacedStr = vsReplaceBefore.replace(/(\r\n|\n\r|\r|\n)/g,"<br>")
							vsReplacedStr = vsReplacedStr.replaceAll(" ", "&nbsp;");
						var vcHspt = app.lookup("hspt1");
						vcHspt.value = vsReplacedStr;
						//vcHspt.value = "<div class= \"NoticePop\" >" + vsNoticeValue + " </div>";
					});
				}
			});
			cpr.core.Platform.INSTANCE.register(vcPopup);
			vcPopup.createNewInstance();
			
			return vcPopup;
		}
		
		/**
		 * 레이아웃을 구성할 데이터셋을 정해주는 함수
		 * @param {cpr.data.DataSet} dsLinkList
		 */
		function setLinkList(dsLinkList) {
			dsLinkList.getRowDataRanged().filter(function(each) {
				switch (each[msRelateSrvGb]) {
					case '1':
						vaDataTab.push(each);
						break;
					case '2':
						vaDataLink.push(each);
						break;
				}
			});
		}
		
		exports.createAppAndLayout = CreateAppLayout;
	});
})();
/// end - module/createAppAndLayout
/// start - module/createGrid
/*
 * Module URI: module/createGrid
 * SRC: module/createGrid.module.js
 *
 * This file was generated by eXbuilder6 compiler, Don't edit manually.
 */
(function(){
	cpr.core.Module.define("module/createGrid", function(exports, globals, module){
		/************************************************
		 * createGrid.module.js
		 * Created at 2020. 6. 2. 오전 10:19:09.
		 *
		 * @author HANS
		 ************************************************/
		
		String.prototype.replaceAll = function(org, dest) {
			return this.split(org).join(dest);
		}
		/************************************************
		 * 전역 변수 선언
		 ************************************************/
		/**
		 * 그리드 내 숫자 데이터들의 displayText를 처리하기 위한 배열
		 */
		var vaExceptStr = ["#",",","s", "0"];
		
		/**
		 * 헤더 행 높이
		 */
		var mnHeaderHeight = 45;
		
		/**
		 * 디테일 행 높이
		 */
		var mnDetailHeight = 35; 
		
		/**
		 * 증감 아이콘을 표시하기 위한 단어
		 * 증감, 전일대비, 등락율, 전일대비증감, 전월대비증감, 전월말대비증감, 전월대비, 전년대비
		 */
		var moIrdsIcon = ["증감", "전일대비", "등락율", "전일대비증감", "전월대비증감", "전월말대비증감", "전월대비", "전년대비", "전일대비증감율"];
		
		/**
		 * 그리드를 표시하는 데이터 중, 사용하는 컬럼 정보를 가진 행만 저장
		 * @type {cpr.data.Row[]}
		 */
		var maUsedRows = [];
		
		/**
		 * 그리드 헤더 셀의 ID
		 */
		var msHeaderId = "HEADER_ID";
		
		/**
		 * 그리드 헤더 셀의 텍스트
		 */
		var msHeaderNm = "HEADER_NM";
		
		/**
		 * 그리드 헤더 셀의 부모
		 * 해당 ID를 통해 헤더 셀의 rowSpan, colSpan을 지정한다.
		 */
		var msParentHdrId = "PARENT_HDRID";
		
		/**
		 * 셀 타입
		 */
		var msHeaderTyp = "HEADER_TYP";
		
		/**
		 * 그리드 left split 할 colIndex
		 */
		var msColumnFixYn = "COLUMN_FIXYN";
		
		/**
		 * 헤더 셀 너비
		 */
		var msColumnWid = "COLUMN_WID";
		
		/**
		 * 헤더 셀 그룹핑 여부
		 */
		var msGroupMergeYn = "GROUP_MERGEYN";
		
		/**
		 * 데이터 정렬 컬럼
		 */
		var msOrderSeq = "ORDER_SEQ"; 
		
		/**
		 * 헤더 셀 숨김 여부
		 */
		var msHideYn = "HIDE_YN";
		
		/**
		 * 셀 포맷
		 */
		var msColumnFormat = "COLUMN_FORMAT";
		
		/**
		 * 정렬 컬럼
		 */
		var msAlignCd = "ALIGN_CD";
		/**
		 * 그리드를 그리기 위한 default config
		 * 데이터가 넘어오지 않을경우, 아래의 config가 default로 적용된다.
		 * @type {cpr.controls.gridpart.GridConfig}
		 */
		var moGridStandardConfig = {
			"columnMovable": true,
			"resizableColumns": "all",
			"columns": [{
				"width": "100px"
			}],
			"header": {
				"rows": [{
					"height": "24px"
				}],
				"cells": [{
					"constraint": {
						"rowIndex": 0,
						"colIndex": 0
					},
					"configurator": function(cell) {}
				}]
			},
			"detail": {
				"rows": [{
					"height": "24px"
				}],
				"cells": [{
					"constraint": {
						"rowIndex": 0,
						"colIndex": 0
					},
					"configurator": function(cell) {}
				}]
			}
		};
		
		
		/************************************************
		 * 글로벌 출판 사용자 모듈
		 ************************************************/
		/**
		 * 그리드를 동적으로 생성하는 함수입니다. 
		 * @param {cpr.data.DataSet} pcGridConfigDataSet 그리드의 컬럼 정보를 구성하는 데이터셋
		 * @param {cpr.data.DataSet} pcGridBindDataSet 그리드의 디테일 데이터를 구성하는 데이터셋
		 * @param {Boolean} pbAnalys? [분석상태]에 따라 그리드가 변경될 수 있는지에 대한 유무 (기본값 :false)
		 * @param {
		 *   eventNm : String, 
		 *   func : Function
		 * }[] paEvents? 그리드에 연결할 이벤트
		 */
		function createGrid(pcGridConfigDataSet, pcGridBindDataSet, pbAnalys, paEvents) {
			
			var vcFakeConfigDataSet = new cpr.data.DataSet();
			var vaCols = [];
			pcGridConfigDataSet.getColumnNames().forEach(function(each){
				
				var vsNms = {};
				vsNms.name = each;
				vsNms.dataType = pcGridConfigDataSet.getHeader(each).getDataType();
				vaCols.push(vsNms);
			});
			vcFakeConfigDataSet.parseData({
				columns : vaCols,
				rows : pcGridConfigDataSet.getRowDataRanged()
			})
			
			var vcDataSet = vcFakeConfigDataSet;
			var vcBindDataSet;	
			
			var vbAnalys = ValueUtil.isNull(pbAnalys) ? false : pbAnalys;
			if (vbAnalys) {
				vcBindDataSet = _fakeDataSet(vcDataSet, pcGridBindDataSet);
			} else {
				vcBindDataSet = new cpr.data.DataSet("grdDs");
				
				var vaColData = [];
				var vaColumnNames = _sortColumnNames(pcGridBindDataSet);
				vaColumnNames.forEach(function(each) {
					var vsName = {};
					vsName.name = each;
					vsName.dataType = pcGridBindDataSet.getHeader(each).getDataType();
					vaColData.push(vsName);
				});
				
				vcBindDataSet.parseData({
					columns: vaColData,
					rows: pcGridBindDataSet.getRowDataRanged()
				});
			}
			vcDataSet.setSort(msOrderSeq + " asc");
			var vaAllHeaderID = vcDataSet.getUnfilteredDistinctValues(msHeaderId, function( /*cpr.data.DataRow*/ dataRow) {
				return dataRow.getValue(msHeaderId) != "";
			});
			
			vcDataSet.forEachOfUnfilteredRows(function( /*cpr.data.DataRow*/ dataRow) {
				
				var vsParentHdrid = dataRow.getValue(msParentHdrId);
				if (vsParentHdrid != "") {
					var vnExistInHeader = vaAllHeaderID.indexOf(vsParentHdrid);
					if (vnExistInHeader < 0) {
						dataRow.setValue(msParentHdrId, "");
					}
				}
			});
			
			var vnHeaderDepth = _getHeaderDepth(vcDataSet);
			
			/** @type cpr.controls.gridpart.GridConfig */
			var voGridInitConfig = {
				"columnMovable": true,
				"resizableColumns": "all",
				"dataSet": {},
				"columns": [],
				"header": {
					"rows": [],
					"cells": []
				},
				"detail": {
					"rows": [],
					"cells": []
				},
				"rowGroup":[]
			}
			
			var vcGrid = new cpr.controls.Grid("grd");
			if (vcDataSet.getRowCount() > 0) {
				maUsedRows = classifyUsedColumn(vcDataSet);
		//		var vaAutoFit = maUsedRows.filter(function(each) {
		//			if (each.getValue(msColumnFixYn) == "Y") {
		//				return each;
		//			}
		//		}).map(function(each) {
		//			return each.getIndex();
		//		});
		//		voGridInitConfig.autoFit = vaAutoFit.toString();
				
				voGridInitConfig.dataSet = vcBindDataSet;
				
				for (var headerLeng = 0; headerLeng < vnHeaderDepth; headerLeng++) {
					var vnHeaderHeight = _getHedaerHeight(vcDataSet);
					voGridInitConfig.header.rows.push({
						"height": vnHeaderHeight + "px"
					});
				}
				var vaTempColNm = vcBindDataSet.getColumnNames();
				var vnTempIdx = 0;
				vcDataSet.getRowDataRanged().forEach(function(each, idx) {
					var voHeaderConstraint = _getGridHeaderConfig(vcDataSet, vcDataSet.getRow(idx), vnHeaderDepth);
					if(voHeaderConstraint) {
		
						/*
						 * 2020.10.16 수정
						 * vaTempColNm 에는 HEADER_TYPE 이 "" 인 경우에는 제외되므로, 제외된 항목에 맞게 targetColumn 설정 할 수 있도록 수정
						 * targetColumn 은 setAnalysisColumnVisible(분석통계) 에서 사용되는 항목(columnVisible 처리)
						 */
						if(each[msHeaderTyp].trim() != "") {
							var vnTargetIdx = idx - vnTempIdx;					
							voGridInitConfig.header.cells.push({
								"constraint": voHeaderConstraint,
								"configurator": function(cell) {
									cell.targetColumnName = vaTempColNm[vnTargetIdx];
									cell.text = each[msHeaderNm];
									cell.visible = each[msHideYn] == "Y" ? false : true;
									cell.sortColumnName = vaTempColNm[vnTargetIdx];
								}
							});
						} else {
							vnTempIdx++;
							voGridInitConfig.header.cells.push({
								"constraint": voHeaderConstraint,
								"configurator": function(cell) {
									cell.targetColumnName = "";
									cell.text = each[msHeaderNm];
									cell.visible = each[msHideYn] == "Y" ? false : true;
									cell.sortColumnName = vaTempColNm[vnTargetIdx];
								}
							});
						}
						
					}
					
				});
				
				vcBindDataSet.getColumnNames().forEach(function(each, idx) {
					/** @type cpr.data.Row */
					var voConfigRow = maUsedRows[idx];
					if(voConfigRow != null) {
						var vnColumnWidth = (voConfigRow.getValue(msColumnWid) != "" && voConfigRow.getValue(msColumnWid) != "0") ? voConfigRow.getValue(msColumnWid) : "70";
						var vsHeaderNm = voConfigRow.getValue(msHeaderNm);
						voGridInitConfig.columns.push({
							"width": vnColumnWidth + "px"
						});
						voGridInitConfig.detail.cells.push({
							"constraint": {
								"rowIndex": 0,
								"colIndex": idx
							},
							"configurator": function(cell) {
								cell.columnName = each;
								cell.suppressible = voConfigRow.getValue(msGroupMergeYn) == 'Y' ? true : false;
								cell.control = (function() {
									var opts = _createControlByType(voConfigRow.getValue(msHeaderTyp), voConfigRow.getValue(msColumnFormat),voConfigRow.getValue(msAlignCd));
									opts.bind("value").toDataColumn(each);
									_setIrdsIcon(vsHeaderNm, opts,voConfigRow.getValue(msAlignCd)); // 증감 스타일 적용
									return opts;
								})();
							}
						});
					}
				});
				
				voGridInitConfig.detail.rows.push({
					"height": mnDetailHeight + "px"
				});
				
				//2020.10.15 조한진 최고,최저,평균 그룹헤더 구획 동적추가 스크립트 추가
				if(vbAnalys) {
					
					var vaDetailNms = vcBindDataSet.getColumnNames();
					var voRowGroups = {
							"groupCondition" : "analysis",
							"gheader" : {
								"rows" : [
								],
								"cells" :[]
							},
							
					}
					var analys = ["최고","최저","평균"];
					
					analys.forEach(function(each,rowIndex){
						voRowGroups.gheader.rows.push({"height" : mnDetailHeight+"px"});
						voGridInitConfig.detail.cells.forEach(function(eachCell,idx){
						
							var exprs ;
							switch(each){
								case "최고" :
									exprs = "getMax('Number("+vaDetailNms[idx]+")')";
									break;
								case "최저" :
									exprs = "getMin('Number("+vaDetailNms[idx]+")')"
									break;
								case "평균" :
									exprs = "round(getAvg('Number("+vaDetailNms[idx]+")')*100)/100"
									break;
								default :
									break;
							}
							
							var vsHeaderType = maUsedRows[idx].getValue(msHeaderTyp);
							var vsFormat = maUsedRows[idx].getValue(msColumnFormat);
							var vsAlign = maUsedRows[idx].getValue("ALIGN_CD");
							if(idx == 0) {
								exprs = "'"+each+"'";
								vsHeaderType = "S";
							}
							var tempGrpCellInfo = {
								constraint : {
									"rowIndex" : rowIndex,
									"colIndex" : eachCell.constraint.colIndex
								},
								configurator : function(cell) {
									cell.expr = exprs;
									cell.control = (function(){
										var outputs = _createControlByType(vsHeaderType, vsFormat,vsAlign);
										return outputs;
									})()
								}
							}
							voRowGroups.gheader.cells.push(tempGrpCellInfo);
						});
					});
					
					voGridInitConfig.rowGroup.push(voRowGroups);
				}
				
				
				
				vcGrid.init(voGridInitConfig);
			} else {
				vcGrid.init(moGridStandardConfig);
			}
			
			if (paEvents && paEvents.length > 0) {
				paEvents.forEach(function(each) {
					vcGrid.addEventListener(each.eventNm, each.func);
				});
			}
			
			vcGrid.noDataMessage = "데이터가 존재하지 않습니다.";
			
			//2020-12-22 추가
			var voInsRootAppIns = pcGridConfigDataSet.getAppInstance();
			try {
				/** @type cpr.data.DataMap */
				var vcDmSearchData = voInsRootAppIns.lookup("dmSearchData");
				if (vcDmSearchData) {
					
					if (vcDmSearchData.getValue("language_gb") == "ENG") {
						vcGrid.noDataMessage = "The data does not exist.";
					}
					
				} else {
					throw new Error("dmSearchData를 찾을 수 없습니다");
				}
			} catch (err) {
				console.log(err);
			}
			
			vcGrid.setActivateRowGroup("analysis", false);
		//	2020-12-10 추가
		//	vcGrid.detail.getControl(0).style.addClass("text-center");
			
			
			//2020-12-24 추가 rowStyle
			vcGrid.style.row.bindClass().toExpression("TMPV1 ==\"합계\" || TMPV1== \"Total\" ? \"sums\" : TMPV1 ==\"소계\" || TMPV1 ==\"Asset Management-Total\" ? \"averages\" : TMPV3 ==\"15시30분\" ? \"time1530\" : \"\"");
			return vcGrid;
		}
		globals.createGrid = createGrid;
		
		
		/**
		 * 차트를 리턴하는 함수입니다.
		 * @param {cpr.controls.Grid} pcGrid
		 * @param {cpr.data.DataMap} pcDataMap
		 * @param {cpr.data.DataSet} pcHeaderDs
		 */
		function createChartCtrl(pcGrid, pcDataMap, pcHeaderDs) {
			var vcChartUDC = new udc.cmn.ApexChart("chart");
			vcChartUDC.gridControlId = pcGrid;
			vcChartUDC.chartDataMapId = pcDataMap;
			vcChartUDC.headerDataSetId = pcHeaderDs;
			//2020-12-09 추가, 데이터 뷰로 데이터셋을 복제하여 차트 데이터의 정렬 옵션을 저장하기 위함입니다.
		//	vcChartUDC.dataDataSetId = pcGrid.dataSet;
			var vcViewChart = new cpr.data.DataView("", pcGrid.dataSet);
			vcChartUDC.dataDataSetId = vcViewChart;
			return vcChartUDC;
		}
		
		globals.createChartCtrl = createChartCtrl;
		
		
		/**
		 * 그리드에서 상위헤더가 아닌, 실제 데이터를 보여주는 컬럼들을 배열로 리턴하는 함수입니다.
		 * 각 컬럼별 데이터 타입과 포맷 양식을 파악하기 위해 사용합니다.
		 * @param {cpr.data.DataSet} pcDataSet
		 * @returns {cpr.data.Row[]}
		 */
		function classifyUsedColumn(pcDataSet) {
			
			var vcDataSet = pcDataSet;
			var vnRowCount = vcDataSet.getRowCount();
			
			if (vnRowCount > 0) {
				
				var vaAllChild = vcDataSet.getUnfilteredDistinctValues(msParentHdrId, function( /*cpr.data.DataRow*/ dataRow) {
					return dataRow.getValue(msParentHdrId) != "";
				});
				
				var vsExpress = "";
				if (vaAllChild.length > 0) {
					vaAllChild.forEach(function(each) {
						vsExpress += msHeaderId + "!='" + each + "'&&";
					});
					
					vsExpress = vsExpress.slice(0, -2);
				} else {
					vsExpress += true;
				}
				return vcDataSet.findAllRow(vsExpress);
			}
			
			return null;
		}
		globals.classifyUsedColumn = classifyUsedColumn;
		
		
		///**
		// * 
		// * @param {cpr.controls.Grid} pcGrid
		// */
		//function revertGrids(pcGrid) {
		//	
		//	var vcGrid = pcGrid;
		//	var vcDsDetail = pcGrid.dataSet;
		//	
		//	var vaColNms = vcDsDetail.getColumnNames();
		//	var initConfig = vcGrid.getInitConfig();
		//	
		//	vaColNms.forEach(function(each) {
		//		
		//		var idx = vcGrid.getCellIndex(each);
		//		if (each.indexOf("TEST") >= 0) {
		//			vcDsDetail.deleteColumn(each);
		//			vcGrid.deleteColumn(idx);
		//			initConfig.header.cells.splice(each, 1);
		//			initConfig.detail.cells.splice(each, 1);
		//		}
		//	});
		//	
		//	vcGrid.init(initConfig);
		//	vcGrid.redraw();
		//	
		//}
		//
		//globals.revertGrids = revertGrids;
		
		/**
		 *  분석통계를 사용하는 화면에 대한 분석통계컬럼 visible처리를 하기 위한 함수입니다.
		 * @param {cpr.controls.Grid} pcGrid
		 * @param {String} targetColumn
		 * @param {cpr.data.DataSet} pcBindDataset
		 */
		function setAnalysisColumnVisible (pcGrid,targetColumn, pcBindDataset){
		
			var vcGrid = pcGrid;
			var vcDs = vcGrid.dataSet;
			vcGrid.setActivateRowGroup("analysis", false);
			
			if(targetColumn =="최고.최저.평균") {
				 vcGrid.setActivateRowGroup("analysis", true);
			} 
			if( pcBindDataset  &&  pcBindDataset.getRowCount() > 0) {
			_verifiColumnDataType(vcGrid,pcBindDataset);
			_resetTargetColNms(vcGrid, pcBindDataset);
			}
			var vnGrdHdCellCnt = vcGrid.header.cellCount;
			for(var idx = 0; idx < vnGrdHdCellCnt; idx++){ // 2020.10.16 colcount -> cellcount로 변경
				
				var voColumn = vcGrid.header.getColumn(idx);
				if(voColumn != null) {
					
					var vsTargetColNm = voColumn.targetColumnName;
					var voTargetHeader = vcDs.getHeader(vsTargetColNm);
					if(voTargetHeader != null) {
						if(voTargetHeader.getInfo() == targetColumn) {
							vcGrid.columnVisible(idx, true);
						} else if(voTargetHeader.getInfo() != undefined) {
							vcGrid.columnVisible(idx, false);
						}
					} else {
						var cell = vcGrid.header.getColumn(idx);
						var vnHeaderLength = vcGrid.header.getRowHeights().length;
						var vnRowIndex = cell.rowIndex +1
						if(vnRowIndex + cell.rowSpan >  vnHeaderLength || vnRowIndex == vnHeaderLength) {
						
								vcGrid.columnVisible(idx, false);
						}
					}
				} 
				
			}
		
		}
		
		globals.setAnalysisColumnVisible = setAnalysisColumnVisible;
		
		/**
		 * 그리드를 구성할 때, 헤더 정보는 존재했으나, 실제로 해당 컬럼에 대한 디테일 데이터가 존재하지 않을 경우
		 *  컬럼을 숨김처리 하기 위해 만들어진 함수입니다. 내부 동작합니다.
		 * @param {cpr.controls.Grid} pcGrid
		 * @param {cpr.data.DataSet	} pcBindDataSet
		 */
		function _resetTargetColNms (pcGrid, pcBindDataSet) {
			if( pcBindDataSet  &&  pcBindDataSet.getRowCount() > 0) {
				var vaViewColNms = pcBindDataSet.getColumnNames().map(function(each) {
					var vsColIdx = each.replace("TMPV","");
					return vsColIdx;
				});
				pcGrid.dataSet.getColumnNames().forEach(function(each, idx){
					var vaReplaceStr = ["TMPV","TEST","_"];
					var vsColNm = each;
					vaReplaceStr.forEach(function(vsReplaceStr){
						vsColNm = vsColNm.replace(vsReplaceStr, "");
					});
					var vnIndex =vsColNm;
					if (vaViewColNms.indexOf(vnIndex) == -1) {
						var vnHeaderIdxFromDetail = pcGrid.detail.getColumn(idx).cellProp.constraint.cellIndex;
						var vaCellIdx = pcGrid.getHeaderCellIndices(vnHeaderIdxFromDetail);
						if (vaCellIdx.length > 0) {
							vaCellIdx.forEach(function(eachIdx) {
								pcGrid.header.getColumn(eachIdx).targetColumnName = "";
							})
						}
					}
				});
			}
		}
		
		//2020-12-19 조한진 추가
		/**
		 * 그리드의 첫번째 컬럼의 데이터타입이 넘어온 데이터와 일치하지 않은 경우, 포맷을 지우는 함수입니다.
		 * @param {cpr.controls.Grid} pcGrid 그리드
		 * @param {cpr.data.DataSet} pcBindDataSet 그리드의 detail데이터가 담긴 데이터셋
		 */
		function _verifiColumnDataType (pcGrid, pcBindDataSet){
			
			var vcDetailCtrl = pcGrid.detail.getControl(0);
			
			if(vcDetailCtrl && vcDetailCtrl instanceof cpr.controls.Output) {
				
				var vsDataType = vcDetailCtrl.dataType;
				if(vsDataType == "date") {
					
					var vsFirstColNm = pcGrid.detail.getColumn(0).columnName;
					var vsFirstValue = pcBindDataSet.getRow(0).getValue(vsFirstColNm);
					
					var vbIsDate = moment(vsFirstValue).isValid();
					if(!vbIsDate) {
						vcDetailCtrl.dataType = "string";
						vcDetailCtrl.format = "";
					}
					pcGrid.redraw();
				}
			}
		}
		/************************************************
		 * 내부 API
		 ************************************************/
		/**
		 * 그리드를 설정하는 데이터셋의 행별로 배치 속성을 구하는 함수입니다.
		 * @param {cpr.data.DataSet} pcDataSet
		 * @param {cpr.data.DataRow} poRow
		 * @param {Number} pnHeaderDepth
		 */
		function _getGridHeaderConfig(pcDataSet, poRow, pnHeaderDepth) {
			
			var vcDataSet = pcDataSet;
			
			var vsParentId = poRow.getValue(msParentHdrId);
			var vsHeaderId = poRow.getValue(msHeaderId);
			
			var vnColIndex = _checkColumnIndex(vcDataSet, poRow);
			var att = _getHeaderDeteil(vcDataSet, poRow, pnHeaderDepth);
			if(att) att.colIndex = vnColIndex;
			
			return att;
		}
		
		/**
		 * 
		 * @param {cpr.data.DataSet} pcDataSet
		 * @param {cpr.data.Row} poRow
		 */
		function _checkColumnIndex(pcDataSet, poRow) {
			
			var vcDataSet = pcDataSet;
			
			var vcTree = new cpr.controls.Tree();
			
			vcTree.setItemSet(vcDataSet, {
				label: msHeaderNm,
				value: msHeaderId,
				parentValue: msParentHdrId
			});
			var res = 0;
				
			var item = vcTree.getItemByValue(poRow.getValue(msHeaderId));
			
			var vcMatchedItem = vcTree.getItems().filter(function(each) {
				return !(vcTree.hasChild(each));
			});
			
			if (vcTree.hasChild(item)) {
				var vaHasAncestor = vcMatchedItem.filter(function(each) {
					return each.hasAncestor(item.value);
				});
				vcMatchedItem.map(function(each, index){
					if(each.value == vaHasAncestor[0].value) res= index;
					return;
				});
			} else {
				vcMatchedItem.map(function(each, index){
					if(each.value == item.value) res= index;
					return;
				});
			}
			return res;
		}
		/**
		 * 
		 * @param {String} psAlign
		 */
		function _getAlign(psAlign){
			var vsAlign = "";
			if(psAlign != undefined) {
				vsAlign = psAlign.toUpperCase();
			}
			var result = "";
			switch(psAlign){
				case "L" :
					result = "text-left";
					break;
				case "C" :
					result = "text-center";
					break;
				case "R" :
					result = "text-right";
					break;			
				default :
					result = "text-center";
					break;
			}
			
			return result;
		}
		/**
		 * 입력된 타입과 포맷을 가진 아웃풋 컨트롤을 리턴하는 함수입니다.
		 * @param {String} psType
		 * @param {String} psFormat
		 * @param {String} psAlign
		 * @returns {cpr.controls.Output}
		 */
		function _createControlByType(psType, psFormat,psAlign) {
			
			var vcOutput = new cpr.controls.Output();
			vcOutput.style.addClass(_getAlign(psAlign));
		//	vcOutput.
			switch (psType) {
				case "D":
					if(psFormat.indexOf("#") != -1) {
						vcOutput.format = "s"+psFormat;
						vcOutput.dataType = "number";
						var vsFormat = vcOutput.format;
		//				vcOutput.style.addClass(_getAlign(psAlign));
						vaExceptStr.forEach(function(each){
							vsFormat = vsFormat.replaceAll(each, "");
						});
						if(vsFormat != "" && vsFormat != ".") {
		
							vcOutput.displayExp = "text ==" + vsFormat + " ? value==null||value== '' ? '' : '-' : text";
						} else {
							vsFormat = "0";
							vcOutput.displayExp = "text ==" + vsFormat + " ? value==null||value== '' ? '' : '-' : text";
						}
					}else{
						vcOutput.format = psFormat.toUpperCase();
						vcOutput.dataType = "date";
						vcOutput.displayExp = "text == \"\" ? value : text";
		//				vcOutput.style.addClass(_getAlign(psAlign));
					}
					break;
				case "N":
					vcOutput.dataType = "number";
					var vsInsFormat = psFormat;
					if(vsInsFormat == "###") {
						vsInsFormat = "#,###";
					}
					vcOutput.format = "s"+vsInsFormat;
		//			vcOutput.style.addClass(_getAlign(psAlign));
					var vsFormat = vcOutput.format;
						vaExceptStr.forEach(function(each){
							vsFormat = vsFormat.replaceAll(each, "");
						});
						if(vsFormat != "" && vsFormat != ".") {
							vcOutput.displayExp = "text ==" + vsFormat + " ? value==null||value== '' ? '' : '-' : text"
						}else {
							vsFormat = "0";
							vcOutput.displayExp = "text ==" + vsFormat + " ? value==null||value== '' ? '' : '-' : text";
						}
					break;
				case "S":
					vcOutput.dataType = "string"
					break;
				default:
					if(psFormat.indexOf("yy") != -1 || psFormat.indexOf("YY") != -1) {
						vcOutput.dataType = "date";
						if(psFormat.indexOf("-") != -1) {
							
							vcOutput.format = "YYYY-MM-DD";
						} else {
							
							vcOutput.format = "YYYY/MM/DD";
						}
						vcOutput.displayExp = "text == \"\" ? value : text";
		//				vcOutput.style.addClass(_getAlign(psAlign));
					} 
					else if((psFormat.indexOf("#") != -1 || psFormat.indexOf("0" != -1)) && psFormat != "") {
						
						vcOutput.dataType = "number";
						vcOutput.format = "s"+psFormat;
						var vsFormat = vcOutput.format;
						vaExceptStr.forEach(function(each){
							vsFormat = vsFormat.replaceAll(each, "");
						});
						if(vsFormat != "" && vsFormat != ".") {
							vcOutput.displayExp = "text ==" + vsFormat + " ? value==null||value== '' ? '' : '-' : text"
						}else {
							vsFormat = "0";
							vcOutput.displayExp = "text ==" + vsFormat + " ? value==null||value== '' ? '' : '-' : text";
						}
					}
					else {
						vcOutput.dataType = "string";
					}
					break;
			}
			
			return vcOutput;
		}
		
		//2020-12-23 파라미터 추가
		/**
		 * 증감 아이콘 스타일 클래스 적용
		 * @param {String} psHeaderNm
		 * @param {cpr.controls.Output} pcOutput
		 * @param {String} psAlign bindClass를 통해 이전에 add된 클래스가 사라져 text-align이 사라지는것을 방지하기 위해 추가된 파라미터
		 */
		function _setIrdsIcon (psHeaderNm, pcOutput,psAlign) {
			
			var vsHeaderIcon = psHeaderNm.replace(/\n/gi, "");
			var vsAlignClass = _getAlign(psAlign);
		 	if(moIrdsIcon.indexOf(vsHeaderIcon) != -1) {
		 		var vsExp = "value > 0 ? 'ico-irds plus "+vsAlignClass+"' : (value < 0 ? 'ico-irds minus "+vsAlignClass+"' : '"+vsAlignClass+"')";
				pcOutput.style.bindClass().toExpression(vsExp);
		 	}
		 	
		 	if(vsHeaderIcon == "전일대비증감율") {
		 		pcOutput.format = "s#,###.00";
		 	}
		}
		
		/**
		 * 멀티헤더일 경우 멀티헤더 갯수를 구하는 함수입니다.
		 * @param {cpr.data.DataSet} pcDataSet 데이터셋
		 * @return {Number} 헤더 갯수
		 */
		function _getHeaderDepth(pcDataSet) {
			
			var vcDataSet = pcDataSet;
			
			var vnHeaderDepth = 1;
			var vaAllChild = vcDataSet.getUnfilteredDistinctValues(msParentHdrId, function( /*cpr.data.DataRow*/ dataRow) {
				return dataRow.getValue(msParentHdrId) != "";
			});
			
			while (vaAllChild.length > 0) {
				
				vnHeaderDepth += 1;
				vaAllChild = vaAllChild.map(function(each) {
					var row = vcDataSet.findFirstRow(msHeaderId + " == '" + each + "'").getValue(msParentHdrId);
					
					return row;
				}).filter(function(each) {
					return each != "";
				});;
				
			}
			return vnHeaderDepth;
		}
		
		/**
		 * 헤더 텍스트 줄바꿈에 따른 셀 높이 반환
		 * @param {cpr.data.DataSet} pcDataSet
		 * @return {Number} HeaderHeight
		 */
		function _getHedaerHeight (pcDataSet) {
			
			var vnHeaderLine = 1;
			pcDataSet.getRowDataRanged().forEach(function(each){
				var vnTextLine = each[msHeaderNm].split("\n").length;
				if(vnHeaderLine < vnTextLine) {
					vnHeaderLine = vnTextLine;
				}
			});
			
			return mnHeaderHeight + (15 * (vnHeaderLine-1));
		}
		
		
		/**
		 * 헤더의 상세 크기를 계산하는 함수입니다.
		 * @param {cpr.data.DataSet} pcDataSet
		 * @param {cpr.data.DataRow} poRow
		 * @param {Number} res
		 * @return {{rowIndex :Number, colIndex : Number, rowSpan : Number, colSpan : Number}}
		 */
		function _getHeaderDeteil(pcDataSet, poRow, res) {
			
			var vcDataSet = pcDataSet;
			var rowIndex = 0;
			var vnRowSpan = res;
			var vnColSpan = 1;
			var vsHeaderId = poRow.getValue(msHeaderId);
			var vsParentID = poRow.getValue(msParentHdrId)
			
			var vaAllChild = vcDataSet.findAllRow(msParentHdrId + "== '" + vsHeaderId + "'").map(function(each) {
				return each.getValue(msHeaderId);
			}).filter(function(eachs) {
				return eachs != "";
			});
			
			var vnColSpanAll = 0;
			if (vsParentID) {
				if (vaAllChild.length > 0) {
					rowIndex = res - 1;
					vnRowSpan = res - 1;
					
					while (vaAllChild.length > 0) { //이부분에서 colSpan이 중요
						var vaTempArray = [];
						rowIndex = rowIndex - 1;
						vnRowSpan = vnRowSpan - 1;
						vaAllChild.forEach(function(each) {
							var vaSmallChild = vcDataSet.findAllRow(msParentHdrId + "== '" + each + "'");
							if (vaSmallChild.length < 1) {
								vnColSpanAll += 1;
							}
							vaTempArray = vaTempArray.concat(vaSmallChild);
							
						});
						vaAllChild = vaTempArray;
						
					}
					vnColSpan = vnColSpanAll;
					
				} else {
					rowIndex += 1;
					vnRowSpan = vnRowSpan - 1;
					var voFirstParent = vcDataSet.findFirstRow(msHeaderId + " == '" + vsParentID + "'");
					while (voFirstParent.getValue(msParentHdrId) != "") {
						vnRowSpan = vnRowSpan - 1;
						rowIndex += 1;
						voFirstParent = vcDataSet.findFirstRow(msHeaderId + " =='" + voFirstParent.getValue(msParentHdrId) + "'");
					}
					
				}
			} else {
				if (vaAllChild.length > 0) {
					vnRowSpan = 1;
					while (vaAllChild.length > 0) {
						var vaTempArray = [];
						
						vaAllChild.forEach(function(each, index) {
							var vaSmallChild = vcDataSet.findAllRow(msParentHdrId + "== '" + each + "'");
							if (vaSmallChild.length < 1) {
								vnColSpanAll += 1;
							}
							vaTempArray = vaTempArray.concat(vaSmallChild);
							
						});
						vaAllChild = vaTempArray;
						
					}
					vnColSpan = vnColSpanAll;
					
				} else {
					//최상위
					rowIndex = 0;
					vnColSpan = 1;
					vnRowSpan = res;
		
					// 최상위 헤더에서 다음 텍스트와 동일 할 경우 colSpan (2020.08.06 daye)
					// 2020.12.23 kimjj modify ( msHeaderNm -> msHeaderId )
					var vnIndex = poRow.getIndex();
					if(pcDataSet.getRowCount()-1 > vnIndex) {
						for(var idx = vnIndex+1; idx < pcDataSet.getRowCount(); idx++){
							if(poRow.getValue(msHeaderId) == pcDataSet.getColumnData(msHeaderId)[idx]) {  
								vnColSpan++;
							} else {
								break;
							}
						}
					}
					
					if(vnIndex > 0) {
						if(poRow.getValue(msHeaderId) == pcDataSet.getValue(vnIndex-1, msHeaderId)) {
							return;
						}
					}
				}
			}
			
			return {
				"rowIndex": rowIndex,
				"colIndex": 0,
				"rowSpan": vnRowSpan,
				"colSpan": vnColSpan
			}
		}
		
		
		/**
		 * 
		 * @param {cpr.data.DataSet} pcConfigDataSet
		 * @param {cpr.data.DataSet} pcDetailDataSet
		 */
		function _fakeDataSet(pcConfigDataSet, pcDetailDataSet) {
			
			var vcDsConfig = pcConfigDataSet;
			var vcDsDetail = pcDetailDataSet;
			var vaColNames = vcDsConfig.getColumnNames();
			
			var vaColumnDefine = [];
			
			var vaAllRow = vcDsConfig.findAllRow("true");
			var classifyColumn = classifyUsedColumn(vcDsConfig);
			if(classifyColumn == null) return; 
			classifyColumn.forEach(function(each, idx) {
				
				if (each.getValue(msHeaderTyp) == "N") {
					var rowData = each.getRowData();
					rowData[msHeaderId] = rowData[msHeaderId] + "TEST_1";
					rowData[msOrderSeq] = rowData[msOrderSeq] + 0.1;
					rowData[msHeaderNm] = "전일대비증감";
					rowData[msHideYn] = "Y"
					vaColumnDefine.push(rowData);
					
					
					var rowData2 = each.getRowData();
					rowData2[msHeaderId] = rowData2[msHeaderId] + "TEST_2";
					rowData2[msOrderSeq] = rowData2[msOrderSeq] + 0.2;
					rowData2[msHeaderNm] = "전일대비증감율";
					rowData2[msHideYn] = "Y"
					
					vaColumnDefine.push(rowData2);
				}
			});
			
			var newDs = new cpr.data.DataSet('TEST');
			var vaColData = [];
			var vaColumnNames = _sortColumnNames(vcDsDetail);
			vaColumnNames.forEach(function(each, idx) {
				
				var vsName = {};
				vsName.name = each;
				vsName.dataType = vcDsDetail.getHeader(each).getDataType();
				vaColData.push(vsName);
				
				if (!!classifyColumn[idx] && classifyColumn[idx].getValue(msHeaderTyp) == "N") {
					var tempName = {};
					tempName.name = "TEST" +(idx+1);
					tempName.dataType = "expression";
					tempName.displayOnly = true;
					tempName.expression = "self.getRowCount() > getIndex() + 1 ? round((self.getValue(getIndex(),'" + each + "') - self.getValue(getIndex()+1,'" + each + "'))*100)/100 : '-'";
					tempName.info = "전일대비증감";
					vaColData.push(tempName);
					
					var tempName2 = {};
					tempName2.name = "TEST_" +(idx+1);
					tempName2.dataType = "expression";
					tempName2.displayOnly = true;
					tempName2.expression = "self.getRowCount() > getIndex() +1 ? round((self.getValue(getIndex(),'"+each+"') -self.getValue(getIndex()+1,'"+each+"'))/ self.getValue(getIndex()+1,'"+each+"')*100*100)/100 : '-'"
					tempName2.info = "전일대비증감율";
					vaColData.push(tempName2);
				}
				
			});
			var ds = new cpr.data.DataSet();
			var vaDatas = vcDsDetail.getRowDataRanged();
			ds.parseData({
				columns: vaColData,
				rows: vaDatas
			})
			
			vcDsConfig.build(vaColumnDefine, true);
			return ds;
		}
		
		
		/**
		 * 그리드 디테일에 바인딩 할 컬럼명 정렬
		 * @param {cpr.data.DataSet} pcGridBindDataset
		 */
		function _sortColumnNames (pcGridBindDataset) {
			
			var vsStaticText = "TMPV";
			var vaColumnNames = pcGridBindDataset.getColumnNames();
			
			var vaBindGridColumns = vaColumnNames.map(function(each) {
				return each.split(vsStaticText)[1]
			}).sort(function(a, b) {
				return a - b; // 오름차순 정렬
			}).map(function(each) {
				return vsStaticText + each
			});
			
			return vaColumnNames;
		//	return vaBindGridColumns
		}
	});
})();
/// end - module/createGrid
/// start - module/createNewAppIns
/*
 * Module URI: module/createNewAppIns
 * SRC: module/createNewAppIns.module.js
 *
 * This file was generated by eXbuilder6 compiler, Don't edit manually.
 */
(function(){
	cpr.core.Module.define("module/createNewAppIns", function(exports, globals, module){
		/************************************************
		 * createNewAppIns.module.js
		 * Created at 2019. 11. 15. 오전 9:46:36.
		 *
		 * VERSION 0.9
		 * 
		 * 수정내역
		 * - 2020.03.30 : selection-change 이벤트버스 오류가 수정되었습니다.
		 * - 2020.05.13 : keydown이벤트를 window이벤트로 수정되었습니다.
		 * 
		 * @author daye
		 ************************************************/
		
		/**
		 * 런타임 시 테스트 관정을 지원하기 위하여 현재의 dataSet 및 dataMap의 값 및 상태를 조회할 수 있는 기능을 제공하는 공통모듈입니다.
		 * 화면 로드시 단축키(기본 ctrl+alt+a) 를 누르면 팝업을 통해 확인 할 수 있습니다.
		 * 
		 * ※ 주의사항
		 * 해당 모듈은 테스트를 위한 모듈입니다.
		 * 운영서버 배포시에는 이를 포함하여 서버에 배포하시면 안됩니다. (보안상의 문제 발생 소지가 있습니다.) 
		 */
		
		/* 사용자 설정가능 멤버변수 ***********************************************************************/
		var isCreateApp = true; // true일 경우에만 데이터 컴포넌트를 확인할 수 있는 앱 생성 및 단축키를 지원합니다.
		var vsRowHeight = "28px";
		var dynamicKey = cpr.events.KeyCode.A; // 단축키의 마지막 키를 설정합니다.(Ctrl + Alt + *)
		/* **************************************************************************************************/
		
		/* 내부 시스템 멤버변수 (변경X)******************************************************************/
		var appInstance = null;
		var pcControl = null;
		var vsAppId = null;
		var voEmbedded = null;
		var isPopup = false;
		/* **************************************************************************************************/
		
		
		if(isCreateApp) cpr.events.EventBus.INSTANCE.addFilter("load", fn_load);
		
		function fn_load (e) {
		
			var control = e.control
			
			if (control instanceof cpr.core.AppInstance) {
				if(control.id.indexOf("Dialog") == -1 ) { // 다이얼로그 아닌 화면을 대상
					
					if(control.isRootAppInstance()) {
		
						var voChild = control.getContainer().getAllRecursiveChildren().map(function(each){
							if(each.type == "embeddedapp" || each.type == "mdifolder") return each;
						});
						if(!voChild || voChild.length == 0) return;
					}
						
					var voEmbAppInstance = control;
		
					if(control.getHost()) {
						
						if(control.getHost().type == "dialog" && control.id.indexOf("udc") == -1) {
							
							// Dialog 띄웠을 경우 rootAppInstance 저장
							var voMainApp = voEmbAppInstance.getHostAppInstance();
							
							/** @type cpr.controls.EmbeddedApp */
							var emb = voMainApp.getContainer().getAllRecursiveChildren().filter(function(each){
								if(each.type == "embeddedapp") return each;
							})[0];
							
							if(emb) voEmbedded = emb.getEmbeddedAppInstance();
						} else if(control.id.indexOf("udc") != -1) {
							
							// 다일로그에서 조회할 경우
							if(control.getHost().type == "dialog" || control.app.id.indexOf("udc") != -1) return;
							
							// 반환된 control이 udc(loadmsk)일 경우
							var voMainApp = voEmbAppInstance.getHostAppInstance();
		//					var voMainApp = voEmbAppInstance.getRootAppInstance(); // 2020.10.29 수정(root -> host)
							
							/** @type cpr.controls.EmbeddedApp */
							var emb = voMainApp.getContainer().getAllRecursiveChildren().filter(function(each){
								if(each.type == "embeddedapp") return each;
							})[0];
							
							if(emb) voEmbAppInstance = emb.getEmbeddedAppInstance();
						}
					}
					
					appInstance = voEmbAppInstance;
					
					// DataSource를 확인 할 앱 생성
					_createApp(appInstance);
				
					// 단축키 사용
					_setAccessKey();
				}
			}
		}
		
		
		/**
		 * MDI, TAB 일 경우, 선택한 탭의 앱인스턴스를 반환합니다.
		 * @param {Event} e
		 */
		cpr.events.EventBus.INSTANCE.addFilter("selection-change",function(/** @type cpr.events.CSelectionEvent */e){
			var vcSelectionControl = e.control;
		
			if(vcSelectionControl instanceof cpr.controls.MDIFolder) {
		
				if(e.newSelection.content) {
		
					if(e.newSelection.content.app) {
		
						// MDI 하위 콘텐츠의 앱인스턴스가 로드되었을 때
						appInstance = e.newSelection.content.app.getInstances()[0];
					}
		
				}
			} else if (vcSelectionControl instanceof cpr.controls.TabFolder) {
		
				if(e.newSelection.content) {
					// filter -> map 수정(20200330 수정)
					appInstance = e.newSelection.content.getAllRecursiveChildren().map(function(each) {
		
						if(each.type == "embeddedapp") return each.getEmbeddedAppInstance();
					})[0];
		
				}
			}
			
			// DataSource를 확인 할 앱 생성
			_createApp(appInstance); 
		
			// 단축키 사용
			_setAccessKey(); 
		
			return;
		});
		
		
		/**
		 * 팝업을 띄웁니다.
		 */
		function _openDialog() {	
			
			isPopup = true;
		
			// 특정 다일로그를 닫았을 경우, rootAppInstance를 앱인스턴스로 저장합니다.
			if(appInstance.disposed) appInstance = voEmbedded;
				
			appInstance.getRootAppInstance().openDialog(vsAppId, {width : 500, height : 400}, function(dialog){
				dialog.ready(function(dialogApp){
					// 필요한 경우, 다이얼로그의 앱이 초기화 된 후, 앱 속성을 전달하십시오.
					dialog.headerTitle = "데이터 컴포넌트";
					dialog.addEventListener("close", function(){
		
						isPopup = false;
					})
				});
			}).then(function(returnValue){
		
			});
		}
		
		
		/**
		 * 팝업을 열기위한 단축키를 지정합니다.<br>
		 * 단축키 : Ctrl + Alt + A (default)
		 */
		
		function _setAccessKey() {
			
			if(appInstance) {
		
				var container = appInstance.getContainer();
				if(container) {	
					window.addEventListener("keydown", function (e) {
						if(e.ctrlKey && e.altKey && e.keyCode == dynamicKey) {
						
							if(!isPopup) _openDialog();
			
						}
					});
				}
			}
		}
		
		
		/**
		 * 새로운 앱을 생성합니다.
		 */
		function _createApp() {
		
			if(appInstance) {
		
				vsAppId = appInstance.id + "Dialog" + (Math.floor(Math.random()*100)+1);
		
				var voAppInstance = appInstance;
				
				var newApp = new cpr.core.App(vsAppId, {
					onPrepare: function(loader){
					},
					onCreate: function(/* cpr.core.AppInstance */ newApp, exports){
						var container = newApp.getContainer();
						var vcCombo = new cpr.controls.ComboBox();
						var vcDataset = new cpr.data.DataSet();
						vcCombo.setItemSet(vcDataset, {
							label: "label",
							value: "value" 
						});
						
						// script start
						newApp.addEventListener("load", function(e){
							// 특정 다일로그를 닫았을 경우, rootAppInstance를 앱인스턴스로 저장합니다.
		
							if(voAppInstance.disposed) voAppInstance = voEmbedded;
		
							// 콤보박스 생성 및 데이터셋을 바인딩 합니다.
							var vaDatasetId = voAppInstance.getAllDataControls().map(function(each){
		
								if(each.type.indexOf("submission") == -1) return each.id;
							});
							
							vcDataset.parseData({
								"columns" : [
									{dataType:"string", name:"label"},
									{dataType:"string", name:"value"}
								],
								"rows" : _setComboData(vaDatasetId)
							});
							
							container.addChild(vcCombo, {
								top : "20px",
								left : "20px",
								width : "120px",
								height : vsRowHeight
							});
							vcCombo.selectItem(0);
						});
						
						vcCombo.addEventListener("selection-change", function(e){
							// 1. 기존 그리드 또는 폼레이아웃이 그려져 있는 경우 삭제합니다.
							var grid = container.getChildren().filter(function(each) {
								if (each.type == "grid" || each.type == "container") return each;
							})[0];
							container.removeChild(grid);
							
							// 2. 콤보박스에서 선택한 데이터 컨트롤의 정보를 호출합니다.
							/** @type cpr.data.DataSet */
							var dataControl = voAppInstance.getAllDataControls().filter(function(each){
								if(each.id == vcCombo.value) return each;
							})[0];
							
							if(dataControl.type == "dataset" || dataControl.type == "dataview") {
								// 데이터셋일 경우
								// 데이터셋의 status 컬럼을 추가합니다.
								var voColumns = dataControl.getColumnNames();
								voColumns.splice(0, 0, "status");
								var data = _setDatasetData(voColumns, dataControl);
			
								var newDataset = new cpr.data.DataSet();
								newDataset.parseData({
									columns : data["column"],
									rows : data["row"]
								});
								
								// 그리드 생성
								_createGrid(newDataset);
							} else if(dataControl.type == "datamap"){
		
								// 데이터맵일 경우
								// 폼레이아웃 생성
		
								_createFormLayout(dataControl);
							}
							
							// 컨테이너에 생성한 그리드를 추가합니다.
							container.addChild(pcControl, {
								top : "58px",
								left : "20px",
								right : "20px",
								bottom : "20px"
							});
						});
					}
				});
				cpr.core.Platform.INSTANCE.register(newApp);
		
			}
		}
		
		
		/**
		 * 그리드를 생성하고 초기정보를 설정합니다.
		 * @param {cpr.controls.UIControl} vcDataControl
		 */
		function _createGrid(vcDataControl) {
		
			pcControl = new cpr.controls.Grid();
			
			pcControl.init({
				"dataSet" : vcDataControl,
				"columnMovable": true,
				"columnResizable": true,
				"columns": _getColumns(vcDataControl),
				"header": {
					"rows": [{"height": vsRowHeight}],
					"cells": _getCells(vcDataControl),
				},
				"detail": {
					"rows": [{"height": vsRowHeight}],
					"cells": _getCells(vcDataControl)
				}
			});
		}
		
		
		/**
		 * 폼레이아웃을 생성합니다.
		 * @param {cpr.controls.UIControl} vcDataControl
		 * @param {any} vsStyleClass?
		 */
		function _createFormLayout(vcDataControl, vsStyleClass) {
			pcControl = new cpr.controls.Container();
		
			var formLayout = new cpr.controls.layouts.FormLayout;
			formLayout.horizontalSeparatorWidth = 1;
			formLayout.verticalSeparatorWidth = 1;
			formLayout.setColumns(["1fr", "1fr"]);
			formLayout.setRows(_setFormRows(vcDataControl));
			formLayout.setUseRowShade(0, true);
		
			pcControl.setLayout(formLayout);
			
			// TODO 스타일클래스 적용
			pcControl.style.css("border", "1px solid black");
		//	vcControl.style.addClass(vsStyleClass);
			
			_setControlToForm(pcControl, vcDataControl);
		}
		
		/**
		 * 데이터셋의 row 정보를 반환합니다.
		 * @param {any} poColumns
		 */
		function _setComboData(poColumns) {
			var voRows = [];
			
			poColumns.forEach(function( /* String */ each) {
		
				if(each) {
		
					var eachRow = {};
					eachRow["label"] = each;
					eachRow["value"] = each;
					voRows.push(eachRow);
		
				}
			});
			
			return voRows;
		}
		
		
		/**
		 * 폼레이아웃의 row를 설정합니다.
		 * @param {cpr.data.DataMap} datamap
		 */
		function _setFormRows(datamap) {
			var voRows = [];
			for(var idx = 0; idx < datamap.getColumnCount()+1; idx++){
				voRows.push(vsRowHeight);
			}
			return voRows;
		}
		
		
		/**
		 * 데이터맵의 컬럼 수 만큼 폼레이아웃에 자식컨트롤을 추가합니다.
		 * @param {any} pcContainer
		 * @param {cpr.data.DataMap} datamap
		 */
		function _setControlToForm(pcContainer, datamap) {
			_addFormChild(pcContainer, "KEY", 0, 0);
			_addFormChild(pcContainer, "VALUE", 1, 0);
			
			for(var idx = 0; idx < datamap.getColumnCount(); idx++){
				var vsKey = datamap.getColumnNames()[idx];
				_addFormChild(pcContainer, vsKey, 0, idx+1);
				_addFormChild(pcContainer, datamap.getValue(vsKey), 1, idx+1);
			}
		}
		
		
		/**
		 * 폼레이아웃 안에 아웃풋을 추가합니다.
		 * @param {any} pcContainer
		 * @param {any} psValue
		 * @param {any} colIndex
		 * @param {any} rowIndex
		 * @param {any} vsStyleClass?
		 */
		function _addFormChild(pcContainer, psValue, colIndex, rowIndex, vsStyleClass) {
			var vcOutput = new cpr.controls.Output();
			vcOutput.value = psValue;
			
			// 스타일 클래스 추가
		//	vcOutput.style.addClass(vsStyleClass);
			vcOutput.style.css("text-align", "center");
			
			pcContainer.addChild(vcOutput, {
				"colIndex" : colIndex, 
				"rowIndex" : rowIndex
			});
			
			pcContainer.redraw();
		}
		
		
		/**
		 * 그리드 헤더의 컬럼 수를 저장합니다.
		 * @param {cpr.data.DataSet} dataset
		 */
		function _getColumns(dataset) {
			var vaColumns = [];
			for(var idx = 0; idx < dataset.getHeaders().length; idx++){
				if(idx == 0) {
		
					vaColumns.push({"width": "50px"}); // status column 너비
				} else {
					vaColumns.push({"width": "100px"});
				}
			}
			return vaColumns;
		}
		
		
		/**
		 * 그리드 header, detail의 cells에 들어갈 데이터를 저장합니다.
		 * @param {cpr.data.DataSet} dataset
		 */
		function _getCells(dataset) {
			var vaHeaderCell = [];
		
			for(var idx = 0; idx < dataset.getHeaders().length; idx++){
				vaHeaderCell.push(_setGridConfigurator(idx, dataset.getColumnNames()[idx]));
			}
			
			return vaHeaderCell;
		}
		
		
		/**
		 * cell의 configurator를 반환합니다.
		 * @param {Number} colIndex
		 * @param {any} vsColumn
		 */
		function _setGridConfigurator(colIndex, vsColumn) {
			var configurator = {
				"constraint" : {
					"rowIndex" : 0,
					"colIndex" : colIndex
				},
				"configurator": function(cell) {
					cell.targetColumnName = vsColumn; // header
					cell.text = vsColumn; // header
					cell.targetColumnName = vsColumn; // detail
					cell.columnName = vsColumn; // detail
				}
			}
			return configurator;
		}
		
		
		/**
		 * 데이터셋의 row 정보를 반환합니다.
		 * @param {any} poColumns
		 * @param {cpr.data.DataSet} dataset
		 */
		function _setDatasetData(poColumns, dataset) {
			var voColumns = [];
			var voRows = [];
			
			// column
			poColumns.forEach(function( /* String */ each) {
				var eachColumn = {dataType: "string"};
				eachColumn["name"] = each;
				voColumns.push(eachColumn);
			});
				
			// row
			for (var idx = 0; idx < dataset.getRowCount(); idx++) {
				var eachRow = {};
				poColumns.forEach(function( /* String */ each) {
					if(each == "status") {
		
						eachRow[each] = dataset.getRowStateString(idx);
					} else {
						eachRow[each] = dataset.getColumnData(each)[idx];
					}
				});
				voRows.push(eachRow);
			}
			
			return {
				"column" : voColumns,
				"row" : voRows
			};
		}
	});
})();
/// end - module/createNewAppIns
/// start - module/createNormalGrid
/*
 * Module URI: module/createNormalGrid
 * SRC: module/createNormalGrid.module.js
 *
 * This file was generated by eXbuilder6 compiler, Don't edit manually.
 */
(function(){
	cpr.core.Module.define("module/createNormalGrid", function(exports, globals, module){
		/************************************************
		 * createNormalGrid.module.js
		 * Created at 2020. 12. 4. 오후 3:17:09.
		 *
		 * @author csj
		 ************************************************/
		/**
		 * 금융투자업자 - 일반현황 - 추이 처럼 조회시 데이터로 그리드 헤더와 디테일 영역을
		 * 다시 그리는 모듈함수입니다.
		 */
		
		/**
		 * 헤더 행 높이
		 */
		var mnHeaderHeight = 35; 
		
		/**
		 * 디테일 행 높이
		 */
		var mnDetailHeight = 35;
		
		/**
		 * 데이터 정렬 컬럼
		 */
		var msOrderSeq = "ORDER_SEQ"; 
		
		/**
		 * 그리드 헤더 셀의 텍스트
		 */
		var msHeaderNm = "HEADER_NM";
		
		/**
		 * 헤더 셀 너비
		 */
		var msColumnWid = 100;//"COLUMN_WID";
		
		function RenderGrid(){
			
		}
		
		
		/**
		 * 서버에서 내려온 데이터로 그리드 헤더와 디테일  세팅
		 * @param {cpr.core.AppInstance} poApp
		 * @param {cpr.controls.Grid} poGrid 
		 * @param {cpr.data.DataSet} dsHeader
		 * @param {cpr.data.DataSet} dsGridTemp 
		 */
		RenderGrid.prototype.renderGrid = function( poApp, poGrid, dsHeader , dsGridTemp){
			
			var vcGrid = poGrid;
			var dsHeader = dsHeader;
			
			var moGridStandardConfig = {
				"columnMovable": true,
				"resizableColumns": "all",
				"dataSet": null,
				"columns": [],
				"header": {
					"rows": [{
						"height": mnHeaderHeight
					}],
					"cells": [],
				},
				"detail": {
					"rows": [{
						"height": mnDetailHeight
					}],
					"cells": [],
				}
			};
			var dsHeaderColNms = _sortColumnNames(dsHeader);
			moGridStandardConfig.dataSet =  dsGridTemp;	
			
			dsHeaderColNms.forEach(function(each, idx) {
				
				moGridStandardConfig.columns.push({
					"width": msColumnWid + "px"
				});
				moGridStandardConfig.header.cells.push({
					"constraint": {"rowIndex": 0 , "colIndex" : idx },
					"configurator": function(cell) {
						cell.targetColumnName = each
						cell.text = dsHeader.getValue(0, each);
						cell.sortColumnName = each;
						cell.visible = dsHeader.getValue(0, each) != "" ? true : false ;
					}
				});
				moGridStandardConfig.detail.cells.push({
					"constraint": {"rowIndex": 0 , "colIndex" :idx },
					"configurator": function(cell) {
						cell.columnName = each;
						
					}
				});
			});
			vcGrid.init(moGridStandardConfig);	
		}
		
		/**
		 * 그리드 디테일에 바인딩 할 컬럼명 정렬
		 * @param {cpr.data.DataSet} pcGridBindDataset
		 */
		function _sortColumnNames (pcGridBindDataset) {
			
			var vsStaticText = "TMPV";
			var vaColumnNames = pcGridBindDataset.getColumnNames();
			
			var vaBindGridColumns = vaColumnNames.map(function(each) {
				return each.split(vsStaticText)[1]
			}).sort(function(a, b) {
				return a - b; // 오름차순 정렬
			}).map(function(each) {
				return vsStaticText + each
			});
			
			return vaBindGridColumns
		}
		
		
		globals.RenderGrid = function(){
			return new RenderGrid();
		}
	});
})();
/// end - module/createNormalGrid
/// start - module/createSearchBox
/*
 * Module URI: module/createSearchBox
 * SRC: module/createSearchBox.module.js
 *
 * This file was generated by eXbuilder6 compiler, Don't edit manually.
 */
(function(){
	cpr.core.Module.define("module/createSearchBox", function(exports, globals, module){
		/************************************************
		 * createSearchBox.module.js
		 * Created at 2020. 6. 2. 오전 10:03:42.
		 *
		 * @author daye
		 ************************************************/
		
		/**
		 * 본 모듈은 데이터를 통해 검색조건을 동적으로 구성합니다.
		 * 검색조건을 넣을 그룹은 파라미터 인자로 전달받으며, 데이터 값을 통해 UDC 또는 기본 컨트롤을 배치합니다.
		 */
		
		
		/************************************************
		 * 데이터컴포넌트 및 컬럼명
		 ************************************************/
		/**
		 * 필수로 보여줄 검색조건 (Y/N)
		 */
		var msDisplayYn = "DISPLAY_DSPDMNYN";
		
		/**
		 * flex 데이터 컴포넌트 타입 번호
		 */
		var msFlexType = "FLEX_OUTPUT_TYP";
		
		/**
		 * 자료주기 유형
		 */
		var msDataType = "DATA_PRDTYP";
		
		/**
		 * 컴포넌트 아이템 코드 키
		 */
		var msItemCode = "DISPLAY_OUTPUTFUNC";
		
		/**
		 * 그룹 코드
		 */
		var msGroupCd = "GROUP_CD";
		
		/**
		 * 기본값
		 */
		var msDefaultVal = "DISPLAY_DSPVLU";
		
		var msDefaultVal2 = "D_DISPLAY_DSPVLU";
		
		var msItemDmnId = "ITEM_DMNID";
		
		/**
		 * 타이틀
		 */
		var msItemNm = "ITEM_DMNNM";
		
		/**
		 * 바인딩 하기위한 key 값
		 */
		var msMetaKey = "SRCH_METAVAR";
		
		/**
		 * [전체] 데이터 추가유무
		 */
		var msTotalYn = "TOTALITY_DSPYN";
		
		/**
		 * 참조 테이블 아이디
		 */
		var msRefTabID = "REF_TABID";
		
		/**
		 * 정렬순서
		 */
		var msOrderSeq = "ORDER_SEQ";
		
		/**
		 * 검색조건 코드 데이터셋
		 */
		var msDsSearch = "dsSearch";
		
		/**
		 * 검색조건 코드 데이터셋
		 */
		var msDsSearchCd = "dsSearchCd";
		
		/**
		 * 검색조건 데이터맵
		 */
		var msDmSearch = "dmSearch";
		
		/**
		 * list_app_dt  데이터 셋 ID 
		 */
		var msListAppDt = "dsListAppDt";
		
		/**
		 * list_app_dt 데이터셋에서 일자관련 컬럼명
		 */
		var msMaxDateColNm = "TMPV4";
		
		
		/************************************************
		 * 멤버변수
		 ************************************************/
		
		var mbDebug = true;
		
		/************************************************
		 * 사용가능 API
		 ************************************************/
		/**
		 * 검색조건 영역을 만듭니다.
		 * @param {cpr.core.AppInstance} app
		 * @param {cpr.data.DataMap} pcDatamap 검색조건 데이터맵
		 * @param {cpr.controls.Container} pcGrpSearch
		 * @param {
		 * 		style : {
		 * 			form : #css-class <!-- 검색조건 그룹 클래스 -->,
		 * 			label : #css-class <!-- 라벨 클래스 -->
		 * 		} <!-- 검색조건 스타일 클래스 적용 -->
		 * 	} poOptions
		 * @param {()=>void} poCallBackFunc
		 */
		globals.createSearchBox = function (app, pcDatamap, pcGrpSearch, poOptions, poCallBackFunc) {
			
			var vcDsSearch = app.lookup(msDsSearch);
			var vcDmSearch = pcDatamap;
			var vsLangGb = app.lookup("dmSearchData").getValue("language_gb");
			
			if(vcDsSearch.getRowCount() == 0 || ValueUtil.isNull(vcDmSearch) || ValueUtil.isNull(pcGrpSearch)) {
				return;
			}
			
			/* 그룹 스타일 클래스 적용 */
			pcGrpSearch.style.addClass(poOptions.style.form);
			// 여러개의 스타일 클래스를 적용 할 경우 아래의 코드를 사용하십시오.
		//	pcGrpSearch.style.setClasses([""]); 
			
			/* 데이터맵 바인딩 */
			var voDataMapContext = new cpr.bind.DataMapContext(vcDmSearch);
			pcGrpSearch.setBindContext(voDataMapContext);
				
			/* 폼레이아웃 설정 */
			var voFormLayout = new cpr.controls.layouts.FormLayout();
			voFormLayout.topMargin = "10px";
			voFormLayout.bottomMargin = "10px";
			voFormLayout.horizontalSpacing = "20px";
			voFormLayout.verticalSpacing = "20px";
			voFormLayout.horizontalSeparatorWidth = 1;
			voFormLayout.setColumns(["1fr", "70px"]);
			pcGrpSearch.setLayout(voFormLayout);
			pcGrpSearch.style.css("padding", "0px 20px");
		
			/* 검색을 위한 그룹 추가 */ 
			var vaDisplayYn = _.uniq(vcDsSearch.getColumnData(msDisplayYn));
			var vnDisplayLen = vaDisplayYn.length <= 2 ? vaDisplayYn.length : 2;
			for(var idx = 0; idx < vnDisplayLen; idx++){
				var vcGroup = new cpr.controls.Container();
				var voFlowLayout = new cpr.controls.layouts.FlowLayout();
				voFlowLayout.spacing = 5;
				voFlowLayout.rightMargin = app.getContainer().getActualRect().width / 8;
				vcGroup.setLayout(voFlowLayout);
				
				var vaFilterData = null;
				
				/* 그룹에 검색 컨트롤 추가 */
				if(vaDisplayYn[idx] == "Y") {
					vaFilterData = vcDsSearch.getRowDataRanged().filter(function(each){
						return each[msDisplayYn] == "Y";
					});
				} else {
					vaFilterData = vcDsSearch.getRowDataRanged().filter(function(each){
						return (each[msDisplayYn] == "N" || each[msDisplayYn] == "");
					});
				}
				
				_insertScrhCtrls(app, pcDatamap, vcGroup, vaFilterData, poOptions, vsLangGb);
				pcGrpSearch.addChild(vcGroup, {
					"colIndex": 0,
					"rowIndex": idx
				});
			}
			
			/* 추가된 컨트롤에 맞게 레이아웃 Row 설정 */
			_setFormRows(pcGrpSearch, voFormLayout)
			
			/* [조회] 버튼 추가 */
			var vcBtnSrch = new cpr.controls.Button("btn1");
			vcBtnSrch.value = vsLangGb != "KOR" ? "Search" : "조회";
			vcBtnSrch.userAttr("search", "true");
		//	vcBtnSrch.style.addClass("btn-search"); // TODO [조회]버튼 스타일을 추가합니다.
			vcBtnSrch.addEventListener("click", function(e) {
				var control = e.control;
				// [조회] 클릭 시, 콜백함수 실행
		//		console.log(vcDmSearch.getDatas());
				if(_.isFunction(poCallBackFunc)) poCallBackFunc();
			});
			pcGrpSearch.addChild(vcBtnSrch, {
				"colIndex": 1,
				"rowIndex": 0,
				"colSpan": 1,
				"rowSpan": voFormLayout.getRows().length,
				"verticalAlign": "center",
				"height" : 30
			});
			
			var vnMinWidth = (app.getContainer().getActualRect().width * 0.8) - 230; // 검색영역 최소너비
			return {
				width : vnMinWidth
			}
		}
		
		
		/************************************************
		 * 내부 API
		 ************************************************/
		/**
		 * 컨트롤  추가
		 * @param {cpr.core.AppInstance} app
		 * @param {cpr.data.DataMap} pcDatamap
		 * @param {cpr.controls.Container} pcGrp
		 * @param {any} paData
		 * @param {any} options
		 */
		function _insertScrhCtrls (app, pcDatamap, pcGrp, paData, options, psLang) {
			
			/* udc 추가 */
			function getUDC (pnFlexType, pnDataPrdTyp) {
				var vnFlexType = _getFlexType(pnFlexType, pnDataPrdTyp);
				var voApp = cpr.core.Platform.INSTANCE.lookup("udc/cmn/search/Component" + vnFlexType);
				
				if(mbDebug) {
					console.log("Component" + vnFlexType);
				}
				
				var vcNewEmb = null;
				if(voApp) {
					vcNewEmb = new cpr.controls.EmbeddedApp();
					vcNewEmb.app = voApp;
					vcNewEmb.app.createNewInstance();
					vcNewEmb.forceRun();
				}
				
				return vcNewEmb;
			}
			
			function setEngTxt (comp, psFlexType) {
				var vcEmb = comp.getEmbeddedAppInstance();
				if(vcEmb && psLang == "ENG") {
					/** @type cpr.controls.Container */
					var voEmbContainer = vcEmb.getContainer();
					
					var vcOptStatic = voEmbContainer.getChild("optStatic");
					if(vcOptStatic) {
						var vnNum = voEmbContainer.getAppInstance().app.id.replace("udc/cmn/search/Component", "");
						var vsEngVal = "Search Period";
						switch(vnNum){
							case "10" : case 10:
								vsEngVal = "Search Period";
								break;
							case "20": case 20 :
								vsEngVal = "Base Date";
								break;
						}
						
						vcOptStatic.value = vsEngVal;
					}
					
					var vcBtnPop = voEmbContainer.getChild("btnPop");
					if(vcBtnPop) {
						vcBtnPop.value = "Search";
					}
					
					voEmbContainer.getAllRecursiveChildren().forEach(function(each){
						if("getItems" in  each) {
							var voItems = each.getItems();
							if(voItems.length > 0) {
								/** @type cpr.controls.Item */
								var voFirstItem = each.getItems()[0];
								if(voFirstItem.label == "전체") {
									voFirstItem.label = "All";
								}
							}
						} 
		
						if (each.type == "inputbox" && each.placeholder == "전체") {
							each.placeholder = "Total";
						}
					});
					
					voEmbContainer.redraw();
				}
			}
			
			/* 컨트롤 추가 */
			// flowLayout 에 컨트롤을 추가합니다. (autoSize : width, height)
			function addChild(pcInsCtrl) {
				pcGrp.addChild(pcInsCtrl, { 
					autoSize: "both"
				});
			}
				
			paData.forEach(function(each, index){
				
				var vnFlexOuputType = each[msFlexType];
				if(vnFlexOuputType != "") {
					var vnDataPrdTyp = each[msDataType];
					var vcCtrl = getUDC(vnFlexOuputType, vnDataPrdTyp);
					if(vcCtrl == null) return;
					
					if(mbDebug) {
						console.log("Group_Cd : " + each[msItemCode]);
					}
				
					/** @type cpr.data.DataSet */
					var vcDsSrchCd = app.lookup(msDsSearchCd);
					vcDsSrchCd.setFilter(msGroupCd + " == '" + each[msItemCode] + "'");
		//			vcDsSrchCd.setSort(msOrderSeq);
		
					//local data 와 운영 dat의 다른부분이 있어 이를 수정함 ( local : 2'  , 운영 : '2' )
					/** @type String */
					var vsDefaultValue = each[msDefaultVal];
					if( vsDefaultValue == "" || vsDefaultValue == null ||  vsDefaultValue == undefined ){
					    vsDefaultValue = each[msDefaultVal2];
					}
					if( vsDefaultValue == null ||  vsDefaultValue == undefined )
					    vsDefaultValue = "";
					        
					var vnSpliceIndex = vsDefaultValue.lastIndexOf("'");
					if(vsDefaultValue.length == vnSpliceIndex+1) {
						vsDefaultValue = vsDefaultValue.substring(0, vnSpliceIndex); 
						if( vsDefaultValue.length > 0 && vsDefaultValue.indexOf("'") == 0 ){
							if( vsDefaultValue.length > 1 )
								vsDefaultValue = vsDefaultValue.substring(1);
						}
					}
					
					/* 앱속성 설정 */
					vcCtrl.setAppProperty("prdType", vnDataPrdTyp); // 자료주기 유형
					vcCtrl.setAppProperty("title", each[msItemNm]); // 타이틀
					vcCtrl.setAppProperty("key", each[msMetaKey]); // meta key
					vcCtrl.setAppProperty("tableId", each[msRefTabID]); // 참조 테이블id
					vcCtrl.setAppProperty("code", vcDsSrchCd); // 아이템 코드
					vcCtrl.setAppProperty("flexType", vnFlexOuputType); // 플렉스 컴포넌트 타입
					vcCtrl.setAppProperty("totalYn", each[msTotalYn]); // total
					vcCtrl.setAppProperty("defaultValue", vsDefaultValue); // 기본값
					vcCtrl.setAppProperty("lang", psLang); // 기본값
					vcCtrl.setAppProperty("itemDmnId", each[msItemDmnId]);
					
					// list_app_dt 데이터 셋의 TMPV4 컬럼 값으로 일자 가져 올 수 있도록 수정 (2020.12.04 daye 수정)
					if( !!app.lookup(msListAppDt) && app.lookup(msListAppDt).getRowCount() > 0) {
						var vsMaxDate = app.lookup(msListAppDt).getColumnData(msMaxDateColNm)[0];
						if(vsMaxDate) {
							vcCtrl.setAppProperty("maxDate", vsMaxDate);  
							vcCtrl.setAppProperty("approvalInfo", app.lookup(msListAppDt).getRowDataRanged()[0]);  
						}
					}
					
					var vcDmSearch5101 = app.lookup("dsSearch5101");
					if(vcDmSearch5101) {
						vcCtrl.setAppProperty("code5101", vcDmSearch5101);  
					}
					
					// 2020.12.22 dayse append 최근일자 데이터셋 추가
					var vcDsLatestDate = app.lookup("dsLatestDate");
					if(vcDsLatestDate) {
						vcCtrl.setAppProperty("latestDate", vcDsLatestDate);  
					}
					
					//2020.12.23 dyseo append 회원전용-지수 메타 데이터 추가
					var vcDsMetaCombo = app.lookup("dsMetaCombo");
					if(vcDsMetaCombo) {
						vcCtrl.setAppProperty("metaCombo", vcDsMetaCombo);
					}
					
					vcCtrl.callAppMethod("setData");
								
					/* 타이틀에 따른 최소길이 설정*/
					var voCtrlLayout = vcCtrl.getEmbeddedAppInstance().getContainer().getLayout();
					var vnLen = getSearchMinWidth(each[msItemNm]);
					voCtrlLayout.setColumnMinWidth(0, vnLen);
					
					/* 상대컬럼 바인딩 */
					var vcDmSearch = pcDatamap;
					var vaSrchMetaVar = each[msMetaKey].split(",");
					vaSrchMetaVar.forEach(function(each) {
						vcCtrl.getEmbeddedAppInstance().getContainer().getAllRecursiveChildren().map(function(ctrl){
							if(ctrl.getBindInfo("value") && ctrl.getBindInfo("value").property == each) {
								ctrl.bind("value").toDataColumn(each);
								return;
							}
						});
						vcDmSearch.setValue(each, vcCtrl.getAppProperty(each));
					})
					
					setEngTxt(vcCtrl, vnFlexOuputType.toString());
					
					addChild(vcCtrl);
		
				} else {
					// Layout
					var vcTempGroup = new cpr.controls.Container();
					var vcTempFormLayout = new cpr.controls.layouts.FormLayout();
					vcTempFormLayout.rightMargin = "20px";
					vcTempFormLayout.setColumns(["1fr", "150px"]);
					vcTempFormLayout.setColumnAutoSizing(0, true);
					vcTempFormLayout.setRows(["30px"]);
					vcTempFormLayout.setRowAutoSizing(0, true);
					vcTempGroup.setLayout(vcTempFormLayout);
					
					var vcOutput = new cpr.controls.Output();
					vcOutput.value = each[msItemNm];
					vcOutput.style.addClass(options.style.label);
					vcTempGroup.addChild(vcOutput, {
						rowIndex : 0,
						colIndex : 0
					});
					
					// default : inputBox
					var vcInputBox = new cpr.controls.InputBox();
					vcInputBox.tooltip = each[msItemNm];
					vcTempGroup.addChild(vcInputBox, {
						rowIndex : 0,
						colIndex : 1
					});
					
					vcInputBox.addEventListener("value-change", function(e){
						if(vcInputBox.value == "*") vcInputBox.value = "전체";
					});
					
					addChild(vcTempGroup);
				}
			});
		}
		
		
		/**
		 * 폼레이아웃의 row 설정
		 * @param {cpr.controls.Container} pcGrp
		 * @param {cpr.controls.layouts.FormLayout} poFormLayout
		 */
		function _setFormRows (pcGrp, poFormLayout) {
			
			if(!pcGrp) return;
			
			var vaRows = [];
			pcGrp.getChildren().forEach(function(each){
				vaRows.push("30px");
			});
			poFormLayout.setRows(vaRows);	
			
			vaRows.forEach(function(each, index){
				poFormLayout.setRowAutoSizing(index, true);
			});
			
			return vaRows;
		}
		
		
		/**
		 * 
		 * @param {any} pnFlexType
		 * @param {any} pnDataPrdType
		 */
		function _getFlexType (pnFlexType, pnDataPrdType) {
			var vnTypeNum = 0;
			
			switch(pnFlexType){
				case "10" : case "280" :
					switch(pnDataPrdType){
						case "10" : case "20" : case "30" : case "40" : case "50" : case "60" : case "70" : case "80" : case "170" : default :
							vnTypeNum = 10;
							break;
						case "90" : case "110" : case "120" : case "130" : case "140" : case "160" :
							vnTypeNum = 20;
							break;
						case "150" :
							vnTypeNum = 260;
							break; 
					}
					break;
				case "160" :
					vnTypeNum = 40;
					break;
				case "290" :
					vnTypeNum = 60;
					break;
				case "180" :
					vnTypeNum = 100;
					break;
				case "201" : case "300" :
					vnTypeNum = 110;
					break;
				default :
					vnTypeNum = pnFlexType;
					break;
			}
			return vnTypeNum;
		}
		
		
		/**
		 * 검색조건 컴포넌트 타이틀 최소너비 설정
		 * 1. 한글 : 한글자 당 15px
		 * 2. 영어 : 한글자 당 7px 
		 * 3. 숫자 : 한글자 당 7px
		 * 4. 특수문자 : 하나 당 2px
		 * @param {String} psText
		 */
		function getSearchMinWidth (psText) {
		
			var pattern_num = /[0-9]/;	// 숫자 
			var pattern_spc = /[/,~!@#$%^&*()_+|<>?:{}]/; // 특수문자
			var pattern_eng = /[a-zA-Z]/;	// 영어
			var pattern_kor = /[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/; // 한글
			
			var vnLen = 0;
			var vaText = psText.split("");
			vaText.forEach(function(each){
				if(pattern_num.test(each)) vnLen += 9;
				if(pattern_spc.test(each)) vnLen += 0;
				if(pattern_eng.test(each)) vnLen += 10;
				if(pattern_kor.test(each)) vnLen += 15;
			});
			
			return vnLen;
		}
		globals.getSearchMinWidth = getSearchMinWidth;
		
		
		/************************************************
		 * 기본 컨트롤 생성
		 ************************************************/
		/* 아웃풋 */
		function _insertOutput (poData) {
			var vcOutput = new cpr.controls.Output();
			
			// TODO 아웃풋의 value를 설정합니다.
		//	vcOutput.value = poData[msItemNm]
			
			// TODO 아웃풋의 스타일을 추가하십시오.
		//	vcOutput.style.addClass("output Style"); 
		
			return vcOutput;
		}
		
		/* 콤보박스 */
		function _insertComboBox (poData) {
			var vcCombo = new cpr.controls.ComboBox();
			
			// TODO 콤보박스 데이터셋 바인딩을 위해 아래의 주석을 해제하여 수정하십시오.
		//	vcCombo.setItemSet("dataSet", {
		//		label : "labelCol",
		//		value : "valueCol"
		//	});
		
			// TODO 콤보박스 스타일을 추가하십시오.
		//	vcCombo.style.addClass("comboStyle"); // 콤보박스
		//	vcCombo.style.item.addClass("combo item Style"); // 콤보박스 아이템
		//	vcCombo.style.list.addClass("combo list Style"); // 콤보박스 리스트
			
			return vcCombo;
		}
		
		/* 데이트인풋 */
		function _insertDateInput (poData) {
			var vcDateInput = new cpr.controls.DateInput();
			
			// TODO 데이트인풋의 value를 설정합니다.
		//	vcDateInput.value = "";
			
			// TODO 데이트인풋 스타일을 추가하십시오.
		//	vcDateInput.style.addClass("dateInput Style");
		//	vcDateInput.style.calendar.addClass("dateInput calendar Style");
		//	vcDateInput.style.content.addClass("dateInput content Style");
		//	vcDateInput.style.button.addClass("dateInput button Style");
		//	vcDateInput.style.header.addClass("dateInput header Style");
		//	vcDateInput.style.footer.addClass("dateInput footer Style");
			
			return vcDateInput;
		}
		
		/* 인풋박스 */
		function _insertInputBox (poData) {
			var vcInputBox = new cpr.controls.InputBox();
			
			// TODO 인풋박스 스타일을 추가하십시오.
		//	vcInputBox.style.addClass("inputbox Style");
		//	vcInputBox.style.clear.addClass("inputbox clear Style");
		
			return vcInputBox;
		}
		
		/* 버튼 */
		function _insertButton (poData) {
			var vcButton = new cpr.controls.Button();
			
			// TODO 버튼 스타일을 추가하십시오.
		//	vcButton.style.addClass("button Style");
		//	vcButton.style.icon.addClass("button Icon Style");
			
			return vcButton;
		}
		
		/* 넘버에디터 */
		function _insertNumberEditor (poData) {
			var vcNumEditor = new cpr.controls.NumberEditor();
			
			// TODO 넘버에디터 스타일을 추가하십시오.
		//	vcNumEditor.style.addClass("numberEditor Style");
		
			return vcNumEditor;
		}
		
		/* 체크박스 */
		function _insertCheckBox (poData) {
			var vcChckBox = new cpr.controls.CheckBox();
			
			// TODO 체크박스 스타일을 추가하십시오.
		//	vcChckBox.style.addClass("checkbox Style");
		//	vcChckBox.style.icon.addClass("checkbox icon Style");
			
			return vcChckBox;
		}
		
		/* 체크박스그룹 */
		function _insertCheckBoxGroup (poData) {
			var vcChckBoxGrp = new cpr.controls.CheckBoxGroup();
			
			// TODO 체크박스그룹 데이터셋 바인딩을 위해 아래의 주석을 해제하여 수정하십시오.
		//	vcChckBoxGrp.setItemSet("dataSet", {
		//		label : "labelCol",
		//		value : "valueCol"
		//	});
		
			// TODO 데이트인풋 스타일을 추가하십시오.
		//	vcChckBoxGrp.style.addClass("checkboxgroup Style");
		//	vcChckBoxGrp.style.item.addClass("checkboxgroup item Style");
		//	vcChckBoxGrp.style.icon.addClass("checkboxgroup icon Style");
			
			return vcChckBoxGrp;
		}
		
		/* 라디오 버튼 */
		function _insertRadioButton (poData) {
			var vcRadioButton = new cpr.controls.RadioButton();
			
			// TODO 라디오버튼 데이터셋 바인딩을 위해 아래의 주석을 해제하여 수정하십시오.
		//	vcRadioButton.setItemSet("dataSet", {
		//		label : "labelCol",
		//		value : "valueCol"
		//	});
		
			// TODO 데이트인풋 스타일을 추가하십시오.	
		//	vcRadioButton.style.addClass("radio Style");
		//	vcRadioButton.style.item.addClass("radio item Style");
		//	vcRadioButton.style.icon.addClass("radio icon Style");
		
			return vcRadioButton;
		}
	});
})();
/// end - module/createSearchBox
/// start - module/extension
/*
 * Module URI: module/extension
 * SRC: module/extension.module.js
 *
 * This file was generated by eXbuilder6 compiler, Don't edit manually.
 */
(function(){
	cpr.core.Module.define("module/extension", function(exports, globals, module){
		/************************************************
		 * Control Wrapping Utils
		 * 각 사이트별 커스터마이징하여 사용 가능
		 * version 2.0
		 ************************************************/
		
		// 의존 모듈 선언.
		module.depends("module/common");
		
		/**
		 * Msg(메시지) 유틸
		 * @constructor
		 * @param {common.module} appKit
		 */
		function MsgKit(appKit){ 
			this._appKit = appKit;
		};
		
		/**
		 * 메시지 ID에 해당되는 메시지를 반환한다.
		 * @param {String} psMsgId  메시지 ID
		 * @param {String | Array} paArgs 메시지 내용 중 @로 표시된 부분 넣어줄 데이터 배열
		 * @return {String} 메시지 문자열
		 */
		MsgKit.prototype.getMsg = function(psMsgId, paArgs) {
		    if (psMsgId == null || psMsgId == "") return "";
		    var psMsg = cpr.I18N.INSTANCE.message("NLS-"+psMsgId);
		    if (psMsg == null || psMsg.indexOf("NLS-") >= 0) { return psMsgId.replace(/\\n/gi, "\n"); }
		    
		    if(!ValueUtil.isNull(paArgs)){
		    	if(!(paArgs instanceof Array)){
		    		paArgs = [paArgs];
		    	}
		    	//정규 표현식 사용하여 동적 메시지 치환
		    	var regExp = psMsg.match(/\{[0-9]+\}/ig);
				regExp.forEach(function(/* String */ exp){
					var idx = ValueUtil.fixNumber(exp.replace("{", "").replace("}", "").trim());
					psMsg = psMsg.replace(exp, new String(paArgs[idx]).replace(/\r\n/ig, ""));
				});
		    }
		    
		    return psMsg.replace(/\\n/ig, "\n");
		};
		
		/**
		 * 확인 선택용 Confirm 메시지 박스를 띄운다.
		 * <pre><code>
		 * Msg.confirm("CRM-M001");
		 * </code></pre>
		 * @param {String} psMsgId 메시지 ID
		 * @param {String | Array} paArgs (Optional)메시지 내용 중 @로 표시된 부분 넣어줄 데이터 배열
		 * @return {Boolean} Confirm 창의 확인 결과
		 */
		MsgKit.prototype.confirm = function(psMsgId, paArgs) {
			return confirm(this.getMsg(psMsgId, paArgs));
		};
		
		/**
		 * 메시지를 어플리케이션 화면 헤더 영역에 메시지를 보여준다.
		 * <pre><code>
		 * Msg.notify(app, "CMN-M001");
		 * </code></pre>
		 * @param {cpr.core.AppInstance} app - 앱인스턴스 객체
		 * @param {#lang} psMsgId 메시지ID
		 * @param {String | Array} paArgs (Optional)메시지 내용 중 @로 표시된 부분 넣어줄 데이터 배열
		 * @param {Boolean} pbKeep (Optional) 이전 메시지를 유지시킬지 여부
		 * @param {String} psMsgType (Optional) 출력타입 (생략가능) default : INFO
		 *                 INFO | WARNING | DENGER
		 * @return void
		 */
		MsgKit.prototype.notify = function(app, psMsgId, paArgs, psMsgType) {
		
			var voMsgInfo = {};
			voMsgInfo.TYPE = psMsgType;
			voMsgInfo.MSG = this.getMsg(psMsgId, paArgs);
			cpr.core.NotificationCenter.INSTANCE.post("app-msg", voMsgInfo);
			
			/* 헤더에 메시지 표현.
			var _app = this._appKit.getMainApp(app);
			_app = _app != null ? _app : app;
			
			var vaChildCtrls = _app.getContainer().getChildren();
			var vaSubChildCtrls = null;
			var appHeader = null;
			for (var i=0, len=vaChildCtrls.length; i<len; i++) {
		        if (vaChildCtrls[i] instanceof udc.com.appHeader ) {
		        	appHeader = vaChildCtrls[i];
		        	break;
		        }else if(vaChildCtrls[i].type == "container" && vaChildCtrls[i].style.getClasses().indexOf("header-box") != -1){
		        	vaSubChildCtrls = vaChildCtrls[i].getChildren();
		        	for (var j=0, jlen=vaSubChildCtrls.length; j<jlen; j++) {
		        		if (vaSubChildCtrls[j] instanceof udc.com.appHeader ) {
				        	appHeader = vaSubChildCtrls[j];
				        	break;
				        }
		        	}
		        	if(appHeader != null) break;
		        }
		    }
		    //갱신된 데이터가 조회되었습니다. 메시지 유지 여부 설정
		    if(!ValueUtil.isNull(_app.__keep_msg)){
		    	psMsgId = _app.__keep_msg;
		    	_app.__keep_msg = "";
		    }else{
		    	if(ValueUtil.fixBoolean(pbKeep)) app.__keep_msg = psMsgId;
				else _app.__keep_msg = "";
		    }
		    
		    if(appHeader){
		    	var embApp = appHeader.getEmbeddedAppInstance();
				var vcOptMsg = embApp.lookup("optAppMsg");
				var vcNotifier = embApp.lookup("notiInfo");
				var vsNotiMsg = this.getMsg(psMsgId, paArgs);
				if(vcNotifier.visible){
					vcNotifier.info(vsNotiMsg);
				}
				vcOptMsg.value = vsNotiMsg;
				vcOptMsg.style.animateFrom({
					"transform": "translateY(-30px) ",
					"opacity": "0"
				});
		    }*/
		};
		
		/**
		 * 메시지를 웹브라우저의 alert 메시지로 띄운다.
		 * <pre><code>
		 * Msg.alert("CMN-M001");
		 * </code></pre>
		 * @param {String} psMsgId 메시지ID
		 * @param {String | Array} paArgs (Optional)메시지 내용 중 @로 표시된 부분 넣어줄 데이터 배열
		 * @return void
		 */
		MsgKit.prototype.alert = function(psMsgId, paArgs) {
		    alert(this.getMsg(psMsgId, paArgs));
		};
		
		/**
		 * Dialog 유틸
		 * @constructor
		 * @param {common.AppKit} appKit
		 */
		function DialogKit(appKit){
			this._appKit = appKit;
		};
		
		/**
		 * 모달(Modal) 팝업을 호출한다.
		 * <pre><code>
		 * Dialog.open(app, "app/cmn/CMN001", 700, 500, function(dialog){...}, {key1:"value1", key2:"value2"});
		 * </code></pre>
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {String} appid 팝업 화면 주소.
		 * @param {Number} width 팝업 창의 가로 사이즈.
		 * @param {Number} height 팝업 창의 세로 사이즈.
		 * @param {Function} handler 팝업이 닫힐 때 콜백함수(callback function).
		 * @param {Object} initValue (Optional) 초기 파라메터 key/value쌍으로 팝업창에 넘길 파라메터 JSON 데이터[ 예시)-{key1:"value1", key2:"value2"}]
		 * @param {object} prop (Optional) 팝업 설정 속성
		 * 		left			{number}  다이얼로그의 x좌표 default : 가운데 위치
		 * 		top				{number}  다이얼로그의 y좌표 default : 가운데 위치
		 * 		headerVisible	{boolean} 다이얼로그 헤더를 보이기 여부 default : true
		 * 		headerMovable	{boolean} 다이얼로그 헤더를 통해 이동 가능 여부 default : true
		 * 		headerClose		{boolean} 다이얼로그 헤더 close 버튼 보이기 여부 default : true
		 * 		resizable		{boolean} 다이얼로그 Rect 부분에 크기 조정 가능 여부 default : true
		 * 		appId			{String} 다이얼로그 헤더 이름
		 */
		DialogKit.prototype.open = function(app, appid, width, height, handler, initValue, prop) {
			if (initValue == null) {
				initValue = {};
			}
			
			//윈도우 최소 창크기보다 작은 경우... 윈도우 사이즈에 맞게 사이즈 조정
			var windowWidth = (window.innerWidth | document.body.clientWidth)-10;
			var windowHeight = (window.innerHeight | document.body.clientHeight)-45;
			if(windowWidth < width) width = windowWidth;
			if(windowHeight < height) height = windowHeight;
			
		
			
			var dialogProp = {
				width : Number(width) + 10,
				height : Number(height) + 45,
				headerVisible : (prop && prop.headerVisible != undefined) ? prop.headerVisible : true,
				headerMovable : (prop && prop.headerMovable != undefined) ? prop.headerMovable : true,
				headerClose : (prop && prop.headerClose != undefined) ? prop.headerClose : true,
				resizable : (prop && prop.resizable != undefined) ? prop.resizable : false,
				appId : (prop && prop.appId != undefined) ? prop.appId : null
			};
			
			if(prop != null && prop.left) { dialogProp.left = prop.left; }
			if(prop != null && prop.top) { dialogProp.top = prop.top; }
		
			// App에서 Dialog
			app.getRootAppInstance().openDialog(appid, dialogProp, function(/* cpr.controls.Dialog */dialog) {
				dialog.app.isPopup = true;
				dialog.app.modal = true;
				dialog._originWidth = dialogProp["width"];
				dialog._originHeight = dialogProp["height"];
				
				
				initValue._dialogRef = dialog;
				if(dialogProp.appId){
					dialog.headerTitle = dialogProp.appId;
				}else{
					if (dialog.app.title) { 
					dialog.headerTitle = dialog.app.title;
					}
				}	
				
				if (handler) {
					dialog.addEventListenerOnce("close", handler);
				}
				if (initValue) {
					dialog.initValue = initValue;
				}
			});
		};
		
		/**
		 * 모달리스(Modaless) 팝업을 호출한다.
		 * <pre><code>
		 * Dialog.open(app, "app/cmn/CMN001", 700, 500, function(dialog){...}, {key1:"value1", key2:"value2"});
		 * </code></pre>
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#app} appid 팝업 화면 주소.
		 * @param {Number} width 팝업 창의 가로 사이즈.
		 * @param {Number} height 팝업 창의 세로 사이즈.
		 * @param {Function} handler 팝업이 닫힐 때 콜백함수(callback function).
		 * @param {Object} initValue (Optional) 초기 파라메터 key/value쌍으로 팝업창에 넘길 파라메터 JSON 데이터[ 예시)-{key1:"value1", key2:"value2"}]
		 * @param {Object} prop (Optional) 팝업 설정 속성
		 * 		left			{number}  다이얼로그의 x좌표 default : 가운데 위치
		 * 		top				{number}  다이얼로그의 y좌표 default : 가운데 위치
		 * 		headerVisible	{boolean} 다이얼로그 헤더를 보이기 여부 default : true
		 * 		headerMovable	{boolean} 다이얼로그 헤더를 통해 이동 가능 여부 default : true
		 * 		headerClose		{boolean} 다이얼로그 헤더 close 버튼 보이기 여부 default : true
		 * 		resizable		{boolean} 다이얼로그 Rect 부분에 크기 조정 가능 여부 default : true
		 */
		DialogKit.prototype.openModaless = function(app, appid, width, height, handler, initValue, prop) {
			if (initValue == null) {
				initValue = {};
			}
			
			var dialogProp = {
				width : width,
				height : height,
				modal: false,
				headerVisible : (prop && prop.headerVisible) ? prop.headerVisible : true,
				headerMovable : (prop && prop.headerMovable) ? prop.headerMovable : true,
				headerMax : (prop && prop.headerMax) ? prop.headerMax : true,
				headerClose : (prop && prop.headerClose) ? prop.headerClose : true,
				resizable : (prop && prop.resizable) ? prop.resizable : true
			};
			
			if(prop != null && prop.left) { dialogProp.left = prop.left; }
			if(prop != null && prop.top) { dialogProp.top = prop.top; }
		
			app.getRootAppInstance().openDialog(appid, dialogProp, function(/* cpr.controls.Dialog */dialog) {
				dialog.app.isPopup = true;
				dialog.app.modal = false;
				if (dialog.app.title) { 
					dialog.headerTitle = dialog.app.title;
				}
				if (handler) {
					dialog.addEventListenerOnce("close", handler);
				}
				if (initValue) {
					dialog.initValue = initValue;
				}
			});
		};
		
		/**
		 * 현재 앱이 팝업인지 여부를 반환한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @return {Boolean} true/false
		 */
		DialogKit.prototype.isPopup = function(app){
			return (!ValueUtil.isNull(app.getHost()) && app.app.isPopup === true) ? true : false;
		};
		
		
		/**
		 * window open
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {String} psUrl 팝업 URL
		 * @param {String} psPopId 팝업 ID
		 * @param {Number} width
		 * @param {Number} height
		 * @param {Number} top
		 * @param {Number} left
		 * @param {Boolean} isModal
		 * @return {Boolean} true/false
		 */
		DialogKit.prototype.windowOpen = function(app, psUrl,psPopId, width, height, top, left, isModal){
		
		   var vnWidth     = width == null ? window.screen.availWidth : width;
		   var vnHeight     = height == null ? window.screen.availHeight : height;
		   var vnTop       = top == null ? (window.screen.availHeight - height) / 2 : top;
		   var vnLeft      = left == null ? (window.screen.availWidth - width) / 2 : left;
		   var initValue   = {}
		    if (vnTop < 0)  vnTop  = 0;
		    if (vnLeft < 0) vnLeft = 0;
		   var vbIsModal = isModal == null ? false : isModal;
		   var vsProp = "menubar=0,resizable=yes,scrollbars=yes,status=0,top="+vnTop+",left="+vnLeft+",width="+vnWidth+",height="+vnHeight;
		
		   if(vbIsModal){
		   		var modalCtrl = new cpr.controls.Container("windowPopModal");
		   		modalCtrl.style.css({
		   			width: app.getRootAppInstance().getActualRect().width + "px",
		   			height: app.getRootAppInstance().getActualRect().height + "px",
		   			left: "0px",
		   			top: "0px",
		   			"background-color": "rgba(0,0,0,0.5)"
		   		});
		   		modalCtrl.addEventListener("click", function(e){
		   			e.preventDefault();
		   		});
		   		modalCtrl.addEventListener("contextmenu", function(e){
		   			e.preventDefault();
		   		});
		   		app.getRootAppInstance().floatControl(modalCtrl);
		   }
		
		   var openWindow = window.open("indexPopup.html?app=" + psUrl , psPopId, vsProp);
		   openWindow.onbeforeunload = function(e){
		   	 if(vbIsModal){
		   	 	if(app.getRootAppInstance().lookup("windowPopModal")){
		   	 		app.getRootAppInstance().lookup("windowPopModal").dispose();
		   	 	}
		   	 }
		   }
		   window._app = app;
		   return openWindow;
		
		//	if(openWindow){
		//		openWindow.parentApp = app;
		//	}
		////   openWindow.addEventListener("load", function(e){
		//		openWindow.parentApp = app;
		//	});
		};
		
		
		/**
		 * Group컨트롤 유틸
		 * @constructor
		 * @param {common.AppKit} appKit
		 */
		function GroupKit(appKit){
			this._appKit = appKit;
		};
		
		/**
		 * 조회조건 및 작업영역 그룹 컨트롤 초기화 
		 * context-properties의 use.searchbox.clear 여부에 따라
		 *  - 조회영역내 조회조건 컨트롤의 selection-change, value-change시 작업영역(paDisableCtl) disable 및 그리드, 프리폼 초기화
		 *  - appHeader에서 공통 적용됨
		 *  - 사이트별 Customizing 필요
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Container} psSearchBoxId  		 조회조건 영역 그룹컨트롤ID
		 * @param {#Container | Array} paDisableCtl	 조회조건 변경시 비활성화 처리되는 영역의 그룹 컨트롤ID
		 * @param {#uicontrol | Array} paExceptCtl  (Optional) 적용 제외 컨트롤 ID
		 * @return void
		 */
		GroupKit.prototype.initSearchBox = function(app, psSearchBoxId, paDisableCtl, paExceptCtl){
			//비활성화 영역 컨트롤
			paDisableCtl = paDisableCtl != null ? paDisableCtl : new Array();
			if(!(paDisableCtl instanceof Array)){
				paDisableCtl = [paDisableCtl];
			}
			//적용 제외 컨트롤 목록
			paExceptCtl = paExceptCtl != null ? paExceptCtl : new Array();
			if(!(paExceptCtl instanceof Array)){
				paExceptCtl = [paExceptCtl];
			}
			
			var _app = app;
			var _appKit = this._appKit;
			var vsSchBtnId = "btnSearch";
			
			paDisableCtl.forEach(function(item){
				var ctrl = _app.lookup(item);
				if(ctrl) ctrl.style.addClass("data-box");
			});
			
			function doAddSearchBoxEvent(ctrl){
				//조회버튼인 경우
				if ( ctrl.type == "button" && (ctrl.id && ctrl.id.match("btnSearch") || ctrl.value == "조회")){
					
					paExceptCtl.push(ctrl.id);
					vsSchBtnId = ctrl.id;
					ctrl.addEventListener("click", function(/* cpr.events.CEvent */ e){
						if(e.defaultPrevented === false){
							doShadowView(_app, true);					
						}
					});
				}else{
					if(ctrl.type == "button" || ctrl.type == "output" || ctrl.type == "img" || ctrl.visible === false || ctrl.readOnly === true) return;
					//년도는 중앙정렬
					if(ctrl.type == "numbereditor"){
						if(ctrl.spinButton != false && ctrl.style.css("text-align") == ""){
							ctrl.style.css({"text-align":"center"});
						}
						if((ctrl.format === "0000" || ctrl.format === "9999") && ctrl.max == 0){
							ctrl.max = 1.7976931348623157E308;
						}
					}
		
					/**
					 * 변경사항이 있는 경우
					 * 계속진행을 하시겠습니까? 에서 취소 선택시 업무단 value-change 이벤트가 호출되지 않게 하기 위해
					 * before 이벤드를 추가함.
					 */			
					var bfEventType = (ctrl.type == "combobox" || ctrl.type == "radiobutton") ? "before-selection-change" : "before-value-change";
					ctrl.addEventListener(bfEventType, function(e){
						if(_appKit.isAppModified(_app, "CRM")){
							return false;
						}else{
							return true;
						}
						return true;
					});
					var dmAppConfig = app.getRootAppInstance().callAppMethod("getAppConfig");
				    var vbUseSearchBoxClear = dmAppConfig.getValue("useSearchBoxClear") === "N" ? false : true;
				
					if(vbUseSearchBoxClear){
						var eventType = (ctrl.type == "combobox" || ctrl.type == "radiobutton") ? "selection-change" : "value-change";
						ctrl.addEventListener(eventType, function(e){
							
							//화면내의 모든 데이터 Clear
							var dataSets = _appKit.getAllAppModifiedDataSet(_app);
							if(dataSets != null && dataSets.length > 0){
								dataSets.forEach(function(ds){
									ds.clear();
								});
							}
							
							//헤더 내의 버튼 비활성화
							var header = _appKit.Group.getAllChildrenByType(_app, "udc.com.appHeader");
							if(header != null && header.length > 0){
								var vcCtrl = header[0].getEmbeddedAppInstance().lookup("grpButtons");
								if(vcCtrl) vcCtrl.enabled = false;
							}
							
							doShadowView(_app, false);
						});
					}
					
					//인풋박스 컨트롤 Keydown 이벤트 추가
					if(ctrl.type == "inputbox" && ctrl.userAttr("autoKeydownSearch") == "Y"){
						ctrl.addEventListener("keydown", function(/* cpr.events.CKeyboardEvent */ e){
							if(e.keyCode == cpr.events.KeyCode.ENTER){
								//Enter키 입력시, 조회 버튼 클릭 이벤트 발생
								_appKit.Control.dispatchEvent(app, vsSchBtnId, "click");
								//_appKit.Header.dispatchEvent(app, "btnSearch", "click");
								var comBtnSch = _appKit.Group.getAllChildrenByType(app, "udc.com.comBtnSearch");
								if(comBtnSch != null && comBtnSch.length > 0){
									var vcCtrl = comBtnSch[0].getEmbeddedAppInstance().lookup(vsSchBtnId);
									if(vcCtrl){
										vcCtrl.dispatchEvent(new cpr.events.CEvent("click"));
									}
								}
							}
						});
					}
				}
			}
			
			function doShadowView(app, pbEnable){
				if(paDisableCtl.length > 0){
		//			setTimeout(function(){
						_appKit.Control.setEnable(app, pbEnable, paDisableCtl);
		//			}, 50);
				}else{
					if(pbEnable === false){
						if(_app.lookup("grpSchShell") == null){
							var disableCtl = new cpr.controls.Container("grpSchShell");
							disableCtl.style.css({"background-color":"#ededed", "opacity":"0.2"});
							disableCtl.setLayout(new cpr.controls.layouts.XYLayout());
							/** @type cpr.controls.Container */
							var vcSearchBox = _app.lookup(psSearchBoxId);
							var heightPosix = vcSearchBox.getActualRect()["height"];
							_app.getContainer().addChild(disableCtl, {
										"top": (Number(heightPosix)+35)+"px",
										"right": "5px",
										"bottom": "5px",
										"left": "5px"
									});
						}
					}else{
						_app.getContainer().removeChild(_app.lookup("grpSchShell"), true);
					}
				}
			}
			
			var initFocus = false;
			function doFocusCtrl(poCtrl){
				if(poCtrl.type == "button" || poCtrl.type == "output" || poCtrl.type == "img") return;
				
				if(!initFocus){
					poCtrl.focus();
					initFocus = true;
				}
			}
			var vaSearchBoxIds = ValueUtil.split(psSearchBoxId, ",");
			for(var z=0, zlen=vaSearchBoxIds.length; z<zlen; z++){
				/** @type cpr.controls.Container */
				var vcSearchBox = app.lookup(vaSearchBoxIds[z]);
				if(vcSearchBox){
					var childCtrls = _appKit._getChildren(vcSearchBox);
					for (var i=0, len=childCtrls.length; i<len; i++) {
						//udc컨트롤일 경우.
						if(childCtrls[i] instanceof cpr.controls.UDCBase){
							var embApp = childCtrls[i].getEmbeddedAppInstance();
							embApp.getContainer().getChildren().some(function(ctrl){
								if(ctrl instanceof cpr.controls.Container){
									ctrl.getChildren().some(function(subCtrl){
										doAddSearchBoxEvent(subCtrl);
										doFocusCtrl(subCtrl);
									});
								}else{
									doAddSearchBoxEvent(ctrl);
									doFocusCtrl(ctrl);
								}
							});
						}else{
							//이벤트 추가
							doAddSearchBoxEvent(childCtrls[i]);
							//포커싱
							doFocusCtrl(childCtrls[i]);
						}
					}
					
					doShadowView(_app, false);
				}
			}
		};
		
		/**
		 * 해당 그룹 컴포넌트 내의 DataColumn에 바인딩된 컨트롤 객체를 반환한다.
		 * 이는 프리폼 내의 DataColumn의 값을 갖는(바인딩) 컨트롤을 찾기 위해 사용된다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Container} psGrpId 그룹ID
		 * @param {String} psDataColumnNm datacolumn 명
		 * @return {Object} 컨트롤 객체
		 */
		GroupKit.prototype.getDataBindedControl = function(app, psGrpId, psDataColumnNm){
			/** @type cpr.controls.Container */
			var _grpKit = this._appKit.Group;
			var vcFrf = app.lookup(psGrpId);
			var vaChild = vcFrf.getChildren();
			var vcBindCtrl = null;
			vaChild.some(function(ctrl, idx){
				if(vcBindCtrl) return true;
				
				if(ctrl.type == "container") vcBindCtrl = _grpKit.getDataBindedControl(app, ctrl.id, psDataColumnNm);
				if(ctrl.type == "output") return false;
				var bind = ctrl.getBindInfo("value");
				if(bind && bind.type == "datacolumn" && psDataColumnNm === bind.columnName){
					if(ctrl instanceof cpr.controls.UDCBase){
						vcBindCtrl = AppUtil.getUDCBindValueControl(ctrl);
					}else{
						vcBindCtrl = ctrl;
					}
					
					return true;
				}
			});
			
			return vcBindCtrl;
		};
		
		/**
		 * 그룹 또는 컨테이너 내의 특정 타입에 해당하는 자식 컨트롤을 취득한다.
		 * (사용처) 해당 화면내의 특정 유형의 컨트롤 목록을 얻고자 하는 경우에 사용
		 * @param {cpr.core.AppInstance} 	app 앱인스턴스
		 * @param {String} psCtlType		 컨트롤 타입(ex: grid)
		 * @param {cpr.controls.Container} poContainer 		자식 컨트롤을 취득하고자 하는 부모 컨테이너 객체  	
		 * @param {Boolean} pbRecursive		(Optional) 자식 컨테이너를 Recusive하게 찾을건지 여부
		 * @return {Array} 자식 컨트롤 객체 배열
		 */
		GroupKit.prototype.getAllChildrenByType = function(app, psCtlType, poContainer, pbRecursive) {
			var vaTypesChild = new Array();
			
			var container = app.getContainer();
			function getChildRecursive(psCtlType, poContainer){
			    var vaChildCtrls = poContainer ? (pbRecursive === true ? poContainer.getAllRecursiveChildren() : poContainer.getChildren()) : (pbRecursive === true ? container.getAllRecursiveChildren() : container.getChildren());
			    for (var i=0, len=vaChildCtrls.length; i<len; i++) {
			        if (vaChildCtrls[i].type == psCtlType) {
			        	vaTypesChild.push(vaChildCtrls[i]);
			        }else if (vaChildCtrls[i] instanceof cpr.controls.Container ) {
			        	getChildRecursive(psCtlType, vaChildCtrls[i]);
			        }else if(vaChildCtrls[i] instanceof cpr.controls.UDCBase){
			        	var voUdcApp = vaChildCtrls[i].getEmbeddedAppInstance();
			        	if(voUdcApp) getChildRecursive(psCtlType, voUdcApp.getContainer());
			        }else if(vaChildCtrls[i] instanceof cpr.controls.EmbeddedApp){
			        	var voEmbApp = vaChildCtrls[i].getEmbeddedAppInstance();
			        	if(voEmbApp) getChildRecursive(psCtlType, voEmbApp.getContainer());
			        }
			    }
			    vaChildCtrls = null;
			}
			
			getChildRecursive(psCtlType, poContainer);
			
			return vaTypesChild;
		};
		
		/**
		 * 그룹 또는 컨테이너 내의 특정 ID를 갖는 자식 컨트롤을 취득한다.
		 * @param {cpr.core.AppInstance} 	app 앱인스턴스
		 * @param {Array} paCtrlIds		 컨트롤 ID 배열
		 * @param {cpr.controls.Container} poContainer 	(Optional) 자식 컨트롤을 취득하고자 하는 부모 컨테이너 객체  	
		 * @return {Array} 자식 컨트롤 객체 배열
		 */
		GroupKit.prototype.getControlByID = function(app, paCtrlIds, poContainer) {
			if(!(paCtrlIds instanceof Array)){
				paCtrlIds = [paCtrlIds];
			}
			var vaChildCtrls = new Array();
			var container = poContainer ? poContainer : this._appKit.getMainApp(app).getContainer();
			function getChildRecursive(paCtrlIds, poContainer){
			    var childCtrls = poContainer.getAllRecursiveChildren();
			    for (var i=0, len=childCtrls.length; i<len; i++) {
			        if (paCtrlIds.indexOf(childCtrls[i].id) != -1) {
			        	vaChildCtrls.push(childCtrls[i]);
			        }else if(childCtrls[i] instanceof cpr.controls.UDCBase){
			        	var voUdcApp = childCtrls[i].getEmbeddedAppInstance();
			        	if(voUdcApp) getChildRecursive(paCtrlIds, voUdcApp.getContainer());
			        }else if(childCtrls[i] instanceof cpr.controls.EmbeddedApp){
			        	var voEmbApp = childCtrls[i].getEmbeddedAppInstance();
			        	if(voEmbApp) getChildRecursive(paCtrlIds, voEmbApp.getContainer());
			        }
			    }
			}
			
			getChildRecursive(paCtrlIds, container);
			
			return vaChildCtrls;
		};
		
		/**
		 * 그룹 컨트롤에 바인딩된 데이터셋을 반환한다.
		 * @param {cpr.core.AppInstance} 	app 앱인스턴스
		 * @param {cpr.controls.Container} poContainer 		자식 컨트롤을 취득하고자 하는 부모 컨테이너 객체 
		 * @return {cpr.data.DataSet} 바인딩된 데이터셋 객체
		 */
		GroupKit.prototype.getBindDataSet = function(app, poContainer){
			/**@type cpr.data.DataSet */
			var voDataSet = null;
			/** @type cpr.bind.BindContext */
			var voBindContext = this.getBindContext(app, poContainer);
			if(voBindContext instanceof cpr.bind.GridSelectionContext){
				voDataSet = voBindContext.grid.dataSet;
			}else if(voBindContext instanceof cpr.bind.DataRowContext){
				voDataSet = voBindContext.dataSet;
			}
			
			return voDataSet;
		};
		
		/**
		 * 그룹 컨트롤의 바인딩 문맥(Context) 객체를 반환한다.
		 * @param {cpr.core.AppInstance} 	app 앱인스턴스
		 * @param {cpr.controls.Container} poContainer 		자식 컨트롤을 취득하고자 하는 부모 컨테이너 객체 
		 * @return {cpr.bind.BindContext} 바인딩 Context 객체
		 */
		GroupKit.prototype.getBindContext = function(app, poContainer){
			/** @type cpr.bind.BindContext */
			var voBindContext = poContainer.getBindContext();
			if(voBindContext == null || voBindContext == undefined){
				var vaChildCtrls = this.getAllChildrenByType(app, "container", poContainer);
				vaChildCtrls.forEach(function(/* Object */ ctrl){
					if(ctrl.getBindContext()){
						voBindContext = ctrl.getBindContext();
						return true;
					}
				});
			}
			
			return voBindContext;
		};
		
		/**
		 * FreeForm컨트롤 유틸
		 * - 일반적으로 그리드가 바인딩되었거나 데이터셋을 사용하는 폼레이아웃 컨트롤에 적용.
		 * - 그리드 + 상세(폼레이아웃) 화면에서 주로 사용 
		 * @constructor
		 * @param {common.AppKit} appKit
		 */
		function FreeFormKit(appKit){
			this._appKit = appKit;
		};
		
		/**
		 * 프리폼 컨트롤들에 대해 초기화 로직을 수행한다.
		 *  - appHeader에서 공통 적용됨
		 *  - 사이트별 Customizing 필요
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Container | Array} paFreeFormId 프리폼 ID 또는 ID배열
		 */
		FreeFormKit.prototype.init = function(app, paFreeFormId) {
			if(!(paFreeFormId instanceof Array)){
				paFreeFormId = [paFreeFormId];
			}
			var vcForm = null, voBindContext = null, voDs = null;
			var voMap = new cpr.utils.ObjectMap();
			var voBindMap = new cpr.utils.ObjectMap();
			for(var i=0, len=paFreeFormId.length; i<len; i++){
				/**@type cpr.controls.Container */
				vcForm = app.lookup(paFreeFormId[i]);
				if(vcForm == null) continue;
				
				voBindContext = this._appKit.Group.getBindContext(app, vcForm);
				voDs = voBindContext.grid ? voBindContext.grid.dataSet : voBindContext.dataSet;
				vcForm._originEnabled = vcForm.enabled;
				if(vcForm.getBindInfo("enabled") != null){
					vcForm._expressEnabled = vcForm.getBindInfo("enabled").expression;
				}
				if(voDs._freeforms){
					voDs._freeforms.push(vcForm.id);
				}else{
					voDs._freeforms = [vcForm.id];
				}
				var childCtrls = vcForm.getAllRecursiveChildren();
				childCtrls.forEach(function(ctrl){
					if(ctrl.type == "numbereditor"){
						if(ctrl.spinButton != false && ctrl.style.css("text-align") == ""){
							ctrl.style.css({"text-align":"center"});
						}
						if((ctrl.format === "0000" || ctrl.format === "9999") && ctrl.max == 0){
							ctrl.max = 1.7976931348623157E308;
						}
					}
				});
				
				if(voMap.get(voDs.id) == null){
					voMap.put(voDs.id, voDs);
				}
				
				if(voBindContext.grid == null && voBindMap.get(voDs.id) == null){
					voBindMap.put(voDs.id, voDs);
					voDs.stateRestore = true; //현재값과 Origin이 같으면... 변경없도록 처리
				}
			}
			
			var _app = app, _appKit = this._appKit;
			voMap.keys().forEach(function(key){
				voMap.get(key).addEventListener("load", function(/* cpr.events.CDataEvent */e){
					var dataset = e.control;
					var freeforms = dataset._freeforms;
					freeforms.forEach(function(/* eachType */ formId){
						/**@type cpr.controls.Container */
						var form = _app.lookup(formId);
						//데이터가 없으면... 프리폼 비활성화
						if(dataset.getRowCount() < 1) {
							if(form._expressEnabled){
								form.unbind("enabled");
							}
							form.enabled = false;
						}else{
							//데이터가 있으면 있고, 조회권한이 아니고... 프리폼 활성화
							if(!_appKit.Auth.isReadUseAuth(_app) && form._originEnabled !== false){
								if(form._expressEnabled){
									form.bind("enabled").toExpression(form._expressEnabled);
								}else{
									form.enabled = true;
								}
							}
						}
					});
				});
				
				voMap.get(key).addEventListener("filter", function(/* cpr.events.CDataEvent */e){
					var dataset = e.control;
					var freeforms = dataset._freeforms;
					freeforms.forEach(function(/* eachType */ formId){
						/**@type cpr.controls.Container */
						var form = _app.lookup(formId);
						//데이터가 없으면... 프리폼 비활성화
						if(dataset.getRowCount() < 1) {
							if(form._expressEnabled){
								form.unbind("enabled");
							}
							form.enabled = false;
						}else{
							//데이터가 있으면 있고, 조회권한이 아니고... 프리폼 활성화
							if(!_appKit.Auth.isReadUseAuth(_app) && form._originEnabled !== false){
								if(form._expressEnabled){
									form.bind("enabled").toExpression(form._expressEnabled);
								}else{
									form.enabled = true;
								}
							}
						}
					});
				});
			});
		};
		
		
		/**
		 * 프리폼에 신규 행(Row)을 추가한다.
		 *  - 사이트별 Customizing 필요
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Container} psFreeFormId 프리폼 ID
		 * @param {String} psEditCol 신규 이후 포커스 COLUMN명
		 * @param {Number} pnRowIdx (Optional) 추가하고자 하는 Row index
		 *                 defalut : 현재 선택된 로우 이후
		 * @param {Object} poRowData (Optional) 추가할 row data. (key: header명, value: value 를 갖는 json data)
		 * @return {cpr.controls.provider.GridRow} 추가한 Row의 GridRow 객체.
		 */
		FreeFormKit.prototype.insertRow = function(app, psFreeFormId, psEditCol, pnRowIdx, poRowData) {
			/**@type cpr.controls.Container */
			var vcForm = app.lookup(psFreeFormId);
			var voBindContext = this._appKit.Group.getBindContext(app, vcForm);
			
			var vcGrid = voBindContext.grid;
			var voDs = voBindContext.grid ? voBindContext.grid.dataSet : voBindContext.dataSet;
			/**@type cpr.controls.Tree */
			var vcTree = app.lookup(voDs._treeId);
			var rowIndex = -1;
			if(!ValueUtil.isNull(pnRowIdx)){
				rowIndex = pnRowIdx;
			}else{
				if(vcTree){
					rowIndex = vcTree.getIndex(vcTree.getSelectionFirst());
				}else{
					rowIndex = voBindContext.grid ? this._appKit.Grid.getIndex(app, voBindContext.grid.id) : voBindContext.rowIndex;
				}
			}
			
			// InsertRow
			var insertedRow = null;
			if(poRowData != null){
				insertedRow = voDs.insertRowData(rowIndex, true, poRowData);
			}else{
				if(vcTree){
					// 트리
					var vsSelVal = ValueUtil.fixNull(vcTree.value);
					
					var voRow = {};
					voRow[vcTree.itemSetConfig.label] = "";
					voRow[vcTree.itemSetConfig.value] = "";
					voRow[vcTree.itemSetConfig.parentValue] = vsSelVal;
					
					insertedRow = voDs.insertRowData(rowIndex, true, voRow);
					if(vsSelVal != ""){
						var voItem = vcTree.getItemByValue(vsSelVal);
						vcTree.expandItem(voItem);
					}
				}else{
					insertedRow = voDs.insertRow(rowIndex, true);
				}
			}
			
			// SelectRow
			if(vcTree){
				// 트리
				vcTree.selectItemByValue("DEFAULT", true);
				vcTree.focusItem(vcTree.getItem(insertedRow.getIndex()));
			}else if(vcGrid){
				// 그리드
		//		vcGrid.selectRows(-1, false);
				vcGrid.clearSelection();
				vcGrid.selectRows(insertedRow.getIndex(), true);
			}else{
				// 프리폼
				vcForm.redraw();
			}
			if(vcForm._expressEnabled){
				vcForm.bind("enabled").toExpression(vcForm._expressEnabled);
			}else{
				vcForm.enabled = true;
			}
			
			// Focus
			if(psEditCol){
				var vcCtrl = this._appKit.Group.getDataBindedControl(app, vcForm.id, psEditCol);
				if(vcCtrl) vcCtrl.focus();
			}
			
			return insertedRow;
		};
		
		/**
		 * 프리폼에 행(Row)을 삭제한다.
		 * - 사이트별 Customizing 필요
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Container} psFreeFormId 프리폼 ID
		 * @param {String} psAftMsg (Optional) 메시지 유형(CRM)
		 * @return void
		 */
		FreeFormKit.prototype.deleteRow = function(app, psFreeFormId, psAftMsg) {
			var _this = this;
			/** @type cpr.controls.Container */
			var vcForm = app.lookup(psFreeFormId);
			/** @type cpr.bind.BindContext */
			var voBindContext = this._appKit.Group.getBindContext(app, vcForm);
			
			var voDs = voBindContext.grid ? voBindContext.grid.dataSet : voBindContext.dataSet;
			var vcGrid = voBindContext.grid;
			/** @type cpr.controls.Tree */
			var vcTree = app.lookup(voDs._treeId);
			var rowIndex = voBindContext.grid ? this._appKit.Grid.getIndex(app, voBindContext.grid.id) : voBindContext.rowIndex;
			if(voDs == null || voDs.getRowCount() < 1){
				//삭제할 데이터가 없습니다.
				this._appKit.Msg.alert("INF-M007");
			}else{
				if(!ValueUtil.isNull(psAftMsg)){
					//삭제하시겠습니까?
					if(this._appKit.Msg.confirm("CRM-M002")){
						if(voDs.getRowState(rowIndex) == cpr.data.tabledata.RowState.INSERTED){
							
							voDs.revertRow(rowIndex);
							vcForm.redraw();
							if(vcGrid){
								vcGrid.redraw();
								//가장 마지막 행에서 신규 행 추가 후, 삭제할 경우에 가장 마지막 행을 선택해줌
								if(voDs.getRowCount() -1 < rowIndex){
									vcGrid.selectRows([rowIndex-1]);
								}
							} 
							if(vcTree) vcTree.redraw();
							//데이터 건수가 없으면... 프리폼 비활성화
							if(voDs.getRowCount() < 1) {
								vcForm.enabled = false;
							}
							return false;
						}else{
							voDs.setRowState(rowIndex, cpr.data.tabledata.RowState.DELETED);
							return true;
						}
					}
				}else{
					if(voDs.getRowState(rowIndex) == cpr.data.tabledata.RowState.INSERTED){
						voDs.revertRow(rowIndex);
					}else{
						voDs.setRowState(rowIndex, cpr.data.tabledata.RowState.DELETED);
					}
				}
			}
			
			return false;
		};
		
		/**
		 * 프리폼에 바인딩된 값을 반환한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Container} psFreeFormId 프리폼 ID
		 * @param {String} psColumnName 컬럼명
		 * @return {String} 프리폼의 컬럼값
		 */
		FreeFormKit.prototype.getValue = function(app, psFreeFormId, psColumnName){
			/** @type cpr.controls.Container */
			var vcForm = app.lookup(psFreeFormId);
			var voBindContext = this._appKit.Group.getBindContext(app, vcForm);
			/** @type cpr.data.DataSet */
			var voDs = voBindContext.grid ? voBindContext.grid.dataSet : voBindContext.dataSet;
			var rowIndex = voBindContext.grid ? this._appKit.Grid.getIndex(app, voBindContext.grid.id) : voBindContext.rowIndex;
			
			return voDs.getValue(rowIndex, psColumnName);
		};
		
		/**
		 * 프리폼에 바인딩된 값을 변경한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Container} psFreeFormId 프리폼 ID
		 * @param {String} psColumnName 컬럼명
		 * @param {String} psValue 변경하고자 하는 값.
		 * @return void
		 */
		FreeFormKit.prototype.setValue = function(app, psFreeFormId, psColumnName, psValue){
			/** @type cpr.controls.Container */
			var vcForm = app.lookup(psFreeFormId);
			var voBindContext = this._appKit.Group.getBindContext(app, vcForm);
			/** @type cpr.data.DataSet */
			var voDs = voBindContext.grid ? voBindContext.grid.dataSet : voBindContext.dataSet;
			var rowIndex = voBindContext.grid ? this._appKit.Grid.getIndex(app, voBindContext.grid.id) : voBindContext.rowIndex;
			
			voDs.setValue(rowIndex, psColumnName, psValue);
			vcForm.redraw();
		};
		
		/**
		 * 프리폼 내의 특정 컬럼을 포커싱한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Container} psFreeFormId 프리폼 ID
		 * @param {String} psColumnName 포커싱할 컬럼명
		 */
		FreeFormKit.prototype.setFocus = function(app, psFreeFormId, psColumnName){
			/** @type cpr.controls.Container */
			var vcForm = app.lookup(psFreeFormId);
			
			var vcCtrl = this._appKit.Group.getDataBindedControl(app, vcForm.id, psColumnName);
			if(vcCtrl) this._appKit.Control.setFocus(app, vcCtrl.id);
		};
		
		/**
		 * 프리폼의 변경사항을 되돌린다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Container} psFreeFormId  해당 그룹 아이디
		 * @param {Number} pnRowIndex (Optional) 되돌릴 행의 index
		 * @param {String} psEditCol (Optional) 포커싱할 컬럼명
		 * @return void
		 */
		FreeFormKit.prototype.revertRow = function(app, psFreeFormId, pnRowIndex, psEditCol){
			/**@type cpr.controls.Container */
			var vcForm = app.lookup(psFreeFormId);
			var voBindContext = this._appKit.Group.getBindContext(app, vcForm);
			/**@type cpr.controls.Grid */
			var vcGrid = voBindContext.grid;
			/**@type cpr.data.DataSet */
			var voDs = voBindContext.grid ? voBindContext.grid.dataSet : voBindContext.dataSet;
			
			var vnRowIndex = 0;
			if(!ValueUtil.isNull(pnRowIndex)){
				vnRowIndex = pnRowIndex;
			}else{
				if(vcGrid) vnRowIndex = this._appKit.Grid.getIndex(vcGrid.getAppInstance(), vcGrid.id);
				else vnRowIndex = voBindContext.rowIndex;
			}
			//데이터 Revert
			var rowData = voDs.getRow(vnRowIndex).getRowData();
			var vsGridRowState = vcGrid.getRowState(vnRowIndex);
			for(var column in rowData){
				voDs.setValue(vnRowIndex, column, voDs.getOriginalValue(vnRowIndex, column));
			}
			//2019.11.21 추가
			if(vsGridRowState == cpr.data.tabledata.RowState.INSERTED){
				vcGrid.setRowState(vnRowIndex, vsGridRowState);
			}
			var vcTree = app.lookup(voDs._treeId); 
				
			if(vcGrid) vcGrid.redraw();
			if(vcTree) vcTree.redraw();
			vcForm.redraw();
			
			if(!ValueUtil.isNull(psEditCol)){
				var vcCtrl = this._appKit.Group.getDataBindedControl(app, vcForm.id, psEditCol);
				if(vcCtrl) vcCtrl.focus();
			}
		};
		
		/**
		 * 프리폼의 변경사항을 되돌린다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Container} psFreeFormId  해당 그룹 아이디
		 * @return void
		 */
		FreeFormKit.prototype.revertAllData = function(app, psFreeFormId){
			/**@type cpr.controls.Container */
			var vcForm = app.lookup(psFreeFormId);
			var voBindContext = this._appKit.Group.getBindContext(app, vcForm);
			
			var vcGrid = voBindContext.grid;
			var voDs = voBindContext.grid ? voBindContext.grid.dataSet : voBindContext.dataSet;
			var vcTree = app.lookup(voDs._treeId); 
			
			//데이터 Revert
			voDs.revert();
				
			if(vcGrid) vcGrid.redraw();
			if(vcTree) vcTree.redraw();
			vcForm.redraw();
		};
		
		/**
		 * 그룹의 변경사항 유/무를 반환를 반환한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Container | Array} paFreeFormId 프리폼 ID
		 * @param {String} psAftMsg  (Optional) MSG or CRM
		 *						MSG : 변경사항 내역이 없을 경우 '변경된 내역이 없습니다.' 메세지 출력
		 *  					CRM : 변경내역이 존재할경우 '변경사항이 반영되지 않았습니다. 계속 하시겠습니까?' confirm 메시지출력 
		 * @return {Boolean} 데이터 변경 여부
		 */
		FreeFormKit.prototype.isModified = function(app, paFreeFormId, psAftMsg){
			if(!(paFreeFormId instanceof Array)){
				paFreeFormId = [paFreeFormId];
			}
			
			psAftMsg = psAftMsg == null ? "" : psAftMsg;
			
			var modify = false;
			var vcGroup = null;
			for (var i=0, len=paFreeFormId.length; i<len; i++) {
				if(paFreeFormId[i] instanceof cpr.controls.Container) {
					vcGroup = paFreeFormId[i];
				}else{
					vcGroup = app.lookup(paFreeFormId[i]);
				}
				
				var voDataSet = this._appKit.Group.getBindDataSet(app, vcGroup);
				if(voDataSet != null && voDataSet.isModified()) {
					modify = true;
					break;
				}
			}
			
			if(modify){
				if(psAftMsg.toUpperCase() == "CRM"){//변경사항이 반영되지 않았습니다. 계속 하시겠습니까? confirm
					if(!this._appKit.Msg.confirm("CRM-M003", [vcGroup.fieldLabel])) return true;
					else return false;
				}
			}else{
				if(psAftMsg.toUpperCase() == "MSG"){//변경된 내역이 없습니다.
					this._appKit.Msg.notify(app, "INF-M006");
				}
			}
			
			return modify;
		};
		
		
		/**
		 * 리스트 형태 컨트롤 유틸
		 * @constructor
		 * @param {common.AppKit} appKit
		 */
		function SelectKit(appKit){
			this._appKit = appKit;
		};
		
		/**
		 * 입력한 index의 위치에 새로운 item을 추가한다.
		 * <pre><code>
		 * SelectCtl.addItem(app, "cmb1", "라벨1", "값1");
		 * </code></pre>
		 * @param {cpr.core.AppInstance} 	app 앱인스턴스
		 * @param {#uicontrol} psCtlId		 select ID (only Combo, List, Radio, CheckBox Group)
		 * @param {String} psLabel		 추가할 item의 label
		 * @param {String} psValue		 추가할 item의 value
		 * @param {Number} pnIndex (Optional) 추가할 item의 index (default는 마지막 행 뒤에 추가됨)
		 * @return void 
		 */
		SelectKit.prototype.addItem = function(app, psCtlId, psLabel, psValue, pnIndex){
			/** @type cpr.controls.ComboBox */
			var vcCtl = app.lookup(psCtlId);
			var item;
			
			if(ValueUtil.isNull(pnIndex)){
				vcCtl.addItem(new cpr.controls.Item(psLabel, psValue));
			}else{
				if(pnIndex >= 0 && pnIndex <= vcCtl.getItemCount()){
					if(pnIndex == 0){
						item = vcCtl.getItem(pnIndex);
						vcCtl.insertItemBefore(new cpr.controls.Item(psLabel, psValue), item);
					} else {
						item = vcCtl.getItem(pnIndex - 1);
						vcCtl.insertItemAfter(new cpr.controls.Item(psLabel, psValue), item);
					}
				}
			}
		};
		
		/**
		 * 지정한 인덱스(Index)의 아이템 라벨(label)을 반환한다.
		 * multiple "true"의 경우 index에 해당하는 여러 라벨값을 알고자 할 때, pnIndex는 구분자를 기준으로 조인된 String 형태를 가진다.
		 * <pre><code>
		 * SelectCtl.getItemLabel(app, "cmb1", "1,2,3");
		 * </code></pre>
		 * @param {cpr.core.AppInstance} 	app 앱인스턴스
		 * @param {#uicontrol} psCtlId		컨트롤ID
		 * @param {Number} pnIndex		(Optional) 인덱스 번호
		 * @return {String | Array}	multiple : true 일 경우 Array(String)
		 * 										   false 일 경우 String
		 */
		SelectKit.prototype.getItemLabel = function(app, psCtlId, pnIndex){
			var vcCtl = app.lookup(psCtlId);
			if(ValueUtil.isNull(pnIndex)){
				var item = vcCtl.getSelectionFirst();
				return item ? item.label : "";
			}else{
				if(vcCtl.multiple){//다중 선택 가능한 경우 라벨 배열 반환
					var vaIdx = ValueUtil.split(pnIndex, ",");
					for(var i=0, len=vaIdx.length; i<len; i++){
						vaIdx[i] = vcCtl.getItem(vaIdx[i]).label;
					}
					return vaIdx;
				}else{
					return vcCtl.getItem(pnIndex).label;
				}
			}
		};
		
		/**
		 * 지정한 인덱스(Index)의 아이템 값(value)을 반환한다.
		 * multiple "true"의 경우 index에 해당하는 여러 value 값을 알고자 할 때, pnIndex는 구분자를 기준으로 조인된 String 형태를 가진다.
		 * <pre><code>
		 * SelectCtl.getItemValue(app, "cmb1", "1,2,3");
		 * </code></pre>
		 * @param {cpr.core.AppInstance} 	app 앱인스턴스
		 * @param {#uicontrol} psCtlId		컨트롤ID
		 * @param {Number} pnIndex		(Optional) 인덱스 번호
		 * @return {String | Array}	multiple : true 일 경우 Array(String)
		 * 										   false 일 경우 String
		 */
		SelectKit.prototype.getItemValue = function(app, psCtlId, pnIndex){
			/**@type cpr.controls.ComboBox */
			var vcCtl = app.lookup(psCtlId);
			if(ValueUtil.isNull(pnIndex)){
				var item = vcCtl.getSelectionFirst();
				return item ? item.value : "";
			}else{
				if(vcCtl.multiple){//다중 선택 가능한 경우 값 배열 반환
					var vaIdx = ValueUtil.split(pnIndex, ",");
					for(var i=0, len=vaIdx.length; i<len; i++){
						vaIdx[i] = vcCtl.getItem(vaIdx[i]).value;
					}
					return vaIdx;
				}else{
					return vcCtl.getItem(pnIndex).value;
				}
			}
		};
		
		/**
		 * 현재 선택 중인 아이템의 index를 반환한다.
		 * multiple "true"의 경우, index는 배열의 형태로 반환된다.
		 * @param {cpr.core.AppInstance} 	app 앱인스턴스
		 * @param {#uicontrol} psCtlId		select ID
		 * @return {Number | Array}	multiple : true 일 경우 Array(Number)
		 * 										   false 일 경우 Number 		
		 */
		SelectKit.prototype.getSelectedIndex = function(app, psCtlId){
			/** @type cpr.controls.ComboBox */
			var vcCtl = app.lookup(psCtlId);
			var vaItems = vcCtl.getSelection();
			if(vcCtl.multiple){
				var vaIndices = new Array();
				for(var i=0, len=vaItems.length; i<len; i++){
					vaIndices.push(vcCtl.getIndex(vaItems[i]));
				}
				return vaIndices;
			}else{
				return vcCtl.getIndex(vaItems[0]);
			}
		};
		
		/**
		 * 인덱스(Index) 또는 value에 해당하는 아이템(Item)을 선택한다.
		 * multiple "true"의 경우 여러 개의 아이템을 선택하고자 할 때, puRowIdx는 구분자를 기준으로 조인된 String 형태를 가진다.
		 * <pre><code>
		 * SelectCtl.selectItem(app, "cmb1", "0");
		 * 또는
		 * SelectCtl.selectItem(app, "cmb1", "값1,값2,값3");
		 * </code></pre>
		 * @param {cpr.core.AppInstance} 	app 앱인스턴스
		 * @param {#uicontrol} psCtlId		컨트롤ID
		 * @param {String | Array} puRowIdx 인덱스 또는 value 값
		 * @param {Boolean} emitEvent (Optional) 이벤트(before-selection-change, selection-change)를 발생시킬지 여부
		 * @return {Boolean} 성공여부
		 */
		SelectKit.prototype.selectItem = function(app, psCtlId, puRowIdx, emitEvent){
			/**@type cpr.controls.CheckBoxGroup */
			var vcCtl = app.lookup(psCtlId);
			
			if(vcCtl == null || vcCtl == undefined) return false;
			
			puRowIdx = ValueUtil.split(puRowIdx, ",");
			if(vcCtl.multiple){//다중 선택 가능한 경우
				if(puRowIdx.length > 0){
					if(!ValueUtil.isNumber(puRowIdx[0])){
						for(var i=0, len=puRowIdx.length; i<len; i++){
							puRowIdx[i] = vcCtl.getIndexByValue(puRowIdx[i]);
						}
					}
					vcCtl.selectItems(puRowIdx, emitEvent);
				}
			}else{
				if(puRowIdx.length > 0){
					if(!ValueUtil.isNumber(puRowIdx[0])){
						var item = vcCtl.getItemByValue(puRowIdx[0]);
						if(item) vcCtl.selectItemByValue(puRowIdx[0], emitEvent);
						else vcCtl.selectItem(0, emitEvent);
					} else {
						if(Number(puRowIdx[0]) >= vcCtl.getItemCount()){
							vcCtl.selectItem(0, emitEvent);
						}else{
							vcCtl.selectItem(puRowIdx[0], emitEvent);
						}
					}
				}
			}
			
			return true;
		};
		
		/**
		 * 모든 아이템을 선택한다.
		 * @param {cpr.core.AppInstance} 	app 앱인스턴스
		 * @param {#uicontrol} psCtlId	컨트롤ID
		 * @return void
		 */
		SelectKit.prototype.selectAllItem = function(app, psCtlId){
			/** @type cpr.controls.ComboBox */
			var vcCtl = app.lookup(psCtlId);
			var indices = new Array();
			for(var i=0, len=vcCtl.getItemCount(); i<len; i++){
				indices.push(i);
			}
			vcCtl.selectItems(indices);
		};
		
		/**
		 * 콤보박스의 값을 Reset한다.
		 * @param {cpr.core.AppInstance} 	app 앱인스턴스
		 * @param {#uicontrol} psCtlId	컨트롤ID
		 */
		SelectKit.prototype.reset = function(app, psCtlId){
			/** @type cpr.controls.ComboBox */
			var vcCtl = app.lookup(psCtlId);
			if(vcCtl.dataSet){
				vcCtl.dataSet.clear();
			}
			vcCtl.value = "";
		};
		
		
		/**
		 * 필터링 할 컬럼명(psFilterColumnName)은 데이터셋의 컬럼명을 작성한다.
		 * 그리드에서 사용 금지.
		 * @desc 두 개의 List형 컨트롤이 종속 관계를 가질 때, 종속되는 컨트롤의 데이터를 필터링하기 위한 메소드
		 * @param {#uicontrol} psMainId				 메인 컨트롤 ID
		 * @param {#uicontrol} psSubId				 적용될 컨트롤 ID
		 * @param {String} psFilterColumnName	 적용될 컨트롤의 필터링 할 컬럼명
		 * @param {Number} pbFirstItemSelect	(Optional)  첫번째 아이템 선택 여부  default : true (선택)
		 * @return void
		 */
		SelectKit.prototype.cascadeList = function(app, psMainId, psSubId, psFilterColumnName, pbFirstItemSelect){
			var voMainCtl = app.lookup(psMainId);
			var voSubCtl = app.lookup(psSubId);
		
			if(voMainCtl == null || voSubCtl == null){
				return;
			}
			pbFirstItemSelect = pbFirstItemSelect == null ? true : pbFirstItemSelect;
		
			var vaItems = voMainCtl.getSelection();
			var vsValue = "";
			if(vaItems.length > 0){
				vsValue = vaItems[0].value;
			}
		
			voSubCtl.clearFilter();
		
			var voFirstItem = voSubCtl.getItem(0);
			var vsFirstItemValue = voFirstItem.value;
			var vsFirstItemLable = voFirstItem.label;
		
			//'전체' 아이템 여부
			var vbAllStatus = false;
			//var vsGlsAll = cpr.I18N.INSTANCE.message("UI-GLS-ALL");
			var vsGlsAll = "전체";
		
			if( vsGlsAll ==  vsFirstItemLable && ( ValueUtil.isNull(vsFirstItemValue) || vsFirstItemValue.indexOf("%") != -1)){
				vbAllStatus = true;
			}
		
			//전체아이템이 포함됐을 경우
			if(vbAllStatus)	{
				var vsFilter = psFilterColumnName + "== '" + vsValue + "' || ( label == '" +  vsGlsAll + "' && (value == '' || value == '%'))";
				voSubCtl.setFilter(vsFilter);
				if(pbFirstItemSelect)
					this.selectItem(app, psSubId, 0);
			}else{
				voSubCtl.setFilter(psFilterColumnName + "== '" + vsValue + "'");
				if(pbFirstItemSelect){
					var vaSubCtlItems = voSubCtl.getItems();
					if(vaSubCtlItems.length > 0){
						this.selectItem(app, psSubId, vaSubCtlItems[0].value);
					}else{
						this.selectItem(app, psSubId, 0);
					}
				}
		
			}
		
			voSubCtl.redraw();
		};
		/**
		 * 트리(Tree) 컨트롤 유틸
		 * @constructor
		 * @param {common.AppKit} appKit
		 */
		function TreeKit(appKit){
			this._appKit = appKit;
		};
		
		/**
		 * 트리 컨트롤 초기화.
		 *  - 사이트별 Customizing 필요
		 * @param {any} app
		 * @param {#Tree} paTreeId
		 */
		TreeKit.prototype.init = function(app, paTreeId){
			if(!(paTreeId instanceof Array)){
				paTreeId = [paTreeId];
			}
			
			var _app = app;
			var _appKit = this._appKit;
			for (var i=0, len=paTreeId.length; i <len; i++) {
				/**
				 * @type cpr.controls.Tree
				 */
				var vcTree = (paTreeId[i] instanceof cpr.controls.Tree) ? paTreeId[i] : _app.lookup(paTreeId[i]);
				if(vcTree == null) continue;
				
				var vcDataSet =  vcTree.dataSet;
				vcDataSet._treeId = vcTree.id;
				var vsDataBindCtxId = vcTree.userAttr("bindDataFormId");
				
				if(vsDataBindCtxId != null && vsDataBindCtxId != ""){
					vcTree.addEventListener("selection-change", function(e){
						/**
						 * @type cpr.controls.Tree
						 */
						var tree = e.control;
						var voSelectedItem = e.newSelection[0];
						var itemIdx = tree.getIndex(voSelectedItem);
						var voContext = new cpr.bind.DataRowContext(vcDataSet, itemIdx);
						
						var freeformes = ValueUtil.split(vsDataBindCtxId, ",");
						freeformes.forEach(function(/* eachType */ formId){
							var vcGrp = _app.lookup(formId);
							vcGrp.setBindContext(voContext);
							
							vcGrp.redraw();
						});
					});
					
					//그룹 PK컬럼 enable 설정
					var vaPkColumnNames = ValueUtil.split(vcDataSet.info, ",");
					vaPkColumnNames.some(function(value, idx){
						if(value == "") return false;
						
						//프리폼 PK 컬럼 취득 
						if(!ValueUtil.isNull(vsDataBindCtxId)){
							var freeformes = ValueUtil.split(vsDataBindCtxId, ",");
							freeformes.forEach(function(/* eachType */ formId){
								/**@type cpr.controls.Container */
								var freeform = _app.lookup(formId);
								var vaChildCtrls = freeform.getChildren();
								vaChildCtrls.some(function(ctrl, idx){
									if(ctrl.type == "output") return false;
									if(ctrl.userAttr("ignorePk") == "Y") return false;
									
									var bind = ctrl.getBindInfo("value");
									if(bind && bind.type == "datacolumn" && value == bind.columnName){
										ctrl.bind("enabled").toExpression("getStateString() == 'I' ? true : false");
										ctrl.userAttr("required", "Y");
										ctrl.style.setClasses("require");
									}
								});
							});
						}
					});
					
					//마지막 작업행 findRow
					vcDataSet.addEventListener("update", function(/* cpr.events.CDataEvent */e){
						/** 
						 * @type cpr.data.DataSet
						 */
						var dataset = e.control;
						var rowIndex = e.row.getIndex();
						var vaPkColumns = ValueUtil.split(dataset.info, ",");
						if(vaPkColumns.length < 1){
							dataset._findTreeCondition = null;
						}else{
							var vaTempCond = [];
							vaPkColumns.forEach(function(column){
								vaTempCond.push(dataset.getValue(rowIndex, column));
							});
							
							if(vaTempCond.length > 0){
								dataset._findTreeCondition = vaTempCond.join("");
							}else{
								dataset._findTreeCondition = null;
							}
						}
					});
					
					//트리에 바인딩된 데이터셋(Dataset)이 로드될 때 처리
					//마지막행 찾기, 조회 건수 업데이트
					vcDataSet.addEventListener("load", function(/* cpr.events.CDataEvent */e){
						/** @type cpr.data.DataSet */
						var dataset = e.control;
						/** @type cpr.controls.Tree */
						var tree = _app.lookup(dataset._treeId);
						if(tree == null) return;
						
						//대상 그리드가 정렬된 상태라면... 정렬을 푼다.
						if(dataset.getSort() != ""){
							dataset.clearSort();
						}
						
						//마지막 작업행 찾기
						if(dataset.getRowCount() > 0) {
							if(dataset._findTreeCondition){
								var voRow = dataset.findFirstRow(tree.itemSetConfig.value + "=='" + dataset._findTreeCondition + "'");
								var vnIdx = voRow.getIndex();
								tree.selectItem(vnIdx);
								tree.focusItem(tree.getItem(vnIdx));
							}else{
								tree.selectItem(0, true);
							}
						}
						
						//마지막 작업행 정보 Clear
						dataset._findTreeCondition = null;
						
					});
				}
			}
		}
		
		/**
		 * 현재 선택된 아이템의 value를 반환한다.
		 * @param {cpr.core.AppInstance} 		app 앱인스턴스
		 * @param {#Tree} psTreeId	 트리 Id
		 * @param {String} psDiv (Optional) 얻어올 값 영역(label 또는 value)
		 * @return {String | Array}  multiple 속성이 true 일 경우 Array(String)
		 *                                      false 일 경우 String  
		 */
		TreeKit.prototype.getSelectedValue = function(app, psTreeId, psDiv){
			/** @type cpr.controls.Tree */
			var vcTree = app.lookup(psTreeId);
			var vaItem = vcTree.getSelection();
			//아이템이 없으면... 공백 반환
			if(vaItem.length < 1) return "";
			
			psDiv = (psDiv != null ? psDiv.toUpperCase() : "VALUE");
			if(vcTree.multiple){
				var vaValues = new Array();
				vaItem.forEach(function(vcItem){
					if(psDiv == "LABEL")
						vaValues.push(vcItem.label);
					else
						vaValues.push(vcItem.value);
				});
				return vaValues;
			}else{
				return psDiv == "LABEL" ? vaItem[0].label : vaItem[0].value;
			}
		};
		
		
		/**
		 * @desc 입력한 value에 해당하는 아이템의 label 또는 parentValue를 반환한다.
		 * @param {cpr.core.AppInstance} app
		 * @param {#Tree} psTreeId	트리 id
		 * @param {String} psValue	search value
		 * @param {String} psDiv	가지고 오는 구분자 값(LABEL(디폴트), PVALUE);
		 * @return {String}
		 */
		TreeKit.prototype.getItem = function(app, psTreeId, psValue, psDiv){
			var vcTree = app.lookup(psTreeId);
			if(!!psDiv) psDiv = psDiv.toUpperCase();
			psDiv = !!psDiv ? psDiv : "LABEL";
		
			try {
				var vaItem = vcTree.getSelection();
				if(!psValue && vcItem > 0) psValue = vaItem[0].value;
			} catch(e){
				return null;
			}
		
			var voItem = vcTree.getItemByValue(psValue);
		
			if(!voItem) return null;
		
			if(psDiv == "LABEL"){
				return voItem.label;
			} else {
				return voItem.parentValue;
			}
		};
		
		/**
		 * 해당 아이템의 상위 아이템을 펼친다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Tree} psTreeId		 트리 Id
		 * @param {Object} poItem		 item
		 * @param {Boolean} pbHierarchy (Optional) 계층적으로 모든 상위까지 펼칠지 여부(false인 경우, 바로 상위의 부모 아이템만 펼친다.)
		 * @return void
		 */
		TreeKit.prototype.expandParentItem = function(app, psTreeId, poItem, pbHierarchy){
			/** @type cpr.controls.Tree */
			var vcTree = app.lookup(psTreeId);
			var vaParentItem = new Array();
			
			pbHierarchy == !!pbHierarchy ? pbHierarchy : true;
			
			function checkExpandItem(poPItem){
				var item = vcTree.getItemByValue(poPItem.parentValue);
				if(item != null && item.value != "" && !vcTree.isExpanded(item)){
					vaParentItem.push(item);
					checkExpandItem(item);
				}
			}
			if(pbHierarchy){
				checkExpandItem(poItem);
			}else{
				vaParentItem.push(poItem);
			}
			
			for(var i=0, len=vaParentItem.length; i<len; i++){
				vcTree.expandItem(vaParentItem[i]);
			}
		};
		
		/**
		 * 트리 선택 아이템 변경 이벤트 발생시, 변경 이전에 선택된 아이템을 선택해준다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {cpr.events.CSelectionEvent} event 트리 선택 아이템 변경 이벤트
		 * @param {Boolean} emitEvent (Optional) 이벤트(before-selection-change, selection-change)를 발생시킬지 여부
		 * @return void
		 */
		TreeKit.prototype.selectBeforeRow = function(app, event, emitEvent) {
			/** @type cpr.controls.Tree */
			var vcTree = event.control;
			var emit = emitEvent === true ? true : false;
			
			var voOldSelection = event.oldSelection[0];
			var vsOldVal = voOldSelection.value;
			vcTree.selectItemByValue(vsOldVal, emit);
			vcTree.focusItem(voOldSelection);
		};
		
		/**
		 * @desc 입력한 label 또는 value에 해당하는 트리 아이템을 선택한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Tree} psTreeId	 트리 id
		 * @param {String} psValue	 search value
		 * @param {String} psDiv	 가지고 오는 구분자 값(VALUE(디폴트), LABEL)
		 * @return void
		 */
		TreeKit.prototype.selectItem = function(app, psTreeId, psValue, psDiv){
			var vcTree = app.lookup(psTreeId);
		
			if(!!psDiv) psDiv = psDiv.toUpperCase();
			psDiv = !!psDiv ? psDiv : "VALUE";
		
			if(psDiv == "VALUE"){
				vcTree.selectItemByValue(psValue);
			} else {
				vcTree.selectItemByLabel(psValue);
			}
		};
		
		/**
		 * @desc 아이템에 해당하는 모든 child item을 펼치거나 닫습니다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Tree} psTreeId		트리 Id
		 * @param {Boolean} pbExpand	펴기 : true, 닫기 : false
		 * @param {Object} poItem		item 생략가능 default 최상위 item
		 * @return void
		 */
		TreeKit.prototype.expandAllItems = function(app, psTreeId, pbExpand, poItem){
			var vcTree = app.lookup(psTreeId);
		
			if(!!poItem){
				if(pbExpand){
					vcTree.expandItem(poItem);
					vcTree.expandAllItems(poItem);
				} else {
					vcTree.collapseItem(poItem);
					vcTree.collapseAllItems(poItem);
				}
			} else {
				pbExpand ? vcTree.expandAllItems() : vcTree.collapseAllItems();
			}
		
		};
		
		/**
		 * @desc 현재 드래그 중인 선택 항목을 표시하는 컨트롤을 띄운다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {cpr.events.CMouseEvent} event 마우스 이벤트
		 * @param {String} psText	 라벨에 표시할 텍스트
		 * @param {String} psWidth	(optional) 라벨 width
		 * @param {String} psHeight	(optional) 라벨 height
		 * @return void
		 */
		TreeKit.prototype.draggingLabel = function(app, event, psText, psWidth, psHeight){
		
			var dragMessage = new cpr.controls.Output("rowmessage");
			dragMessage.style.css({
				"position": "absolute",
				"box-shadow": "0px 2px 2px 0px rgba(0, 0, 0, .3)",
				width: psWidth?psWidth:"268px",
				height: psHeight?psHeight:"50px",
				border: "solid 1px",
				backgroundColor: "#FFF"
			});
		
			dragMessage.value = /*임시로 적어둠*/"현재 드래그하고 있는 대상 : " + psText;
		
			var dataDragManager = cpr.core.Module.require("module/dataDragManager");
		
			dataDragManager.dragStart(dragMessage, app, event);
		};
		
		/**
		 * 탭(TabFolder) 컨트롤 유틸
		 * @constructor
		 * @param {common.AppKit} appKit
		 */
		function TabKit(appKit){
			this._appKit = appKit;
		};
		
		/**
		 * 현재 선택된 탭아이템 id를 반환한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#TabFolder} psTabId 탭 Id
		 * @return {Number} 탭아이템 id (탭아이템id는 인덱스와 유사 탭아이템 순서대로 id 부여됨)
		 */
		TabKit.prototype.getSelectedId = function(app, psTabId){
			/** @type cpr.controls.TabFolder */
			var vcTab = app.lookup(psTabId);
			var vcTabItem = vcTab.getSelectedTabItem();
			
			return vcTabItem ? vcTabItem.id : "";
		};
		
		/**
		 * 입력한 id에 해당하는 탭 아이템을 선택한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#TabFolder} psTabId 탭Id
		 * @param {Number} pnIndex 탭아이템 id
		 * @param {Boolean} emitEvent (Optional) 이벤트(before-selection-change, selection-change)를 발생시킬지 여부
		 */
		TabKit.prototype.setSelectedTabItemById = function(app, psTabId, pnIndex, emitEvent){
			/** @type cpr.controls.TabFolder */
			var vcTab = app.lookup(psTabId);
			
			var vaTabItem = vcTab.getTabItems();
			var vcTabItem = vaTabItem.filter(function(item){
				return item.id == pnIndex;
			});
			
			var emit = emitEvent != undefined ? emitEvent : true;
			vcTab.setSelectedTabItem(vcTabItem[0], emit);
		};
		
		/**
		 * 탭 페이지를 숨기거나/보여준다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#TabFolder} psTabId 탭Id
		 * @param {Number} pnIndex 탭아이템 id
		 * @param {Boolean} pbVisible - 숨김여주(true/false)
		 */
		TabKit.prototype.setVisibleTabItem = function(app, psTabId, pnIndex, pbVisible){
			/** @type cpr.controls.TabFolder */
			var vcTab = app.lookup(psTabId);
			
			var vaTabItem = vcTab.getTabItems();
			var vcTabItem = vaTabItem.filter(function(item){
				return item.id == pnIndex;
			});
			
			if(vcTabItem){
				vcTabItem[0].visible = pbVisible;
			}
		};
		
		/**
		 * 탭 페이지 버튼을 활성화시키거나 비활성화 시킨다
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#TabFolder} psTabId 탭Id
		 * @param {String || Array} paIndex - 활성화/비활성화 할 탭 Index 또는 Index 배열 (탭 index 시작 = 1) 
		 * @param {Boolean} psEnable - 활성화여부(true/false)
		 */
		TabKit.prototype.setEnableTabItem = function(app, psTabId, paIndex, psEnable){
			/* 2019-05-13 ssb 작성 */
			/** @type cpr.controls.TabFolder */
			var vcTab = app.lookup(psTabId);
			
			if(!(paIndex instanceof Array)){
				paIndex = [paIndex];
			}
			var vaTabItem = vcTab.getTabItems();
			
			for (var i=0, len=paIndex.length; i<len; i++) {
				var vnTabIdx  = paIndex[i] - 1;
				vaTabItem[vnTabIdx].enabled = psEnable;
			}
		};
		
		
		
		/**
		 * Embeded앱 컨트롤 유틸
		 * @constructor
		 * @param {common.AppKit} appKit
		 */
		function EmbeddedAppKit(appKit){
			this._appKit = appKit;
		};
		
		/**
		 * Embeded 앱내의 함수를 호출한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#EmbeddedApp} psEmbeddedappId 임베디드 앱 ID
		 * @param {String} psFuncName 호출 함수명
		 * @param {String | Array} paArgs 함수에 전달할 아규먼트 
		 * @return {any} method 내 파라미터.
		 */
		EmbeddedAppKit.prototype.callAppMethod = function(app, psEmbeddedappId, psFuncName, paArgs){
			/** @type cpr.controls.EmbeddedApp */
			var vcEmbed = app.lookup(psEmbeddedappId);
			var vsValue = null;
			if(vcEmbed){
				vcEmbed.ready(function(e){
					vsValue = e.callAppMethod(psFuncName, paArgs);
				});
			}
			return vsValue;
		};
		
		
		/**
		 * 메인 MDI 컨트롤 유틸
		 * @constructor
		 * @param {common.AppKit} appKit
		 */
		function MDIKit(appKit){
			this._appKit = appKit;
		};
		
		/**
		 * close 메인 MDI의 탭으로 화면을 오픈한다.
		 * - Root App에 해당 함수 필요
		 * - 사이트별 Customizing 필요
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#MDIFolder} psMenuId 메뉴ID
		 */
		MDIKit.prototype.open = function(app, psMenuId, poParam){
			app.getRootAppInstance().callAppMethod("doOpenMenuToMdi", psMenuId, poParam);
		};
		
		/**
		 * close 메인 MDI의 화면을 닫는다.
		 * - 사이트별 Customizing 필요
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 */
		MDIKit.prototype.close = function(app){
			var vsMenuId = this._appKit.Auth.getMenuInfo(app, "MENU_ID");
			app.getRootAppInstance().callAppMethod("doCloseMdiTab", vsMenuId);
		};
		
		/**
		 * 일반 컨트롤 유틸
		 * @constructor
		 * @param {common.AppKit} appKit
		 */
		function ControlKit(appKit){
			this._appKit = appKit;
		};
		
		/**
		 * 지정한 컨트롤의 Visible 속성을 설정한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {Boolean} pbVisible 컨트롤 숨김 여부(true/false)
		 * @param {#uicontrol | Array} paCtlId 컨트롤 아이디 또는 아이디 배열
		 * @return void
		 */
		ControlKit.prototype.setVisible = function(app, pbVisible, paCtlId) {
			if(!(paCtlId instanceof Array)){
				paCtlId = [paCtlId];
			}
			if (typeof (pbVisible) != "boolean") {
				pbVisible = ValueUtil.fixBoolean(pbVisible);
			}
			for (var i=0, len=paCtlId.length; i<len; i++) {
				app.lookup(paCtlId[i]).visible = pbVisible;
			}
		};
		
		/**
		 * 지정한 컨트롤의 Enable 속성을 설정한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {Boolean} pbEnable 컨트롤 활성화 여부(true/false)
		 * @param {#uicontrol | Array} paCtlId 컨트롤 아이디 또는 아이디 배열
		 * @return void
		 */
		ControlKit.prototype.setEnable = function(app, pbEnable, paCtlId) {
			if(!(paCtlId instanceof Array)){
				paCtlId = [paCtlId];
			}
			if (typeof (pbEnable) != "boolean") {
				pbEnable = ValueUtil.fixBoolean(pbEnable);
			}
			var ctrl;
			for (var i=0, len=paCtlId.length; i<len; i++) {	
				ctrl = app.lookup(paCtlId[i]);
				if(ctrl) ctrl.enabled = pbEnable;
			}
		};
		
		/**
		 * 지정한 컨트롤의 ReadOnly 속성을 설정한다.
		 * 만약, 해당 컨트롤에 readonly이 없을경우 enable 속성으로 제어된다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {Boolean} 		pbReadOnly  컨트롤 readOnly 여부(true/false)
		 * @param {#uicontrol | Array} paCtlId 컨트롤 아이디 또는 아이디 배열
		 * @return void
		 */
		ControlKit.prototype.setReadOnly = function(app, pbReadOnly, paCtlId) {
			if(!(paCtlId instanceof Array)){
				paCtlId = [paCtlId];
			}
				
			for (var i=0, len=paCtlId.length; i<len; i++) {
				var voCtl = app.lookup(paCtlId[i]);
			  	if(voCtl == null || "undefined" == voCtl) continue;
				
				var vsCtlType = voCtl.type;
				if(voCtl.readOnly !== undefined){
					voCtl.readOnly = pbReadOnly;
				}else{
					this.setEnable(app, !pbReadOnly, paCtlId[i]);
				}
			}
		};
		
		/**
		 * 컨트롤의 지정된 사용자 정의 속성(userattr) 반환한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#uicontrol} psCtlId 	  컨트롤 아이디
		 * @param {String} psAttrName  속성
		 * @return {String} 속성값
		 */
		ControlKit.prototype.getUserAttr = function(app, psCtlId, psAttrName){
		   return app.lookup(psCtlId).userAttr(psAttrName);
		};
		
		/**
		 * 컨트롤의 지정된 사용자 정의 속성(userattr)의 값을 설정한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#uicontrol} psCtlId 	   컨트롤 아이디
		 * @param {String} psAttrName  속성
		 * @param {String} psAttrValue 속성값
		 * @return void
		 */
		ControlKit.prototype.setUserAttr = function(app, psCtlId, psAttrName, psAttrValue){
			var ctrl = app.lookup(psCtlId);
			var userAttr = ctrl.userAttr();
			userAttr[psAttrName] = psAttrValue;
		};
		
		/**
		 * 컨트롤를 포커스(focus) 한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#uicontrol} psCtlId 	   컨트롤 아이디
		 */
		ControlKit.prototype.setFocus = function(app, psCtlId){
			var ctrl = app.lookup(psCtlId);
			if(ctrl instanceof cpr.controls.UDCBase){
				var focused = false;
				var embApp = ctrl.getEmbeddedAppInstance();
				embApp.getContainer().getChildren().some(function(embCtrl){
					if(embCtrl.getBindInfo("value") && embCtrl.getBindInfo("value").property == "value"){
						embCtrl.focus();
						focused = true;
						return true;
					}
				});
				if(focused !== true){
					app.focus(ctrl);
				}
			}else{
				app.focus(ctrl);
			}
		}
		
		/**
		 * 컨트롤의 값을 초기화한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#uicontrol | Array} paCtlId  일반 컨트롤 및 그리드 컨트롤 아이디		
		 * @return void
		 */
		ControlKit.prototype.reset = function(app, paCtlId) {
			if(!(paCtlId instanceof Array)){
				paCtlId = [paCtlId];
			}
			var vcCtrl;
			for (var i=0, len=paCtlId.length; i<len; i++) {
				vcCtrl = app.lookup(paCtlId[i]);
				if(vcCtrl == null) continue;
				if(vcCtrl.type == "grid"){
					vcCtrl.dataSet.clear();
					//그리드 타이틀 영역의 데이터 건수 업데이트
					var titles = this._appKit.Group.getAllChildrenByType(app, "udc.com.comTitle");
					for(var j=0, jlen=titles.length; j<jlen; j++){
						if(titles[j] == null || titles[j].getAppProperty("ctrl") == null) continue;
						if(titles[j].getAppProperty("ctrl").id == vcCtrl.id){
							titles[j].setAppProperty("rowCount", vcCtrl.dataSet.getRowCount());
						}
					}
				}else if(vcCtrl.type == "container"){
					var voDs = this._appKit.Group.getBindDataSet(app, vcCtrl);
					if(voDs) voDs.clear();
					vcCtrl.redraw();
				}else{
					vcCtrl.value = "";
				}
			}
		};
		
		/**
		 * 특정 컨트롤의 자료를 갱신하고 다시 그린다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#uicontrol | Array} paCtlId 일반 컨트롤 및 그리드 컨트롤 아이디
		 * @return void
		 */
		ControlKit.prototype.redraw = function(app, paCtlId) {
			if(!(paCtlId instanceof Array)){
				paCtlId = [paCtlId];
			}
			for (var i=0, len=paCtlId.length; i<len; i++) {
				var vcCtrl = app.lookup(paCtlId[i]);
				if(vcCtrl) vcCtrl.redraw();
			}
		};
		
		/**
		 * 컨트롤의 지정된 style 속성 값을 가져옵니다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#uicontrol} psCtlId 컨트롤 아이디
		 * @param {String} psAttrName style 속성명
		 * @return {String} style 속성값
		 */
		ControlKit.prototype.getStyleAttr = function(app, psCtlId, psAttrName){
			/**@type cpr.controls.UIControl*/
			var vcCtrl = app.lookup(psCtlId);
			return vcCtrl.style.css(psAttrName);
		};
		
		/**
		 * 컨트롤의 지정된 style 속성값을 설정한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#uicontrol} psCtlId 컨트롤 아이디
		 * @param {String} psAttrName 속성
		 * @param {String} psAttrValue 속성값
		 * @return void
		 */
		ControlKit.prototype.setStyleAttr = function(app, psCtlId, psAttrName, psAttrValue){
			/**@type cpr.controls.UIControl*/
			var vcCtrl = app.lookup(psCtlId);
			return vcCtrl.style.css(psAttrName, psAttrValue);
		};
		
		/**
		 * 컨트롤이 실제 그려진 사이즈를 리턴합니다.
		 * 컨트롤이 화면에 그려지지 않은 상태인 경우는 모든 값이 0인 객체가 리턴됩니다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#uicontrol} psCtlId  컨트롤 아이디
		 * @param {String} psPosition 구하고자하는 위치 및 크기 정보(width, height, left, top, bottom, right)
		 * @return {Interface{width: Number, height: Number, left: Number, top: Number, bottom: Number, right: Number}} HTML DOM에서의 컨트롤의 위치 및 크기 정보
		 */
		ControlKit.prototype.getActualRectPosition = function(app, psCtlId, psPosition){
			/** @type cpr.controls.UIControl */
			var vcCtrl = app.lookup(psCtlId);
			var voActRec = vcCtrl.getActualRect();
			return voActRec[psType];
		};
		
		/**
		 * 해당 컨트롤의 제약 조건을 반환합니다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#uicontrol} psCtlId 반환하고자 하는 컨트롤.
		 * @param {String} psParentGrp (Optional) 상위 컨트롤
		 *                                        그룹내 컨트롤의 제약 조건을 구할시 사용
		 * @return {cpr.controls.layouts.Constraint} 해당하는 제약조건.
		 */
		ControlKit.prototype.getConsraint = function(app, psCtlId, psParentGrp){
			var vcCtrl = app.lookup(psCtlId);
			var voContainer;
			if(!ValueUtil.isNull(psParentGrp)){
				voContainer = app.lookup(psParentGrp);
			}else{
				voContainer = app.getContainer();		
			}
			var vcChild = app.lookup(psCtlId);
			
			return voContainer.getConstraint(vcChild);
		};
		
		/**
		 * 컨트롤의 지정된 제약 조건(constraint)을 변경합니다.
		 * 타겟 컨트롤에서 부모 컨트롤과의 연계된 위치를 변경합니다.
		 * parameter의 constraints가 포함한 항목만 변경합니다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#uicontrol} psCtlId 컨트롤의 아이디
		 * @param {#Container} psParentGrpId 상위 컨트롤의 아이디. app의 container일 경우 null
		 * @param {Object} poConstraint 제약조건
		 * 					상위 컨트롤의 레이아웃이 formlayout일 경우 rowIndex, colIndex 를 반드시 포함한 조건을 설정하여야합니다.
		 * @return {boolean} 성공여부
		 */
		ControlKit.prototype.updateConstraint = function(app, psCtlId, psParentGrpId, poConstraint){
		 	/** @type cpr.controls.UIControl */
		 	var vcChild = app.lookup(psCtlId);
		 	if(vcChild == null) return false;
		 	/** @type cpr.controls.Container */
		 	var voContainer = null;
		 	if(!ValueUtil.isNull(psParentGrpId)){
		 		voContainer = app.lookup(psParentGrpId);
		 	}else {
		 		voContainer = app.getContainer();
		 	}
		 	
		 	var voLayout = voContainer.getLayout();
		 	var voConstraint = null;
		 	if(voLayout instanceof cpr.controls.layouts.ResponsiveXYLayout){
		 		var voSrcConstraint = voContainer.getConstraint(vcChild)["positions"][0];
		 		voConstraint = {
		 			positions:[Object.assign(voSrcConstraint, poConstraint)]
		 		}
		 	}else {
		 		voConstraint = poConstraint;
		 	}
		 	
		 	return voContainer.updateConstraint(vcChild, voConstraint);
		};
		
		/**
		 * 해당 컨트롤의 이벤트를 발생시킨다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#uicontrol} psCtlId 컨트롤의 아이디
		 * @param {String} psEventType 이벤트명(ex-click)
		 */
		ControlKit.prototype.dispatchEvent = function(app, psCtlId, psEventType){
			var vcCtrl = app.lookup(psCtlId);
			if(vcCtrl){
				vcCtrl.dispatchEvent(new cpr.events.CEvent(psEventType));
			}
		};
		
		/**
		 * @desc 지정한 컨트롤의 value를 지정한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#uicontrol} psCtrlId 컨트롤 아이디
		 * @param {String} psValue value
		 * @return void
		 */
		ControlKit.prototype.setValue = function(app, psCtlId, psValue){
		   app.lookup(psCtlId).value = psValue;
		};
		
		/**
		 * @desc 지정한 컨트롤의 value를 취득한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#uicontrol} psCtlId 컨트롤 아이디
		 * @return void
		 */
		ControlKit.prototype.getValue = function(app, psCtlId){
		   return app.lookup(psCtlId).value;
		};
		
		/**
		 * @desc 지정한 컨트롤의 value를 취득한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#uicontrol} psCtlId 컨트롤 아이디
		 * @param {String} psProperty 컨트롤 속성명
		 * @return void
		 */
		ControlKit.prototype.getProperty = function(app, psCtlId, psProperty){
		   return app.lookup(psCtlId)[psProperty];
		};
		
		/**
		 * 데이터셋 컨트롤 유틸
		 * @constructor
		 * @param {common.AppKit} appKit
		 */
		function DataSetKit(appKit){
			this._appKit = appKit;
		};
		
		/**
		 * 데이터셋 또는 데이터맵에 컬럼(Column)을 추가합니다.
		 * Header정보 추가되며, data가 있는 경우 row data에도 해당 column data가 추가됩니다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {String} psDataSetId 데이터셋
		 * @param {String} psColumnNm 추가하려는 컬럼명
		 * @param {Object} psValue (Optional) 초기값 설정
		 * @param {String} psColumnType (Optional) 컬럼유형(string/number/decimal/expression)
		 * @return {Boolean} 컬럼 추가 성공 여부
		 */
		DataSetKit.prototype.addColumn = function(app, psDataSetId, psColumnNm, psValue, psColumnType){
			/** @type cpr.data.DataSet */
			var dataset = app.lookup(psDataSetId);
			
			var columnType = !ValueUtil.isNull(psColumnType) ? psColumnType.toLowerCase() : "string";
			return dataset.addColumn(new cpr.data.header.DataHeader(psColumnNm, columnType), psValue);
		};
		
		/**
		 * 데이터셋 특정 값을 가져오는 함수 입니다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#DataSet} psDataSetId 데이터셋
		 * @param {String} psCondition 특정 값을 가져올 조건
		 * @param {String} psColumnName 가져오려는 값의 컬럼명
		 * 
		 * ex) util.DataSet.getValue(app, "dsLttmRcd", "CD == '" + vsNewVal + "'", "CD_USG_01");
		 */
		DataSetKit.prototype.getValue = function(app, psDataSetId, psCondition, psColumnName){
			/** @type cpr.data.DataSet */
			var dataset = app.lookup(psDataSetId);
			
			var voRow = dataset.findFirstRow(psCondition);
			return voRow != null ? voRow.getValue(psColumnName) : "";
		};
		
		
		/**
		 * 입력 받은 rowIndex와 columnName에 해당되는 데이터를 수정합니다.<br>
		 * <br>
		 * 1. 상태변경<br>
		 * 해당 columnName에 해당되는 Column이 DisplayColumn이 아니고 Row상태가 UNCHANGED 상태인 경우
		 * Row 상태가 UPDATED로 바뀝니다.(UNCHANGED -> UPDATED)<br>
		 * DELEDED상태이거나 INSERTED상태인 row는 수정할 수 없습니다.<br>
		 * 2. 이벤트<br>
		 * 수정이 된 경우 <b>UPDATED 이벤트가 발생합니다.</b><br>
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#DataSet} psDataSetId Id
		 * @param {Number} pnRowIndex 수정할 row의 row index.
		 * @param {String} psColumnName 수정할 column의 columnName.
		 * @param {Object} psValue 수정할 value 값.
		 * @return {Boolean} 값 수정 성공 여부.
		 */
		DataSetKit.prototype.setValue = function(app, psDataSetId, pnRowIndex, psColumnName, psValue){
			/** @type cpr.data.DataSet */
			var vcDataSet =  app.lookup(psDataSetId);
			return vcDataSet.setValue(pnRowIndex, psColumnName, psValue);
		};
		
		/**
		 * 모든 데이터셋 정보를 제거합니다.<br>
		 * data, sort, filter가 제거됩니다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#DataSet} psDataSetId DataSet Id
		 */
		DataSetKit.prototype.clear = function(app, psDataSetId) {
			/** @type cpr.data.DataSet */
			var vcDataSet = app.lookup(psDataSetId);
			vcDataSet.clear();
		}
		
		/**
		 * 지정한 범위 내의 row들 중 조건에 맞는 모든 Row 객체의 배열을 반환
		 * 또는 지정한 범위 내의 row들 중 조건에 맞는 첫번째 Row 객체를 반환
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#DataSet} psDataSetId 데이터셋 ID
		 * @param {String} psCondition 조건식
		 *                 ex)"STUD_DIV_RCD == 'CT101REGU' && SA_NM == '컴퓨터정보과'"
		 * 					사용가능수식 !=", "!==", "$=", "%", "&&", "(", "*", "*=", "+", ",", "-", ".", "/", "/*", "//", "<", "<=", "==", "===", ">", ">=", "?", "[", "^=", "||"
		 * @param {Number} pbAllStatus (optional)
		 *                             true : 조건에 맞는 모든 row 리턴
		 *                             default : 조건에 맞는 첫번째 row 리턴
		 * @param {Number} pnStartIdx (optional) Number  범위지정 시작 row index.
		 * @param {Number} pnEndIdx (optional) Number  범위지정 끝 row index.
		 * @retrun 데이터 로우
		 */
		DataSetKit.prototype.findRow = function(app, psDataSetId, psCondition, pbAllStatus, pnStartIdx, pnEndIdx) {
			/** @type cpr.data.DataSet */
			var vcDataSet = app.lookup(psDataSetId);
		
			if(pbAllStatus){
				return vcDataSet.findAllRow(psCondition, pnStartIdx, pnEndIdx);
			}else{
				return vcDataSet.findFirstRow(psCondition, pnStartIdx, pnEndIdx);
			}
		};
		
		/**
		 * 지정한 범위 내의 row들 중 조건에 맞는 첫번째 Row 객체에 해당하는 컬럼의 value를 취득
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#DataSet} psDataSetId 데이터셋 ID
		 * @param {String} psCondition 조건식
		 *                 ex)"STUD_DIV_RCD == 'CT101REGU' && SA_NM == '컴퓨터정보과'"
		 * 					사용가능수식 !=", "!==", "$=", "%", "&&", "(", "*", "*=", "+", ",", "-", ".", "/", "/*", "//", "<", "<=", "==", "===", ">", ">=", "?", "[", "^=", "||"
		 * @param {String} psColumnName 컬럼명
		 * @param {Number} pnStartIdx (optional)  범위지정 시작 row index.
		 * @param {Number} pnEndIdx (optional)   범위지정 끝 row index.
		 * @retrun 데이터 로우
		 */
		DataSetKit.prototype.getFindRowValue = function(app, psDataSetId, psCondition, psColumnName, pnStartIdx, pnEndIdx) {
			var voRow = this.findRow(app, psDataSetId, psCondition, false, pnStartIdx, pnEndIdx);
			if(voRow != null){
				return voRow.getValue(psColumnName);
			}else{
				return null;
			}
		};
		
		/**
		 * 현재 Row 수를 반환
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#DataSet} psDataSetId 데이터셋 ID
		 * @retrun {Number} 로우 갯수
		 */
		DataSetKit.prototype.getRowCount = function(app, psDataSetId) {
			/** @type cpr.data.DataSet */
			var vcDataSet = app.lookup(psDataSetId);
			return vcDataSet.getRowCount();
		};
		
		/**
		 * 현재 데이터셋의 데이터를 타겟 데이터셋으로 복사합니다.<br>
		 * 타겟 데이터셋의 존재하는 컬럼의 데이터만 복사됩니다.<br>
		 * 복사시 추가되는 데이터는 INSERT 상태입니다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스 
		 * @param {#DataSet} psSourceDataSetId DataSet Id
		 * @param {#DataSet} psTargetDataSetId 복사 데이터가 들어갈 타겟 DataSet Id.
		 * @param {String} psFilterCondition (Optional) 복사시 필터링할 조건. (생략시 전체 복사, target의 기존 데이터는 삭제됨)
		 * 				   "STUD_DIV_RCD == 'CT101REGU' && SA_NM == '컴퓨터정보과'" (동일한 로우가 있을경우 복사안함)
		 * @return {Boolean}
		 */
		DataSetKit.prototype.copyToDataSet = function(app, psSourceDataSetId, psTargetDataSetId, psFilterCondition){
			var vcSourceDataSet = app.lookup(psSourceDataSetId);
			var vcTargetDataSet = app.lookup(psTargetDataSetId);
			if(!psFilterCondition) vcTargetDataSet.clear();
			else{
				var vaFindRow = vcTargetDataSet.findAllRow(psFilterCondition);
				if(vaFindRow != null && vaFindRow.length > 0){
					return;
				}
			}
			return vcSourceDataSet.copyToDataSet(vcTargetDataSet, psFilterCondition);
		};
		
		/**
		 * rowData를 입력받아 원하는 특정 row index의 앞이나 뒤에 신규 row를 추가합니다.<br>
		 * <b>INSERTED 이벤트가 발생합니다.</b>
		 * @param {Number} index 삽입하고자 하는 row index.
		 * @param {Boolean} after? 해당 row index의 뒤에 삽입할지 여부. (true:뒤 / false:앞)
		 * @param {cpr.data.RowConfigInfo} rowData? 추가할 row data. (key: header명, value: value 를 갖는 json data)<br>
		{[columnName: string]: string | number | boolean}
		 * @return {cpr.data.Row} 추가한 신규 Row 객체.
		 */
		/**
		 * 
		 * @param {cpr.core.AppInstance} app 앱인스턴스 
		 * @param {#DataSet} psDataSetId DataSet Id
		 * @param {Number} pnIndex index 삽입하고자 하는 row index
		 * @param {Boolean} pbAfter 해당 row index의 뒤에 삽입할지 여부. (true:뒤 / false:앞)
		 * @param {cpr.data.RowConfigInfo} poRowData 추가할 row data. (key: header명, value: value 를 갖는 json data)<br>
		{[columnName: string]: string | number | boolean}
		 */
		DataSetKit.prototype.insertRow = function(app, psDataSetId, pnIndex, pbAfter, poRowData){
			/** @type cpr.data.DataSet */
			var vcDataSet = app.lookup(psDataSetId);
			if(poRowData == null){
				return vcDataSet.insertRow(pnIndex, pbAfter);
			}else{
				return vcDataSet.insertRowData(pnIndex, pbAfter, poRowData);
			}
		
		};
		
		/**
		 * 데이터맵(DataMap) 데이터 컴포넌트 유틸
		 * @param {common.AppKit} appKit
		 */
		function DataMapKit(appKit){
			this._appKit = appKit;
		}
		
		/**
		 * 입력 받은 columnName에 해당되는 데이터를 반환
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#DataMap} psDataMapId 데이터맵 Id
		 * @param {String} psColumnName 값을 가져오고자 하는 컬럼명
		 * @return {Object} 해당 데이터
		 * 					header dataType에 따라 반환타입이 정해짐.
							해당 columnName의 column이 존재 할 경우 해당 값 반환
							해당 columnName의 값이 없을 경우 ""(공백) 반환
							해당 columnName이 존재하지 않을 경우 null 반환
		 */
		DataMapKit.prototype.getValue = function(app, psDataMapId, psColumnName){
			/** @type cpr.data.DataMap */
			var vcDataMap = app.lookup(psDataMapId);
			return vcDataMap.getValue(psColumnName);
		};
		
		/**
		 * 입력 받은 columnName에 해당되는 데이터를 수정
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#DataMap} psDataMapId 데이터맵 Id
		 * @param {String} psColumnName 값을 가져오고자 하는 컬럼명
		 * @param {String} value 수정할 value 값
		 * @return {Boolean} 값 수정 성공 여부
		 */
		DataMapKit.prototype.setValue = function(app, psDataMapId, psColumnName, psValue){
			/** @type cpr.data.DataMap */
			var vcDataMap = app.lookup(psDataMapId);
			return vcDataMap.setValue(psColumnName, psValue);
		};
		
		/**
		 * 데이터를 모두 제거합니다.
		 * (data가 모두 공백으로 설정됩니다.)
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param  {#DataMap} psDataMapId 데이터맵 Id
		 */
		DataMapKit.prototype.clear = function(app, psDataMapId){
			/** @type cpr.data.DataMap */
			var vcDataMap = app.lookup(psDataMapId);
			vcDataMap.clear();
		};
		
		/**
		 * 데이터를 모두 초기화합니다.
		 * (data 모두 초기 설정값으로 설정됩니다.)
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param  {#DataMap} psDataMapId 데이터맵 Id
		 */
		DataMapKit.prototype.reset = function(app, psDataMapId){
			/** @type cpr.data.DataMap */
			var vcDataMap = app.lookup(psDataMapId);
			vcDataMap.reset();
		};
		
		/**
		 * 현재 데이터맵의 데이터를 타겟 데이터맵으로 복사합니다. <br>
		 * 복사시 타겟 데이터맵의 alterColumnLayout 속성에 따라 복사방법의 설정됩니다. <br>
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#DataMap} psSourceDataMapId 데이터맵 Id
		 * @param {#DataMap} psTargetDataMapId 복사 데이터가 들어갈 타겟 맵 ID.
		 * @return {Boolean}
		 */
		DataMapKit.prototype.copyToDataMap = function(app, psSourceDataMapId, psTargetDataMapId){
			var vcDataMap = app.lookup(psSourceDataMapId);
			var vcTargetDataMap = app.lookup(psTargetDataMapId);
			return vcDataMap.copyToDataMap(vcTargetDataMap);
		};
		
		/**
		 * Column을 추가합니다.
		 * Header정보에 추가되며, data가 있는 경우 row data에도 해당 column data가 추가됩니다.
		 * @param {#DataMap} psDataMapId 데이터맵 ID
		 * @param {String} psColumnNm 추가하려는 Header 명
		 * @param {String} psValue (Optional) 초기값 설정
		 * @return {Boolean} 컬럼 추가 성공 여부
		 */
		DataMapKit.prototype.addColumn = function(app, psDataMapId, psColumnNm, psValue){
			var vcDataMap = app.lookup(psDataMapId);
			return vcDataMap.addColumn(new cpr.data.header.DataHeader(psColumnNm, "string"), psValue);
		};
		
		/**
		 * Column을 삭제합니다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#DataMap} psDataMapId 데이터맵 ID
		 * @param {String} psColumnName 삭제할 컬럼 명
		 * @return {Boolean} 컬럼 삭제 성공 여부
		 */
		DataMapKit.prototype.deleteColumn = function(app, psDataMapId, psColumnName){
			var vcDataMap = app.lookup(psDataMapId);
			return vcDataMap.deleteColumn(psColumnName);
		};
		
		/**
		 * 그리드(Grid) 컨트롤 유틸
		 * @constructor
		 * @param {common.AppKit} appKit
		 */
		function GridKit(appKit){
			this._appKit = appKit;
		};
		
		/**
		 * 그리드를 초기화한다.<br/>
		 * 1. 상태 컬럽 바인드 지정  (N, U, D)<br/>
		 * 2. 인덱스컬럼 text및 css지정<br/>
		 * 3. 소트 컬럼 자동지정<br/>
		 * 4. 읽기 전용 컬럼 헤더 Text 변경<br/>
		 * 5. 그리드, 프리폼 PK컬럼 enable 설정<br/>
		 * 6. update이벤트 추가 ( 저장후 그리드의 마지막 작업행을 찾기 위함)<br/>
		 * 7. 그리드 매핑 데이터셋에 load 이벤트 추가 (그리드의 마지막행 찾기, 조회 건수 업데이트)<br/>
		 * 8. 그리드 selection-dispose 이벤트 추가(삭제로 인한, 선택행이 없는 경우... 이전 행 자동 선택하도록(행 추가 -> 삭제시))<br/>
		 * 9. enableCheckDuplicatePk 사용자정의 속성에 따른 PK 중복체크<br/>
		 * 10. 그리드 contextmenu 이벤트 추가 (찾기, 정렬, 필터, 컬럼숨김)<br/>
		 * 그리드에 대한 공통 로직 및 이벤트 추가 용도<br/>
		 *  - appHeader에서 공통 적용됨<br/>
		 *  - 사이트별 Customizing 필요<br/>
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid | Array} paGridId 그리드 ID
		 * @return void
		 */
		GridKit.prototype.init = function(app, paGridId){
			if(!(paGridId instanceof Array)){
				paGridId = [paGridId];
			}
			
			//Index 컬럼 반환
			function getIndexDetailColumn(poGrid){
				var detail = poGrid.detail;
				var column;
				for(var i=0, len=detail.cellCount; i<len; i++){
					column = detail.getColumn(i);
					if(column.columnType == "rowindex"){
						return column;
					}
				}
				return null;
			}
			
			var _app = app;
			var _appKit = this._appKit;
			for (var i=0, len=paGridId.length; i <len; i++) {
				/**
				 * @type cpr.controls.Grid
				 */
				var vcGrid = (paGridId[i] instanceof cpr.controls.Grid) ? paGridId[i] : _app.lookup(paGridId[i]);
				if(vcGrid == null) continue;
				
				var columnMoveableYn = ValueUtil.fixNull(vcGrid.userAttr("columnMoveFix"));
				var columnResiableYn = ValueUtil.fixNull(vcGrid.userAttr("columnResizeFix"));
				var columnSortableYn = ValueUtil.fixNull(vcGrid.userAttr("columnSortFix"));
				var clickModeFixYn = ValueUtil.fixNull(vcGrid.userAttr("clickModeFix"));
				if(columnMoveableYn == "Y") {
					vcGrid.columnMovable = false;
				}else{
					vcGrid.columnMovable = true;
				}
				if(columnResiableYn == "Y") {
					vcGrid.columnResizable = false;
				}else{
					vcGrid.columnResizable = true;
				}
				//그리드 선택 모드 변경
				if(clickModeFixYn != "Y"){
					if(vcGrid.readOnly === true){
						vcGrid.clickMode = "select";
					}else{
						vcGrid.clickMode = "edit";
					}
				}
				
				//디자인시 적용한 AutoFit 백업(컬럼숨김 팝업에서 사용)
				vcGrid._origin_autoFit = vcGrid.autoFit;
				
				var vcDataSet =  vcGrid.dataSet;
				vcDataSet._gridId = vcGrid.id;
				vcDataSet.stateRestore = true; //현재값과 Origin이 같으면... 변경없도록 처리
				
				//상태컬럼
				var statusColumn = this.getHeaderStatusColumn(app, vcGrid.id);
				if(statusColumn != null){
					// Status 컬럼 숨김
					//statusColumn.visible = false;
					var detailColumn = vcGrid.detail.getColumn(statusColumn.colIndex);
					var statusColumnCtrl = detailColumn ? detailColumn.control : null;
					if(statusColumnCtrl){
						statusColumnCtrl.bind("value").toExpression("switch(getStateString()){ case 'I' : 'N'  case 'U' : 'U'  case 'D' : 'D'  default : ''}");
						statusColumnCtrl.style.css({"text-align" : "center"});
					}
				}
				//인덱스컬럼
				var indexColumn = getIndexDetailColumn(vcGrid);
				if(indexColumn != null){
					indexColumn.style.css({"text-align" : "center"});
					var hIndexColumn = vcGrid.header.getColumn(indexColumn.colIndex);
					if(hIndexColumn && hIndexColumn.text != "No") hIndexColumn.text = "No";
				}
				
				//소트 컬럼 자동지정
				if(columnSortableYn != "Y"){
					var dColumn, hColumn, vaHColumns;
					var vsFixColSort = "";
					for(var j=0, jlen=vcGrid.detail.cellCount; j<jlen; j++){
						dColumn = vcGrid.detail.getColumn(j);
						if(dColumn.columnType == "checkbox" || dColumn.columnType == "rowindex") continue;
						if(dColumn.columnName == null || dColumn.columnName == "") continue;
						
						vsFixColSort = dColumn.control ? dColumn.control.userAttr("columnSortFix") : ""; //컬럼 정렬무시옵션
						vaHColumns = vcGrid.header.getColumnByColIndex(dColumn.colIndex, dColumn.colSpan);
						if(vaHColumns){
							vaHColumns.forEach(function(/* cpr.controls.gridpart.GridColumn */ column){
								if(vsFixColSort !== "Y"){
									column.sortable = true;
								}
								if(column.targetColumnName == null || column.targetColumnName == "") {
									column.targetColumnName = dColumn.columnName;
								}
							});
						}
					}
				}
				/*
				//읽기 전용 컬럼 헤더 Text 변경
				var vbGReadOnly = vcGrid.readOnly === true ? true : false;
				var readHTextClass = "readonly";
				var readHColor = "#959495";
				if(vbGReadOnly){
					vcGrid.style.header.css({color: readHColor});
					for(var k=0, klen=vcGrid.detail.cellCount; k<klen; k++){
						var dColumn = vcGrid.detail.getColumn(k);
						if(dColumn.control){
							if(dColumn.controlType == "numbereditor"){
								if(dColumn.control.spinButton != false && dColumn.control.style.css("text-align") == ""){
									dColumn.control.style.css({"text-align":"center"});
								}
							}
							if((dColumn.control.format === "0000" || dColumn.control.format === "9999") && dColumn.control.max == 0){
								dColumn.control.max = 1.7976931348623157E308;
							}
						}
					};
				}else{
					var vaHeaderColumn = null;
					for(var k=0, klen=vcGrid.detail.cellCount; k<klen; k++){
						var dColumn = vcGrid.detail.getColumn(k);
						if(dColumn.columnType == "rowindex"){
							vaHeaderColumn = vcGrid.header.getColumnByColIndex(dColumn.colIndex, dColumn.colSpan);
							vaHeaderColumn.forEach(function(column){
								column.style.addClass(readHTextClass);
							});
						}else if(dColumn.control == null || dColumn.controlType == "output" || dColumn.controlType == "image" || dColumn.controlType == "button"){
							vaHeaderColumn = this.getHeaderColumnByColIdex(app, vcGrid.id, dColumn.colIndex);
							vaHeaderColumn.forEach(function(column){
								column.style.addClass(readHTextClass);
							});
						}else if((dColumn.control.getBindInfo("readOnly") == undefined && dColumn.control.readOnly === true) || (dColumn.control.getBindInfo("enabled") == undefined && dColumn.control.enabled === false)){
							vaHeaderColumn = this.getHeaderColumnByColIdex(app, vcGrid.id, dColumn.colIndex);
							vaHeaderColumn.forEach(function(column){
								if(column.style.getClasses().indexOf("require") == -1){
									column.style.addClass(readHTextClass);
								}
							});
						}
						
						if(dColumn.control){
							if(dColumn.controlType == "numbereditor" && dColumn.control.spinButton != false && dColumn.control.style.css("text-align") == ""){
								dColumn.control.style.css({"text-align":"center"});
							}
						}
					}
				}
				*/
				//헤더 컬럼 Visible 원래값 저장
				var vsHidenColumnIdxs = "";
				for(var k=0, klen=vcGrid.header.cellCount; k<klen; k++){
					if(vcGrid.header.getColumn(k).visible === false){
						vsHidenColumnIdxs += k+",";
					}
				}
				vcGrid.userAttr("originHiddenColumns", vsHidenColumnIdxs);
				
				//그리드 PK컬럼 enable 설정
				var vaPkColumnNames = ValueUtil.split(vcDataSet.info, ",");
				var vsDataBindCtxId = vcGrid.userAttr("bindDataFormId");
				vaPkColumnNames.some(function(value, idx){
					if(value == "") return false;
					//그리드 PK컬럼 설정(필수 스타일, 활성화/비활성화 바인딩 처리등)
					var columns = vcGrid.detail.getColumnByName(value);
					var vaHColumns = _appKit.Grid.getHeaderColumn(_app, vcGrid.id, value);
					if(columns != null && columns.length > 0){
						columns.forEach(function(col){
							if(col.control){
								if(col.control.userAttr("editablePK") !== "Y"){
									col.control.bind("enabled").toExpression("getStateString() == 'I' ? true : false");
									col.control.userAttr("required", "Y");
									if(vaHColumns){
										vaHColumns.forEach(function(/* cpr.controls.gridpart.GridColumn */ column){
											column.style.setClasses("require");
										});
									}
									
								}
							}
						});
					}
				});
				
				//프리폼 PK 컬럼 설정 
				if(!ValueUtil.isNull(vsDataBindCtxId) && vaPkColumnNames.length > 0){
					var freeformes = ValueUtil.split(vsDataBindCtxId, ",");
					freeformes.forEach(function(/* eachType */ formId){
						/**@type cpr.controls.Container */
						var freeform = _app.lookup(formId);
						if(freeform != null){
							var vaChildCtrls = freeform.getAllRecursiveChildren();
							vaPkColumnNames.some(function(value, idx){
								if(value == "") return false;
								vaChildCtrls.some(function(ctrl, ix){
									if(ctrl.type == "output") return false;
									if(ctrl.userAttr("ignorePk") == "Y") return false;
									if(ctrl.userAttr("editablePK") == "Y") return false;
									var bind = ctrl.getBindInfo("value");
									if(bind && bind.type == "datacolumn" && value == bind.columnName){
										ctrl.bind("enabled").toExpression("getStateString() == 'I' ? true : false");
										ctrl.userAttr("required", "Y");
									}
								});
							});	
						}
					});
				}
				
				//마지막 작업행을 찾기위해서...그리드 findRow 설정
				vcDataSet.addEventListener("update", function(/* cpr.events.CDataEvent */e){
					/** 
					 * @type cpr.data.DataSet
					 */
					var dataset = e.control;
					var rowIndex = e.row.getIndex();
					var row = e.row;
					var vaPkColumns = ValueUtil.split(dataset.info, ",");
					if(vaPkColumns.length < 1){
						dataset._findRowCondition = null;
					}else{
						var vaTempCond = [];
						vaPkColumns.forEach(function(column){
							vaTempCond.push(column + "==" + "'" + dataset.getValue(rowIndex, column) + "'");
						});
						dataset._findRowCondition = vaTempCond.length > 0 ? vaTempCond.join(" && ") : null;
					}
					
				});
				
				//그리드에 바인딩된 데이터셋(Dataset)이 로드될 때 처리
				//마지막행 찾기, 조회 건수 업데이트
				vcDataSet.addEventListener("load", function(/* cpr.events.CDataEvent */e){
					/** @type cpr.data.DataSet */
					var dataset = e.control;
					/** @type cpr.controls.Grid */
					var grd = dataset.getAppInstance().lookup(dataset._gridId);
					if(grd == null) return;
					
					//대상 그리드가 정렬된 상태라면... 정렬을 푼다.
					if(dataset.getSort() != ""){
						dataset.clearSort();
					}
					
					//마지막 작업행 찾기
					if(dataset.getRowCount() > 0) {
						if(dataset._findRowCondition){
							var row = dataset.findFirstRow(dataset._findRowCondition);
							if(row) {
								if(grd.selectionUnit == "cell"){
									grd.focusCell(row.getIndex(), 0);
								}else{
									setTimeout(function(){
										_appKit.Grid.selectRow(_app, grd.id, row.getIndex());
									}, 200);
								}
							}else{
								grd.selectionUnit == "cell" ? grd.focusCell(0, 0) : _appKit.Grid.selectRow(_app, grd.id, 0);
							}
						}else{
							if(grd.selectionUnit == "cell") grd.focusCell(0, 0); else _appKit.Grid.selectRow(_app, grd.id, 0);
						}
					}else{
					}
					
					//마지막 작업행 정보 Clear
					dataset._findRowCondition = null;
					
					//그리드 타이틀 영역의 데이터 건수 업데이트
					var titles = _appKit.Group.getAllChildrenByType(_app, "udc.com.comTitle");
					for(var i=0, len=titles.length; i<len; i++){
						if(titles[i] == null) continue;
						if(titles[i].getAppProperty("ctrl") == null) continue;
						if(titles[i].getAppProperty("ctrl").id == grd.id){
							titles[i].setAppProperty("rowCount", dataset.getRowCount());
							break;
						}
					}
				});
				
				//행 삭제로 인한, 선택행이 없는 경우... 이전 행 자동 선택하도록(행 추가 -> 삭제시)
				vcGrid.addEventListener("selection-dispose", function(/* cpr.events.CGridEvent */e){
					var oldSelection = e.oldSelection;
					if (oldSelection != null && oldSelection.length > 0 && oldSelection[0] > -1 && oldSelection[0] < e.control.rowCount) {
						e.control.selectRows(oldSelection[0]);
					}
				});
				
				//그리드 키다운(Up/Down) 이벤트 처리 - 현재 포커싱된 그리드 객체정보 저장
		//		vcGrid.addEventListener("keydown", function(/* cpr.events.CKeyboardEvent */ e){
		//			if(e.keyCode == cpr.events.KeyCode.UP || e.keyCode == cpr.events.KeyCode.DOWN){
		//				_appKit.getMainApp(e.control.getAppInstance()).__focusGrid = e.control;
		//			}
		//		});
				
				//PK 중복체크
				if(vcGrid.userAttr("enableCheckDuplicatePk") === "true"){
					var _this = this;
					vcGrid.addEventListener("update", function(/* cpr.events.CGridEvent */ e){
						var vaPkColumnNames = ValueUtil.split(e.control.dataSet.info, ",");
						if(e.row.getState() == cpr.data.tabledata.RowState.INSERTED && vaPkColumnNames.indexOf(e.columnName) != -1 && e.newValue != ""){
							var pkValue = "";
							var check = true;
							var codition = "";
							vaPkColumnNames.some(function(columnName, idx){
								if(columnName == "") return false;
								var value = e.control.getCellValue(e.rowIndex, columnName);
								if(value == null || value == ""){
									check = false;
									return false;
								}
								if(codition == "") codition = columnName + "=='" + value + "'";
								else codition += "&&" + columnName + "=='" + value + "'";
							});
							
							if(check){
								var vaRows = e.control.findAllRow(codition);
								if(vaRows != null && vaRows.length > 1){
									var msgValue = "";
									var vaChildCtrls = e.control.getChildren();
									
									vaPkColumnNames.some(function(columnName, idx){
										if(columnName == "") return false;
										//var value = e.control.getCellValue(e.rowIndex, columnName);
										
										var value = _this.getHeaderColumnText(app, e.control.id, columnName);
										
										if(!ValueUtil.isNull(value)){
											vaChildCtrls.some(function(ctrl, idx){
												if(!ValueUtil.isNull(ctrl.getBindInfo("value"))){
													if(ctrl.getBindInfo("value").columnName == columnName){
									       
									       				if(ctrl instanceof cpr.controls.ComboBox
									       				  || ctrl instanceof cpr.controls.CheckBox
									       				  || ctrl instanceof cpr.controls.RadioButton){
									       				  	
															value = ctrl.getSelectionFirst().label;
														 //2019.12.02 udc예외추가	
									       				}else if(ctrl instanceof cpr.controls.UDCBase){
									       					value = ctrl.getText();
									       				}else{
									       					value = e.control.getCellValue(e.rowIndex, columnName);
									       				}
									       			    if(msgValue == "") msgValue = ValueUtil.isNull(value) ? "" : value;								
														else msgValue += ValueUtil.isNull(value) ? "" : ", "+value;
										        	}
												}
								        		
									        });
										}
									});
									
									var vnTargetRowIdx = 0;
									for(var j=0, jlen=vaRows.length; j<jlen; j++){
										if(vaRows[j].getIndex() != e.rowIndex){
											vnTargetRowIdx = vaRows[j].getIndex();
											e.control.setEditRowIndex(e.rowIndex, true);
											break;
										}
									}
									e.control.setCellValue(e.rowIndex, e.columnName, "");
									_appKit.Msg.alert("WRN-M281", [msgValue, (vnTargetRowIdx + 1)]);
									
								}
							}
						}
					});
				}
				
				vcGrid.addEventListener("contextmenu", function(/* cpr.events.CDataEvent */e){
		//			if(e.targetObject == null) return;
					/** @type cpr.controls.Grid */
					var targetGrid = e.control;
					e.preventDefault();
					
					var ctxMenu = targetGrid._contextMenu;
					if(ctxMenu == undefined || ctxMenu == null){
						ctxMenu = new cpr.controls.Menu();
						//ctxMenu.addItem(new cpr.controls.TreeItem("찾기", "1", "root"));
						ctxMenu.addItem(new cpr.controls.TreeItem("정렬취소", "2", "root"));
						ctxMenu.addItem(new cpr.controls.TreeItem("필터", "3", "root"));
						ctxMenu.addItem(new cpr.controls.TreeItem("필터취소", "4", "root"));
						ctxMenu.addItem(new cpr.controls.TreeItem("컬럼숨김", "5", "root"));
						
						targetGrid._contextMenu = ctxMenu;
						
						ctxMenu.addEventListener("item-click", function(/**@type cpr.events.CItemEvent */ e){
							var itemValue = e.item.value;
							//찾기
							if(itemValue == "1"){
								//팝업 호출 파라메터
								var initValue = {"grid": targetGrid};
								_appKit.Dialog.open(app, "app/cmn/cmnPFind", 500, 110, function(e){
								
								}, initValue);
							//정렬취소
							}else if(itemValue == "2"){
								if(targetGrid) targetGrid.header.clearSort();
							//필터 보여주기
							}else if(itemValue == "3"){
								var isHFiltered = false;
								var column, filterStr;
								for(var i=0, len=targetGrid.header.cellCount; i<len; i++){
									column = targetGrid.header.getColumn(i);
									filterStr = column.getFilter();
									if(filterStr != null && filterStr[0] != "__all__"){
										isHFiltered = true;
										break;
									}
								}
								if(!isHFiltered){
									for(var i=0, len=targetGrid.header.cellCount; i<len; i++){
										column = targetGrid.header.getColumn(i);
										if(column.targetColumnName != ""){
											column.filterable = true;
										}
									}
								}
							//필터 해제 및 필터 숨김
							}else if(itemValue == "4"){
								if(targetGrid){
									targetGrid.header.clearFilter();
									var column;
									for(var i=0, len=targetGrid.header.cellCount; i<len; i++){
										column = targetGrid.header.getColumn(i);
										column.filterable = false;
									}
								}
							//컬럼 숨김/보이기
							}else if(itemValue == "5"){
								//팝업 호출 파라메터
								var initValue = {"grid": targetGrid};
								_appKit.Dialog.open(app, "app/cmn/cmnPColumns", 400, 300, function(e){
								
								}, initValue);
							}
							ctxMenu.dispose();
							targetGrid._contextMenu = null;
						});
						ctxMenu.addEventListenerOnce("blur", function(/**@type cpr.events.CFocusEvent*/ e){
							ctxMenu.dispose();
							targetGrid._contextMenu = null;
						});
					}
				
					/**@type cpr.controls.Container */
					var rootContainer = null;
					var showConstraint = {
						"position" : "absolute",
						"top" : e.clientY + "px",
						"left" : (e.clientX - 130) + "px",
						"width" : "130px",
						"height" : "60px"
					};
					if(_appKit.Dialog.isPopup(targetGrid.getAppInstance())){
						rootContainer = targetGrid.getAppInstance().getContainer();
						if((e.clientY - rootContainer.getActualRect().top + 130) > rootContainer.getActualRect().height )
							showConstraint.top = (e.clientY - rootContainer.getActualRect().top - 130) +"px";
						else
							showConstraint.top = (e.clientY - rootContainer.getActualRect().top) +"px";
						
						//팝업은 항상 가운데 띄우기
						showConstraint.left = (targetGrid.getActualRect().width - 130)/2 +"px";
		//				if((e.x + 130) > targetGrid.getActualRect().width)
		//					showConstraint.left = targetGrid.getActualRect().width-130 +"px";
		//				else
		//					showConstraint.left = e.x +"px";
					}else{
						rootContainer = targetGrid.getAppInstance().getRootAppInstance().getContainer();
						if(e.clientX < 130){
							showConstraint.left = "0px";
						}
					}
					if(rootContainer.getLayout() instanceof cpr.controls.layouts.FormLayout){
						app.floatControl(ctxMenu, showConstraint);
					}else{
						rootContainer.addChild(ctxMenu, showConstraint);
					}
					ctxMenu.focus();
				});
				
				//그리드 noData
				vcGrid.noDataMessage = "조회된 내역이 없습니다.";
			}
		};
		
		/**
		 * 그리드 특정 cell의 값을 변경한다. (detail 영역) 
		 * (주의) for문 등으로 대량의 데이터를 setCellValue 호출하는 경우에는 pbEmitEvent값을 false로 주어서, 스크립트 실행시간을 줄여줄 수 있다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psGridId  그리드 아이디
		 * @param {String | Number} psDataColmnId cellIndex 값을 변경하고자 하는 cell의 cell index. (또는 binding된 data column name)
		 * @param {String} psValue 변경하고자 하는 값.
		 * @param {Number} pnRowIndex (Optional) 값을 변경하고자 하는 cell의 row index.
		 *                 defalut : 선택된 rowindex
		 * @param {Boolean} pbEmitEvent (Optional) 이벤트(before-update, update)를 발생시킬지 여부.
		 * @return void
		 */
		GridKit.prototype.setCellValue = function(app, psGridId, psDataColmnId, psValue, pnRowIndex, pbEmitEvent){
			/** @type cpr.controls.Grid */
			var vcGrid = app.lookup(psGridId);
			var rowIndex = pnRowIndex == null ? this.getIndex(app, psGridId) : pnRowIndex;
			
			vcGrid.setCellValue(rowIndex, psDataColmnId, psValue, pbEmitEvent);
		};
		
		/**
		 * 그리드 특정 cell의 값을 반환한다.(detail 영역) 
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psGridId  그리드 ID
		 * @param {String | Number} psDataColmnId cellIndex 값을 가져오고자 하는 cell의 cell index. (또는 binding된 data column name)
		 * @param {Number} pnRowIdx (Optional) 값을 변경하고자 하는 cell의 row index.
		 *                 defalut : 선택된 rowindex
		 * @return {Object} 해당 cell의 값.
		 */
		GridKit.prototype.getCellValue = function(app, psGridId, psDataColmnId, pnRowIndex){
			/**@type cpr.controls.Grid */
			var vcGrid = app.lookup(psGridId);
			var rowIndex = pnRowIndex == null ? this.getIndex(app, psGridId) : pnRowIndex;
			return vcGrid.getCellValue(rowIndex, psDataColmnId);
		};
		
		/**
		 * 현재 연결된 데이터 구조체에 sort 조건을 변경하고, sort 적용
		 * Grid.sort(app, "grd1", "a, b DESC")
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psGridId  그리드 아이디
		 * @param {String} psCondition sort 조건식.
		 * @return void
		 */
		GridKit.prototype.sort = function(app, psGridId, psCondition){
			/** @type cpr.controls.Grid */
			var vcGrid = app.lookup(psGridId);
			vcGrid.clearSort();
			vcGrid.sort(psCondition);
			vcGrid.redraw();
		};
		
		/**
		 * 그리드 초기화(데이터 clear)
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid | Array} paGridId 그리드 ID
		 * @return void
		 */
		GridKit.prototype.reset = function(app, paGridId){
			if(!(paGridId instanceof Array)){
				paGridId = [paGridId];
			 }
			
			for (var i = 0; i < paGridId.length; i++) {
				var vcGrid = app.lookup(paGridId[i]);
				vcGrid.dataSet.clear();
				vcGrid.redraw();
			}
		};
		
		
		/**
		 * 현재 연결된 데이터 구조체에 filter 조건을 변경하고, filter합니다.<br/>
		 * Grid.filter(app, "grd1", "age >= 20")<br/>
		 * 	=> "age"컬럼의 값이 20이상인 값만 필터링합니다.<br/>
		 * Grid.filter(app, "grd1", "name ^= '김'")<br/>
		 * 	=> "name"컬럼의 값이 '김'으로 시작하는 값만 필터링합니다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psGridId 그리드 아이디
		 * @param {String} psCondition filter 조건식.
		 * @return void
		 */
		GridKit.prototype.filter = function(app, psGridId, psCondition){
			/** @type cpr.controls.Grid */
			var vcGrid = app.lookup(psGridId);
			var vsFilter = vcGrid.getFilter();
			if(!ValueUtil.isNull(vsFilter)){
				vcGrid.clearFilter();	
			}
			vcGrid.filter(psCondition);
		};
		
		
		/**
		 * 그리드의 변경사항 유/무를 반환를 반환한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid | Array} paGridId 그리드 ID
		 * @param {String} psAftMsg  (Optional) MSG or CRM or SAVE
		 *						MSG : 변경사항 내역이 없을 경우 '변경된 내역이 없습니다.' 메세지 출력
		 *  					CRM : 변경내역이 존재할경우 '변경사항이 반영되지 않았습니다. 계속 하시겠습니까?' confirm 메시지출력 
		 * 						SAVE : 변경된 데이터가 있습니다. 저장 하시겠습니까? confirm 메시지출력
		 * @param {cpr.events.CSelectionEvent} event (Optional) 이벤트 객체
		 * @param {Function} poCallBackFunc  (Optional) 콜백함수
		 * @return {Boolean} 데이터 변경 여부
		 */
		GridKit.prototype.isModified = function(app, paGridId, psAftMsg, event, poCallBackFunc){
			//유효성 체크로 인해서 행선택 변경 발생으로 변경여부 체크가 되는 경우는 SKIP...
			if(event != null && event.control != null && event.control.userAttr("selectionChangeByValidation") === "true"){
				event.control.removeUserAttr("selectionChangeByValidation");
				return false;
			}
			
			if(!(paGridId instanceof Array)){
				paGridId = [paGridId];
			}
			psAftMsg = psAftMsg == null ? "" : psAftMsg;
			
			var modify = false;
			var vcGrid = null;
			for (var i=0, len=paGridId.length; i<len; i++) {
				if(paGridId[i] instanceof cpr.controls.Grid) { 
					vcGrid = paGridId[i];
				}else{
					vcGrid = app.lookup(paGridId[i]);
				}
				
				//사용자 정의 속성에 modify 무시 속성이 있는 경우... SKIP
				if(vcGrid.userAttr("ignoreModify") === "Y") continue;
				if(vcGrid.dataSet == null) continue;
				
				if (vcGrid.dataSet.isModified()) {
					modify = true;
					break;
				}
			}
			
			if(modify){
				if(psAftMsg.toUpperCase() == "CRM"){//변경사항이 반영되지 않았습니다. 계속 하시겠습니까? confirm
					if(!this._appKit.Msg.confirm("CRM-M003", [vcGrid.fieldLabel])) return true;
					else return false;
				}
			}else{
				if(psAftMsg.toUpperCase() == "MSG"){//변경된 내역이 없습니다.
					this._appKit.Msg.notify(app, "INF-M006");
				}
			}
			
			return modify;
		};
		
		/**
		 * 해당 그리드의 체크된 행(Row)이나 선택된 행의 인덱스를 반환한다.(check된 행이 있는 경우, 체크된 행이 우선적으로 반환된다.)
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psGridId  그리드 ID
		 * @return {Number[]} 선택된 row index 배열.
		 */
		GridKit.prototype.getCheckOrSelectedRowIndex = function(app, psGridId){
			/** @type cpr.controls.Grid */
			var vcGrid = app.lookup(psGridId);
			if(vcGrid.rowCount < 1) return [];
			
			var vaChkIndexs = vcGrid.getCheckRowIndices();
			if(vaChkIndexs != null && vaChkIndexs.length > 0 ){
				return vaChkIndexs;
			}else{
				if(vcGrid.selectionUnit == "cell"){
					var vaSelIndices = vcGrid.getSelectedIndices();
					var rowIndices = [];
					for(var i=0, len=vaSelIndices.length; i<len; i++){
						rowIndices.push(vaSelIndices[i].rowIndex);
					}
					return rowIndices;
				}else{
					return vcGrid.getSelectedRowIndices();
				}
			}
		};
		
		/**
		 * 해당 그리드의 체크된 행(Row)의 인덱스를 반환한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psGridId  그리드 ID
		 * @return {Number[]} 선택된 row index 배열.
		 */
		GridKit.prototype.getCheckedRowIndex = function(app, psGridId){
			/** @type cpr.controls.Grid */
			var vcGrid = app.lookup(psGridId);
			if(vcGrid.rowCount < 1) return [];
			
			var vaChkIndexs = vcGrid.getCheckRowIndices();
			if(vaChkIndexs != null && vaChkIndexs.length > 0 ){
				return vaChkIndexs;
			}else{
				return [];
			}
		};
		
		/**
		 * 그리드 내 변경된 특정 행(Row)의 데이터를 원상태로 복구한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psGridId  그리드 ID
		 * @param {Number} pnRowIndex (Optional) 원복하고 싶은 row index
		 *                 default : 현재 체크 및 선택된 로우
		 * @return void
		 */
		GridKit.prototype.revertRowData = function(app, psGridId, pnRowIndex){
			/**@type cpr.controls.Grid */
			var vcGrid = app.lookup(psGridId);
			
			if(pnRowIndex == null){
				var vaSelectIdx = this.getCheckOrSelectedRowIndex(app, psGridId);
				if(vaSelectIdx.length < 1){
					return false;
				}
				var vcDataSet = vcGrid.dataSet;
				var rowIndex;
				for(var i = vaSelectIdx.length - 1; i >= 0; i--) {
					rowIndex = vaSelectIdx[i];
					vcGrid.setCheckRowIndex(rowIndex, false); //체크 해제
					var vsStatus = "";
				    if (vcDataSet != null ){
						vsStatus = vcDataSet.getRowStateString(rowIndex);			    	
				    }
					vcGrid.revertRowData(rowIndex); //데이터 원복
					//신규 행이면...
					if (vsStatus == "I") {
						if(rowIndex == vcGrid.getRowCount()){
							if(rowIndex == 0){
								vcGrid.clearSelection();
							}else{
								this.selectRow(app, vcGrid.id, rowIndex-1);
							}
						}else{
							this.selectRow(app, vcGrid.id, rowIndex);
						}
					}
				}
			}else{
				vcGrid.revertRowData(pnRowIndex);
			}
		};
		
		/**
		 * 그리드 내에서 변경된 모든 데이터를 원상태로 복구한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psGridId  그리드 ID
		 */
		GridKit.prototype.revertAllData = function(app, psGridId){
			var vcGrid = app.lookup(psGridId);
			vcGrid.revertData();
		};
		
		/**
		 * 그리드의 특정 행 데이터를 그룹 폼의 데이터셋에 복사한다.<br/>
		 * (사용처) 그리드의 데이터셋을 바인딩하여 사용하지 않는 경우에... 그리드의 선택된 행 데이터를 프리폼/그룹에 매핑하기 위한 용도임
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psGridId 그리드ID
		 * @param {#Container} psTargetForm 복사할 Group Form ID
		 * @param {Number} pnRowIdx 복사할 그리드 로우 인덱스 
		 * @return void
		 */
		GridKit.prototype.copyRowToGroupForm = function(app, psGridId, psTargetForm, pnRowIdx){
			var vcGrid = app.lookup(psGridId);
			var rowIndex = pnRowIdx == null ? this.getIndex(app, psGridId) : pnRowIdx;
			
			var vcGrpFrm = app.lookup(psTargetForm);
			vcGrpFrm.getBindContext().rowIndex = rowIndex;
			vcGrpFrm.redraw();
		};
		
		/**
		 * 소스(Source) 그리드의 선택된 행(Row)의 데이터를 타겟(Target) 그리드로 복사한다.
		 * 단, 복사할려는 데이터가 타겟 그리드에 이미 존재하는 경우에는 복사하지 않는다.(중복 복사 방지)
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psSrcGridId 그리드ID
		 * @param {#Grid} psDesGridId 복사할 그리드 ID
		 * @param {Number} pnSrcRowIdx (Optional) 그리드 로우 인덱스
		 *                 default : 체크된 row 나 선택된 row 인덱스를 취득 (check우선)
		 * @return void
		 */
		GridKit.prototype.copyToGridData = function(app, psSrcGridId, psDesGridId, pnSrcRowIdx){
			var vcSrcGrid = app.lookup(psSrcGridId);
			var vcDesGrid = app.lookup(psDesGridId);
			
			var rowIndexs = pnSrcRowIdx == null ? this.getCheckOrSelectedRowIndex(app, psSrcGridId) :  pnSrcRowIdx;
			if(!(rowIndexs instanceof Array)){
				rowIndexs = [rowIndexs];
			}
			//복사할 ROW가 없으면...SKIP
			if (rowIndexs.length < 1) return;
			
			var srcDataSet = vcSrcGrid.dataSet;
			var tarDataSet = vcDesGrid.dataSet;
			for (var i=0, len=rowIndexs.length; i<len; i++) {
				//신규 후 삭제된 행은 제외
				if(srcDataSet.getRowState(rowIndexs[i]) == cpr.data.tabledata.RowState.INSERTDELETED) continue;
				
				var data = srcDataSet.getRowData(rowIndexs[i]);
				// json 형식의 row의 데이터
				var str = [];
				// 이미 존재하는 row를 찾기 위해 row의 모든 column을 비교하는 조건 제작
				// str = "column1 == 'value1' && column2 == 'value2'..."
				for ( var key in data) {
					str.push(key + " == '" + data[key] + "'");
				}
				str = str.join(" && ");
				// 조건에 맞는 row 탐색
				var findRow = tarDataSet.findAllRow(str);
				// 조건에 해당하는 row가 없다면 target 그리드에 선택된 row를 추가
				if (findRow == null || findRow.length < 1) {
					tarDataSet.addRowData(data);
				}
			}
			
			vcDesGrid.redraw();
		};
		
		/**
		 * 소스(Source) 그리드의 모든 행(Row)의 데이터를 타겟(Target) 그리드로 복사한다.
		 * 단, 복사할려는 데이터가 타겟 그리드에 이미 존재하는 경우에는 복사하지 않는다.(중복 복사 방지)
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psSrcGridId 그리드ID
		 * @param {#Grid} psDesGridId 복사할 그리드 ID
		 * @return void
		 */
		GridKit.prototype.copyToAllGridData = function(app, psSrcGridId, psDesGridId){
			var vcSrcGrid = app.lookup(psSrcGridId);
		
			var indices = [];
			for (var i=0, len=vcSrcGrid.rowCount; i<len; i++) {
				indices.push(i);
			}
			
			this.copyToGridData(app, psSrcGridId, psDesGridId, indices);
		};
		
		/**
		 * 그리드 작업행을 찾기 위한 조건을 설정한다. 데이터셋에 설정된 PK정보를 기준으로 자동 지정된다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psGridId 그리드ID
		 * @param {Number} pnRowIndex (Optional) 그리드 로우(Row) 인덱스
		 * @param {Boolean} pbForce (Optional) 기존에 로우에 대한 정보가 있으면 SKIP 여부
		 * @return void
		 */
		GridKit.prototype.markFindRowCondition = function(app, psGridId, pnRowIndex, pbForce){
			/** @type cpr.controls.Grid */
			var vcGrid = app.lookup(psGridId);
			var voDataSet = vcGrid.dataSet;
			
			if(pbForce != undefined && ValueUtil.fixBoolean(pbForce) === true){
				if(!ValueUtil.isNull(voDataSet._findRowCondition)) return;
			}
			
			var rowIndex = !ValueUtil.isNull(pnRowIndex) ? pnRowIndex : this.getIndex(app, psGridId);
			
			var vaTempCond = [];
			var vaPkColumns = ValueUtil.split(voDataSet.info, ",");
			vaPkColumns.forEach(function(column){
				vaTempCond.push(column + "==" + "'" + voDataSet.getValue(rowIndex, column) + "'");
			});
			
			if(vaTempCond.length > 0){
				voDataSet._findRowCondition = vaTempCond.join(" && ");
			}else{
				voDataSet._findRowCondition = null;
			}
		};
		
		/**
		 * 소스(Source) 그리드의 선택된 행(Row)의 데이터를 타겟(Target) 그리드로 이동한다.
		 * 데이터 이동 후, 소스(Source) 그리드의 이동된 행(Row)의 상태는 delete모드로 상태값만 변경된다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psSrcGridId 그리드ID
		 * @param {#Grid} psDesGridId 이동할 그리드 ID
		 * @param {Number | Number[]} pnSrcRowIdx (Optional) 그리드 로우 인덱스<br/>
		 *                            default : 체크된 row 나 선택된 row 인덱스를 취득 (check우선)
		 * @return void
		 */
		GridKit.prototype.moveToGridData = function(app, psSrcGridId, psDesGridId, pnSrcRowIdx){
			var vcSrcGrid = app.lookup(psSrcGridId);
			var vcDesGrid = app.lookup(psDesGridId);
			
			var rowIndexs = pnSrcRowIdx == null ? this.getCheckOrSelectedRowIndex(app, psSrcGridId) :  pnSrcRowIdx;
			if(!(rowIndexs instanceof Array)){
				rowIndexs = [rowIndexs];
			}
			//이동할 ROW가 없으면...SKIP
			if(rowIndexs.length < 1) return;
			
			var srcDataSet = vcSrcGrid.dataSet;
			var tarDataSet = vcDesGrid.dataSet;
			for(var i=0, len=rowIndexs.length; i<len; i++){
				//신규 후 삭제된 행은 제외
				if(srcDataSet.getRowState(rowIndexs[i]) == cpr.data.tabledata.RowState.INSERTDELETED) continue;
				
				tarDataSet.addRowData(srcDataSet.getRowData(rowIndexs[i]));
			}
			vcDesGrid.redraw();
			vcSrcGrid.deleteRow(pnSrcRowIdx);
		};
		
		/**
		 * 소스(Source) 그리드의 모든 데이터행(Row)을 타겟(Target) 그리드로 이동한다.
		 * 데이터 이동 후, 소스(Source) 그리드의 이동된 행(Row)의 상태는 delete모드로 상태값만 변경된다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psSrcGridId 그리드ID
		 * @param {#Grid} psDesGridId 이동할 그리드 ID
		 */
		GridKit.prototype.moveToAllGridData = function(app, psSrcGridId, psDesGridId) {
			var vcSrcGrid = app.lookup(psSrcGridId);
		
			var indices = [];
			for (var i=0, len=vcSrcGrid.rowCount; i<len; i++) {
				indices.push(i);
			}
			
			this.moveToGridData(app, psSrcGridId, psDesGridId, indices);
		};
		
		/**
		 * 그리드에서 로우(Row)를 선택해준다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psGridId 그리드ID
		 * @param {Number} pnRowIndex (Optional) 포커스를 부여할 Row의 인덱스(default : 현재 행 인덱스)
		 * @return void
		 */
		GridKit.prototype.selectRow = function(app, psGridId, pnRowIndex) {
			/** @type cpr.controls.Grid */
			var vcGrid = app.lookup(psGridId);
			if(pnRowIndex == null || pnRowIndex == undefined){
				pnRowIndex = this.getIndex(app, psGridId);
			}
			
			vcGrid.selectRows(pnRowIndex);
			vcGrid.focusCell(pnRowIndex, 0);
		};
		
		/**
		 * 그리드에서 조건을 만족하는 로우(Row)를 선택해준다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psGridId 그리드ID
		 * @param {String} psCondition 조건식
		 *                 ex)"STUD_DIV_RCD == 'CT101REGU' && SA_NM == '컴퓨터정보과'"
		 * 					사용가능수식 !=", "!==", "$=", "%", "&&", "(", "*", "*=", "+", ",", "-", ".", "/", "/*", "//", "<", "<=", "==", "===", ">", ">=", "?", "[", "^=", "||"
		 * @param {Number} pnCellIdx (Optional) 포커스를 부여할 Cell의 인덱스
		 *                 default : 조건에 만족하는 행 select
		 * @return void
		 */
		GridKit.prototype.selectRowByCondition = function(app, psGridId, psCondition, pnCellIdx) {
			/** @type cpr.controls.Grid */
			var vcGrid = app.lookup(psGridId);
			var voRow = vcGrid.findFirstRow(psCondition);
			
			if(voRow){
				if(pnCellIdx) vcGrid.focusCell(voRow.getIndex(), pnCellIdx);
				else vcGrid.selectRows(voRow.getIndex());
			}
		};
		
		/**
		 * 그리드 행선택 변경 이벤트 발생시, 변경 이전에 선택된 행을 선택해준다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {cpr.events.CSelectionEvent} event 그리드 선택행 변경 이벤트
		 * @param {Boolean} emitEvent (Optional) 이벤트(before-selection-change, selection-change)를 발생시킬지 여부
		 * @return void
		 */
		GridKit.prototype.selectBeforeRow = function(app, event, emitEvent) {
			/** @type cpr.controls.Grid */
			var vcGrid = event.control;
			var emit = emitEvent === true ? true : false;
			
			var voOldSelection = event.oldSelection[0];
			var vsPkValues = this.getRowPKColumnValues(app, vcGrid.id, voOldSelection);
			var voFindRow = vcGrid.findFirstRow(vsPkValues);
			if(voFindRow){
				vcGrid.clearSelection(false);
				if(vcGrid.selectionUnit == "cell"){
					vcGrid.selectCells([{rowIndex:voFindRow["rowIndex"], cellIndex:voFindRow["cellIndex"]}], emit);
				}else{
					vcGrid.selectRows(voFindRow.getIndex(), emit);
				}
			}
		};
		
		/**
		 * 그리드에 신규 행(Row)을 추가한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psGridId      그리드 ID
		 * @param {String | Number} pnEditCellIdx 시작 cellIndex cell index 또는 column name.
		 * @param {Number} pnRowIdx (Optional) 추가하고자 하는 Row index
		 *                 defalut : 현재 선택된 로우 이후
		 * @param {Object} poRowData (Optional) 추가할 row data. (key: header명, value: value 를 갖는 json data)
		 * @return {cpr.controls.provider.GridRow} 추가한 Row의 GridRow 객체.
		 */
		GridKit.prototype.insertRow = function(app, psGridId, pnEditCellIdx, pnRowIdx, poRowData) {
			/** @type cpr.controls.Grid */
			var vcGrid = app.lookup(psGridId);
			var rowIndex = pnRowIdx == null ? this.getIndex(app, psGridId) : pnRowIdx;
			
			var insertedRow = null;
			if(poRowData != null)
				insertedRow = vcGrid.insertRowData(rowIndex, true, poRowData);
			else
				insertedRow = vcGrid.insertRow(rowIndex, true);
			
			var vnInsIdx = insertedRow.getIndex();
			
			if(vcGrid.readOnly){
				vcGrid.selectRows([ vnInsIdx ]);
			}else{
				vcGrid.selectRows([ vnInsIdx ]);
				vcGrid.setEditRowIndex(vnInsIdx, true);
			}
			
				
			if(pnEditCellIdx){
				vcGrid.focusCell(vnInsIdx, pnEditCellIdx);
				//포커싱할 컬럼이 UDC인 경우에...
				if(!ValueUtil.isNumber(pnEditCellIdx)){
					for(var i=0,len=vcGrid.detail.cellCount; i<len; i++){
						if(vcGrid.detail.getColumn(i).columnName == pnEditCellIdx){
							var ctrl = vcGrid.detail.getColumn(i).control;
							if(ctrl instanceof cpr.controls.UDCBase){
								var empApp = ctrl.getEmbeddedAppInstance();
								ctrl = AppUtil.getUDCBindValueControl(ctrl);
								if(ctrl) empApp.focus(ctrl.id);
							}
							break;
						}
					}
				}
			}else{
				vcGrid.focusCell(vnInsIdx, 0);
			}
			//그리드에 바인딩된 프리폼이 있는 경우... 프리폼 활성화
			if(!ValueUtil.isNull(vcGrid.userAttr("bindDataFormId"))){
				var freeformes = ValueUtil.split(vcGrid.userAttr("bindDataFormId"), ",");
				freeformes.forEach(function(/* eachType */ formId){
					/**@type cpr.controls.Container */
					var freeform = vcGrid.getAppInstance().lookup(formId);
					if(freeform != null){
						if(freeform._expressEnabled) freeform.bind("enabled").toExpression(freeform._expressEnabled);
						else freeform.enabled = true;
					}
				});
			}
			
			return insertedRow;
		};
		
		/**
		 * 그리드에 단 한건의 신규 행(Row)을 추가한다. (단하나의 신규 행만 추가하고자 하는 경우에 사용)
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psGridId      그리드 ID
		 * @param {String | Number} pnEditCellIdx 시작 cellIndex cell index 또는 column name.
		 * @param {#Grid | Array} paModifiedGrid
		 * @param {Number} pnRowIdx (Optional) 추가하고자 하는 Row index
		 *                 defalut : 현재 선택된 로우 이후
		 * @param {Object} poRowData (Optional) 추가할 row data. (key: header명, value: value 를 갖는 json data)
		 * @return {cpr.controls.provider.GridRow} 추가한 Row의 GridRow 객체.
		 */
		GridKit.prototype.insertRowOnlyOne = function(app, psGridId, pnEditCellIdx, paModifiedGrid, pnRowIdx, poRowData) {
			/** @type cpr.controls.Grid */
			var vcGrid = app.lookup(psGridId);
			if(vcGrid == null) return null;
			var rowIndex = pnRowIdx == null ? this.getIndex(app, psGridId) : pnRowIdx;
			
			var insertedRow = null;
			if(vcGrid.dataSet.getRowStatedCount(cpr.data.tabledata.RowState.INSERTED) > 0){
				for(var i=0, len=vcGrid.rowCount; i<len; i++){
					if(vcGrid.getRowState(i) == cpr.data.tabledata.RowState.INSERTED){
						insertedRow = vcGrid.getRow(i);
						break;
					}
				}
			}
			
			if(insertedRow){
				//신규 추가된 행이 존재합니다.변경사항이 반영되지 않았습니다. 계속 하시겠습니까?
				if(!this._appKit.Msg.confirm("CRM-M206", [vcGrid.fieldLabel])) return null;
				this._appKit.Control.reset(app, paModifiedGrid);
				var vaColumns = vcGrid.dataSet.getColumnNames();
				vaColumns.forEach(function(column){
					vcGrid.setCellValue(insertedRow.getIndex(), column, "", false);
				});
			}else{
				if(this.isModified(app, paModifiedGrid, "CRM")) return null;
				this._appKit.Control.reset(app, paModifiedGrid);
				insertedRow = vcGrid.insertRow(rowIndex, true);
			}
			
			var vnInsIdx = insertedRow.getIndex();
			if(vcGrid.readOnly){
				vcGrid.selectRows([ vnInsIdx ]);
			}else{
				vcGrid.selectRows([ vnInsIdx ]);
				vcGrid.setEditRowIndex(vnInsIdx, true);
			}
				
			if(pnEditCellIdx){
				vcGrid.focusCell(vnInsIdx, pnEditCellIdx);
				//포커싱할 컬럼이 UDC인 경우에...
				if(!ValueUtil.isNumber(pnEditCellIdx)){
					for(var i=0,len=vcGrid.detail.cellCount; i<len; i++){
						if(vcGrid.detail.getColumn(i).columnName == pnEditCellIdx){
							var ctrl = vcGrid.detail.getColumn(i).control;
							if(ctrl instanceof cpr.controls.UDCBase){
								var empApp = ctrl.getEmbeddedAppInstance();
								ctrl = AppUtil.getUDCBindValueControl(ctrl);
								if(ctrl) empApp.focus(ctrl.id);
							}
							break;
						}
					}
				}
			}
			
			return insertedRow;
		};
		
		/**
		 * 그리드의 선택된 행(Row)를 삭제한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psGridId 그리드 아이디
		 * @param {Number | Number[]} pnRowIdx (Optional) 삭제하고자 하는 Row index
		 *                 default : 체크된 row 나 선택된 row 인덱스를 취득 (check우선)
		 * @return {Number[]} 삭제된 행 (배열)                
		 */
		GridKit.prototype.deleteRow = function(app, psGridId, pnRowIdx) {
			/** @type cpr.controls.Grid */
			var vcGrid = app.lookup(psGridId);
			var _this = this;
			
			var rowIndexs = pnRowIdx == null ? this.getCheckOrSelectedRowIndex(app, psGridId) :  pnRowIdx;
			
			if(!(rowIndexs instanceof Array)){
				rowIndexs = [pnRowIdx];
			}
			//삭제할 행이 없는 경우... 메시지 박스를 보여줌
			if(rowIndexs.length < 1){
				//삭제할 데이터가 없습니다.
				this._appKit.Msg.alert("INF-M007");
				return false;
			}
			
			//신규 후 삭제시... 디테일 데이터에 대한 Reference 삭제(삭제 플래그로 업데이트)
			var vaDetailCtrls = null;
			
			var vcDataSet = vcGrid.dataSet;
			for(var i = rowIndexs.length - 1; i >= 0; i--) {
			    var rowIdx = rowIndexs[i];
			    vcGrid.deleteRow(rowIdx);
			    
				if (vcDataSet != null ){
					if (vcDataSet.getRowState(rowIdx) == cpr.data.tabledata.RowState.INSERTDELETED) {
						
						vcGrid.revertRowData(rowIdx);
						if(rowIdx == vcGrid.getRowCount()){
							if(rowIdx == 0){
								vcGrid.clearSelection();
							}else{
								vcGrid.selectRows(rowIdx-1);						
							}
						}else{
							vcGrid.selectRows(rowIdx);
						}
					}
				}	
			}
			
			//그리드에 바인딩된 프리폼이 있는 경우... 프리폼 활성화
			if(!ValueUtil.isNull(vcGrid.userAttr("bindDataFormId"))){
				var freeformes = ValueUtil.split(vcGrid.userAttr("bindDataFormId"), ",");
				freeformes.forEach(function(/* eachType */ formId){
					/**@type cpr.controls.Container */
					var freeform = vcGrid.getAppInstance().lookup(formId);
					if(freeform != null){
						var voDs = _this._appKit.Group.getBindDataSet(freeform.getAppInstance(), freeform);
						//데이터 건수가 없으면... 프리폼 비활성화
						if(voDs.getRowCount() < 1) {
							freeform.enabled = false;
						}
					}
				});
			}
			
			return rowIndexs;
		};
		
		/**
		 * 특정 row의 상태값을 반환한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psGridId
		 * @param {Number} pnRowIdx 상태를 알고자 하는 row index
		 * @return {cpr.data.tabledata.RowState} 해당 row index의 상태값.<br>
					<state 종류><br>
					cpr.data.tabledata.RowState.EMPTIED : 삭제된 로우를 커밋 시 삭제된 배열을에서 제거하기 위한 임시 상태.<br>
					cpr.data.tabledata.RowState.UNCHANGED : 변경되지 않은 상태.<br>
					cpr.data.tabledata.RowState.INSERTED : 행이 신규로 추가된 상태.<br>
					cpr.data.tabledata.RowState.UPDATED : 행이 수정된 상태.<br>
					cpr.data.tabledata.RowState.DELETED : 행이 삭제된 상태.<br>
					cpr.data.tabledata.RowState.INSERTDELETED : 행이 추가되었다가 삭제된 상태.
		 */
		GridKit.prototype.getRowState = function(app, psGridId, pnRowIdx) {
			var vcGrid = app.lookup(psGridId);
			var rowIndex = pnRowIdx == null ? this.getIndex(app, psGridId) : pnRowIdx;
			return vcGrid.getRowState(rowIndex);
		};
		
		/**
		 * 특정 row의 상태를 변경한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psGridId 그리드 ID
		 * @param {cpr.data.tabledata.RowState} state 변경할 상태값. <br>
				<state 종류><br>
				cpr.data.tabledata.RowState.UNCHANGED : 변경되지 않은 상태.<br>
				cpr.data.tabledata.RowState.INSERTED : 행이 신규로 추가된 상태.<br>
				cpr.data.tabledata.RowState.UPDATED : 행이 수정된 상태.<br>
				cpr.data.tabledata.RowState.DELETED : 행이 삭제된 상태.<br>
		 * @param {Number} pnRowIdx pnRowIdx 변경하고자 하는 row index
		 */
		GridKit.prototype.setRowState = function(app, psGridId, state, pnRowIdx) {
			var vcGrid = app.lookup(psGridId);
			var rowIndex = pnRowIdx == null ? this.getIndex(app, psGridId) : pnRowIdx;
			vcGrid.setRowState(rowIndex, state);
		};
		
		/**
		 * 전체 row의 상태값을 특정 상태(state)로 일괄 업데이트 한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psGridId 그리드 ID
		 * @param {cpr.data.tabledata.RowState} state 변경할 상태값. <br>
				<state 종류><br>
				cpr.data.tabledata.RowState.UNCHANGED : 변경되지 않은 상태.<br>
				cpr.data.tabledata.RowState.INSERTED : 행이 신규로 추가된 상태.<br>
				cpr.data.tabledata.RowState.UPDATED : 행이 수정된 상태.<br>
				cpr.data.tabledata.RowState.DELETED : 행이 삭제된 상태.<br>
		 */
		GridKit.prototype.setRowStateAll = function(app, psGridId, state) {
			var vcGrid = app.lookup(psGridId);
			var vcDataSet = vcGrid.dataSet;
			vcDataSet.setRowStateAll(state);
			vcGrid.redraw();
		};
		
		/**
		 * 해당 상태 값을 갖는 row를 검색하여 row index 배열을 반환합니다.
		 * <pre><code>
		 * Grid.getRowStatedIndices("grd1",cpr.data.tabledata.RowState.UPDATED)
		 * </code></pre>
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} 그리드 ID
		 * @param {cpr.data.tabledata.RowState} state 검색할 상태값.<br>
					<state 종류><br>
					cpr.data.tabledata.RowState.UNCHANGED : 변경되지 않은 상태.<br>
					cpr.data.tabledata.RowState.INSERTED : 행이 신규로 추가된 상태.<br>
					cpr.data.tabledata.RowState.UPDATED : 행이 수정된 상태.<br>
					cpr.data.tabledata.RowState.DELETED : 행이 삭제된 상태.<br>
		 * @return {Array} row index 배열
		 */
		GridKit.prototype.getRowStatedIndices = function(app, psGridId, state) {
			var vcGrid = app.lookup(psGridId);
			return vcGrid.dataSet.getRowStatedIndices(state);
		};
		
		/**
		 * 그리드의 로우 갯수 반환
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psGridId 그리드 ID
		 * @return {Number}  로우 카운트 
		 */
		GridKit.prototype.getRowCount = function(app, psGridId) {
			var vcGrid = app.lookup(psGridId);
			return vcGrid.rowCount;
		};
		
		/**
		 * 그리드의 현재 선택된 행의 인덱스(Index)를 반환한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psGridId 그리드 ID
		 * @return {Number}  로우(Row) 인덱스 
		 */
		GridKit.prototype.getIndex = function(app, psGridId) {
			/** @type cpr.controls.Grid */
			var vcGrid = app.lookup(psGridId);
			return vcGrid.selectionUnit != "cell" ? vcGrid.getSelectedRowIndex() : vcGrid.getSelectedIndices()[0]["rowIndex"];
		};
		
		/**
		 * 그리드 디테일 columnname로 헤더 컬럼 취득
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psGridId 그리드ID
		 * @param {String} psColumnName 컬럼명
		 * @return {Array} 헤더 컬럼 배열Array(cpr.controls.gridpart.GridColumn)
		 */
		GridKit.prototype.getHeaderColumn = function(app, psGridId, psColumnName) {
			/** @type cpr.controls.Grid*/
			var vcGrid = app.lookup(psGridId);
		    var vaDetailColumn = vcGrid.detail.getColumnByName(psColumnName);
			
			var vaHeaderColumns = new Array();
			vaDetailColumn.forEach(function(dColumn){
				var vaHeaderColumn = vcGrid.header.getColumnByColIndex(dColumn.colIndex, dColumn.colSpan);
				vaHeaderColumn.forEach(function(hColumn){
					vaHeaderColumns.push(hColumn);	
				});
			});
			
			return vaHeaderColumns;
		};
		
		/**
		 * 그리드 디테일 컬럼의 ColIndex로 헤더 컬럼 취득
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psGridId 그리드ID
		 * @param {Number} pnColIndex 컬럼 ColIndex
		 * @return {Array} 헤더 컬럼 배열Array(cpr.controls.gridpart.GridColumn)
		 */
		GridKit.prototype.getHeaderColumnByColIdex = function(app, psGridId, pnColIndex) {
			/** @type cpr.controls.Grid*/
			var vcGrid = app.lookup(psGridId);
			var voHeader = vcGrid.header;
			
			var vaHeaderColumns = new Array();
			var hColumn;
			for(var i=0, len=voHeader.cellCount; i<len; i++){
				hColumn = voHeader.getColumn(i);
				if(hColumn != null && hColumn.colIndex == pnColIndex){
					vaHeaderColumns.push(hColumn);
				}
			}
			
			return vaHeaderColumns;
		};
		
		/**
		 * 그리드 헤더 컬럼의 텍스트(text) 문자열을 반환한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psGridId 그리드 ID
		 * @param {String} psColumnName 컬럼명
		 * @return {String} 헤더 컬럼 text
		 */
		GridKit.prototype.getHeaderColumnText = function(app, psGridId, psColumnName) {
			var vaColumns = this.getHeaderColumn(app, psGridId, psColumnName);
			return vaColumns.length > 0 ? vaColumns[0].getText() : "";
		};
		
		/**
		 * 그리드 헤더 중에 STATUS 컬럼 객체를 반환한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psGridId 그리드 ID
		 * @return {cpr.controls.gridpart.GridHeaderColumn} 헤더 컬럼
		 */
		GridKit.prototype.getHeaderStatusColumn = function(app, psGridId) {
			var header = app.lookup(psGridId).header;
			var column = null;
			for(var i=0, len=header.cellCount; i<len; i++){
				column = header.getColumn(i);
				if(column.getText() == "F"){
					return column;
				}
			}
			return null;
		};
		
		/**
		 * 그리드 내 컬럼을 숨긴다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psGridId 그리드 ID
		 * @param {String} psColumnName 컬럼 명
		 * @return void
		 */	 
		GridKit.prototype.hideColumn = function(app, psGridId, psColumnName){
		 	/** @type cpr.controls.Grid*/
		 	var vcGrid = app.lookup(psGridId);
			var vaColumns = this.getHeaderColumn(app, psGridId, psColumnName);
			if(vaColumns.length > 0){
			 	vcGrid.columnVisible(vaColumns[0].colIndex, false);
			}
		};
		 
		/**
		 * 그리드 컬럼을 보이도록 변경한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psGridId 그리드 ID
		 * @param {String} psColumnName 컬럼명
		 * @return void
		 */	 
		GridKit.prototype.showColumn = function(app, psGridId, psColumnName){
		 	/** @type cpr.controls.Grid*/
		 	var vcGrid = app.lookup(psGridId);
			var vaColumns = this.getHeaderColumn(app, psGridId, psColumnName);
			if(vaColumns.length > 0){
			 	vcGrid.columnVisible(vaColumns[0].colIndex, true);
			}
		};
		
		/**
		 * 그리드의 데이터셋의 FindRow를 지정한다.
		 * 해당 함수 사용시 그리드 조회시 psFindRowCond로 지정된 행이 자동 선택된다. 
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psGridId 그리드 ID
		 * @param {String} psCondition 조건식 <br/>
		 *                 ex)"STUD_DIV_RCD == 'CT101REGU' && SA_NM == '컴퓨터정보과'" <br/>
		 * 					사용가능수식 !=", "!==", "$=", "%", "&&", "(", "*", "*=", "+", ",", "-", ".", "/", "/*", "//", "<", "<=", "==", "===", ">", ">=", "?", "[", "^=", "||" 
		 * @return void
		 */
		GridKit.prototype.setFindRowCondition = function(app, psGridId, psCondition){
			var vcGrid = app.lookup(psGridId);
			vcGrid.dataSet._findRowCondition = psCondition;
		};
		
		/**
		 * 현재 로우의 key(pk) value를 반환한다. 
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psGridId 그리드 ID
		 * @param {Number} pnRowIndex (Optional) 취득하고자하는 row index. <br/>
		 *                 defalut : 선택된 rowindex
		 * @return {String}
		 */
		GridKit.prototype.getRowPKColumnValues = function(app, psGridId, pnRowIndex){
			/** @type cpr.controls.Grid*/
			var vcGrid = app.lookup(psGridId);
			var vcDataSet = vcGrid.dataSet;
			
			var rowIndex = pnRowIndex == null ? this.getIndex(app, psGridId) : pnRowIndex;
			
			var vaPkColmns = ValueUtil.split(vcDataSet.info, ",");
			var vaTempCond = [];
			vaPkColmns.forEach(function(column){
				var vsPkValue = vcDataSet.getValue(rowIndex, column);
				vaTempCond.push(column + "==" + "'" + vcDataSet.getValue(rowIndex, column) + "'"); 
			});
			
			return vaTempCond.length > 0 ? vaTempCond.join(" && ") : "";
		};
		
		/**
		 * 그리드의 선택 유/무 체크 및 PK값이 입려되어 있는지를 체크한다. 
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psGridId 그리드 ID
		 * @param {String | Array} paIgnoreCol PK값 입력 체크 예외 컬럼명
		 * @return {Boolean}
		 */
		GridKit.prototype.checkSelectionWithPkValues = function(app, psGridId, paIgnoreCol){
			/** @type cpr.controls.Grid*/
			var vcGrid = app.lookup(psGridId);
			var rowIndex = this.getIndex(app, vcGrid.id);
			//Row 선택여부 확인
			if(rowIndex < 0){
				this._appKit.Msg.alert("INF-M226", [vcGrid.fieldLabel]); //선택된 데이터가 없습니다.
				return false;
			}
			//ROW의 PK값 입력여부 체크
			var vaPKColumns = ValueUtil.split(ValueUtil.fixNull(vcGrid.dataSet.info), ",");
			var valid = true, text, focusColumn, vbChk = false;
			for(var i=0, len=vaPKColumns.length; i<len; i++){
				if(ValueUtil.isNull(vcGrid.getCellValue(rowIndex, vaPKColumns[i]))){
				    if(!(paIgnoreCol instanceof Array)){
				        paIgnoreCol = [paIgnoreCol];
				    }
				    
				    vbChk = false;
		            paIgnoreCol.some(function(colName){
		                if(colName == vaPKColumns[i]) {
		                	vbChk = true;
		                	return false;
		                }
		            });
		            
		            if(vbChk) continue;
					
					valid = false;
					focusColumn = vaPKColumns[i];
					text = this.getHeaderColumnText(app, vcGrid.id, focusColumn);
					break;
				}
			}
			
			if(!valid){
				//항목은 필수입력 항목입니다.
				this._appKit.Msg.alert("WRN-M001", [vcGrid.fieldLabel+"의 "+text]);
				vcGrid.setEditRowIndex(rowIndex, true);
				vcGrid.focusCell(rowIndex, focusColumn);
				//포커싱할 컬럼이 UDC인 경우에...
				var vaDetailColumns = vcGrid.detail.getColumnByName(focusColumn);
				var dctrl = vaDetailColumns != null && vaDetailColumns.length > 0 ? vaDetailColumns[0].control : null;
				if(dctrl != null && dctrl instanceof cpr.controls.UDCBase){
					var empApp = dctrl.getEmbeddedAppInstance();
					dctrl = AppUtil.getUDCBindValueControl(dctrl);
					if(dctrl) empApp.focus(dctrl.id);
				}
				return false;
			}
			
			return true;
		};
		
		/**
		 * 현재 로우의 key(pk) value를 반환한다. 
		 * - 사이트별 Customizing 필요
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Grid} psGridId 그리드 ID
		 * @param {
		 *     fileName : String <!-- 파일명 -->,
		 *     excludeColumns? :  String <!--  (Optional) 출력시 제외할 컬럼명(여러개인 경우 콤마로 구분) ex) COL1,COL2,COL3 -->,
		 *     fileType? : String <!-- (Optional) 파일유형(xls,xlsx,cvs) -->,
		 *     metadata : Array,
		 *     excludeHideColumn : Boolean,
		 *     excludePart? : String <!-- (Optional) 출력시 제외영역(ex) footer, gfooter) -->,
		 *     unit : String <!-- 단위 -->
		 * } poOptions export 옵션
		 * @return void
		 */
		GridKit.prototype.exportData = function(app, psGridId, poOptions){
			var _this = this;
			/** @type cpr.controls.Grid */
			var vcGrid = app.lookup(psGridId);
			var vsFileType = !ValueUtil.isNull(poOptions.fileType) ? poOptions.fileType : "xlsx";
			
			poOptions.excludeHideColumn = ValueUtil.isNull(poOptions.excludeHideColumn) ? true : poOptions.excludeHideColumn;
			
			// 특수문자 제거 정규식 (특수문자 -> _)
			var reqExp = /[\{\}\[\]\/?.,;:|\)*~`!^\-+<>@\#$%&\\\=\(\'\"]/gi;
			var subExport = new cpr.protocols.Submission();
			subExport.action = encodeURI("../export/" + poOptions.fileName.replace(reqExp, "_") + "."+vsFileType); 
			subExport.mediaType = "application/json";
			subExport.responseType = "blob";	
			
		//	subExport.mediaType = cpr.protocols.Submission.MEDIA_JSON;
		//	subExport.responseType = cpr.protocols.ResponseType.blob;
			 
			//기본 출력 제외 컬럼(인덱스 컬럼, 선택용 체크 컬럼)
			var voColumn, voHColumn;
			var vaExcludeCellIndexs = [];
			for(var i=0, len=vcGrid.detail.cellCount; i<len; i++){
				voColumn = vcGrid.detail.getColumn(i);
				if(voColumn.columnType == "checkbox" || voColumn.columnType == "rowindex"){
					vaExcludeCellIndexs.push(i);
				}else if(voColumn.control instanceof cpr.controls.UDCBase){
					if(voColumn.control == null || voColumn.control.getBindInfo("value") == null){
						vaExcludeCellIndexs.push(i);
					}
				}
		//		else if(voColumn.columnName == null || voColumn.columnName == ""){
		//			vaExcludeCellIndexs.push(i);
		//		}
				else{
					//숨김컬럼 제외
					if(poOptions.excludeHideColumn){
						voHColumn = this.getHeaderColumn(app, psGridId, voColumn.columnName);
						if(voHColumn != null && voHColumn.length > 0){
							if(voHColumn[0].visible === false){
								vaExcludeCellIndexs.push(i);
							}
						}
					}
				}
			}
			
			//상태컬럼 제외
			var statusColumn = this.getHeaderStatusColumn(app, vcGrid.id);
			if(statusColumn != null){
				vaExcludeCellIndexs.push(statusColumn.colIndex);
			}
			
			//그외 추가적인 출력 제외 컬럼이 존재하는 경우
			if(!ValueUtil.isNull(poOptions.excludeColumns)){
				var vaExclColumns = ValueUtil.split(poOptions.excludeColumns, ",");
				var vaDColumns;
				for(var j=0, jlen=vaExclColumns.length; j<jlen; j++){
					vaDColumns = vcGrid.detail.getColumnByName(vaExclColumns[j]);
					if(vaDColumns){
						vaDColumns.forEach(function(/* Object */ each){
							vaExcludeCellIndexs.push(each.colIndex);
						});
					}
				}
			}
			
			var exportData = vcGrid.getExportData({exceptStyle:false, freezeHeader:true, excludeCols:poOptions.excludeColumns, applyFormat:true, useFormat:false});
		
			// 금융투자협회 - 그리드export 시 헤더로우 추가 (2020.08.28 daye 추가)
			var voGridHeader = exportData.rowgroups[0];
			var vnColSpan = 1;
			var vnInsertRow = poOptions.unit != "" ? 2 : 1;
			voGridHeader.style.forEach(function(each) {
				each.rowindex += vnInsertRow;
				if(vnColSpan < each.colindex) vnColSpan = each.colindex;
			});
			var voRowStyle = voGridHeader.style[0].style;
			/* style */
			if(poOptions.unit != "") voGridHeader.style.unshift({type:"string", rowindex:1, rowspan:1, colindex:0, colspan:vnColSpan+1, style:{}}); // 단위
			voGridHeader.style.unshift({type:"string", rowindex:0, rowspan:1, colindex:0, colspan:vnColSpan+1, style:{}}); // 타이틀
			/* data */
			if(poOptions.unit != "") voGridHeader.data[0].unshift({value : poOptions.unit, style:{"text-align" : "right"}}); // 단위
			voGridHeader.data[0].unshift({value : poOptions.fileName}); // 타이틀
			
			/* 틀고정 */
			exportData.freezepane.rowsplit += vnInsertRow;
			
			if (poOptions.metadata != null) {
				exportData["metadata"] = {};
				if (poOptions.metadata["password"] != null) {
					exportData["metadata"]["password"] = poOptions.metadata["password"];
				}
				if (poOptions.metadata["printPageOrientation"] != null) {
					exportData["metadata"]["printPageOrientation"] = poOptions.metadata["printPageOrientation"];
				}
			}
			//풋터 또는 그룹풋터 제외하는 경우
			if(!ValueUtil.isNull(poOptions.excludePart)){
				var len = exportData.rowgroups.length;
				for(var i = (len-1); i >= 0; i--) {
					if (exportData.rowgroups[i].region == poOptions.excludePart) {
						exportData.rowgroups.splice(i,1);
					}
				}
			}
			
			//그리드 출력 스타일지정
			for (var i=0, len=exportData.rowgroups.length; i<len; i++) {
				// band별로 원하는 스타일 추가 가능 (header, detail, footer, gheader, gfooter)
				var rowGroup = exportData.rowgroups[i];
				var cellLength = rowGroup.style.length;
				for (var j = 0; j < cellLength; j++) {
					rowGroup.style[j].style["border-bottom-color"] = "black";
					rowGroup.style[j].style["border-bottom-style"] = "solid";
					rowGroup.style[j].style["border-bottom-width"] = "1px";
					rowGroup.style[j].style["border-left-color"] = "black";
					rowGroup.style[j].style["border-left-style"] = "solid";
					rowGroup.style[j].style["border-left-width"] = "1px";
					rowGroup.style[j].style["border-right-color"] = "black";
					rowGroup.style[j].style["border-right-style"] = "solid";
					rowGroup.style[j].style["border-right-width"] = "1px";
					rowGroup.style[j].style["border-top-color"] = "black";
					rowGroup.style[j].style["border-top-style"] = "solid";
					rowGroup.style[j].style["border-top-width"] = "1px";
					
					if (rowGroup.region == "header") {
						rowGroup.style[j].style["background-color"] = "#dddddd";
						rowGroup.style[j].style["text-align"] = "center";
					}
				}
			} ;
			exportData.name = poOptions.fileName;
			subExport.setRequestObject(exportData);
			subExport.userAttr("fileExcel" , "Y" ) ; 
			
			subExport.send();
		};
		
		/**
		 * 데이터셋 컨트롤 유틸
		 * @constructor
		 * @param {common.AppKit} appKit
		 */
		function SubmissionKit(appKit){
			this._appKit = appKit;
		};
		
		/**
		 * Submission Before Handler<br/>
		 * 사이트별 Customizing 필요<br/>
		 *  1. _AUTH_MENU_KEY를 Parameter로 보내는 부분 보완 예정 (uri를 통해 메뉴정보를 취득 할 예정)<br/>
		 *  2. 시스템 컬럼 수정 필요 (CRT_USER_ID, CRT_PGM_ID, CRT_IP_MAC, UPD_USER_ID, UPD_PGM_ID, UPD_IP_MAC)
		 * @param {cpr.events.CSubmissionEvent} e
		 */
		SubmissionKit.prototype._onBeforeSubmit = function(e) {
			/** 
			 * @type cpr.protocols.Submission
			 */
			var submit = e.control;
			var _app = submit.getAppInstance();
			
			//메뉴정보(메뉴키) 추가
			var voMenuInfo = this._appKit.Auth.getMenuInfo(_app);
			if(voMenuInfo != null && voMenuInfo.size() > 0){
				var vsMenuKey = voMenuInfo.get("MENU_ID");
				submit.addParameter("_AUTH_MENU_KEY", vsMenuKey); 
			}
		
			//for.AUTO SAVE
			submit.setDataRowHandler(function(/** @type cpr.data.Row */ rowdata) {
				var additionalValue = {};
				//추가 DB 관리필드 컬럼 추가
				if(rowdata.getState() == cpr.data.tabledata.RowState.INSERTED || cpr.data.tabledata.RowState.UPDATED ||cpr.data.tabledata.RowState.DELETED){
					additionalValue["CRT_USER_ID"] = "";
					additionalValue["CRT_PGM_ID"] = "";
					additionalValue["CRT_IP_MAC"] = "";
					additionalValue["UPD_USER_ID"] = "";
					additionalValue["UPD_PGM_ID"] = "";
					additionalValue["UPD_IP_MAC"] = "";
				}
				
				//PK키 original값 추가
				var dsInfo = rowdata.getDataSetInfo();
				if(dsInfo && (rowdata.getState() == cpr.data.tabledata.RowState.UPDATED || cpr.data.tabledata.RowState.DELETED)){
					var vaPks = dsInfo.split(",");
					vaPks.some(function(value, idx){
						value = value.replace(/(^\s*)|(\s*$)/g, "")
						if(value == "") return false;
						
						additionalValue[value + "__origin"] = rowdata.getOriginalValue(value);
					});
				}else if(dsInfo && (rowdata.getState() == cpr.data.tabledata.RowState.INSERTED)){
					var vaPks = dsInfo.split(",");
					vaPks.some(function(value, idx){
						value = value.replace(/(^\s*)|(\s*$)/g, "")
						if(value == "") return false;
						
						additionalValue[value + "__origin"] = rowdata.getValue(value);
					});
				}
				
				return additionalValue;
			});
		};
		
		/**
		 * @private
		 * Submission Receive Handler<br/>
		 * 사이트별 Customizing 필요<br/>
		 *  - 1. 에러메시지 키 변경 필요
		 * @param {cpr.events.CSubmissionEvent} e
		 * @param {Boolean} pbSuccess
		 */
		SubmissionKit.prototype._onSubmitReceive = function(e, pbSuccess) {
			/** 
			 * @type cpr.protocols.Submission
			 */
			var submission = e.control;
			var xhr = submission.xhr;
			var contentType = xhr.getResponseHeader("Content-Type");
			if(contentType == null) return true;
			
			contentType = contentType.toLowerCase();
			if (contentType.indexOf(";") > -1) {
				contentType = contentType.substring(0, contentType.indexOf(";"));
			}
			contentType = ValueUtil.trim(contentType);
			if ("application/json" != contentType || "text/tab-separated-values" == contentType) {
				return true;
			}
			
			var response = xhr.responseText;
			var jsonRes = JSON.parse(response);
			var errMsgInfo = jsonRes["ERRMSGINFO"];
			if (errMsgInfo) {
				var vsErrMsg = "";
				try{
					vsErrMsg = eval("\"" +  errMsgInfo.ERRMSG+ "\"");	
				}catch(e){
					vsErrMsg = errMsgInfo.ERRMSG;
				}
				
				alert(vsErrMsg.replace(/\r\n/ig, "\n").replace(/\\n/gi, "\n"));
				var urlContext = top.location.pathname.substring(0, top.location.pathname.indexOf("/",2));
				if(urlContext == "/") urlContext = "";
				//사용자 세션없는 오류인 경우
				if("CMN003.CMN@CMN003" == errMsgInfo.ERRCODE){
					top.location.href = urlContext+"/logout.jsp";
				//중복로그인 오류인 경우
				}else if("CMN003.CMN@CMN062" == errMsgInfo.ERRCODE){
					top.location.href = urlContext+"/logout.jsp";
				//수강신청 기간체크 - 재로그인처리
				}else if("CMN003.CMN@CMN063" == errMsgInfo.ERRCODE){
					top.location.href = urlContext+"/logout.jsp";
				}
				return false;
			}
				
			return true;
		};
		
		/**
		 * 
		 * @param {cpr.events.CSubmissionEvent} e
		 */
		SubmissionKit.prototype._onSubmitLoadProgress = function(e) {
			/** 
			 * @type cpr.protocols.Submission
			 */
			var submission = e.control;
			var loadmask = this._getLoadMask(submission);
			if(loadmask){
				try {
					if(submission.getResponseDataCount() > 0){
						var rowCnt = submission.getResponseData(0).data.getRowCount();
						loadmask.module.count(rowCnt);
					}
				}catch(ex){}
			}
		};
		
		/**
		 * 
		 * @param {cpr.events.CSubmissionEvent} e
		 * @param {Boolean} pbSuccess
		 */
		SubmissionKit.prototype._onSubmitSuccess = function(e, pbSuccess) {
			return pbSuccess;
		};
		
		/**
		 * @private
		 * Submission Error Handler
		 * @param {cpr.events.CSubmissionEvent} e
		 */
		SubmissionKit.prototype._onSubmitError = function(e) {
			/** 
			 * @type cpr.protocols.Submission
			 */
			var submission = e.control;
			var _app = submission.getAppInstance();
			//시스템 내부 장애가 발생하였습니다.\n 관리자에게 문의 하시기 바랍니다.
			this._appKit.Msg.alert("ERR-SRV");
			
			var msg = submission.getMetadata("msg");
			if(msg) {
				this._appKit.Msg.notify(_app, msg);
			} else if(e.nativeEvent) {
				this._appKit.Msg.notify(_app, "network : " + e.nativeEvent.type);
			}
			
			return false;
		};
		
		/**
		 * @private
		 * Submission Done Handler<br/>
		 * 1. 서버에서 생성된 최신 로우 찾기<br/>
		 * 2. 어플리케이션 비즈니스 콜백 메소드 실행<br/>
		 * 3. 로딩 마스크 제거<br/>
		 * @param {cpr.events.CSubmissionEvent} e
		 * @param {Function} poCallbackFunc
		 * @param {Boolean} pbSuccess
		 * @param {Boolean} pbAppDisable
		 */
		SubmissionKit.prototype._onSubmitDone = function(e, poCallbackFunc, pbSuccess, pbAppDisable) {
			/** 
			 * @type cpr.protocols.Submission
			 */
			var submission = e.control;
			var _app = submission.getAppInstance();
			
			//마지막 행찾기
			var vsFindRowKey = submission.getMetadata("strFindRowKey");
			if(!ValueUtil.isNull(vsFindRowKey)){
				var vnDsCnt = submission.getRequestDataCount();
				var voDs, vaFindKey;
				var vaFindRowKeys = ValueUtil.split(vsFindRowKey, "|");
				var findKey = null;
				for(var i=0, len=vaFindRowKeys.length; i<len; i++){
					findKey = ValueUtil.trim(vaFindRowKeys[i]);
					if(findKey == "") continue;
					vaFindKey = ValueUtil.split(findKey, ":");
					if(vaFindKey.length == 2){
						for(var j=0; j<vnDsCnt; j++){
							voDs = submission.getRequestData(j).data;
							if(voDs.type != "dataset") continue;
							if(voDs.id == vaFindKey[0]){
								voDs._findRowCondition = vaFindKey[1];
								break;
							}
						}
					}else{
						for(var j=0; j<vnDsCnt; j++){
							voDs = submission.getRequestData(j).data;
							if(voDs.type != "dataset") continue;
							voDs._findRowCondition = vaFindKey[0];
						}
					}
				}
			}
			
			var idx = this._appKit._activeSubmission.indexOf(submission);
			if(idx != -1) {
				this._appKit._activeSubmission.splice(idx, 1);
			}
			
			//실패한 경우.. 커버를 씌움
			if(pbAppDisable === true && pbSuccess != true){
				this._appKit.coverPage(_app);
			}
			
			submission.removeAllFileParameters();
			submission.removeAllParameters();
			submission.removeAllEventListeners();
		
			//콜백이 존재하는 경우... 콜백함수 호출	
			//콜백을 제일 뒤로 옮김
			if (poCallbackFunc != null && (typeof poCallbackFunc == "function")) {
				poCallbackFunc(pbSuccess, e.control);
			}
			
			// submission success에서 다른 submission을 실행했을 경우 loadmask를 내리지 않는다.
			if(this._appKit._activeSubmission.length == 0) {
				// hide loadmask
				try{
					this._appKit.hideLoadMask(_app);
				}catch(ex){}
			}
		};
		
		/**
		 * @param {cpr.protocols.Submission} poSubmission
		 */
		SubmissionKit.prototype._getLoadMask = function(poSubmission) {
			var _app = poSubmission.getAppInstance();
			var _container = null;
			if(_app.getHost() && _app.getHost().modal === true){
				_container = _app.getContainer();
			}else{
				_container = _app.getRootAppInstance().getContainer();
			}
			_app = _container.getAppInstance();
			
			return _app.lookup("__loadmask__");
		};
		
		/**
		 * 해당 서브미션 요청 데이터를 가지고 있는지 체크
		 * @private
		 * @param {cpr.protocols.Submission} poSubmission - 서브미션 객체
		 * @param {String} psDataId - 데이터셋/맵 ID
		 */
		SubmissionKit.prototype._hasRequestData = function(poSubmission, psDataId){
			for(var i=0, len=poSubmission.getRequestDataCount(); i<len; i++){
				if(poSubmission.getRequestData(i).data.id == psDataId){
					return true;
				}
			}
			return false;
		}
		
		/**
		 * 해당 서브미션 요청 데이터를 가지고 있는지 체크
		 * @private
		 * @param {cpr.protocols.Submission} poSubmission - 서브미션 객체
		 * @param {String} psDataId - 데이터셋/맵 ID
		 */
		SubmissionKit.prototype._hasResponseData = function(poSubmission, psDataId){
			for(var i=0, len=poSubmission.getResponseDataCount(); i<len; i++){
				if(poSubmission.getResponseData(i).data.id == psDataId){
					return true;
				}
			}
			return false;
		}
		
		/**
		 * 전송시 추가로 전달되는 파라미터를 추가합니다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Submission} psSubmissionId 서브미션 ID
		 * @param {String} psParamName 파라미터의 이름
		 * @param {String} psValue 파라미터의 값
		 * @return void
		 */
		SubmissionKit.prototype.addParameter = function(app, psSubmissionId, psParamName, psValue){
			/** @type cpr.protocols.Submission */
			var vcSubmission = app.lookup(psSubmissionId);
			vcSubmission.addParameter(psParamName, psValue);
		};
		
		/**
		 * 전송시 추가로 전달되는 파라미터를 추가합니다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Submission} psSubmissionId 서브미션 ID
		 * @param {String} name 파라미터의 이름
		 * @param {String} value 파라미터의 값
		 * @return void
		 */
		SubmissionKit.prototype.addFileParameter = function(app, psSubmissionId, paFiles){
			/** @type cpr.protocols.Submission */
			var vcSubmission = app.lookup(psSubmissionId);
			if(paFiles == null) return;
			if(paFiles instanceof Array){
				paFiles.forEach(function(voFile){
					vcSubmission.addFileParameter("exb.fileupload.filelist", voFile);
				});
			}else{
				vcSubmission.addFileParameter("exb.fileupload.filelist", paFiles);
			}
		};
		
		/**
		 * 서브미션 호출<br/>
		 * - 사이트별 Customizing 필요<br/>
		 * 1. 공통 코드, 현재일자 서브미션 가능 (옵션)<br/>
		 *  - CRUD용 파라메터, 코드조회용 파라메터, 현재일자조회 파라메터 (개발가이드 2.24	공통 서브미션 호출 서비스 참고)
		 * 2. 서브미션에 before-submit,  receive, submit-error, submit-success, submit-done 이벤트 부여
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#Submission} 	 psSvcId 서브미션 ID
		 * @param {Array} paParams
		 * @param {Function} successCallback 서브미션 후 콜백 메소드
		 * @param {Boolean}  pbAppEnable (Optional) 서브미션 오류 및 exception 발생시 커버페이지를 쒸움
		 * @param {Boolean}  pbAsync (Optional) 공통서브미션 호출시 비동기 호출여부(디폴트 비동기)
		 * @param {String} maskType
		 */
		SubmissionKit.prototype.send = function(app, psSvcId, paParams, successCallback, pbAppEnable, pbAsync, maskType){
			var _app = app;
		
			var submission = _app.lookup(psSvcId);
			if(submission == null || !(submission instanceof cpr.protocols.Submission)){
				//요청 서브미션
				submission = _app.lookup("subCommExbuilder_"+psSvcId);
				if(submission == null){
					submission 				= new cpr.protocols.Submission("subCommExbuilder_"+psSvcId);
					submission.action 		= "../CommSubmit/egovXbuilder.do";
					submission.async		= (pbAsync != "undefined" && pbAsync != null) ? pbAsync : true;
					submission.mediaType 	= "application/x-www-form-urlencoded";
					submission.method 		= "post";
					submission.responseType = "text";
					_app.register(submission);
				}
				
				if(!(paParams instanceof Array)){
					paParams = [paParams];
				}
				if(paParams.length > 0){
					submission.removeAllRequestData();
					submission.removeAllResponseData();
					
					var dsReqParam = _app.lookup("dsCommExbuilderParam"); //CRUD용 파라메터
					if(dsReqParam == null){
						dsReqParam = new cpr.data.DataSet("dsCommExbuilderParam");
						dsReqParam.addColumn(new cpr.data.header.DataHeader("MAPPER", "string"));
						dsReqParam.addColumn(new cpr.data.header.DataHeader("QRY", "string"));
						dsReqParam.addColumn(new cpr.data.header.DataHeader("TYPE", "string"));
						dsReqParam.addColumn(new cpr.data.header.DataHeader("IN_DS", "string"));
						dsReqParam.addColumn(new cpr.data.header.DataHeader("OUT_DS", "string"));
						dsReqParam.addColumn(new cpr.data.header.DataHeader("OUT_DS_TYPE", "string"));
						dsReqParam.addColumn(new cpr.data.header.DataHeader("REQ_PARAM", "string"));
						_app.register(dsReqParam);
					}else{
						dsReqParam.clear();
					}
					
					var dsReqCodeParam = _app.lookup("dsCommExbuilderCodeParam"); //코드조회용 파라메터
					if(dsReqCodeParam == null){
						dsReqCodeParam = new cpr.data.DataSet("dsCommExbuilderCodeParam");
						dsReqCodeParam.addColumn(new cpr.data.header.DataHeader("CODE", "string"));
						dsReqCodeParam.addColumn(new cpr.data.header.DataHeader("CD", "string"));
						dsReqCodeParam.addColumn(new cpr.data.header.DataHeader("USE_YN", "string"));
						dsReqCodeParam.addColumn(new cpr.data.header.DataHeader("AUTH_YN", "string"));
						dsReqCodeParam.addColumn(new cpr.data.header.DataHeader("OUT_DS", "string"));
						dsReqCodeParam.addColumn(new cpr.data.header.DataHeader("OUT_DS_TYPE", "string"));
						_app.register(dsReqCodeParam);
					}else{
						dsReqCodeParam.clear();
					}
					var dmReqTimeParam = _app.lookup("dmCommExbuilderTimeParam"); //현재일자조회 파라메터
					if(dmReqTimeParam == null){
						dmReqTimeParam = new cpr.data.DataMap("dmCommExbuilderTimeParam");
						dmReqTimeParam.addColumn(new cpr.data.header.DataHeader("NODE", "string"));
						dmReqTimeParam.addColumn(new cpr.data.header.DataHeader("FORMAT", "string"));
						dmReqTimeParam.addColumn(new cpr.data.header.DataHeader("OUT_DS", "string"));
						dmReqTimeParam.addColumn(new cpr.data.header.DataHeader("OUT_DS_TYPE", "string"));
						_app.register(dmReqTimeParam);
					}else{
						dmReqTimeParam.clear();
					}
					
					var voParam = null;
					for(var i=0, len=paParams.length; i<len; i++){
						voParam = paParams[i];
						if(!ValueUtil.isNull(voParam["mapper"]) && !ValueUtil.isNull(voParam["qry"])){
							var in_dataset = [];
							var out_dataset = [];
							//요청 데이터셋 셋팅
							if(!ValueUtil.isNull(voParam["inds"])){
								var vaInDs = ValueUtil.split(voParam["inds"], ",");
								for(var k=0, klen=vaInDs.length; k<klen; k++){
									var vaInDsInfo = ValueUtil.split(ValueUtil.fixNull(vaInDs[k]), ":");
									var voInDs = _app.lookup(vaInDsInfo[0]);
									if(voInDs && !this._hasRequestData(submission, voInDs.id)){
										in_dataset.push(voInDs.id);
										if(vaInDsInfo.length == 2){
											submission.addRequestData(voInDs, "", vaInDsInfo[1]);
										}else{
											submission.addRequestData(voInDs, "");
										}
									}
								}
							}
							//응답 데이터셋 셋팅
							if(!ValueUtil.isNull(voParam["outds"])){
								var vaOutDs = ValueUtil.split(voParam["outds"], ",");
								for(var k=0, klen=vaOutDs.length; k<klen; k++){
									var vaOutDsInfo = ValueUtil.split(ValueUtil.fixNull(vaOutDs[k]), ":");
									var voOutDs = _app.lookup(vaOutDsInfo[0]);
									if(!this._hasResponseData(submission, voOutDs.id)){
										out_dataset.push(voOutDs.id);
										if(vaOutDsInfo.length == 2){
											submission.addResponseData(voOutDs, ValueUtil.fixBoolean(vaOutDsInfo[1]));
										}else{
											submission.addResponseData(voOutDs, false);
										}
									}
								}
							}
				
							if(!(voParam["qry"] instanceof Array)){
								voParam["qry"] = [voParam["qry"]];
							}
							for(var j=0, jlen=voParam["qry"].length; j<jlen; j++){
								var vaDsInfo = ValueUtil.split(voParam["qry"][j], ":");
								var vaSqlIds = ValueUtil.split(vaDsInfo[0], ",");
								var vsDsId = ValueUtil.trim(vaDsInfo[1]);
								var vsOutDsType = "";
								
								var vsInDsId = "";
								var vsOutDsId = "";
								for(var k=0, klen=vaSqlIds.length; k<klen; k++){
									vsInDsId = "", vsOutDsId = "", vsOutDsType = "";
									for(var z=0, zlen=in_dataset.length; z<zlen; z++){
										if(vsDsId == in_dataset[z]){
											vsInDsId = in_dataset[z];
											break;
										}
									}
									for(var z=0, zlen=out_dataset.length; z<zlen; z++){
										if(vsDsId == out_dataset[z]){
											vsOutDsId = out_dataset[z];
											break;
										}
									}
									
									vsOutDsType = (_app.lookup(vsOutDsId) instanceof cpr.data.DataSet ? "ds" : "dm")
									dsReqParam.addRowData({"MAPPER":voParam["mapper"], "QRY":ValueUtil.trim(vaSqlIds[k]), "TYPE":(!ValueUtil.isNull(voParam["type"]) ? voParam["type"] : "R"), "IN_DS":vsInDsId, "OUT_DS":vsOutDsId, "REQ_PARAM":voParam["param"], "OUT_DS_TYPE":vsOutDsType});
								}
							}
							var voParamDs = _app.lookup(voParam["param"]);
							if(voParamDs && !this._hasRequestData(submission, voParamDs.id)){
								submission.addRequestData(voParamDs, "", "all");
							}
						}else if(!ValueUtil.isNull(voParam["stime"])){
							var vsTimeOutDsName = voParam["stime"][0];
							var voOutDs = _app.lookup(vsTimeOutDsName);
							if(voOutDs == null) continue;
							//응답 데이터셋 셋팅
							if(!this._hasResponseData(submission, voOutDs.id)){
								submission.addResponseData(voOutDs, false);
							}
							dmReqTimeParam.setValue("NODE", voParam["stime"][1]);
							dmReqTimeParam.setValue("OUT_DS", voParam["stime"][0]);
							dmReqTimeParam.setValue("FORMAT", voParam["stime"][2]);
							dmReqTimeParam.setValue("OUT_DS_TYPE", (voOutDs instanceof cpr.data.DataSet ? "ds" : "dm"));
						}else{
							var vsCodeOutDsName = voParam[0];
							var voOutDs = _app.lookup(vsCodeOutDsName);
							if(voOutDs == null) continue;
							//응답 데이터셋 셋팅
							if(!this._hasResponseData(submission, voOutDs.id)){
								submission.addResponseData(voOutDs, false);
							}
							dsReqCodeParam.addRowData({"CODE":voParam[1], "CD":voParam[2], "USE_YN":voParam[3], "AUTH_YN":voParam[4], "OUT_DS":voParam[0], "OUT_DS_TYPE":(voOutDs instanceof cpr.data.DataSet ? "ds" : "dm")});
						}
					}
					//요청 파라메터 셋팅
					dsReqParam.setRowStateAll(cpr.data.tabledata.RowState.UNCHANGED);
					dsReqCodeParam.setRowStateAll(cpr.data.tabledata.RowState.UNCHANGED);
					submission.addRequestData(dsReqParam, "", "all");
					submission.addRequestData(dsReqCodeParam, "", "all");
					submission.addRequestData(dmReqTimeParam, "", "");
				}
			}
			
			if(!submission || submission.status == "SENDING") return;
		//	if(!submission) return;
			
			//context-path를 고려하여, action URL이 ../로 시작하도록 변경
			if(submission.action.indexOf("/") == 0){
				submission.action = ".."+submission.action;
			}
			
			//어플리케이션 전체에 마스크(Mask)를 씌운다.
			this._appKit.showLoadMask(app, maskType);
			
			if(submission.userAttr("responseType") === "TSV"){
				var loadmask = this._getLoadMask(submission);
				if(loadmask && loadmask.module.count){
					loadmask.module.count(0);
					loadmask.module.show();
				}
			}
			
			var vbSuccess = true;
			var _this = this;
			submission.addEventListenerOnce("before-submit", function(e){
				_this._onBeforeSubmit(e);
			});
			
			if(submission.userAttr("responseType") === "TSV"){
				submission.addEventListener("submit-load-progress", function(e){
					_this._onSubmitLoadProgress(e);
				}); 
			}
			
			submission.addEventListenerOnce("receive", function(e){
				vbSuccess = _this._onSubmitReceive(e);
			}); 
					
			submission.addEventListenerOnce("submit-error", function(e){
				vbSuccess = _this._onSubmitError(e);
			}); 
					
			submission.addEventListenerOnce("submit-success", function(e){
				vbSuccess = _this._onSubmitSuccess(e, vbSuccess);
			});
			
			submission.addEventListenerOnce("submit-done", function(e) {
				_this._onSubmitDone(e, successCallback, vbSuccess, pbAppEnable);
			});
			
			this._appKit._activeSubmission[this._appKit._activeSubmission.length] = submission;
			submission.send();
		};
		
		
		/**
		 * HeaderKit(헤더) 유틸
		 * @constructor
		 * @param {common.AppKit} appKit
		 */
		function HeaderKit(appKit){
			this._appKit = appKit;
		};
		
		/**
		 * 지정한 컨트롤의 Enable 속성을 설정한다.
		 * - 사이트별 Customizing 필요
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {Boolean} pbEnable 컨트롤 활성화 여부(true/false)
		 * @param {String | Array} paCtlId 배열 [I:신규, D:삭제, S:저장, 미지정 : 전체]
		 * @return void
		 */
		HeaderKit.prototype.setEnable = function(app, pbEnable, paCtlId) {
			if(!(paCtlId instanceof Array)){
				paCtlId = [paCtlId];
			}
			if (typeof (pbEnable) != "boolean") {
				pbEnable = ValueUtil.fixBoolean(pbEnable);
			}
			
			var appHeader = this._appKit.Group.getAllChildrenByType(app, "udc.com.appHeader");
			for (var i=0, len=appHeader.length; i<len; i++) {	
				var ctrl = appHeader[i];
				ctrl.setEnableCtrls(pbEnable, paCtlId);
			}
		};
		
		/**
		 * 헤더에 있는 컨트롤의 이벤트를 발생시킨다.
		 * - 사이트별 Customizing 필요
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#uicontrol} psCtlId 컨트롤의 아이디
		 * @param {String} psEventType 이벤트명(ex-click)
		 */
		HeaderKit.prototype.dispatchEvent = function(app, psCtlId, psEventType){
			var header = this._appKit.Group.getAllChildrenByType(app, "udc.com.appHeader");
			if(header != null && header.length > 0){
				var vcCtrl = header[0].getEmbeddedAppInstance().lookup(psCtlId);
				if(vcCtrl){
					vcCtrl.dispatchEvent(new cpr.events.CEvent(psEventType));
				}
			}
		};
		
		
		/**
		 * ComUdcBtnKit(공통, 버튼) 유틸
		 * @constructor
		 * @param {common.AppKit} appKit
		 */
		function ComUdcBtnKit(appKit){
			this._appKit = appKit;
		};
		
		/**
		 * 지정한 컨트롤의 Enable 속성을 설정한다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {Boolean} pbEnable 컨트롤 활성화 여부(true/false)
		 * @param {String | Array} paCtlId 배열 [I:신규, D:삭제, S:저장, 미지정 : 전체]
		 * @return void
		 */
		ComUdcBtnKit.prototype.setEnable = function(app, pbEnable, paCtlId) {
			if(!(paCtlId instanceof Array)){
				paCtlId = [paCtlId];
			}
			if (typeof (pbEnable) != "boolean") {
				pbEnable = ValueUtil.fixBoolean(pbEnable);
			}
			
			var comButton = this._appKit.Group.getAllChildrenByType(app, "udc.com.comButton");
			for (var i=0, len=comButton.length; i<len; i++) {	
				var ctrl = comButton[i];
				ctrl.setEnableCtrls(pbEnable, paCtlId);
			}
		};
		
		/**
		 * 공통 버튼 UDC 있는 컨트롤의 이벤트를 발생시킨다.
		 * @param {cpr.core.AppInstance} app 앱인스턴스
		 * @param {#uicontrol} psCtlId 컨트롤의 아이디
		 * @param {String} psEventType 이벤트명(ex-click)
		 */
		ComUdcBtnKit.prototype.dispatchEvent = function(app, psCtlId, psEventType){
			
			var comUdc;
			
			if(psCtlId == "btnSearch"){
				comUdc = this._appKit.Group.getAllChildrenByType(app, "udc.com.comBtnSearch");
			}else{
				comUdc = this._appKit.Group.getAllChildrenByType(app, "udc.com.comButton");
			}
			
			if(comUdc != null && comUdc.length > 0){
				var vcCtrl = comUdc[0].getEmbeddedAppInstance().lookup(psCtlId);
				if(vcCtrl){
					vcCtrl.dispatchEvent(new cpr.events.CEvent(psEventType));
				}
			}
		};
		
		
		exports.MsgKit = MsgKit;
		exports.DialogKit = DialogKit;
		exports.GroupKit = GroupKit;
		exports.FreeFormKit = FreeFormKit;
		exports.SelectKit = SelectKit;
		exports.TreeKit = TreeKit;
		exports.TabKit = TabKit;
		exports.EmbeddedAppKit = EmbeddedAppKit;
		exports.MDIKit = MDIKit;
		exports.ControlKit = ControlKit;
		exports.DataSetKit = DataSetKit;
		exports.DataMapKit = DataMapKit;
		exports.GridKit = GridKit;
		exports.SubmissionKit = SubmissionKit;
		exports.HeaderKit = HeaderKit;
		exports.ComUdcBtnKit = ComUdcBtnKit;
	});
})();
/// end - module/extension
/// start - module/information
/*
 * Module URI: module/information
 * SRC: module/information.module.js
 *
 * This file was generated by eXbuilder6 compiler, Don't edit manually.
 */
(function(){
	cpr.core.Module.define("module/information", function(exports, globals, module){
		/************************************************
		 * information.module.js
		 * Created at 2020. 7. 6. 오전 9:45:18.
		 *
		 * @author ryu
		 ************************************************/
		
		module.depends("module/initialize");
		
		/************************************************
		 * 전역 변수 선언
		 ************************************************/
		
		var msBtnServiceIdLoc = ["STATSCU0100000130", "STATSCU0100000140", "STATSCU0100000150", "STATSCU0100000160", "STATSCU0100000170", "STATSCU0100000180", "STATBND0100000280", "STATBND0100000290", "STATBND0100000300", "STATBND0100000310"];
		
		/* 웹 접근성 */
		var mbWebAccessibility = true;
		
		/* 구분자 */
		
		/**
		 * 콤마 구분자
		 */
		var msCommaSeparated = ",";
		
		/**
		 * 세미콜론 구분자
		 */
		var msSemicolonSeparated = ";";
		
		/**
		 * 캐럿 구분자
		 */
		var msCaretSeparated = "^";
		
		/* 다국어 */
		
		//TODO 올바른 다국어 키값으로 값을 변경하십시오. 미변경 시 기본값은 한국어입니다.
		
		/**
		 * "총"에 해당하는 다국어.
		 * 언어 코드에 따른 값 또는 일반 문자열을 아웃풋의 값으로 출력합니다.
		 * @type {String}
		 */
		var msLangTotal = cpr.I18N.INSTANCE.message("total") || "총";
		
		/**
		 * "건"에 해당하는 다국어.
		 * 언어 코드에 따른 값 또는 일반 문자열을 아웃풋의 값으로 출력합니다.
		 * @type {String}
		 */
		var msLangCount = cpr.I18N.INSTANCE.message("count") || "건";
		
		/**
		 * "단위"에 해당하는 다국어.
		 * 언어 코드에 따른 값 또는 일반 문자열을 아웃풋의 값으로 출력합니다.
		 * @type {String}
		 */
		var msLangUnit = cpr.I18N.INSTANCE.message("unit") || "단위";
		
		/**
		 * "차트 유형"에 해당하는 다국어.
		 * 언어 코드에 따른 값 또는 일반 문자열을 아웃풋의 값으로 출력합니다.
		 * @type {String}
		 */
		var msLangChartTyp = cpr.I18N.INSTANCE.message("ChartTyp") || "차트 유형";
		
		/**
		 * "차트 범례"에 해당하는 다국어.
		 * 언어 코드에 따른 값 또는 일반 문자열을 아웃풋의 값으로 출력합니다.
		 * @type {String}
		 */
		var msLangChartLegend = cpr.I18N.INSTANCE.message("ChartLegend") || "차트 범례";
		
		/* 레이아웃 */
		
		/**
		 * 기본 정보 레이아웃(폼 레이아웃) 가로 배치 간격
		 * @type {Number}
		 */
		var mnLtHorizontalSpacing = 10;
		
		/**
		 * 기본 정보 레이아웃(폼 레이아웃) 행 높이 (px 또는 fr)
		 * @type {String}
		 */
		var msLtRowSize = "28px";
		
		/**
		 * 기본 정보 레이아웃(폼 레이아웃) 컬럼 너비 (px 또는 fr)
		 * px 단위의 경우 자동 크기 사용이 적용됩니다.
		 * @type {String[]}
		 */
		var maLtColumnSize = ["30px", "1fr", "1fr", "50px", "30px"];
		
		/**
		 * 단위 정보 레이아웃(플로우 레이아웃) 컨트롤 간 사이 간격
		 * @type {Number}
		 */
		var mnUnitLtSpacing = 3;
		
		/* 정보 컨트롤 */
		
		/**
		 * 콤보박스 형태의 단위 정보를 나타내는 컨트롤 너비 값
		 * @type {String}
		 */
		var msCmbUnitDspWidth = "80px";
		
		/**
		 * 차트 유형 정보를 나타내는 컨트롤 너비 값
		 * @type {String}
		 */
		var msCmbChartTypOptWidth = "100px";
		
		/**
		 * 차트 범례 정보를 나타내는 컨트롤 너비 값
		 * @type {String}
		 */
		var msCmbChartLegendWidth = "150px";
		
		/* 컬럼 명칭 */
		
		/**
		 * 차트 정보 KEY 값(출력양식번호).
		 * 해당 값으로 차트와 그리드 버튼 정보 생성 여부를 파악한다.
		 * @type {String}
		 */
		var msColOutputFormNo = "OUTPUT_FORMNO";
		
		/**
		 * 기본 정보 특이사항 값.
		 * @type {String}
		 */
		var msColSpclCont = "SPCL_CONT";
		
		/**
		 * 콤보박스 형태의 단위에 대한 단위 목록 및 기본값.
		 * 목록을 공통 코드로 부터 제공받고, 해당 목록 중 기본값을 설정한다.
		 * e.g. T2050^08
		 * @type {String}
		 */
		var msColBasicUnit = "BASIC_UNIT";
		
		/**
		 * 단위 표시 값.
		 * 특정 구분자가 표시된 단위는 콤보박스 형태로 나타나며,
		 * 그 외의 단위는 일반 텍스트로 표시한다.
		 * @type {String}
		 */
		var msColBasicUnitDsp = "BASIC_UNITDSP";
		
		/**
		 * 콤보박스 형태의 단위와 연결될 바인딩 컬럼명.
		 * 조회 조건 데이터 맵의 컬럼과 일치하는 경우 바인딩한다.
		 * @type {String}
		 */
		var msColBindUnit1 = "tmpV40";
		
		/**
		 * 콤보박스 형태의 단위와 연결될 바인딩 컬럼명.
		 * 조회 조건 데이터 맵의 컬럼과 일치하는 경우 바인딩한다.
		 * @type {String}
		 */
		var msColBindUnit2 = "tmpV41";
		
		/**
		 * 공통 코드명 (label)
		 * @type {String}
		 */
		var msColCodeNm = "CODE_NM";
		
		/**
		 * 공통 코드 값 (value)
		 * @type {String}
		 */
		var msColCommonCd = "COMMON_CD";
		
		/**
		 * 차트 유형 기본값
		 * @type {String}
		 */
		var msColChartTyp = "CHART_TYP";
		
		/**
		 * 차트 유형 목록 값
		 * @type {String}
		 */
		var msColChartOpt = "CHART_CHCOPT";
		
		/**
		 * 차트 범례 목록 값.
		 * 차트 유형 기본값이 다축 또는 양축이 아닌 경우 해당 컬럼을 사용한다.
		 * @type {String}
		 */
		var msColSrchCondIdx = "SRCH_CONDIDX";
		
		/**
		 * 차트 범례 목록 값.
		 * 차트 유형 기본값이 다축 또는 양축인 경우 해당 컬럼을 사용한다.
		 * 값은 라인 차트와 막대 차트로 구분된다.
		 * @type {String}
		 */
		var msColSrchCondAxs = "SRCH_CONDAXSGB";
		
		/**
		 * 차트 범례 기본값.
		 * 차트 범례 목록이 다축 또는 양축 기준으로 생성되었을 경우
		 * 해당 컬럼을 이용하지 않는다.
		 * @type {String}
		 */
		var msColYaxsDefaultIdxVlu = "YAXS_DEFAULTIDXVLU";
		
		/**
		 * 차트 범례 기본값.
		 * 차트 범례 목록이 다축 또는 양축 기준으로 생성되었을 경우
		 * 해당 컬럼을 이용한다.
		 * @type {String}
		 */
		var msColDefaultAxs = "DEFAULT_AXSGB";
		
		/**
		 * 차트 범례 목록 구성 시 임시로 생성되는 트리와 바인딩되는 컬럼.
		 * 트리 아이템의 명으로 사용된다. (label)
		 * @type {String}
		 */
		var msColHeaderNm = "HEADER_NM";
		
		/**
		 * 차트 범례 목록 구성 시 임시로 생성되는 트리와 바인딩되는 컬럼.
		 * 트리 아이템의 값으로 사용된다. (value)
		 * @type {String}
		 */
		var msColHeaderId = "HEADER_ID";
		
		/**
		 * 차트 범례 목록 구성 시 임시로 생성되는 트리와 바인딩되는 컬럼.
		 * 트리 아이템의 부모값으로 사용된다. (parentValue)
		 * @type {String}
		 */
		var msColParentHdrId = "PARENT_HDRID";
		
		var msXaxsIdxVlu = "XAXS_IDXVLU";
		/************************************************
		 * 글로벌 출판 사용자 모듈
		 ************************************************/
		
		var information = (function() {
			/* 비공개 멤버 변수 선언 */
			//TODO 해당 함수 내에서만 사용할 비공개 멤버 변수를 선언하십시오.
			
			/* Display Kit */
			
			/**
			 * 
			 * @param {cpr.core.AppInstance} app 앱 인스턴스
			 * @param {
			 *   {
			 *     info : cpr.data.DataSet <!-- 기본 정보 데이터 셋 -->,
			 *     grid : {
			 *       control : cpr.controls.Grid <!-- 그리드 컨트롤 객체 -->,
			 *       data : cpr.data.DataSet <!-- 그리드 생성 관련 데이터 셋 (컬럼 정보 데이터 셋) -->
			 *     },
			 *     chart : {
			 *       control : cpr.controls.UDCBase <!-- 차트 컨트롤 객체 -->,
			 *       data : cpr.data.DataMap <!-- 차트 생성 관련 데이터 맵 -->
			 *     }
			 *   }
			 * } poConfig 정보 생성 환경
			 */
			function displayInfo(app, poConfig) {
				/* 필수 정보가 없을 시 표시하지 않음 */
				if (ValueUtil.isNull(poConfig)) {
					return;
				}
				
				var voRootAppIns = app.getRootAppInstance();
				var vsLangGb = voRootAppIns.lookup("dmSearchData").getValue("language_gb");
				if(vsLangGb == "ENG") {
					msLangUnit = "Unit(s)";
					msLangTotal = "A total of";
					msLangCount = "";
				}
				
				/* 정보 박스 생성 */
				var vcGrpInfoBox = makeBasicInfoBox();
				
				/* 그리드 정보 생성 - 건수 표시 */
				var vcGridInfo = makeGridInfo(app, poConfig["grid"]);
				
				/* 기본 정보 생성 - 특이사항 표시 */
				var vcBasicInfo = makeBasicInfo(app, poConfig["info"]);
				
				/* 차트 정보 생성 - 차트 유형 및 차트 범례 */
				var vcChartInfo = makeChartInfo(app, poConfig);
				
				/* 단위 정보 생성 - 단위 표시 */
				var vcUnitInfo = makeUnitInfo(app, poConfig["info"]);
				
				/* 버튼 정보 생성 - 파일 다운로드 및 레이아웃 변경 버튼 표시 */
				var vcBtnInfo = makeButtonInfo(app, poConfig);
				
				/* 배열 순서대로 정보 박스에 컨트롤을 추가 */
				var vaInfoChildren = [vcGridInfo, vcBasicInfo, vcChartInfo, vcUnitInfo, vcBtnInfo];
				vaInfoChildren.forEach(function( /* cpr.controls.UIControl */ each, /* Number */ index) {
					if (!ValueUtil.isNull(each)) {
						vcGrpInfoBox.addChild(each, {
							rowIndex: 0,
							colIndex: index
						});
					}
				});
				
				return vcGrpInfoBox;
			}
			
			/**
			 * 현재 표시된 정보 박스의 높이값을 가져옵니다.
			 * @return {String}
			 */
			function getDisplayRowSize() {
				return msLtRowSize;
			}
			
			/**
			 * 
			 * @param {cpr.core.AppInstance} app
			 * @param {cpr.data.DataMap} poChartData
			 */
			function revertInfoBox(app, poChartData) {
				var vaInfoBox = app.getContainer().getAllRecursiveChildren().filter(function(each) {
					return each.userAttr("infobox") == "true";
				});
				
				vaInfoBox.forEach(function(each) {
					each.getAllRecursiveChildren().forEach(function( /** @type cpr.controls.ComboBox */ cmb) {
						if (cmb.userAttr("chart-type") == "true") {
							cmb.value = poChartData.getValue(msColChartTyp);
						}
						
						if (cmb.userAttr("chart-legend") == "true") {
							
							var grd = app.lookup("grd");
							if(grd) {
								
								/** @type cpr.data.DataSet */
								var vcDsGrd = grd.dataSet;
								var vcGrdRowData = vcDsGrd.getRowDataRanged();
								
								if(vcGrdRowData.length > 0) {
									var vcDmChart = poChartData;
									
									/** @type String */
									var vsSrchCond = vcDmChart.getValue(msColSrchCondIdx) || vcDmChart.getValue(msColSrchCondAxs); // 차트
									/** @type String */
									var vsYaxsDfVlu = vcDmChart.getValue(msColYaxsDefaultIdxVlu) || vcDmChart.getValue(msColDefaultAxs);
									var vbMultiChart = ValueUtil.isNull(vcDmChart.getValue(msColSrchCondIdx)); // 멀티 축 여부
									
									if (!ValueUtil.isNull(vsSrchCond)) {
										/** @type String[] */
										var vaSrchCond = _refineChartData(vbMultiChart, vsSrchCond, msCaretSeparated, true);
										/** @type String */
										var vsYaxsDfVlu = _refineChartData(vbMultiChart, vsYaxsDfVlu, msCaretSeparated);
										
										/** @type Array */
										var arr = _chunk(vaSrchCond);
										
										var vaSrchCond1 = arr[0];
										for (var itemIdx = 0; itemIdx < vaSrchCond1.length; itemIdx++) {
											var vsSrchCondVal = vaSrchCond1[itemIdx];
											
											var vsSrchCondTxt = null;
											if (vsSrchCondVal.indexOf("R") != -1) {
		//										vsSrchCondTxt = _getRowText(grd, vsSrchCondVal);
												var vsXaxs = vcDmChart.getValue(msXaxsIdxVlu);
												if(vsXaxs[0] =="C"){
													
													vsSrchCondTxt = _getRowText2(grd,vsSrchCondVal,vsXaxs);
												} else {
													vsSrchCondTxt = _getRowText(grd, vsSrchCondVal);
												}
											} else if (vsSrchCondVal.indexOf("C") != -1) {
												vsSrchCondTxt = _getColumnText(vcDsGrd, vsSrchCondVal);
											}
											
											if (!(ValueUtil.isNull(vsSrchCondTxt))) {
												/** @type cpr.controls.ComboBox */
												var vcCombo = cmb;
												var item = vcCombo.getItemByValue(vsSrchCondVal);
												if(item){
													
													item.label = vsSrchCondTxt;
												} else {
													
													cmb.addItem(new cpr.controls.Item(vsSrchCondTxt, vsSrchCondVal));
												}
											}
										}
									}
								}
							}
							
							cmb.clearSelection(false);
							
							var vsYaxsDfVlu = poChartData.getValue(msColYaxsDefaultIdxVlu) || poChartData.getValue(msColDefaultAxs);
							var vsDelimiter = cmb.delimiter;
							
							vsYaxsDfVlu = _parser(vsYaxsDfVlu, vsYaxsDfVlu.substr(0, 1), msCaretSeparated)
							var vsCvYaxsDfVluVal = vsYaxsDfVlu.replace(/[\^]/g, vsDelimiter);
							var vaCvYaxsDfVluVal = vsCvYaxsDfVluVal.split(vsDelimiter);
							
							vaCvYaxsDfVluVal.forEach(function(val) {
								cmb.selectItemByValue(val, false);
							});
							
							if (cmb.getSelection().length == 0) {
								cmb.selectItem(0, false);
							}
						}
					})
				});
			}
			
			/* Basic Kit */
			
			/**
			 * 정보 컨테이너와 기본 레이아웃을 동적으로 생성합니다.
			 * @return {cpr.controls.Container}
			 */
			function makeBasicInfoBox() {
				var vcGrpInfoBox = new cpr.controls.Container();
				var voGrpInfoBoxLt = new cpr.controls.layouts.FormLayout();
				
				/* 기본 레이아웃 속성 설정 */
				voGrpInfoBoxLt.setRows([msLtRowSize]);
				voGrpInfoBoxLt.setColumns(maLtColumnSize);
				
				voGrpInfoBoxLt.horizontalSpacing = mnLtHorizontalSpacing;
				
				/* px 구획 자동 크기 사용 적용 */
				voGrpInfoBoxLt.getColumns().forEach(function( /* String */ each, /* Number */ index) {
					if (each.indexOf("px") !== -1) {
						voGrpInfoBoxLt.setColumnAutoSizing(index, true);
					}
				});
				
				voGrpInfoBoxLt.setColumnVisible(2, false);
				
				vcGrpInfoBox.setLayout(voGrpInfoBoxLt);
				
				vcGrpInfoBox.userAttr("infobox", "true");
				
				return vcGrpInfoBox;
			}
			
			/**
			 * 
			 * @param {cpr.core.AppInstance} app
			 * @param {cpr.data.DataSet} pcDataSet
			 */
			function makeBasicInfo(app, pcDataSet) {
				if (ValueUtil.isNull(pcDataSet)) {
					return;
				}
				
				/** @type cpr.data.DataSet */
				var vcDsGrdInfo = pcDataSet;
				
				if (vcDsGrdInfo.getRowCount() === 0) {
					return;
				}
				
				// 대차거래, 채권 등 버튼 추가
				var vsServiceIdloc = pcDataSet.getColumnData("SERVICE_ID")[0];
				if ( msBtnServiceIdLoc.indexOf(vsServiceIdloc) != -1 ) {
					var vcContainer = new cpr.controls.Container();
					
					var vcBtn = new cpr.controls.Button();
					vcBtn.value = "대차거래-공매도 비교 및 공시";
					vcBtn.addEventListener("click", function(e){
						createCommonUtil().Dialog.open(app.getRootAppInstance(), "app/imp/popup/GridInfoPop", 500, 650, function(dialog) {
						}, {
							url : "/stat/popup/loanTransactionPopup.do"
						});
					});
					
					vcContainer.addChild(vcBtn, {
						"top" : "0px",
						"bottom" : "0px",
						"left" : "10px",
						"width" : "300px"
					});
					
					return vcContainer;
				}
				
				var vsSpclCont = vcDsGrdInfo.getValue(0, msColSpclCont);
				
				/*
				 * if (ValueUtil.isNull(vsSpclCont)){
				 * 	return;
				 * }
				 */
				
				var vcLblSpclCont = new cpr.controls.Output();
				
				vcLblSpclCont.ellipsis = true;
				vcLblSpclCont.value = vsSpclCont;
				
				if (mbWebAccessibility) {
					vcLblSpclCont.tooltip = vcLblSpclCont.value;
				}
				
				return vcLblSpclCont;
			}
			
			/* Grid Kit */
			
			/**
			 * 
			 * @param {cpr.core.AppInstance} app 앱 인스턴스
			 * @param {
			 *   control : cpr.controls.Grid <!-- 그리드 컨트롤 객체 -->,
			 *   data? : cpr.data.DataSet <!-- 그리드 생성 관련 데이터 셋 (컬럼 정보 데이터 셋) -->
			 * } poConfig
			 */
			function makeGridInfo(app, poConfig) {
				if (ValueUtil.isNull(poConfig)) {
					return;
				}
				
				/** @type cpr.controls.Grid */
				var vcGrdMst = poConfig["control"];
				
				var vcLblRowCnt = new cpr.controls.Output();
				
				vcLblRowCnt.dataType = "number";
				vcLblRowCnt.displayExp = "\"[" + msLangTotal + " \"+text+\"" + msLangCount + "]\"";
				vcLblRowCnt.bind("value").toExpression("#" + vcGrdMst.id + ".getRowCount()");
				
				return vcLblRowCnt;
			}
			
			/* Chart Kit */
			
			/**
			 * 
			 * @param {cpr.core.AppInstance} app
			 * @param {
			 *   {
			 *     info? : cpr.data.DataSet <!-- 기본 정보 데이터 셋 -->,
			 *     grid : {
			 *       control? : cpr.controls.Grid <!-- 그리드 컨트롤 객체 -->,
			 *       data : cpr.data.DataSet <!-- 그리드 생성 관련 데이터 셋 (컬럼 정보 데이터 셋) -->
			 *     },
			 *     chart : {
			 *       control : cpr.controls.UDCBase <!-- 차트 컨트롤 객체 -->,
			 *       data : cpr.data.DataMap <!-- 차트 생성 관련 데이터 맵 -->
			 *     }
			 *   }
			 * } poConfig 정보 생성 환경
			 */
			function makeChartInfo(app, poConfig) {
				if (ValueUtil.isNull(poConfig)) {
					return;
				}
				
				/** @type cpr.data.DataMap */
				var vcDmChart = poConfig["chart"]["data"];
				/** @type cpr.data.DataSet */
				var vcDsGrd = poConfig["grid"]["data"];
				/** @type cpr.controls.Grid */
				var vcGrdMst = poConfig["grid"]["control"];
				/** @type udc.cmn.ApexChartUDC */
				var vcUdcChart = poConfig["chart"]["control"];
				
				/* 그리드 생성 관련 데이터가 존재하고 차트 데이터가 있는 경우에만 차트 정보 표시 */
				if (vcDsGrd.getRowCount() == 0 ||
					ValueUtil.isNull(vcDmChart.getValue(msColOutputFormNo))) {
					return;
				}
				
				/* 차트 정보를 감싸는 컨테이너(그룹) 생성 */
				var vcGrpChartInfo = new cpr.controls.Container();
				var voGrpChartInfoLt = new cpr.controls.layouts.FlowLayout();
				
				voGrpChartInfoLt.scrollable = false;
				voGrpChartInfoLt.lineWrap = false;
				
				vcGrpChartInfo.setLayout(voGrpChartInfoLt);
				
				/* 차트 정보 중 차트 유형 정보 생성 */
				var vcLblChartTyp = new cpr.controls.Output();
				vcLblChartTyp.value = msLangChartTyp;
				
				vcGrpChartInfo.addChild(vcLblChartTyp, {
					height: "100%",
					autoSize: "width"
				});
				
				var vcCmbChartTyp = new cpr.controls.ComboBox();
				
				//FIXME 차트 유형 목록 코드 값 공통 처리 필요
				var vcDsCmnCd = init.DataSet.getCodeDataSet(app, "C4092");
				
				/** @type String */
				var vsChartTypOpt = vcDmChart.getValue(msColChartOpt);
				
				var vaChartTypOpts = vsChartTypOpt.split(msCaretSeparated);
				var vsChartTypOptCond = vaChartTypOpts.map(function(each) {
					return msColCommonCd + "== '" + each + "'";
				}).join("||");
				
				vcCmbChartTyp.setItemSet(vcDsCmnCd, {
					label: msColCodeNm,
					value: msColCommonCd
				});
				
				vcCmbChartTyp.setFilter(vsChartTypOptCond);
				
				vcCmbChartTyp.userAttr("chart-type", "true");
				
				vcCmbChartTyp.value = vcDmChart.getValue(msColChartTyp);
				
				if (mbWebAccessibility) {
					vcCmbChartTyp.tooltip = msLangChartTyp;
				}
				
				vcCmbChartTyp.addEventListener("selection-change", function(e) {
					//TODO 차트 유형 변경 시 발생할 로직을 작성하십시오.
					var vcGrpChartInfo = e.control.getParent();
					
					/** @type String[] */
					var vaSrchCondVals = vcGrpChartInfo.getAllRecursiveChildren(false).filter(function(each) {
						return each.type == "combobox" && each.userAttr("chart-legend") == "true";
					}).map(function( /* cpr.controls.ComboBox */ each) {
						return each.value;
					});
					var vsSrchCondVals = vaSrchCondVals.length > 0 ? vaSrchCondVals.join(msCaretSeparated) : null;
					
					var vsChartType = e.newSelection[0].value;
					var vaChart = vcGrpChartInfo.getAppInstance().getContainer().getChildren().filter(function(each) {
						return each.type == "udc.cmn.ApexChart";
					});
					if (vaChart.length > 0) {
						vaChart.forEach(function( /*udc.cmn.ApexChart*/ each) {
							each.render(vsChartType, vsSrchCondVals);
						});
					}
				});
				
				vcGrpChartInfo.addChild(vcCmbChartTyp, {
					width: msCmbChartTypOptWidth,
					height: "100%"
				});
				
				/* 차트 정보 중 차트 범례 정보 생성 */
				/** @type String */
				var vsSrchCond = vcDmChart.getValue(msColSrchCondIdx) || vcDmChart.getValue(msColSrchCondAxs); // 차트
				/** @type String */
				var vsYaxsDfVlu = vcDmChart.getValue(msColYaxsDefaultIdxVlu) || vcDmChart.getValue(msColDefaultAxs);
				var vbMultiChart = ValueUtil.isNull(vcDmChart.getValue(msColSrchCondIdx)); // 멀티 축 여부
				
				if (!ValueUtil.isNull(vsSrchCond)) {
					var vcLblChartLegend = new cpr.controls.Output();
					vcLblChartLegend.value = msLangChartLegend;
					
					vcGrpChartInfo.addChild(vcLblChartLegend, {
						height: "100%",
						autoSize: "width"
					});
					
					/** @type String[] */
					var vaSrchCond = _refineChartData(vbMultiChart, vsSrchCond, msCaretSeparated, true);
					/** @type String */
					var vsYaxsDfVlu = _refineChartData(vbMultiChart, vsYaxsDfVlu, msCaretSeparated);
					
					/** @type Array */
					var arr = _chunk(vaSrchCond);
					for (var idx = 0; idx < arr.length; idx++) {
						var vcCmbLegend = _createLegendList(vbMultiChart, {
							items: arr[idx],
							value: vsYaxsDfVlu,
							row: vcGrdMst,
							column: vcDsGrd
						});
						vaComboTemp.push(vcCmbLegend);
						
						vcGrpChartInfo.addChild(vcCmbLegend, {
							width: "180px",
							height: "100%"
						});
					}
				}
				
				return vcGrpChartInfo;
			}
			var vaComboTemp = [];
		globals.getChartCombo = function(){
			return vaComboTemp;
		}
			
			/**
			 * 
			 * @param {Boolean} multi
			 * @param {String} value
			 * @param {String} delimiter
			 * @param {Boolean} split
			 */
			function _refineChartData(multi, value, delimiter, split) {
				var vsRefineVal = value;
				
				if (!multi) {
					var vsSplitCd = vsRefineVal.substring(0, 1); // 첫 코드값 (C 또는 R)
					
					// e.g. C1C2C3 to C1^C2^C3 for split
					vsRefineVal = _parser(vsRefineVal, vsSplitCd, delimiter);
				}
				
				if (split === true) {
					var vaRefineVal = vsRefineVal.split(delimiter);
				}
				
				return vaRefineVal || vsRefineVal;
			}
			
			/**
			 * 
			 * @param {any} multi
			 * @param {
			 *   {
			 *     items : String[] <!-- 아이템 구성 정보 -->,
			 *     value : String <!-- 기본값 -->,
			 *     row : cpr.controls.Grid <!-- R기준, 아이템의 라벨값 추출 그리드 -->,
			 *     column : cpr.data.DataSet <!-- C기준, 아이템의 라벨값 추출 데이터셋 -->
			 *   }
			 * } config
			 */
			function _createLegendList(multi, config) {
				var vcCmbSrchCond = new cpr.controls.ComboBox();
				
				var vaSrchCond = config.items;
				var vsYaxsDfVlu = config.value;
				var vsStrCd = _extract("character", config.items[0]);
				
				/* 콤보박스 아이템 설정 */
				for (var itemIdx = 0; itemIdx < vaSrchCond.length; itemIdx++) {
					var vsSrchCondVal = vaSrchCond[itemIdx];
					
					var vsSrchCondTxt = null;
					if (vsSrchCondVal.indexOf("R") != -1) {
						vsSrchCondTxt = _getRowText(config.row, vsSrchCondVal);
					} else if (vsSrchCondVal.indexOf("C") != -1) {
						vsSrchCondTxt = _getColumnText(config.column, vsSrchCondVal);
					}
					
					if (!(ValueUtil.isNull(vsSrchCondTxt))) {
						vcCmbSrchCond.addItem(new cpr.controls.Item(vsSrchCondTxt, vsSrchCondVal));
					}
				}
				
				/* 콤보박스 속성 및 이벤트 설정 */
				if (!multi && vsStrCd.indexOf("R") == -1) {
					vcCmbSrchCond.multiple = true;
				}
				
				vcCmbSrchCond.userAttr("chart-legend", "true");
				
				/* 콤보박스 기본값 설정(일치하는 아이템이 없는 경우 첫번째 아이템 선택) */
				var vsDelimiter = vcCmbSrchCond.delimiter;
				var vsCvYaxsDfVluVal = vsYaxsDfVlu.replace(/[\^]/g, vsDelimiter);
				
				var vaCvYaxsDfVluVal = vsCvYaxsDfVluVal.split(vsDelimiter);
				
				vaCvYaxsDfVluVal.forEach(function(each) {
					vcCmbSrchCond.selectItemByValue(each);
				});
				
				if (vcCmbSrchCond.getSelection().length == 0) {
					vcCmbSrchCond.selectItem(0);
				}
				
				vcCmbSrchCond.addEventListener("selection-change", function(e) {
					//TODO 차트 범례 변경 시 발생할 로직을 작성하십시오.
					var vcGrpChartInfo = e.control.getParent();
					
					var vsChartType = vcGrpChartInfo.getAllRecursiveChildren(false).filter(function(each) {
						return each.type == "combobox" && each.userAttr("chart-legend") == "";
					}).map(function( /* cpr.controls.ComboBox */ each) {
						return each.value;
					});
					
					/** @type String[] */
					var vaSrchCondVals = vcGrpChartInfo.getAllRecursiveChildren(false).filter(function(each) {
						return each.type == "combobox" && each.userAttr("chart-legend") == "true";
					}).map(function( /* cpr.controls.ComboBox */ each) {
						return each.value;
					});
					
					var vsSrchCondVals = vaSrchCondVals.length > 0 ? vaSrchCondVals.join(msCaretSeparated) : null;
					
					var vaChart = vcGrpChartInfo.getAppInstance().getContainer().getChildren().filter(function(each) {
						return each.type == "udc.cmn.ApexChart";
					});
					if (vaChart.length > 0) {
						vaChart.forEach(function( /*udc.cmn.ApexChart*/ each) {
							each.render(vsChartType[0], vsSrchCondVals);
						});
					}
				});
				
				return vcCmbSrchCond;
			}
			
			/* Unit Kit */
			
			/**
			 * 
			 * @param {cpr.core.AppInstance} app 앱 인스턴스
			 * @param {cpr.data.DataSet} pcDataSet 정보 데이터 셋
			 */
			function makeUnitInfo(app, pcDataSet) {
				if (ValueUtil.isNull(pcDataSet)) {
					return;
				}
				
				/** @type cpr.data.DataSet */
				var vcDsGrdInfo = pcDataSet;
				
				// 시장지수 단위 표시 추가 (2020.12.10)
				var vcGrpSearch = app.lookup("grpSearch");
				if(vcGrpSearch) {
					var vaJisuEmb = vcGrpSearch.getAllRecursiveChildren().filter(function(each) {
						return each.userAttr("jisu") == "true";
					});
					
					if(vaJisuEmb.length > 0) {
						var vcLblUnitTxt = new cpr.controls.Output();
						vcLblUnitTxt.userAttr("info-unit-jisu", "true");
						vaJisuEmb.forEach(function(/*cpr.controls.EmbeddedApp */ each) {
							/** @type cpr.controls.ComboBox */
							var vcCmbJisu = each.getEmbeddedAppInstance().getContainer().getAllRecursiveChildren().filter(function(each){
								return each.userAttr("jisu-unit") == "true";
							})[0];
							
							var vsUnit = vcCmbJisu.getSelection()[0].row.getValue("UNIT");
							vcLblUnitTxt.value = msLangUnit + " : " + vsUnit;
						})
						
						return vcLblUnitTxt;
					}
				}
				
				var vsBasicUnitDsp = vcDsGrdInfo.getValue(0, msColBasicUnitDsp); // 기본단위표시
				var vsBasicUnit = vcDsGrdInfo.getValue(0, msColBasicUnit); // 기본단위
				
				/* 
				 * 기본단위표시값이 없는 경우
				 * 단위 생성에 대한 조건을 알 수 없으므로 단위를 생성하지 않는다.
				 */
				if (ValueUtil.isNull(vsBasicUnitDsp)) {
					return;
				}
				
				/* 단위 정보를 감싸는 컨테이너(그룹) 생성 - 해당 그룹에 단위 정보가 추가된다. */
				var vcGrpUnitInfoWrap = new cpr.controls.Container();
				var voGrpUnitInfoWrapLt = new cpr.controls.layouts.FlowLayout();
				vcGrpUnitInfoWrap.userAttr("unit-box", "true");
				
				voGrpUnitInfoWrapLt.scrollable = false;
				voGrpUnitInfoWrapLt.spacing = mnUnitLtSpacing;
				
				vcGrpUnitInfoWrap.setLayout(voGrpUnitInfoWrapLt);
				
				/* 단위 텍스트 생성 */
				var vcLblUnitTxt = new cpr.controls.Output();
				vcLblUnitTxt.value = msLangUnit + " :";
				
				vcGrpUnitInfoWrap.addChild(vcLblUnitTxt, {
					height: "100%",
					autoSize: "width"
				});
				
				/* 콤보박스 형태 단위 또는 텍스트 형태 단위 생성 */
				
				/* 
				 * 데이터 불일치 문제에 대한 해결로써 데이터 재정제.
				 * 특수한 문자(^) 앞에 구분자(;)를 넣는다.
				 * i.e. 명,건,^원 -> 명,건,;^원
				 */
				vsBasicUnitDsp = _parser(vsBasicUnitDsp, msCaretSeparated, msSemicolonSeparated);
				
				/* 멀티 스플릿하여 문자열을 배열로 전환 */
				var vaSplittingBasicUnitDsp = _splits(vsBasicUnitDsp, [msSemicolonSeparated, msCaretSeparated]); // e.g. 명,건,^원
				var vaSplittingBasicUnit = _splits(vsBasicUnit, [msSemicolonSeparated]); // e.g. T2050^08;T2059^06
				
				//kimjj append 2020.12.23 
				if( vaSplittingBasicUnit.length > 0 && vaSplittingBasicUnitDsp.length == 1){
					vaSplittingBasicUnitDsp = _splits(vsBasicUnitDsp + "^", [msSemicolonSeparated, msCaretSeparated]); // e.g. 명,건,^원
				}
				
				var vnComboboxCnt = 0;
				
				/* 
				 * 기본단위표시 배열 개수만큼 단위를 생성.
				 * 콤보박스 형태 단위는 별개로 카운팅하여 콤보박스인지 텍스트인지에 대한 형태를 구분(vnCmbIdx)
				 */
				for (var vnDspIdx = 0, vnCmbIdx = 0; vnDspIdx < vaSplittingBasicUnitDsp.length; vnDspIdx++) {
					var vsBasicUnitDspVal = vaSplittingBasicUnitDsp[vnDspIdx]; // 기본단위표시 단일 값
					
					if (ValueUtil.isNull(vsBasicUnitDspVal)) { // 공백("") 값인 경우 콤보박스 단위 생성
						var vsBasicUnitVal = vaSplittingBasicUnit[vnCmbIdx]; //콤보박스 단위 목록 값 및 기본값
						
						if (!ValueUtil.isNull(vsBasicUnitVal)) { // 코드가 존재하는 단위만 콤보박스 생성(2020.08.28 daye 추가)
							
							vnComboboxCnt = vnComboboxCnt + 1;
							
							var vcCmbUnitDsp = new cpr.controls.ComboBox("tempV" + vnDspIdx);
							vcCmbUnitDsp.style.item.css("text-align","right");
							vcCmbUnitDsp.style.item.css("padding","0px 12px 0px 0px");
							
							app.register(vcCmbUnitDsp);
							
							if (!ValueUtil.isNull(vsBasicUnitVal)) { // 콤보박스 관련 값이 있는 경우에만 로직 연결
								var vaSplittingUnitVal = vsBasicUnitVal.split(msCaretSeparated);
								var vsBasicUnitCd = vaSplittingUnitVal[0]; // 콤보박스 목록 코드값 (공통 코드)
								var vsBasicUnitDfVal = vaSplittingUnitVal[1]; // 콤보박스 기본값
								
								/* 일치하는 공통 코드가 있는 경우에만 목록 및 기본값 설정 */
								var vcDsCmnCd = init.DataSet.getCodeDataSet(app, vsBasicUnitCd);
								if (!ValueUtil.isNull(vcDsCmnCd)) {
									vcCmbUnitDsp.setItemSet(vcDsCmnCd, {
										label: msColCodeNm,
										value: msColCommonCd
									});
									
									var vcDmReqData = init.DataMap.getRequestDataMap(app);
									
									var vsBindUnitColNm = msColBindUnit1;
								
									if (vaSplittingBasicUnit.length > 1) {
										vsBindUnitColNm = vnComboboxCnt == 1 ? msColBindUnit1 : msColBindUnit2;
									}
									
									//수정한 부분 20201207 바인딩 삭제
									//vcCmbUnitDsp.bind("value").toDataMap(vcDmReqData, vsBindUnitColNm);
									
									vcCmbUnitDsp.userAttr({
										"vsBasicUnitCd": vsBasicUnitCd,
										"vsBasicUnitCdCnt": vnComboboxCnt + "",
										"vsBasicUnitCnt": vaSplittingBasicUnit.length + "",
										"vsBasicUnitColNm" : vsBindUnitColNm +""//2020-12-22추가
									});
									
									vcCmbUnitDsp.addEventListener("selection-change", function(e) { 
									  
										var control = e.control; 
										
										var vsUattBasicUnitCd = control.userAttr("vsBasicUnitCd");
										var vsUattBasicUnitCdCnt = control.userAttr("vsBasicUnitCdCnt");
										var vsUattBasicUnitCnt = control.userAttr("vsBasicUnitCnt");
									 
											var vsKeyValue = e.newSelection[0];
											var vcFindRow = control.dataSet.findFirstRow("COMMON_CD == '" + vsKeyValue.value + "'");
											if (vcFindRow != null) {										
												var vnFindRowIndex = vcFindRow.getIndex();
												var vsTemp = control.dataSet.getValue(vnFindRowIndex, "CODE_ENGNM");
												if (vsUattBasicUnitCdCnt == "1" && vsUattBasicUnitCnt == "1") {
													vcDmReqData.setValue(control.userAttr("vsBasicUnitColNm"), vsTemp);
													vcDmReqData.setValue(msColBindUnit2, "1"); 
		//											vcDmReqData.setValue(vsBindUnitColNm, vsTemp);
		//											vcDmReqData.setValue(msColBindUnit2, vcDsCmnCd.getValue(vnFindRowIndex, "CODE_NM")); 
												}else{
													vcDmReqData.setValue(control.userAttr("vsBasicUnitColNm"), vsTemp);
												}
										}
										
										//TODO 데이터 조회에 대한 동작을 (서브미션 전송) 작성하십시오.
										/** @type cpr.core.AppInstance */
										var _app = e.control.getAppInstance();
										if(_app){
											/** @type cpr.controls.Button */	
											var vcSearchBtn = _app.lookup("btn1");
											vcSearchBtn.click();
										}
																		
										
									});
									//event SetItem 
									vcCmbUnitDsp.selectItemByValue(vsBasicUnitDfVal);
									//kimjj append 2020.12.23
									var vTempValue = vcCmbUnitDsp.value ;
									if( vTempValue == null || vTempValue == ''){
										vcCmbUnitDsp.selectItem(0);
									} 
									
									
								} // end of if
								
								//XXX 웹접근성 관련 코드 위치 수정
								if (mbWebAccessibility) {
									vcCmbUnitDsp.tooltip = msLangUnit + "(" + ValueUtil.fixNull(vaSplittingBasicUnitDsp[vnDspIdx + 1]) + ")";
								}
								
								vnCmbIdx++;
							} // end of if
							
							/* 콤보박스 관련 값 유무에 상관없이 콤보박스 추가 */
							vcGrpUnitInfoWrap.addChild(vcCmbUnitDsp, {
								height: "100%",
								width: msCmbUnitDspWidth
							});
						} else {
							if (vaSplittingBasicUnitDsp.length > vnDspIdx + 1) {
								vaSplittingBasicUnitDsp[vnDspIdx + 1] = ", " + vaSplittingBasicUnitDsp[vnDspIdx + 1]; // 코드는 없지만 단위는 존재할 경우, 콤마(,) 표시
							}
						}
						
					} else { // 공백("") 값이 아닌 경우 일반 텍스트 단위 생성
						var vcLblUnitDsp = new cpr.controls.Output();
						vcLblUnitDsp.value = vsBasicUnitDspVal;
						
						vcGrpUnitInfoWrap.addChild(vcLblUnitDsp, {
							height: "100%",
							autoSize: "width"
						});
					} // end of if
				} // end of for
				
				return vcGrpUnitInfoWrap;
			}
			
			/* Button Kit */
			
			/**
			 * 
			 * @param {cpr.core.AppInstance} app
			 * @param {
			 *   {
			 *     info : cpr.data.DataSet <!-- 기본 정보 데이터 셋 -->,
			 *     grid : {
			 *       control : cpr.controls.Grid <!-- 그리드 컨트롤 객체 -->,
			 *       data : cpr.data.DataSet <!-- 그리드 생성 관련 데이터 셋 (컬럼 정보 데이터 셋) -->
			 *     },
			 *     chart : {
			 *       control? : cpr.controls.UDCBase <!-- 차트 컨트롤 객체 -->,
			 *       data : cpr.data.DataMap <!-- 차트 생성 관련 데이터 맵 -->
			 *     }
			 *   }
			 * } poConfig 
			 */
			function makeButtonInfo(app, poConfig) {
				if (ValueUtil.isNull(poConfig)) {
					return;
				}
				
				/** @type cpr.controls.Grid */
				var vcGrdMst = poConfig["grid"]["control"];
				/** @type cpr.data.DataSet */
				var vcDsGrd = poConfig["grid"]["data"];
				
				/** @type cpr.data.DataMap */
				var vcDmChart = poConfig["chart"]["data"];
				
				var vcGrdInfoBtns = new udc.cmn.GridInfoButtons();
				
				vcGrdInfoBtns.grid = vcGrdMst;
				
				/* 그리드가 그려지지 않았거나 차트 정보가 없는 경우 레이아웃 변경 버튼 숨김 */
				var vsOutputFormNo = vcDmChart.getValue(msColOutputFormNo);
				if (vcDsGrd.getRowCount() === 0 || ValueUtil.isNull(vsOutputFormNo)) {
					vcGrdInfoBtns.setLayoutButtonVisible(false);
				}
				
				return vcGrdInfoBtns;
			}
			
			return {
				Display: {
					getInfoBox: displayInfo,
					getRowSize: getDisplayRowSize,
					revertInfoBox: revertInfoBox
				},
				Basic: {
					makeInfoBox: makeBasicInfoBox,
					makeInfo: makeBasicInfo
				},
				Grid: {
					makeInfo: makeGridInfo
				},
				Chart: {
					makeInfo: makeChartInfo
				},
				Unit: {
					makeInfo: makeUnitInfo
				}
			}
		})();
		globals.info = information;
		
		/************************************************
		 * 유틸성 비공개 함수
		 ************************************************/
		
		/**
		 * 배열을 특정 사이즈로 나눕니다.
		 * @private
		 * @param {Array} array
		 * @param {Number} size
		 */
		function _chunk(array, size) {
			/*
			 * var chunk = [];
			 * for(var idx = 0; idx < array.length; idx+= size){
			 * 	chunk.push(array.slice(idx, idx + size));
			 * }
			 * 
			 * return chunk;
			 */
			
			var extract = array.map(function(each) {
				return _extract("character", each);
			});
			
			var uniq = _.uniq(extract);
			
			var chunk = [];
			
			var tmp = array.join("");
			
			for (var idx = 0; idx < uniq.length; idx++) {
				var first = extract.indexOf(uniq[idx]);
				var last = extract.lastIndexOf(uniq[idx]);
				
				chunk.push(array.slice(first, last + 1));
			}
			
			return chunk;
		}
		
		/**
		 * 
		 * @private
		 * @param {String} value
		 * @param {String[]} tokens
		 * @return {String[]}
		 */
		function _splits(value, tokens) {
			var tempChar = tokens[0]; // We can use the first token as a temporary join character
			for (var i = 1; i < tokens.length; i++) {
				value = value.split(tokens[i]).join(tempChar);
			}
			value = value.split(tempChar);
			return value;
		}
		
		/**
		 * 
		 * @private
		 * @param {String} value
		 * @param {String} token
		 * @param {String} seperator
		 * @return {String}
		 */
		function _parser(value, token, seperator) {
			if (value == null || value == "") {
				return null;
			}
			
			var buffer = [];
			var chars = value.split("");
			for (var idx = 0; idx < chars.length; idx++) {
				var char = chars[idx];
				
				if (idx > 0 && (char == token && buffer[buffer.length - 1] != seperator)) { // 추가
					buffer.push(seperator);
					
					if (chars[idx + 1] == token) { // 교체
						continue;
					}
				}
				
				buffer.push(char);
			}
			
			return buffer.join("");
		}
		
		/**
		 * 
		 * @param {"number" | "character"} type
		 * @param {String} value
		 */
		function _extract(type, value) {
			if (ValueUtil.isNull(type) || ValueUtil.isNull(value)) {
				return;
			}
			
			var vaExtracted = [];
			if (type == "number") {
				vaExtracted = value.match(/\d+/g);
			} else if (type == "character") {
				vaExtracted = value.match(/[a-zA-Z]+/g);
			}
			
			var vsExtractedText = vaExtracted.join("");
			if (ValueUtil.isNumber(vsExtractedText)) {
				vsExtractedText = ValueUtil.fixNumber(vsExtractedText);
			}
			
			return vsExtractedText;
		}
		
		/**
		 * 
		 * @private
		 * @param {cpr.data.DataSet} pcDsHdr
		 * @param {String} psValue
		 */
		function _getColumnText(pcDsHdr, psValue) {
			if (ValueUtil.isNull(pcDsHdr)) {
				return;
			}
			
			/** @type cpr.data.DataSet */
			var vcDsHdr = pcDsHdr;
			
			var vnColIdx = _extract("number", psValue);
			
			var vcTreGrdHd = new cpr.controls.Tree();
			vcTreGrdHd.setItemSet(vcDsHdr, {
				label: "HEADER_NM",
				value: "HEADER_ID",
				parentValue: "PARENT_HDRID"
			});
			
			var vcMatchedItem = vcTreGrdHd.getItems().filter(function(each) {
				if ( each.row != null && each.row.getValue("HEADER_ID") !=null && each.row.getValue("HEADER_ID").indexOf("TEST") == -1) { // 2020.10.19 분석통계 데이터 제외 수정
					return !(vcTreGrdHd.hasChild(each));
				}
			})[vnColIdx];
			
			var vaHdTxt = [];
			var vcTgItem = vcMatchedItem;
			while (vcTgItem) {
				vaHdTxt.push(vcTgItem.label);
				vcTgItem = vcTgItem.parentItem;
			}
			
			/* 데이터 역순 정렬 */
			vaHdTxt.reverse();
			
			return vaHdTxt.join("-");
		}
		//2020-12-10 추가
		/**
		 * 
		 * @param {cpr.controls.Grid} pcGrid
		 * @param {String} psValue
		 */
		function _getColumnText2(pcGrid, psValue) {
		//	var vcGrid = pcGrid;
		//	var index = getIndex(psValue);
		//	var vnIndex = Number(index);
		//	
		//	var vaVisibleCol = getVisibleCol(vcGrid);
		//	var vsColNm = vaVisibleCol[vnIndex];
		//	
		//	var vaDetailCol = vcGrid.detail.getColumnByName(vsColNm)[0];
		//	
		//	var detailCell = vaDetailCol.cellProp.constraint.cellIndex;
		//	var headerCell = vcGrid.getHeaderCellIndices(detailCell);
		//	var vsResult = "";
		//	if(headerCell.length > 0) {
		//		headerCell.forEach(function(each){
		//			var colText = vcGrid.header.getColumn(each);
		//			
		//			vsResult += colText.text + "-";
		//		});
		//		vsResult = vsResult.substr(0,vsResult.length-1);
		//	}
		//	return vsResult;
		}
		
		function getIndex(psValue,pbRetrnStr) {
			
			if(ValueUtil.isNull(psValue)) {
				return;
			}
			
			if(ValueUtil.isNull(pbRetrnStr)) pbRetrnStr = true;
			
			//문자열에서 숫자만 추출
			var vaExtractVals = psValue.match(/\d+/g);
			var vsExtractVal = vaExtractVals.join("");
			
			if(pbRetrnStr) {
				return parseInt(vsExtractVal);
			} else {
				return vaExtractVals;
			}
		}
		/**
		 * 
		 * @param {cpr.controls.Grid} pcGrid
		 */
		function getVisibleCol(pcGrid){
			
			var vcGrd = pcGrid;
			var cellCnt = vcGrd.detail.cellCount;
			var vaResult = [];
			
			for(var i = 0 ; i < cellCnt; i++) {
				
				var voColumn = vcGrd.detail.getColumn(i);
				var vaHeaderCellIdxs = vcGrd.getHeaderCellIndices(i);
				
				if(vaHeaderCellIdxs.length > 0) {
					
					vaHeaderCellIdxs.forEach(function(each){
						var headerCell = vcGrd.header.getColumn(each);
						if(headerCell.targetColumnName != "" && headerCell.visible) {
							
							vaResult.push(headerCell.targetColumnName);
						}
					});
				}
			}
			
			return vaResult;
		}
		/**
		 * 
		 * @private
		 * @param {cpr.controls.Grid} pcGrid
		 * @param {String} psValue
		 */
		function _getRowText(pcGrid, psValue) {
			if (ValueUtil.isNull(pcGrid)) {
				return;
			}
			
			/** @type cpr.controls.Grid */
			var vcGrdMst = pcGrid;
			/** @type cpr.data.DataSet */
			var vcDsGrd = vcGrdMst.dataSet;
			
			var vnRowIndex = _extract("number", psValue);
			
			var vsRowText = vcGrdMst.getCellValue(vnRowIndex, 0);
			
			return vsRowText;
		}
		
		/**
		 * 
		 * @param {cpr.controls.Grid} pcGrid
		 * @param {String} psValue
		 * @param {String} psValue2
		 */
		function _getRowText2(pcGrid,psValue,psValue2){
			var vsLabel = "";
			var vaVisibleCols = getVisibleCol(pcGrid);
			var vcInsDs = pcGrid.dataSet;
			var vnInsTargetFinder = Number(getIndex(psValue2, false)[0]);
					var vaArr = [];
					for(var i = 0 ; i < vnInsTargetFinder ; i++) {
						vaArr.push(i);
					}
					if(vaArr.length > 0) {
						
						var voRow = vcInsDs.getRow(getIndex(psValue));
						vaArr.forEach(function(eachCol){
							vsLabel += pcGrid.getCellValue(getIndex(psValue),eachCol) +'-';
						});
						if(vsLabel != ""){
							vsLabel = vsLabel.substr(0,vsLabel.length-1);
						}
					}
					return vsLabel;
		}
	});
})();
/// end - module/information
/// start - module/initialize
/*
 * Module URI: module/initialize
 * SRC: module/initialize.module.js
 *
 * This file was generated by eXbuilder6 compiler, Don't edit manually.
 */
(function(){
	cpr.core.Module.define("module/initialize", function(exports, globals, module){
		/************************************************
		 * initialize.module.js
		 * Created at 2020. 7. 2. 오전 11:18:19.
		 *
		 * @author ryu
		 ************************************************/
		
		/************************************************
		 * 전역 변수 선언
		 ************************************************/
		
		/* 구분자 */
		
		/**
		 * 콤마 구분자 
		 */
		var msCommaSeparated = ",";
		
		/**
		 * 세미콜론 구분자
		 */
		var msSemicolonSeparated  = ";";
		
		/**
		 * 캐럿 구분자
		 */
		var msCaretSeparated = "^";
		
		/* 데이터 셋 */
		
		/**
		 * 그리드와 연결되는 데이터 셋 아이디 접두사
		 */
		var msResponseDataDataSetPrefix = "ds";
		
		/**
		 * 공통 코드 데이터 셋 아이디 접두사
		 */
		var msCodeDataSetPrefix = "ds";
		
		/* 데이터 맵 */
		
		/**
		 * 조회 조건과 연결되는 파라미터 데이터 맵 명칭
		 */
		var msRequestDataDataMapId = "dmSearch";
		
		/* 데이터 뷰 */
		
		/**
		 * 공통 코드 데이터 뷰 아이디 접두사
		 */
		var msCodeDataViewPrefix = "dv"; 
		
		/**
		 * TODO 적용 시 공백으로 수정하십시오.
		 * 그리드 서브미션 action 접두사
		 */
		var msActionPrefixBefore = "/meta/";
		//var msActionPrefixBefore = "data/";
		
		/**
		 * TODO 적용 시 공백으로 수정하십시오.
		 * 그리드 서브미션 action 접미사
		 */
		var msActionPrefixAfter = ".do";
		//var msActionPrefixAfter = ".json";
		
		/**
		 * true 일 경우 submission Action이 msDefaultUrl으로 고정됨 
		 * and request DataMap에 OBJ_NM( submission Column_nm )가 추가됨 
		 */
		var mIsSendDefaultUrl  = true; 
		/**
		 * 서브미션의 기본 url을 설정한다. 
		 * 조건 : 대상 컬럼명이 없을 경우 
		 */
		var msDefaultUrl = "/messagebroker/amf"
		
		
		/************************************************
		 * 글로벌 출판 사용자 모듈
		 ************************************************/
		
		var initialize = (function(unit){
			/* 비공개 멤버 변수 선언 */
			//TODO 해당 함수 내에서만 사용할 비공개 멤버 변수를 선언하십시오.
			
			/* DataSet Kit */
			
			/**
			 * 데이터셋 초기 속성, Column 정보, Row Data 정보를 세팅합니다.
			 * @param {cpr.core.AppInstance} app 앱 인스턴스
			 * @param {String} psDataSetId 데이터 셋 아이디
			 * @param {
			 *   {
			 *     alterColumnLayout : "client" | "server" | "merge" <!-- 컬럼 구조 변경 기준 -->,
			 *     sortCondition? : String <!-- 정렬 조건 -->,
			 *     filterCondition? : String <!-- 필터링 조건 -->,
			 *     info? : String <!-- 데이터 셋 정보 -->,
			 *     stateRestore? : Boolean <!-- 초기값 저장 여부 -->,
			 *     columns : {
			 *       name : String <!-- 컬럼명 -->,
			 *       dataType? : "string" | "number" | "decimal" | "expression" <!-- 컬럼 타입 -->,
			 *       displayOnly? : Boolean <!-- 원본 데이터 기억 불가 여부 -->,
			 *       expression? : #expression <!-- 표현식 -->,
			 *       info? : String <!-- 컬럼 정보값 -->
			 *     }[] <!-- 컬럼 설정 정보 -->,
			 *     rows? : Object[] <!-- 행 데이터 -->
			 *   }
			 * } poConfig 데이터 셋 생성 정보
			 * @param {Boolean} pbRegister? 식별 가능한 개체 등록 여부 (default:true)
			 * @return {cpr.data.DataSet}
			 */
			function registerDataSet(app, psDataSetId, poConfig, pbRegister) {
				if (ValueUtil.isNull(psDataSetId)){
					return;
				}
				
				var vcDataSet = new cpr.data.DataSet(psDataSetId);
				vcDataSet.userAttr("auto-registered", "true");
				
				vcDataSet.parseData(poConfig);
				
				var vbRegister = ValueUtil.isNull(pbRegister) ? true : pbRegister;
				if (vbRegister){
					app.register(vcDataSet);
				}
				
				return vcDataSet;
			}
			
			
			/**
			 * 그리드와 연결되는 데이터 셋을 동적으로 생성합니다.
			 * 그리드와 연결되는 데이터 셋의 아이디는 prefix(default:ds) +KEY값 + 숫자(1부터) 입니다.
			 * @param {cpr.core.AppInstance} app 앱 인스턴스
			 * @param {cpr.data.DataSet} pcDataSet 데이터 셋
			 * @param {#column} psColumnName KEY에 해당하는 데이터 셋 컬럼명
			 */
			function registerResponseDataDataSet(app, pcDataSet, psColumnName) {
		
				var vcDsGrdObj = pcDataSet;
				
				if (ValueUtil.isNull(vcDsGrdObj)){
					return;
				}
				
				/* 데이터 셋에 해당 컬럼명을 가진 컬럼이 있는지 판별 */
				var voObjColumn = vcDsGrdObj.getColumn(psColumnName);
				if (ValueUtil.isNull(voObjColumn)){
					return;
				}
		
				for(var idx = 0; idx < vcDsGrdObj.getRowCount(); idx++){
					var vsObjNm = vcDsGrdObj.getValue(idx, psColumnName);
					
					registerDataSet(app, msResponseDataDataSetPrefix + (idx + 1), {
		//			registerDataSet(app, msResponseDataDataSetPrefix + vsObjNm + (idx + 1), {
						alterColumnLayout : "server",
						info : vsObjNm
					}).userAttr("targetCol",psColumnName);
					
				}
			}
			
			
			/**
			 * 공통 코드 데이터 셋을 동적으로 생성합니다.
			 * 공통 코드 데이터 셋의 아이디는 prefix(default:ds) + 공통 코드값 입니다.
			 * @param {cpr.core.AppInstance} app 앱 인스턴스
			 * @param {cpr.data.DataSet} pcDataSet 데이터 셋
			 * @param {#column} psColumnName 코드 유형(group)에 해당하는 데이터 셋 컬럼명
			 */
			function registerCodeDataSet(app, pcDataSet, psColumnName) {
			
				var vcDsCmnCd = pcDataSet
				
				if (ValueUtil.isNull(vcDsCmnCd)){
					return;
				}
				
				/* 데이터 셋에 해당 컬럼명을 가진 컬럼이 있는지 판별 */
				var voCdColumn = vcDsCmnCd.getColumn(psColumnName);
				if (ValueUtil.isNull(voCdColumn)){
					return;
				}
				
				var vaDstnctVals = vcDsCmnCd.getUnfilteredDistinctValues(psColumnName);
				
				for(var idx = 0; idx < vaDstnctVals.length; idx++){
					var vsDstnctVal = vaDstnctVals[idx];
					
					var vaAllHds = vcDsCmnCd.getHeaders();
					
					var vaNewDsColumn = vaAllHds.map(function(each){
						return {
							name : each.getName(),
							dataType : each.getDataType(),
							info : each.getInfo()
						};
					});
					
					var vaFindAllRows = vcDsCmnCd.findAllRow(psColumnName + "== '" + vsDstnctVal + "'");
					
					var vaNewDsRow = vaFindAllRows.map(function(each){
						return each.getRowData();
					});
					
					registerDataSet(app, msCodeDataSetPrefix + vsDstnctVal, {
						info : vsDstnctVal,
						columns : vaNewDsColumn,
						rows : vaNewDsRow
					});
				}
			}
			
			
			/**
			 * 동적으로 등록된 데이터 셋을 배열로 가져옵니다.
			 * 동적으로 등록된 데이터 셋이 없는 경우 빈 배열을 리턴합니다.
			 * @param {cpr.core.AppInstance} app 앱 인스턴스
			 * @return {cpr.data.DataSet[]}
			 */
			function getRegisteredDataSet(app, psFilter) {
				var vaRegistDataSet = [];
				
				var vaAllDataCtrls = app.getAllDataControls();
				for(var idx = 0; idx < vaAllDataCtrls.length; idx++){
					var vcDataCtrl = vaAllDataCtrls[idx];
					
					if (vcDataCtrl instanceof cpr.data.DataSet 
						&& vcDataCtrl.userAttr("auto-registered") == "true"){
						vaRegistDataSet.push(vcDataCtrl);
					}
				}
				
				return vaRegistDataSet;
			}
			
			
			/**
			 * 등록된 데이터 셋을 배열로 가져옵니다.
			 * 등록된 데이터 셋이 없는 경우 빈 배열을 리턴합니다.
			 * @param {cpr.core.AppInstance} app
			 */
			function getAllRegisteredDataSet(app) {
				var vaRegistDataSet = [];
				
				var vaAllDataCtrls = app.getAllDataControls();
				for(var idx = 0; idx < vaAllDataCtrls.length; idx++){
					var vcDataCtrl = vaAllDataCtrls[idx];
					
					if (vcDataCtrl instanceof cpr.data.DataSet){
						vaRegistDataSet.push(vcDataCtrl);
					}
				}
				
				return vaRegistDataSet;
			}
			
			
			/**
			 * 동적으로 등록된 공통 코드 데이터셋을 가져옵니다.
			 * @param {cpr.core.AppInstance} app
			 * @param {String} psValue
			 */
			function getRegisteredCodeDataSet(app, psValue) {
				var vaRegisteredDataSet = getRegisteredDataSet(app);
				
				for(var idx = 0; idx < vaRegisteredDataSet.length; idx++){
					var vcDataCtrl = vaRegisteredDataSet[idx];
					
					if (vcDataCtrl.info === psValue){
						return vcDataCtrl;
					}
				}
				
				return null;
			}
			
			/**
			 * 그리드와 연결된 데이터 셋을 배열로 가져옵니다.
			 * 일치하는 데이터 셋이 없는 경우 빈 배열을 리턴합니다.
			 * @param {cpr.core.AppInstance} app
			 * @param {String} psServletNm
			 * @return {cpr.data.DataSet[]}
			 */
			function getResponseDataSet(app, psServletNm) {
				var vaResDataSets = [];
				if (ValueUtil.isNull(psServletNm)){
					return vaResDataSets;
				}
				
				var vaAllDataCtrls = app.getAllDataControls();
				console.log(vaAllDataCtrls);
				for(var idx = 0; idx < vaAllDataCtrls.length; idx++){
					var vcDataCtrl = vaAllDataCtrls[idx];
					if (vcDataCtrl instanceof cpr.data.DataSet){
						if (vcDataCtrl.info == psServletNm){
							vaResDataSets.push(vcDataCtrl);
						}
					}
				}
				
				return vaResDataSets;
			}
			
			/* DataMap Kit */
			
			/**
			 * 데이터 맵 초기 속성, Column 정보, 기본값 정보를 세팅합니다.
			 * @param {cpr.core.AppInstance} app
			 * @param {String} psDataMapId
			 * @param {
			 *   {
			 *     alterColumnLayout? : "client" | "server" | "merge" <!-- 컬럼 구조 변경 기준 -->,
			 *     info? : String <!-- 데이터 맵 정보 -->,
			 *     columns : {
			 *       name : String <!-- 컬럼명 -->,
			 *       dataType? : "string" | "number" | "decimal" | "expression" <!-- 컬럼 타입 -->,
			 *       defaultValue? : String <!-- 기본값 -->,
			 *       expression? : #expression <!-- 표현식 -->,
			 *       info? : String <!-- 컬럼 정보값 -->
			 *     }[] <!-- 컬럼 정보 -->
			 *   }
			 * } poConfig 데이터 맵 생성 정보
			 * @param {Boolean} pbRegister
			 * @return {cpr.data.DataMap}
			 */
			function registerDataMap(app, psDataMapId, poConfig, pbRegister) {
				if (ValueUtil.isNull(psDataMapId)){
					return;
				}
				
				var vcDataMap = new cpr.data.DataMap(psDataMapId);
				vcDataMap.userAttr("auto-registered", "true");
				
				vcDataMap.parseData(poConfig);
				
				var vbRegister = ValueUtil.isNull(pbRegister) ? true : pbRegister;
				if (vbRegister){
					app.register(vcDataMap);
				}
				
				return vcDataMap;
			}
			
			
			/**
			 * 조회조건에 해당하는 데이터 맵을 동적으로 생성합니다.
			 * 정적 데이터 셋 아이디를 파라미터로 넘기면 고정적으로 추가 컬럼을 등록할 수 있습니다.
			 * 정적 데이터 셋에서 KEY에 해당하는 컬럼명이 없으면 일반 데이터 셋명으로 검색하여 등록합니다.
			 * @param {cpr.core.AppInstance} app 앱 인스턴스
			 * @param {cpr.data.DataSet} pcDataSet 데이터 셋
			 * @param {#column} psColumnName KEY에 해당하는 컬럼명
			 * @param {cpr.data.DataSet} pcStaticDataSet? 정적 데이터 셋
			 * @param {#column} psStaticColumnName? 정적 데이터 셋에서 KEY에 해당하는 컬럼명
			 */
			function registerRequestDataDataMap(app, pcDataSet, psColumnName, pcStaticDataSet, psStaticColumnName) {
		
				var vcDsSearch = pcDataSet;
				
				if (ValueUtil.isNull(vcDsSearch)){
					return;
				}
				
				var vaNewDmColumn = []; // 새로 생성되는 데이터 맵 컬럼
				
				/* 정적 컬럼 추가 */
				if (!ValueUtil.isNull(pcStaticDataSet)){
		
					var vcDsStatic = pcStaticDataSet;
					
					var vsStaticColumnName = psStaticColumnName || psColumnName;
					var voStaticColumn = vcDsStatic.getColumn(vsStaticColumnName);
					
					if (!ValueUtil.isNull(voStaticColumn)){
						for(var idx = 0; idx < vcDsStatic.getRowCount(); idx++){
							var vsStaticSearchVal = vcDsStatic.getValue(idx, vsStaticColumnName);
							
							if (ValueUtil.isNull(vsStaticSearchVal)){
								continue;
							}
							
							vaNewDmColumn.push({
								name : vsStaticSearchVal
							});
						}
					}
				}
				
				/* 검색 조건 컬럼 추가 */
				var voSearchColumn = vcDsSearch.getColumn(psColumnName);
				if (!ValueUtil.isNull(voSearchColumn)){
					for(var idx = 0; idx < vcDsSearch.getRowCount(); idx++){
						var vsSearchVal = vcDsSearch.getValue(idx, psColumnName);
						
						if (ValueUtil.isNull(vsSearchVal)){
							continue;
						}
						
						var vaSearchVals = vsSearchVal.split(msCommaSeparated);
						vaSearchVals.forEach(function(each){
							vaNewDmColumn.push({
								name : each
							});
						});
					}
				}
				
				registerDataMap(app, msRequestDataDataMapId, {
					columns : vaNewDmColumn
				});
			}
			
			
			/**
			 * 동적으로 등록된 데이터 맵을 배열로 가져옵니다.
			 * 동적으로 등록된 데이터 맵이 없는 경우 빈 배열을 리턴합니다.
			 * @param {cpr.core.AppInstance} app
			 */
			function getRegisteredDataMap(app) {
				var vaRegistDataMap = [];
				
				var vaAllDataCtrls = app.getAllDataControls();
				for(var idx = 0; idx < vaAllDataCtrls.length; idx++){
					var vcDataCtrl = vaAllDataCtrls[idx];
					
					if (vcDataCtrl instanceof cpr.data.DataMap 
						&& vcDataCtrl.userAttr("auto-registered") == "true"){
						
						vaRegistDataMap.push(vcDataCtrl);
					}
				}
				
				return vaRegistDataMap;
			}
			
			
			/**
			 * 등록된 데이터 맵을 배열로 가져옵니다.
			 * 등록된 데이터 맵이 없는 경우 빈 배열을 리턴합니다.
			 * @param {cpr.core.AppInstance} app
			 */
			function getAllRegisteredDataMap(app) {
				var vaRegistDataMap = [];
				
				var vaAllDataCtrls = app.getAllDataControls();
				for(var idx = 0; idx < vaAllDataCtrls.length; idx++){
					var vcDataCtrl = vaAllDataCtrls[idx];
					
					if (vcDataCtrl instanceof cpr.data.DataMap){
						vaRegistDataMap.push(vcDataCtrl);
					}
				}
				
				return vaRegistDataMap;
			}
			
			
			/**
			 * 
			 * @param {cpr.core.AppInstance} app
			 */
			function getRequestDataDataMap(app) {
				/** @type cpr.data.DataMap */
				var vcDmReqData = app.lookup(msRequestDataDataMapId);
				
				if (!ValueUtil.isNull(vcDmReqData)){
					return vcDmReqData;
				}
				
				return null;
			}
			
			/* DataView Kit */
			
			/**
			 * 데이터 뷰 초기 속성, Column 정보, Row Data 정보를 세팅합니다.
			 * @param {cpr.core.AppInstance} app 앱 인스턴스
			 * @param {cpr.data.DataSet} pcParentDataSet 타켓 데이터 셋 객체
			 * @param {String} psDateViewId 데이터 뷰 아이디
			 * @param {
			 *   {
			 *     sortCondition? : #expression <!-- 정렬 조건 -->,
			 *     filterCondition? : #expression <!-- 필터 조건 -->
			 *   }
			 * } poConfig? 데이터 뷰 생성 정보
			 * @param {Boolean} pbRegister
			 * @return {cpr.data.DataView}
			 */
			function registerDataView(app, pcParentDataSet, psDateViewId, poConfig, pbRegister) {
				if (ValueUtil.isNull(pcParentDataSet) || ValueUtil.isNull(psDateViewId)){
					return;
				}
				
				var vcDataView = new cpr.data.DataView(psDateViewId, pcParentDataSet);
				
				vcDataView.parseData(poConfig);
				
				var vbRegister = ValueUtil.isNull(pbRegister) ? true : pbRegister;
				if (vbRegister){
					app.register(vcDataView);
				}
				
				return vcDataView;
			}
			
			
			/**
			 * 공통 코드 데이터 뷰를 동적으로 생성합니다.
			 * 공통 코드 데이터 셋의 아이디는 prefix(default:ds) + 공통 코드값 입니다.
			 * @param {cpr.core.AppInstance} app
			 * @param {#dataset} psDataSetId
			 * @param {#column} psColumnName
			 */
			function registerCodeDataView(app, psDataSetId, psColumnName) {
				/** @type cpr.data.DataSet */
				var vcDsCmnCd = app.lookup(psDataSetId);
				
				if (ValueUtil.isNull(vcDsCmnCd)){
					return;
				}
				
				/* 데이터 셋에 해당 컬럼명을 가진 컬럼이 있는지 판별 */
				var voCdColumn = vcDsCmnCd.getColumn(psColumnName);
				if (ValueUtil.isNull(voCdColumn)){
					return;
				}
				
				var vaDstnctVals = vcDsCmnCd.getUnfilteredDistinctValues(psColumnName);
				
				for(var idx = 0; idx < vaDstnctVals.length; idx++){
					var vsDstnctVal = vaDstnctVals[idx];
					
					var vaAllHds = vcDsCmnCd.getHeaders();
					
					var vaNewDsColumn = vaAllHds.map(function(each){
						return {
							name : each.getName(),
							dataType : each.getDataType(),
							info : each.getInfo()
						};
					});
					
					var vaFindAllRows = vcDsCmnCd.findAllRow(psColumnName + "== '" + vsDstnctVal + "'");
					
					var vaNewDsRow = vaFindAllRows.map(function(each){
						return each.getRowData();
					});
					
					registerDataView(app, vcDsCmnCd, msCodeDataViewPrefix + vsDstnctVal, {
						filterCondition: psColumnName + "== '" + vsDstnctVal + "'"
					});
				}
			}
			
			/* Submit Kit */
			
			/**
			 * 
			 * @param {cpr.core.AppInstance} app 앱 인스턴스
			 * @param {String} psSubmissionId 서브미션 아이디
			 * @param {
			 *   {
			 *     action : String <!-- 통신 경로 -->,
			 * 	withCredentials? : Boolean <!-- 보안 헤더 사용 여부 -->,
			 * 	async? : Boolean <!-- 비동기 통신 사용 여부 -->,
			 * 	method : "post" | "get" | "delete" | "head" | "options" | "patch" | "put" <!-- 통신 방법 -->,
			 * 	mediaType : "application/json" 
			 * 					   | "application/x-www-form-urlencoded" 
			 * 					   | "application/x-www-form-urlencoded;massdata" 
			 * 					   | "application/x-www-form-urlencoded;simple"
			 * 					   | "multipart/form-data;encoding=json"
			 * 					   | "multipart/form-data"
			 * 					   | "multipart/form-data;simple" <!-- 미디어 타입 -->,
			 * 	responseType : "text" | "javascript" | "blob" | "filedownload" <!-- 응답 타입 -->,
			 * 	requestHeader? : {name : String, value : String} <!-- 요청 헤더 값 -->,
			 * 	requestParameter? : {name : String, value : String} <!-- 요청 파라미터 값 -->,
			 * 	requestData : {
			 * 		id : #datamap | #dataset,
			 * 		alias? : String,
			 * 		payload : "all" | "modified"
			 * 	}[] <!-- 요청 데이터 -->,
			 * 	responseData : {
			 * 		id : #dataset | #datamap,
			 * 		alias? : String,
			 * 		isAdd : Boolean	
			 * 	}[] <!-- 응답 데이터 -->,
			 * 	userAttr? : {key:String, value:String} <!-- 사용자 속성 -->
			 *   }
			 * } poConfig 서브미션 생성 정보
			 * @param {Boolean} pbRegister 앱 인스턴스 등록 여부
			 * @return {cpr.protocols.Submission}
			 */
			function registerSubmission(app, psSubmissionId, poConfig, pbRegister) {
				if (ValueUtil.isNull(psSubmissionId)){
					return;
				}
				
				var vcSubmission = new cpr.protocols.Submission(psSubmissionId);
				
				/* 서브미션 속성 설정 */
				vcSubmission.action = poConfig.action;
				vcSubmission.method = poConfig.method || "post";
				vcSubmission.withCredentials = poConfig.withCredentials || false;
				vcSubmission.async = poConfig.async || true;
		//		vcSubmission.mediaType || "application/x-www-form-urlencoded";
				vcSubmission.mediaType = "application/json";
				vcSubmission.responseType || "text";
				
				if (!ValueUtil.isNull(poConfig.userAttr)){
					vcSubmission.userAttr(poConfig.userAttr);
				}
				
				/* 요청 헤더 및 요청 파라미터 설정 */
				/** @type Array */
				var vaReqHd = poConfig.requestHeader;
				if (!ValueUtil.isNull(vaReqHd)){
					if (!(vaReqHd instanceof Array)){
						vaReqHd = [vaReqHd];
					}
					
					vaReqHd.forEach(function(/* {name:String,value:String}*/ each){
						vcSubmission.setHeader(each.name, each.value);
					});
				}
				
				/** @type Array */
				var vaReqPm = poConfig.requestParameter;
				if (!ValueUtil.isNull(vaReqPm)){
					if (!(vaReqPm instanceof Array)){
						vaReqPm = [vaReqPm];
					}
					
					vaReqPm.forEach(function(/* {name:String,value:String}*/each){
						vcSubmission.setParameters(each.name, each.value);
					});
				}
				
				/* 요청 데이터 및 응답 데이터 설정 */
				/** @type Array */
				var vaReqData = poConfig.requestData;
				if (!ValueUtil.isNull(vaReqData)){
					if (!(vaReqData instanceof Array)){
						vaReqData = [vaReqData];
					}
					
					vaReqData.forEach(function(/* {id:String, alias:String, payload:String} */ each){
						/** @type cpr.data.DataMap | cpr.data.DataSet */
						var vcDataCompn = app.lookup(each.id);
						
						if (!ValueUtil.isNull(vcDataCompn)){
							vcSubmission.addRequestData(vcDataCompn, each.alias, each.payload);
						}
					});
				}
				
				/** @type Array */
				var vaResData = poConfig.responseData;
				if (!ValueUtil.isNull(vaResData)){
					if (!(vaResData instanceof Array)){
						vaResData = [vaResData];
					}
					
					vaResData.forEach(function(/* {id:String, alias:String, isAdd:Boolean} */ each){
						/** @type cpr.data.DataMap | cpr.data.DataSet */
						var vcDataCompn = app.lookup(each.id);
						
						if (!ValueUtil.isNull(vcDataCompn)){
							vcSubmission.addResponseData(vcDataCompn, each.isAdd, each.alias);
						}
					});
				}
				
				var vbRegister = ValueUtil.isNull(pbRegister) ? true : pbRegister;
				if (vbRegister){
					app.register(vcSubmission);
				}
				
				return vcSubmission;
			}
			
			/**
			 * 
			 * @param {cpr.core.AppInstance} app 앱 인스턴스
			 * @param {cpr.data.DataSet} pcDataSet 데이터 셋
			 * @param {#column} psColumnName
			 */
			function registerDataSubmission(app, pcDataSet, psColumnName) {
		
				var vcDsGrdSvlt = pcDataSet;
				
				if (ValueUtil.isNull(vcDsGrdSvlt)){
					return;
				}
			 
				var voSvltColumn = vcDsGrdSvlt.getColumn(psColumnName);
				if (ValueUtil.isNull(voSvltColumn)){
					return;
				}
				
				for(var idx = 0; idx < vcDsGrdSvlt.getRowCount(); idx++){
					var vsSvltNm = vcDsGrdSvlt.getValue(idx, psColumnName);
					
					if (mIsSendDefaultUrl ==  true ){
						/** @type cpr.data.DataMap */
						var voDataMap =  app.lookup(msRequestDataDataMapId) ;
						if( !!voDataMap ){ 
							voDataMap.addColumn( new cpr.data.header.Header(psColumnName)  , vsSvltNm) ;
						} 
						//vsSvltNm = msDefaultUrl ;   
					}
					
					var vaTargetDs = getResponseDataSet(app, vsSvltNm);
					var vaResDatas = vaTargetDs.map(function(each){
						return {
							id : each.id,
							isAdd : false
						};
					});
					
					registerSubmission(app, vsSvltNm, {
						action : "/meta/getMetaDataList.do",
						requestData : {
							id : msRequestDataDataMapId
						},
						responseData : vaResDatas,
						userAttr : {
							"auto-registered" : "true"
						}
					});
				}
			}
			
			
			/**
			 * 동적으로 등록된 서브미션 객체를 배열로 가져옵니다.
			 * 동적으로 등록된 서브미션이 없으면 빈 배열을 리턴합니다.
			 * @param {cpr.core.AppInstance} app
			 * @return {cpr.protocols.Submission[]}
			 */
			function getRegisteredSubmission(app) {
				var vaRegistSubmissions = [];
				
				var vaAllDataCtrls = app.getAllDataControls();
				for(var idx = 0; idx < vaAllDataCtrls.length; idx++){
					var vcDataCtrl = vaAllDataCtrls[idx];
					
					if (vcDataCtrl instanceof cpr.protocols.Submission 
						&& vcDataCtrl.userAttr("auto-registered") == "true"){
						
						vaRegistSubmissions.push(vcDataCtrl);
					}
				}
				
				return vaRegistSubmissions;
			}
			
			
			/**
			 * 현재 앱에 등록된 모든 서브미션 객체를 배열로 가져옵니다.
			 * 등록된 서브미션이 없으면 빈 배열을 리턴합니다.
			 * @param {cpr.core.AppInstance} app
			 * @return {cpr.protocols.Submission[]}
			 */
			function getAllRegisteredSubmission(app) {
				var vaRegistSubmissions = [];
				
				var vaAllDataCtrls = app.getAllDataControls();
				for(var idx = 0; idx < vaAllDataCtrls.length; idx++){
					var vcDataCtrl = vaAllDataCtrls[idx];
					
					if (vcDataCtrl instanceof cpr.protocols.Submission){
						vaRegistSubmissions.push(vcDataCtrl);
					}
				}
				
				return vaRegistSubmissions;
			}
			
			return {
				DataSet : {
					register : registerDataSet,
					registerData : registerResponseDataDataSet,
					registerCode : registerCodeDataSet,
					getResponseDataSet : getResponseDataSet,
					getRegisteredDataSet : getRegisteredDataSet,
					getAllRegisteredDataSet : getAllRegisteredDataSet,
					getCodeDataSet : getRegisteredCodeDataSet
				},
				DataMap : {
					register : registerDataMap,
					registerData : registerRequestDataDataMap,
					getRegisteredDataMap : getRegisteredDataMap,
					getAllRegisteredDataMap : getAllRegisteredDataMap,
					getRequestDataMap : getRequestDataDataMap
				},
				DataView : {
					register : registerDataView,
					registerCode : registerCodeDataView
				},
				Submit : {
					register : registerSubmission,
					registerData : registerDataSubmission,
					getRegisteredSubmission : getRegisteredSubmission,
					getAllRegisteredSubmission : getAllRegisteredSubmission
				}
			}
		})();
		globals.init = initialize;
		
	});
})();
/// end - module/initialize
/// start - module/InitScreen
/*
 * Module URI: module/InitScreen
 * SRC: module/InitScreen.module.js
 *
 * This file was generated by eXbuilder6 compiler, Don't edit manually.
 */
(function(){
	cpr.core.Module.define("module/InitScreen", function(exports, globals, module){
		/************************************************
		 * InitScreen.module.js
		 * Created at 2020. 6. 2. 오전 10:17:26.
		 *
		 * @author csj
		 ************************************************/
		
		/**
		 * 본 모듈은 화면이 init 되었을 시 타이틀, 메뉴Path, 유의사항 버튼, serchBox, gridBox
		 * 레이아웃을 세팅해주는 모듈입니다. 
		 * 
		 * [사용방법] 
		 * 본 모듈을 사용하기 위해, 프로젝트 내에 배치하신 다음, 스크립트에서 init에서 서브미션이 success-done 했을 시
		 * 메서드를 사용하시면 됩니다. * 
		 * 
		 * [호출 가능한 메서드]
		 *  1. darwLayout : 화면 레이아웃을 초기에 세팅해주는 함수입니다. (헤더(타이틀,경로,유의사항), 검색조건영역, 바로가기영역, 그리드 영역)
		 *  
		 * [레이아웃 세팅해줄 그룹 이름들]
		 * 검색박스 : grpSearchBox
		 * 그리드박스 : grpGridBox
		 * 그리드헤더 : grpInfo
		 */
		
		
		/************************************************
		 * 멤버변수
		 ************************************************/
		var mnTitle = 64 + "px";
		var mnSearch = 56+ "px";
		var mnQuick = 40+ "px";
		var mnDetail = 1+ "fr";
		
		
		// 의존 모듈 선언.
		module.depends("module/common");
		
		
		
		/**
		 * 공통 drawScreen Class
		 */
		DrawScreen = function(appKit) {
			/** @type AppKit */
			this._appKit = appKit;
			
		};
		
		/**
		 * 
		 * @param {cpr.core.AppInstance} app
		 * @param {cpr.controls.Container} exGrp // 나중에 삭제할 예정
		 */
		DrawScreen.prototype.drawLayout = function(app, exGrp){
			
			var _app = app;
			
			var vaBodyRows = [mnTitle , mnSearch, mnQuick , mnDetail ];
			
			var dsSTATCOMMetaSearchVI03 = _app.lookup("dsCOMMeta03"); //탭같은데 모르겠음 
			/** @type cpr.data.DataSet */
			var dsSTATCOMMetaSearchVI05 = _app.lookup("dsCOMMeta05"); //바로가기 같음 
		
			
		   // var vcContainer = _app.getContainer();
		   
		   var vcContainer = null; 
		   
		   //나중에 삭제할 예정
		   if(exGrp){ 
		   		vcContainer = _app.lookup(exGrp);
		   	}else{
		   		vcContainer = _app.getContainer();	
		   	}
		   	
		  vcContainer.id = "BodyForm";
		  // console.log(vcContainer);
		   vcContainer.style.css({
		            "width" : "100%",
		            "top" : "0px",
		            "height" : "100%",
		            "left" : "0px"
		         });
		   vcContainer.style.css({
		      "background-color" : "white"
		   });   
		   
		   
		   /**
		    * 바디를 폼으로 만들기
		    */
		   var vcMainLayout = new cpr.controls.layouts.FormLayout();
		   vcMainLayout.topMargin = "10px";
		   vcMainLayout.rightMargin = "10px";
		   vcMainLayout.bottomMargin = "10px";
		   vcMainLayout.leftMargin = "10px";
		   vcMainLayout.horizontalSpacing = "0px";
		   vcMainLayout.verticalSpacing = "10px";
		   vcMainLayout.setColumns(["1fr"]);
		   vcMainLayout.setRows(vaBodyRows);
		   vcMainLayout.setRowAutoSizing(1, true);   
		   vcContainer.setLayout(vcMainLayout);
		
		   /**
		    * 그룹헤더
		    */
		   var vcGrpHeader = new cpr.controls.Container("grpHeader");
		   
		   var vcFormHear = new cpr.controls.layouts.FormLayout();
		   vcFormHear.horizontalSpacing = "0px";
		   vcFormHear.verticalSpacing = "0px";
		   vcFormHear.setColumns(["1fr"]);
		   vcFormHear.setRows(["28px", "36px"]);
		   vcGrpHeader.setLayout(vcFormHear);
		   
		   var vcHeaderTop = new cpr.controls.Container("HeaderTop");
		   
		   var vcTFormLayout= new cpr.controls.layouts.FormLayout();
		   vcTFormLayout.setColumns(["1fr", "200px"]);
		   vcTFormLayout.setColumnAutoSizing(1, true);
		   vcTFormLayout.setRows(["1fr"]);
		   vcHeaderTop.setLayout(vcTFormLayout);
		   
		   var vcPathUdc = new udc.cmn.Breadcrumb();   
		
		   vcPathUdc.class= "arrow";
		   vcPathUdc.values = _app.getHost().initValue["path"];
		   
		   vcHeaderTop.addChild(vcPathUdc, {
		      "colIndex": 1,
		      "rowIndex": 0
		   });
		   vcGrpHeader.addChild(vcHeaderTop, {
		      "colIndex": 0,
		      "rowIndex": 0,
		      "rightSpacing": 0
		   });
		   
		
		   
		   var vcHeaderTitle = new cpr.controls.Container("HeaderTitle");
		   // Layout
		   var vcFormHTitle = new cpr.controls.layouts.FormLayout();
		   vcFormHTitle.setColumns(["1fr", "70px"]);
		   vcFormHTitle.setColumnAutoSizing(0, true);
		   vcFormHTitle.setRows(["1fr"]);
		   vcHeaderTitle.setLayout(vcFormHTitle);
		   
		   var vcTitltOutput = new cpr.controls.Output("TitleOutput");
		   vcTitltOutput.value =  _app.getHost().initValue["label"];
		   vcTitltOutput.style.addClass("h3");
		//   vcTitltOutput.style.css({
		//      "font-size" : "24px"
		//   });
		   
		   vcHeaderTitle.addChild(vcTitltOutput, {
		      "colIndex": 0,
		      "rowIndex": 0
		   });
		   var vcNoticeBtn = new cpr.controls.Button("Notice-Btn");
		   vcNoticeBtn.value = "유의사항";
		   
		   vcNoticeBtn.addEventListener("click", function(e){
		   		var vcBtnApp = e.control.getAppInstance();
		   		
		   		
		   });
		   vcHeaderTitle.addChild(vcNoticeBtn, {
		      "colIndex": 1,
		      "rowIndex": 0,
		      "horizontalAlign": "fill",
		      "verticalAlign": "center"
		   });
		   
		   vcGrpHeader.addChild(vcHeaderTitle, {
		      "colIndex": 0,
		      "rowIndex": 1,
		      "rightSpacing": 0
		   });   
		   
		   vcContainer.addChild(vcGrpHeader, {
		      "colIndex": 0,
		      "rowIndex": 0
		   });
		
		   /**
		    * 검색박스 
		    */
		   var vcGrpSearch = new cpr.controls.Container("grpSearch");
		   // Layout
		   var vcFormSearch = new cpr.controls.layouts.FormLayout();
		   vcFormSearch.horizontalSpacing = "0px";
		   vcFormSearch.verticalSpacing = "0px";
		   vcFormSearch.setColumns(["1fr"]);
		   vcFormSearch.setRows(["1fr", "16px"]);
		   vcGrpSearch.setLayout(vcFormSearch);
		   
		   
		 
		   var grpToggle = new cpr.controls.Container("grpToggle");
		   // Layout
		   var formLayout_7 = new cpr.controls.layouts.FormLayout();
		   formLayout_7.setColumns(["1fr"]);
		   formLayout_7.setRows(["16px"]);
		   grpToggle.setLayout(formLayout_7);
		   
		   var btnToggle = new cpr.controls.Button("ToggleBtn");
		   //btnToggle.value = "∧";
		   btnToggle.style.addClass("btn-arrow-up");
		   btnToggle.userAttr({"isClick": "false"}); 
		   btnToggle.addEventListener("click", function(e){
		 
		   //검색박스를 나중에 넣어주기 때문에 getLastChild() 사용
		   var vcParentLayout = e.control.getParent().getParent();
		   var formLayout = vcParentLayout.getLastChild().getLayout();
		   if(e.control.userAttr("isClick") == "true"){
		     setFormVisible(formLayout,true);
		     btnToggle.style.removeClass("btn-arrow-down");
		     btnToggle.style.addClass("btn-arrow-up");
		     e.control.userAttr("isClick", "false");            
		   }else{
		     setFormVisible(formLayout,false);
		     btnToggle.style.removeClass("btn-arrow-up");
		     btnToggle.style.addClass("btn-arrow-down");
		     e.control.userAttr("isClick", "true");
		   }
		   var grd = _app.lookup("grdMst");
		   if(grd){
		   	 _app.lookup("grpGridAndChart").redraw();
		   }
		  
		
		  });
		         
		  grpToggle.addChild(btnToggle, {
		     "colIndex": 0,
			 "rowIndex": 0,
			 "horizontalAlign": "center"
			 });
			 
		  vcGrpSearch.addChild(grpToggle, {
		     "colIndex": 0,
		     "rowIndex": 1
		  });
		      
		    var group_3 = new cpr.controls.Container("grpSearchBox");
			
		
			vcGrpSearch.addChild(group_3, {
				"colIndex": 0,
				"rowIndex": 0
			});
		      
		  
		   
		   vcContainer.addChild(vcGrpSearch, {
		      "colIndex": 0,
		      "rowIndex": 1
		   });
		   
		   
		   /**
		    * 바로가기? 영역 dsSTATCOMMetaSearchVI03이게 0이 아닐때
		    */
		   var vcQuickCtn = new cpr.controls.Container("quick-Btn");
		   vcQuickCtn.style.css({
		      "background-color" : "#ececec"
		   });
		   
		   // Layout
		   var vcQuickFlowLayout = new cpr.controls.layouts.FlowLayout();
		   vcQuickFlowLayout.scrollable = false;
		   vcQuickFlowLayout.verticalAlign = "top";
		   vcQuickFlowLayout.spacing = 0;
		   vcQuickFlowLayout.topMargin = 8;
		   vcQuickFlowLayout.leftMargin = 10;
		   vcQuickCtn.setLayout(vcQuickFlowLayout);
		   
		   
		   //dsSTATCOMMetaSearchVI05(바로가기? 탭은 아님 어떤건지는 나중에 확인)
		   if(dsSTATCOMMetaSearchVI05 != null){
		      dsSTATCOMMetaSearchVI05.forEachOfUnfilteredRows(function(/*cpr.data.Row*/row){
		      
		      var vsLabelNm = row.getValue("LABELNM");
		      var btnNm = new cpr.controls.Button( vsLabelNm + "-btn");
		      btnNm.style.addClass("btn-0001");
		      btnNm.value = vsLabelNm;
		      vcQuickCtn.addChild(btnNm, {
		         "width": "75px",
		         "height": "24px",
		         "autoSize": "width"
		      });
		   });
		   }else{
		      vcMainLayout.setRowVisible(2,false);
		   }   
		   vcContainer.addChild(vcQuickCtn, {
		      "colIndex": 0,
		      "rowIndex": 2
		   });   
		   
		   
		   //그리드&차트헤더 , 그리드 , 차트 영역
		   var vcGrpInfo = new cpr.controls.Container("grpDetail");
		   var vcGrpDetail = new cpr.controls.layouts.FormLayout();
		   vcGrpDetail.setColumns(["1fr"]);
		   vcGrpDetail.setRows(["28px", "1fr"]);
		   vcGrpInfo.setLayout(vcGrpDetail);
		   
		   //그리드 및 차트 영역
		   var vcGridChartContainer = new cpr.controls.Container("grpGridAndChart");
		   var vcFormGC = new cpr.controls.layouts.FormLayout();
		   vcFormGC.setColumns(["1fr"]);
		   vcFormGC.setRows(["1fr","1fr"]);
		   vcGridChartContainer.setLayout(vcFormGC);
		   
		   vcFormGC.setRowVisible(1, false);
		   
		   vcGrpInfo.addChild(vcGridChartContainer, {
		   	  "colIndex": 0,
		      "rowIndex": 1
		   });
		   
		   vcContainer.addChild(vcGrpInfo, {
		      "colIndex": 0,
		      "rowIndex": 3
		   });	
		}
		
		
		/**
		 * 그리드와 차트를 세팅해줍니다.
		 * @param {cpr.core.AppInstance} app
		 * @param {cpr.controls.Grid} targetGrid
		 * @param {cpr.controls.UIControlShell } targetChart
		 */
		DrawScreen.prototype.setDetail = function(app,targetGrid, targetChart){
			
			var _app = app;
			
			var vcGrpDetail = app.lookup("grpDetail");
			var vcGrpGc = app.lookup("grpGridAndChart");
		
			if(targetGrid){
				vcGrpGc.addChild(targetGrid, {
					 "colIndex": 0,
		     		 "rowIndex": 0
				})
			}	
			
			var grdInfo = createInvisibleUnit().Info.draw(app, "dsGridInfo", "grdMst");
			vcGrpDetail.addChild(grdInfo, {
					 "colIndex": 0,
		     		 "rowIndex": 0
				})
			
		}
		
		
		/**
		 * 화면의 최소넓이를 세팅해줍니다.
		 * @param {cpr.core.AppInstance} app
		 * @param {Number} width
		 */
		DrawScreen.prototype.setMinWidth = function(app,width){
			
			var _app = app;
			
			_app.getContainer().style.css({
				"min-width" : width +"px"
			});
		}
		
		
		
		
		
		
		///**
		// * 
		// * @param {cpr.core.AppInstance} app
		// * @param {int} length
		// */
		//DrawScreen.prototype.setRowHeight = function(app, length){
		//	var _app = app;
		//	/** @type cpr.controls.Container */
		//	var _Container = _app.lookup("GrpBody");
		//	///** @type cpr.controls.layouts.Layout */
		//	var _Layout = _Container.getLayout();	
		//	/** @type String */
		//	var vsLen = length + "px";
		//	console.log("SDad");
		//
		//	_Layout.setRows(["64px", vsLen, "40px", "1fr"])
		//		
		//
		//}
		
		/**
		 * 검색영역에서 버튼클릭했을시 
		 * @param {cpr.controls.layouts.FormLayout} layout
		 * @param {Boolean} isBool
		 */
		function setFormVisible(layout,isBool){
			for(var i =1 ;i < layout.getRows().length; i++){
					layout.setRowVisible(i, isBool);
			}
		}
	});
})();
/// end - module/InitScreen
/// start - module/invisible
/*
 * Module URI: module/invisible
 * SRC: module/invisible.module.js
 *
 * This file was generated by eXbuilder6 compiler, Don't edit manually.
 */
(function(){
	cpr.core.Module.define("module/invisible", function(exports, globals, module){
		/************************************************
		 * invisible.module.js
		 * Created at 2020. 6. 2. 오후 4:05:51.
		 *
		 * @author ryu
		 ************************************************/
		
		/**
		 * @constructor 
		 */
		function InvisibleKit() {
			var unit = cpr.core.Module.require("module/unit");
			var unit2 = cpr.core.Module.require("module/createAppAndLayout");
			
			this.DataSet = new unit.DataSetKit(this);
			this.DataMap = new unit.DataMapKit(this);
			this.Submit = new unit.SubmissionKit(this);
			this.DataView = new unit.DataViewKit(this);
			this.Info = new unit.InfoKit(this);
			this.CreateAppLayout = new unit2.createAppAndLayout(this);
		}
		
		exports.InvisibleKit = InvisibleKit;
		
		globals.createInvisibleUnit = function(){
			return new InvisibleKit();
		}
	});
})();
/// end - module/invisible
/// start - module/submit
/*
 * Module URI: module/submit
 * SRC: module/submit.module.js
 *
 * This file was generated by eXbuilder6 compiler, Don't edit manually.
 */
(function(){
	cpr.core.Module.define("module/submit", function(exports, globals, module){
		/************************************************
		 * submit.module.js
		 * Created at 2020. 11. 11 오후 5:12:34.
		 *
		 * @author user
		 ************************************************/
		
		cpr.events.EventBus.INSTANCE.addFilter("before-submit", function(e){
			/** @type cpr.protocols.Submission */
			var submition = e.control;
			
		
			if( submition.userAttr("fileExcel") == "Y"){
				
			}else{ 
				if( submition.fallbackContentType = "application/json"){
					submition.setRequestEncoder( _requestEnc );
				} 
			}
		});
			 
		
		
		/**
		 * 
		 * @param {cpr.protocols.Submission} pSubmit
		 * @param {cpr.protocols.RequestData} reqData
		 */
		function _requestEnc( pSubmit , reqData ){
			var lApp = pSubmit.getAppInstance() ;
			
			var vObj  = reqData["data"];
			
			return {"content" :vObj } ; 
			
		}
	});
})();
/// end - module/submit
/// start - module/unit
/*
 * Module URI: module/unit
 * SRC: module/unit.module.js
 *
 * This file was generated by eXbuilder6 compiler, Don't edit manually.
 */
(function(){
	cpr.core.Module.define("module/unit", function(exports, globals, module){
		/************************************************
		 * unit.module.js
		 * Created at 2020. 6. 2. 오후 4:02:39.
		 *
		 * @author ryu
		 ************************************************/
		
		module.depends("module/invisible");
		
		
		/**
		 * 데이터 셋 유닛
		 * @constructor
		 * @param {invisble.invisibleKit} invisibleKit
		 */
		function DataSetKit(invisibleKit) { 
			this._invisibleKit = invisibleKit; 
		};
		
		
		/**
		 * 데이터 셋을 정보를 세팅합니다.
		 * <pre><code><code>
		 * dataset.parseData({
		 * 	alterColumnLayout: "client",
		 * 	sortCondition: "a",
		 * 	filterCondition: "c == 'c1'",
		 * 	info: "dataset_info",
		 * 	stateRestore: true,
		 * 	columns: [
		 * 		{ dataType: "string", name: "a" },
		 * 		{ dataType: "string", name: "b" },
		 * 		{ dataType: "string", name: "c" },
		 * 		{ dataType: "number", name: "d" },
		 * 		{ dataType: "number", name: "e", displayOnly: true },
		 * 		{ dataType: "expression", name: "sum", displayOnly: true, expression: "d+e" }
		 * 	],
		 * 	rows: [
		 * 		{ a: "a1", b: "b1", c: "c1", d: 100, e: 10 },
		 * 		{ a: "a2", b: "b2", c: "c2", d: 200, e: 20 },
		 * 		{ a: "a3", b: "b3", c: "c3", d: 300, e: 30 },
		 * 		{ a: "a4", b: "b4", c: "c4", d: 400, e: 40 }
		 * 	]
		 * });
		 * </code></code></pre>
		 * 
		 * @param {cpr.core.AppInstance} app 앱 인스턴스
		 * @param {String} psDsId 데이터 셋 아이디
		 * @param {{
		 * 	alterColumnLayout? : "client" | "server" | "merge" <!-- 컬럼 구조 변경 기준  -->,
		 * 	sortCondition? : String <!-- 정렬 조건 -->,
		 * 	filterCondition? : Stirng <!-- 필터 조건 -->,
		 * 	info? : String <!-- 데이터 셋 정보 -->,
		 * 	stateRestore?: Boolean <!-- 초기값 저장 여부 -->,
		 * 	columns : {
		 * 		name : String <!-- 컬럼 이름 -->,
		 * 		dataType? : "string" | "number" | "decimal" | "expression" <!-- 컬럼 타입 -->,
		 * 		displayOnly? : Boolean <!-- 원본 데이터 기억 불가 여부 -->,
		 * 		expression? : #expression <!-- 표현식 -->,
		 * 		info? : String <!-- 컬럼 정보값 -->
		 * 	} <!-- 컬럼 정보 -->,
		 * 	rows? : Object[] <!-- 데이터 -->
		 * }} poDsInfo 데이터 셋 생성 정보
		 */
		DataSetKit.prototype.register = function(app, psDsId, poDsInfo) {
			if (ValueUtil.isNull(psDsId)){
				return;
			}
			
			var vcDs = new cpr.data.DataSet(psDsId);
			
			vcDs.parseData(poDsInfo);
			
			app.register(vcDs);
		}
		
		
		/**
		 * 자동으로 필요한 데이터 셋을 추가합니다.
		 * @param {cpr.core.AppInstance} app
		 * @param {#dataset} psDsId
		 */
		DataSetKit.prototype.autoRegister = function(app, psDsId) {
			/** @type cpr.data.DataSet */
			var vcGrdObj = app.lookup(psDsId);
			
			if (ValueUtil.isNull(vcGrdObj)){
				return;
			}
			
			for(var rowIdx = 0; rowIdx < vcGrdObj.getRowCount(); rowIdx++){
				var vsObjNm = vcGrdObj.getValue(rowIdx, "OBJ_NM"); // 오브젝트 명
				
				this.register(app, "ds" + (rowIdx + 1), {
					alterColumnLayout : "server",
					info : vsObjNm
				});
			}
		}
		
		
		/**
		 * 공통 코드를 데이터 셋으로 등록합니다.
		 * @param {cpr.core.AppInstance} app
		 * @param {#dataset} psDsId
		 */
		DataSetKit.prototype.autoCodeRegister = function(app, psDsId) {
			/** @type cpr.data.DataSet */
			var vcCdList = app.lookup(psDsId);
			
			if (ValueUtil.isNull(vcCdList)){
				return;
			}
			
			var vaDistinctVals = vcCdList.getUnfilteredDistinctValues("GROUP_CD");
			
			for(var idx = 0; idx < vaDistinctVals.length; idx++){
				var vsDistinctVal = vaDistinctVals[idx];
				
				var vaCol = vcCdList.getHeaders();
				
				var vaColData = vaCol.map(function(each){
					return {
						name : each.getName(),
						dataType : each.getDataType(),
						info : each.getInfo()
					};
				});
				
				var vaRow = vcCdList.findAllRow("GROUP_CD == '" + vsDistinctVal + "'");
				
				var vaRowData = vaRow.map(function(each){
					return each.getRowData();
				});
				
				this.register(app, "ds" + vsDistinctVal, {
					columns : vaColData,
					rows : vaRowData
				});
			}
		}
		
		
		/**
		 * 데이터 뷰 유닛
		 * @constructor
		 * @param {invisble.invisibleKit} invisibleKit
		 */
		function DataViewKit(invisibleKit) {
			this._invisibleKit = invisibleKit; 
		};
		
		
		/**
		 * 
		 * @param {cpr.core.AppInstance} app
		 * @param {#dataset} pcParentDs
		 * @param {String} psDvId
		 * @param {{
		 * 	sortCondition? : String <!-- 정렬 조건 -->,
		 * 	filterCondition? : Stirng <!-- 필터 조건 -->
		 * }} poDvInfo
		 */
		DataViewKit.prototype.register = function(app, pcParentDs, psDvId, poDvInfo) {
			if (ValueUtil.isNull(pcParentDs) || ValueUtil.isNull(psDvId)){
				return;
			}
			
			var vcDv = new cpr.data.DataView(psDvId, pcParentDs);
			
			vcDv.parseData(poDvInfo);
			
			app.register(vcDv);
		}
		
		
		/**
		 * 
		 * @param {cpr.core.AppInstance} app
		 * @param {#dataset} psDsId
		 */
		DataViewKit.prototype.autoRegister = function(app, psDsId) {
			/** @type cpr.data.DataSet */
			var vcCdList = app.lookup(psDsId);
			
			if (ValueUtil.isNull(vcCdList)){
				return;
			}
			
			var vaDistinct = vcCdList.getUnfilteredDistinctValues("GROUP_CD");
			for(var idx = 0; idx < vaDistinct.length; idx++){
				var vsGrpCd = vaDistinct[idx];
				
				this.register(app, vcCdList, "dv" + vsGrpCd, {
					filterCondition : "GROUP_CD == '" + vsGrpCd + "'"
				});
			}
		}
		
		
		/**
		 * 데이터 맵 유닛
		 * @constructor
		 * @param {invisble.invisibleKit} invisibleKit
		 */
		function DataMapKit(invisibleKit) {
			this._invisibleKit = invisibleKit; 
		};
		
		
		/**
		 * 
		 * @param {cpr.core.AppInstance} app
		 * @param {String} psDmId
		 * @param {{
		 * 	alterColumnLayout? : "client" | "server" | "merge" <!-- 컬럼 구조 변경 기준 -->,
		 * 	info? : String <!-- 데이터 셋 정보 -->,
		 * 	columns : {
		 * 		name : String <!-- 컬럼 이름 -->,
		 * 		dataType? : "string" | "number" | "decimal" | "expression" <!-- 컬럼 타입 -->,
		 * 		defaultValue? : String <!-- 기본값 -->,
		 * 		displayOnly? : Boolean <!-- 원본 데이터 기억 불가 여부 -->,
		 * 		expression? : #expression <!-- 표현식 -->,
		 * 		info? : String <!-- 컬럼 정보값 -->
		 * 	} <!-- 컬럼 정보 -->
		 * }} poDmInfo 데이터 맵 생성 정보
		 */
		DataMapKit.prototype.register = function(app, psDmId, poDmInfo) {
			if (ValueUtil.isNull(psDmId)){
				return;
			}
			
			var vcDm = new cpr.data.DataMap(psDmId);
			
			vcDm.parseData(poDmInfo);
			
			app.register(vcDm);
		}
		
		
		
		/**
		 * 
		 * @param {cpr.core.AppInstance} app
		 * @param {#dataset} psDsId
		 */
		DataMapKit.prototype.autoRegister = function(app, psDsId) {
			/** @type cpr.data.DataSet */
			var vcDsSch = app.lookup(psDsId);
			
			if (ValueUtil.isNull(vcDsSch)){
				return;
			}
			
			/* 정적 컬럼 추가 */
			/** @type cpr.data.DataSet */
			var vcDsStaticColList = app.lookup("dsStaticColList");
			
			var vaColInfo = [];
			if (!ValueUtil.isNull(vcDsStaticColList)){
				for(var idx = 0; idx < vcDsStaticColList.getRowCount(); idx++){
					var vsStaticMetaVar = vcDsStaticColList.getValue(idx, "SRCH_METAVAR");
					vaColInfo.push({
						name : vsStaticMetaVar
					});
				}
			}
			
			for(var rowIdx = 0; rowIdx < vcDsSch.getRowCount(); rowIdx++){
				var vsSrchMetaVar = vcDsSch.getValue(rowIdx, "SRCH_METAVAR");
				
				if (ValueUtil.isNull(vsSrchMetaVar)){
					continue;
				}
				
				var vaSrchMetaVar = vsSrchMetaVar.split(",");
				
				for(var idx = 0; idx < vaSrchMetaVar.length; idx++){
					vaColInfo.push({
						name : vaSrchMetaVar[idx]
					});
				}
			}
		
			this.register(app, "dmSearch", {
				columns : vaColInfo
			});
		}
		
		
		
		/**
		 * 서브미션 유닛
		 * @constructor
		 * @param {invisble.module} invisibleKit
		 */
		function SubmissionKit(invisibleKit) {
			this._invisibleKit = invisibleKit; 
		};
		
		
		/**
		 * 
		 * @param {cpr.core.AppInstance} app
		 * @param {String} psSmId
		 * @param {{
		 * 	action : String <!-- 통신 경로 -->,
		 * 	withCredentials? : Boolean <!-- 보안 헤더 사용 여부 -->,
		 * 	async? : Boolean <!-- 비동기 통신 사용 여부 -->,
		 * 	method : "post" | "get" | "delete" | "head" | "options" | "patch" | "put" <!-- 통신 방법 -->,
		 * 	mediaType : "application/json" 
		 * 					   | "application/x-www-form-urlencoded" 
		 * 					   | "application/x-www-form-urlencoded;massdata" 
		 * 					   | "application/x-www-form-urlencoded;simple"
		 * 					   | "multipart/form-data;encoding=json"
		 * 					   | "multipart/form-data"
		 * 					   | "multipart/form-data;simple" <!-- 미디어 타입 -->,
		 * 	responseType : "text" | "javascript" | "blob" | "filedownload" <!-- 응답 타입 -->,
		 * 	requestHeader? : {name : String, value : String} <!-- 요청 헤더 값 -->,
		 * 	requestParameter? : {name : String, value : String} <!-- 요청 파라미터 값 -->,
		 * 	requestData : {
		 * 		id : #datamap | #dataset,
		 * 		alias? : String,
		 * 		payload : "all" | "modified"
		 * 	} <!-- 요청 데이터 -->,
		 * 	responseData : {
		 * 		id : #dataset | #datamap,
		 * 		alias? : String,
		 * 		isAdd : Boolean	
		 * 	} <!-- 응답 데이터 -->,
		 * 	userAttr? : {key:String, value:String} <!-- 사용자 속성 -->
		 * }} poSmInfo 서브미션 생성 정보
		 */
		SubmissionKit.prototype.register = function(app, psSmId, poSmInfo) {
			if (ValueUtil.isNull(psSmId)){
				return;
			}
			
			var vcSm = new cpr.protocols.Submission(psSmId);
			
			/* 서브미션 속성 설정 */
			vcSm.action = poSmInfo.action;
			vcSm.method = poSmInfo.method;
			vcSm.withCredentials = ValueUtil.fixNull(poSmInfo.withCredentials) != null ? poSmInfo.withCredentials : false;
			vcSm.async = ValueUtil.fixNull(poSmInfo.async) != null ? poSmInfo.async : true;
			vcSm.mediaType = poSmInfo.mediaType;
			vcSm.responseType = poSmInfo.responseType;
			/*
			 * if (ValueUtil.isNull(poSmInfo.userAttr)){
			 * 	vcSm.userAttr(poSmInfo.userAttr);
			 * }
			 */
			
			/* 요청 헤더 및 요청 파라미터 설정 */
			/** @type Array */
			var vaRqstHd = poSmInfo.requestHeader;
			if (!ValueUtil.isNull(vaRqstHd)){
				if (!vaRqstHd instanceof Array){
					vaRqstHd = [vaRqstHd];
				}
				
				vaRqstHd.forEach(function(/* {name:String,value:String}*/ each){
					vcSm.setHeader(each.name, each.value);
				});
			}
		
			/** @type Array */
			var vaRqstPm = poSmInfo.requestParameter;
			if (!ValueUtil.isNull(vaRqstPm)){
				if (!vaRqstPm instanceof Array){
					vaRqstPm = [vaRqstPm];
				}
				
				vaRqstPm.forEach(function(/* {name:String,value:String}*/each){
					vcSm.setParameters(each.name, each.value);
				});
			}
			
			/* 요청 데이터 및 응답 데이터 설정 */
			/** @type Array */
			var vaRqstData = poSmInfo.requestData;
			if (!ValueUtil.isNull(vaRqstData)){
				if (!vaRqstData instanceof Array){
					vaRqstData = [vaRqstData];
				}
				
				vaRqstData.forEach(function(/* {id:String, alias:String, payload:String} */ each){
					/** @type cpr.data.DataMap | cpr.data.DataSet */
					var vcDataCompn = app.lookup(each.id);
					
					if (vcDataCompn != null){
						vcSm.addRequestData(vcDataCompn, each.alias, each.payload);
					}
				});
			}
			
			/** @type Array */
			var vaRspnsData = poSmInfo.responseData;
			if (!ValueUtil.isNull(vaRspnsData)){
				if (!vaRspnsData instanceof Array){
					vaRspnsData = [vaRspnsData];
				}
				
				vaRspnsData.forEach(function(/* {id:String, alias:String, isAdd:Boolean} */ each){
					/** @type cpr.data.DataMap | cpr.data.DataSet */
					var vcDataCompn = app.lookup(each.id);
					
					if (vcDataCompn != null){
						vcSm.addResponseData(vcDataCompn, each.isAdd, each.alias);
					}
				});
			}
			
			app.register(vcSm);
		}
		
		
		/**
		 * 
		 * @param {cpr.core.AppInstance} app
		 * @param {#dataset} pcDsId
		 */
		SubmissionKit.prototype.autoRegister = function(app, pcDsId) {
			/** @type cpr.data.DataSet */
			var vcGrdSvlt = app.lookup(pcDsId);
			
			if (ValueUtil.isNull(vcGrdSvlt)){
				return;
			}
			
			for(var rowIdx = 0; rowIdx < vcGrdSvlt.getRowCount(); rowIdx++){
				var vsObjNm = vcGrdSvlt.getValue(rowIdx, "OBJ_NM"); // 오브젝트 명
				
				var vaTrgtDs = app.getAllDataControls().filter(function(each){
					return each instanceof cpr.data.DataSet;
				}).filter(function(each){
					return each.info == vsObjNm;
				}).map(function(each){
					return {
						id : each.id,
						isAdd : false
					};
				});
				
				this.register(app, vsObjNm, {
					action : "data/list.json", // vsObjNm,
					async : true,
					method : "post",
					mediaType : "application/x-www-form-urlencoded",
					responseType : "text",
					responseData : vaTrgtDs,
					userAttr : {
						"auto-registered" : "true"
					}
				});
			}
		}
		
		
		/**
		 * 그리드 정보 유닛
		 * @constructor
		 * @param {invisble.module} invisibleKit
		 */
		function InfoKit(invisibleKit) {
			this._invisibleKit = invisibleKit; 
		};
		
		
		/**
		 * 
		 * @param {cpr.core.AppInstance} app
		 * @param {cpr.data.DataSet} pcDsInfo
		 * @param {cpr.controls.Grid} pcGrd
		 * @param {cpr.data.DataSet} pcDsHdr
		 * @param {cpr.data.DataMap} pcDmChart
		 * @param {#userdefinedcontrol} pcChart
		 * @return {cpr.controls.Container}
		 */
		InfoKit.prototype.draw = function(app, pcDsInfo, pcGrd, pcDsHdr, pcDmChart, pcChart) {
			/** @type cpr.data.DataSet */
			var vcDsGrdInfo = pcDsInfo;
			/** @type cpr.controls.Grid */
			var vcGrdMst = pcGrd;
			/** @type cpr.data.DataSet */
			var vcDsHdr = pcDsHdr;
			/** @type cpr.data.DataMap */
			var vcDmChart = pcDmChart;
			/** @type cpr.controls.UDCBase */
			var vcChart = pcChart;
			
			if (ValueUtil.isNull(vcDsGrdInfo)){
				return;
			}
			
			/* 그리드 인포 영역 컨테이너 생성 */
			var vcGrpInfo = new cpr.controls.Container("grpGrdInfo");
			var voGrpInfoLt = new cpr.controls.layouts.FormLayout();
			
			voGrpInfoLt.horizontalSpacing = 10;
			
			voGrpInfoLt.setRows(["28px"]);
			voGrpInfoLt.setColumns(["30px", "1fr", "1fr", "50px", "60px"]);
			
			voGrpInfoLt.getColumns().map(function(each, index){
				return each.indexOf("px") != -1 ? index : null;
			}).filter(function(each){
				return each != null;
			}).forEach(function(each){
				voGrpInfoLt.setColumnAutoSizing(each, true);
			});
			
			voGrpInfoLt.setColumnVisible(2, false);
			vcGrpInfo.setLayout(voGrpInfoLt);
			
			/* 그리드 로우 수 표시 아웃풋 생성 */
			var vcOptRowIdct = new cpr.controls.Output("optRowIdct");
			
			vcOptRowIdct.dataType = "number";
			vcOptRowIdct.displayExp = "'[총 ' + text + '건]'";
			vcOptRowIdct.bind("value").toExpression("#" + vcGrdMst.id + ".getRowCount()");
			
			vcGrpInfo.addChild(vcOptRowIdct, {
				rowIndex : 0,
				colIndex : 0
			});	
			
			/* 특이사항 라벨 생성 */
			var vsSpclCont = vcDsGrdInfo.getValue(0, "SPCL_CONT");
			if (!ValueUtil.isNull(vsSpclCont)){
				var vcLblSpclCont = new cpr.controls.Output("lblSpclCont");
				
				vcLblSpclCont.bind("value").toDataSet(vcDsGrdInfo, "SPCL_CONT", 0);
				
				vcGrpInfo.addChild(vcLblSpclCont, {
					rowIndex : 0,
					colIndex : 1
				});
			}
			
			/* 차트 라벨 생성 */
			if (vcDsHdr.getRowCount() > 0
				&& !ValueUtil.isNull(vcDmChart.getValue("OUTPUT_FORMNO"))) {
				/** 
				* @type {{
				* 	    CHART_TYP:String <!-- 차트 유형 -->,
				* 		CHART_CHCOPT:String <!-- 차트 유형 목록 -->,
				* 		XAXS_IDXVLU:String <!-- X축에 나타나는 범주 -->, 
				* 		YAXS_DEFAULTIDXVLU:String <!-- 디폴트 차트 범례 -->, 
				* 		DEFAULT_AXSGB:String <!-- 디폴트 차트 범례(다축) -->,
				* 		SRCH_CONDIDX:String <!-- 차트 범례 목록 -->,
				* 		SRCH_CONDAXSGB:String <!-- 차트 범례 목록(다축) -->
				* 	}}
				*/
				var voChartData = vcDmChart.getDatas();
				
				/* 차트 라벨이 붙는 그룹 컨테이너 생성 */
				var vcGrpChartInfo = new cpr.controls.Container("grpChartInfo");
				var voGrpChartInfoLt = new cpr.controls.layouts.FlowLayout();
				
				voGrpChartInfoLt.scrollable = false;
				voGrpChartInfoLt.lineWrap = false;
				
				vcGrpChartInfo.setLayout(voGrpChartInfoLt);
				
				vcGrpInfo.addChild(vcGrpChartInfo, {
					rowIndex : 0,
					colIndex : 2
				});
				
				var vcLblChart = new cpr.controls.Output("lblChart");
				vcLblChart.value = "차트 유형";
				
				vcGrpChartInfo.addChild(vcLblChart, {
					autoSize : "width",
					height : "100%"
				});
				
				/* 차트 유형 생성 */
				var vcCmbChartTyp = new cpr.controls.ComboBox("cmbChartTyp");
				
				/** @type cpr.data.DataSet */
				var vcDsC4092 = app.lookup("dsC4092");
				
				// 현재 표시 가능한 차트 유형으로만 필터
				var vaChartTypCond = voChartData["CHART_CHCOPT"].split("^");
				vaChartTypCond = vaChartTypCond.map(function(each){
					return "COMMON_CD == '" + each + "'";
				});
				
				vcDsC4092.setFilter(vaChartTypCond.join("||"));
				
				vcCmbChartTyp.setItemSet(vcDsC4092, {
					label : "CODE_NM",
					value : "COMMON_CD"
				});
				
				vcCmbChartTyp.value = voChartData["CHART_TYP"];
				
				vcCmbChartTyp.addEventListener("selection-change", function(e) {
					//vcChart.updateChartType(e.control.value);
				});
				
				vcGrpChartInfo.addChild(vcCmbChartTyp, {
					width : "100px",
					height : "100%"
				});
				
				var vsCondIdx = voChartData["SRCH_CONDIDX"] || voChartData["SRCH_CONDAXSGB"];
				var vsYaxsDfVal = voChartData["YAXS_DEFAULTIDXVLU"] || voChartData["DEFAULT_AXSGB"];
				var vbPolyAxs = ValueUtil.isNull(voChartData["SRCH_CONDIDX"]); // 다축 또는 양축인지의 여부
				
				if (!ValueUtil.isNull(vsCondIdx)){
					/* 차트 범례 목록 생성 */
					var vcLblChartYaxs = new cpr.controls.Output();
					vcLblChartYaxs.value = "차트 범례";
					
					vcGrpChartInfo.addChild(vcLblChartYaxs, {
						autoSize : "width",
						height : "100%"
					});
			
					// 다축이나 양축이 아닌 경우 구분자 추가(^)
					if (!vbPolyAxs){
						var vsCondCd = vsCondIdx.substring(0, 1);
			
						vsCondIdx = _parser(vsCondIdx, vsCondCd, "^");
						vsYaxsDfVal = _parser(vsYaxsDfVal, vsCondCd, "^");
					}
					
					var vaCondIdx = vsCondIdx.split("^");
					var vaYaxsDfVal = vsYaxsDfVal.split("^");
					
					if (vbPolyAxs){ // 다축 또는 양축일 때
						vaCondIdx = _chunk(vaCondIdx, parseInt(vaCondIdx.length / 2));
						
						for(var idx = 0; idx < vaCondIdx.length; idx++){
							var vaSrcCondIdx = vaCondIdx[idx];
							var vsDfVal = vaYaxsDfVal[idx];
							
							var vcCmbSrcCondIdx = new cpr.controls.ComboBox("cmbSrcCondIdx" + (idx + 1));
							
							for(var itemIdx = 0; itemIdx < vaSrcCondIdx.length; itemIdx++){
								var vsSrcCondIdxVal = vaSrcCondIdx[itemIdx];
								
								var vsSrcCondIdxTxt = "";
								if (vsSrcCondIdxVal.indexOf("R") != -1){
									vsSrcCondIdxTxt = _getRowText(vcGrdMst, vsSrcCondIdxVal);
								} else if (vsSrcCondIdxVal.indexOf("C") != -1) {
									vsSrcCondIdxTxt = _getColumnText(pcDsHdr, vsSrcCondIdxVal);
								}
								
								if (!(ValueUtil.isNull(vsSrcCondIdxTxt))){
									vcCmbSrcCondIdx.addItem(new cpr.controls.Item(vsSrcCondIdxTxt, vsSrcCondIdxVal));
								}
							}
							
							var vsCsYaxsDfVal = vsDfVal.replace(/[\^]/g, ","); // ^를 ,로 변환
							
							/* 일치하는 아이템이 있는 경우 해당 아이템 선택, 없는 경우 첫번재 아이템 선택 */
							var vcDfItem = vcCmbSrcCondIdx.getItemByValue(vsCsYaxsDfVal);
							if (!ValueUtil.isNull(vcDfItem)){
								vcCmbSrcCondIdx.value = vsCsYaxsDfVal;
							} else {
								vcCmbSrcCondIdx.selectItem(0);
							}
							
							vcCmbSrcCondIdx.userAttr("chart-legend", "true");
							
							vcCmbSrcCondIdx.addEventListener("selection-change", function(e){
								/** @type cpr.controls.Container */
								var vcGrpChartInfo = e.control.getParent();
								/** @type String[]*/
								var vaSrcCondIdxVals = vcGrpChartInfo.getAllRecursiveChildren(false).filter(function(each){
									return each.userAttr("chart-legend") == "true";
								}).map(function(/* cpr.controls.ComboBox */ each){
									return each.value;
								});
								
								var vsSrcCondIdxVal = vaSrcCondIdxVals.length > 0 ? vaSrcCondIdxVals.join(",") : null;
								
								// vcChart.updateOption(vsSrcCondIdxVal);
							});
							
							vcGrpChartInfo.addChild(vcCmbSrcCondIdx, {
								width : "180px",
								height : "100%"
							});
						}
					} else { // 다축 또는 양축 이외의 차트일 때
						var vcCmbSrcCondIdx = new cpr.controls.ComboBox("cmbSrcCondIdx1");
						
						for(var idx = 0; idx < vaCondIdx.length; idx++){
							var vsSrcCondIdx = vaCondIdx[idx];
			
							var vsSrcCondIdxTxt = "";
							if (vsSrcCondIdx.indexOf("R") != -1){
								vsSrcCondIdxTxt = _getRowText(vcGrdMst, vsSrcCondIdx);
							} else if (vsSrcCondIdx.indexOf("C") != -1) {
								vsSrcCondIdxTxt = _getColumnText(pcDsHdr, vsSrcCondIdx);
							}
							
							if (!(ValueUtil.isNull(vsSrcCondIdxTxt))){
								vcCmbSrcCondIdx.addItem(new cpr.controls.Item(vsSrcCondIdxTxt, vsSrcCondIdx));
							}
						}
						
						if (vsCondCd != "R"){
							vcCmbSrcCondIdx.multiple = true;
						}
						
						var vsCsYaxsDfVal = vsYaxsDfVal.replace(/[\^]/g, ","); // ^를 ,로 변환
						
						/* 일치하는 아이템이 있는 경우 해당 아이템 선택, 없는 경우 첫번재 아이템 선택 */
							var vcDfItem = vcCmbSrcCondIdx.getItemByValue(vsCsYaxsDfVal);
							if (!ValueUtil.isNull(vcDfItem)){
								vcCmbSrcCondIdx.value = vsCsYaxsDfVal;
							} else {
								vcCmbSrcCondIdx.selectItem(0);
							}
						
						vcCmbSrcCondIdx.addEventListener("selection-change", function(e) {
							//vcChart.updateOption(e.control.value);
						});
						
						vcGrpChartInfo.addChild(vcCmbSrcCondIdx, {
							width : "180px",
							height : "100%"
						});
					}
				}
			} // 차트 정보 생성 END
			
			/* 단위 생성 */
			var vcGrpUnit = new cpr.controls.Container("grpUnit");
			var voGrpUnitLt = new cpr.controls.layouts.FlowLayout();
			
			voGrpUnitLt.spacing = 3;
			voGrpUnitLt.scrollable = false;
			
			vcGrpUnit.setLayout(voGrpUnitLt);
			
			var vsBassUnit = vcDsGrdInfo.getValue(0, "BASIC_UNIT");
			var vsBassUnitDsp = vcDsGrdInfo.getValue(0, "BASIC_UNITDSP");
			
			vsBassUnitDsp = _parser(vsBassUnitDsp, "^", ";");
			
			if (!ValueUtil.isNull(vsBassUnitDsp)){
				/** @type cpr.data.DataMap */
				var vcDmSearch = app.lookup("dmSearch");
				
				/* 단위 텍스트 생성 */
				var vcLblUnit = new cpr.controls.Output("lblUnit");
				vcLblUnit.value = "단위 :";
				
				vcGrpUnit.addChild(vcLblUnit, {
					"height" : "100%",
					"autoSize" : "width"
				});
				
				var vaSplitedBassUnitDsp = _splits(vsBassUnitDsp, [";", "^"]); // 명,건,^원
				var vaSplitedBassUnit = _splits(vsBassUnit, [";"]); // T0000^08;T0000^00
				
				for(var idx = 0, cdIdx = 0; idx < vaSplitedBassUnitDsp.length; idx++){
					var vsBassUnitDspVal = vaSplitedBassUnitDsp[idx];
					
					if (ValueUtil.isNull(vsBassUnitDspVal)){
						
						if (vaSplitedBassUnit.length > 0 && !ValueUtil.isNull(vaSplitedBassUnit[cdIdx])){
							var vaBassUnitVal = vaSplitedBassUnit[cdIdx].split("^");
							var vsBassUnitCd = vaBassUnitVal[0];
							var vsBassUnitDfVal = vaBassUnitVal[1];
							
							var vcUnit = new cpr.controls.ComboBox();
							
							//TODO 현재 단위에 대한 코드값을 가져오는 부분이 어떻게 해결되어야 할지 보아야 함
							vcUnit.setItemSet(app.lookup("ds" + vsBassUnitCd), {
								label : "CODE_NM",
								value : "COMMON_CD"
							});
							
							vcUnit.bind("value").toDataMap(app.lookup("dmSearch"), "tmpV4" + cdIdx);
							vcUnit.value = vsBassUnitDfVal;
							
							//TODO 코드값 변경에 대한 이벤트 작성
							vcUnit.addEventListener("selection-change", function(e) {
								var control = e.control;
								
								alert(e.newSelection[0].label);
							});
							
							vcGrpUnit.addChild(vcUnit, {
								"height" : "100%",
								"width" : "80px"
							});
							
							cdIdx++;
						}
						
					} else {
						var vcUnitDsp = new cpr.controls.Output();
						vcUnitDsp.value = vsBassUnitDspVal;
						
						vcGrpUnit.addChild(vcUnitDsp, {
							"height" : "100%",
							"autoSize" : "width"
						});
					}
				}
			}
				
			vcGrpInfo.addChild(vcGrpUnit, {
				rowIndex : 0,
				colIndex : 3
			});
			
			/* 그리드 버튼 생성 */
			var vcGrdInfoBtns = new udc.cmn.GridInfoButtons();
			
			vcGrdInfoBtns.grid = vcGrdMst;
			
			if (vcDsHdr.getRowCount() == 0 
				|| ValueUtil.isNull(vcDmChart.getValue("OUTPUT_FORMNO"))){
				vcGrdInfoBtns.setLayoutButtonVisible(false);
			}
			
			vcGrpInfo.addChild(vcGrdInfoBtns, {
				rowIndex : 0,
				colIndex : 4
			});
		
			return vcGrpInfo;
		}
		
		
		/**
		 * 
		 * @private
		 * @param {String} value
		 * @param {String} token
		 * @param {String} seperator
		 * @return {String}
		 */
		function _parser(value, token, seperator) {
			if (value == null || value == ""){
				return null;
			}
			
			var buffer = [];
			var chars = value.split("");
			for(var idx = 0; idx < chars.length; idx++){
				var char = chars[idx];
				
				if (idx > 0 && (char == token && buffer[buffer.length - 1] != seperator)) { // 추가
					buffer.push(seperator);
					
					if (chars[idx + 1] == token) { // 교체
						continue;
					}
				}
				
				buffer.push(char);
			}
			
			return buffer.join("");
		}
		
		
		/**
		 * 배열을 특정 사이즈로 나눕니다.
		 * @private
		 * @param {Array} array
		 * @param {Number} size
		 */
		function _chunk(array, size) {
			var chunk = [];
			
			for(var idx = 0; idx < array.length; idx+= size){
				chunk.push(array.slice(idx, idx + size));
			}
			
			return chunk;
		}
		
		
		/**
		 * @private
		 * @param {String} value
		 * @param {String[]} tokens
		 * @return {String}
		 */
		function _splits(value, tokens) {
			var tempChar = tokens[0]; // We can use the first token as a temporary join character
		    for(var i = 1; i < tokens.length; i++){
		        value = value.split(tokens[i]).join(tempChar);
		    }
		    value = value.split(tempChar);
		    return value;
		}
		
		
		/**
		 * 
		 * @param {"number" | "character"} type
		 * @param {String} value
		 */
		function _extract(type, value) {
			if (ValueUtil.isNull(type) || ValueUtil.isNull(value)){
				return;
			}
			
			var vaExtracted = [];
			if (type == "number"){
				vaExtracted = value.match(/\d+/g);
			} else if (type == "character") {
				vaExtracted = value.match(/[a-zA-Z]+/g);
			}
			
			var vsExtractedText = vaExtracted.join("");
			if (ValueUtil.isNumber(vsExtractedText)){
				vsExtractedText = ValueUtil.fixNumber(vsExtractedText);
			}
			
			return vsExtractedText;
		}
		
		
		/**
		 * 
		 * @param {cpr.data.DataSet} pcDsHdr
		 * @param {String} psValue
		 */
		function _getColumnText(pcDsHdr, psValue) {
			if (ValueUtil.isNull(pcDsHdr)){
				return;
			}
			
			/** @type cpr.data.DataSet */
			var vcDsHdr = pcDsHdr;
		
			var vnColIdx = _extract("number", psValue);
			
			var vcTreGrdHd = new cpr.controls.Tree();
			vcTreGrdHd.setItemSet(vcDsHdr, {
				label : "HEADER_NM",
				value : "HEADER_ID",
				parentValue : "PARENT_HDRID"
			});
			
			var vcMatchedItem = vcTreGrdHd.getItems().filter(function(each){
				if(each.row.getValue("HEADER_ID").indexOf("TEST") == -1) {
					return !(vcTreGrdHd.hasChild(each));
				}
			})[vnColIdx];
			
			var vaHdTxt = [];
			var vcTgItem = vcMatchedItem;
			while(vcTgItem){
				vaHdTxt.push(vcTgItem.label);
				vcTgItem = vcTgItem.parentItem;
			}
			
			/* 데이터 역순 정렬 */
			vaHdTxt.reverse();
			
			return vaHdTxt.join("-");
		}
		
		
		/**
		 * 
		 * @param {any} pcGrid
		 * @param {any} psValue
		 */
		function _getRowText(pcGrid, psValue) {
			if (ValueUtil.isNull(pcGrid)){
				return;
			}
			
			/** @type cpr.controls.Grid */
			var vcGrdMst = pcGrid;
			/** @type cpr.data.DataSet */
			var vcDsGrd = vcGrdMst.dataSet;
			
			var vnRowIndex = _extract("number", psValue);
			
			var vsRowText = vcGrdMst.getCellValue(vnRowIndex, 0);
			
			return vsRowText;
		}
		
		exports.DataSetKit = DataSetKit;
		exports.DataViewKit = DataViewKit;
		exports.DataMapKit = DataMapKit;
		exports.SubmissionKit = SubmissionKit;
		exports.InfoKit = InfoKit;
	});
})();
/// end - module/unit
/// start - module/util
/*
 * Module URI: module/util
 * SRC: module/util.module.js
 *
 * This file was generated by eXbuilder6 compiler, Don't edit manually.
 */
(function(){
	cpr.core.Module.define("module/util", function(exports, globals, module){
		
		//exports.id = "util.module.js";
		
		/**
		 * @class AppUtil AppInstance에 대한 유틸
		 */
		AppUtil = {
		    /**
		     * 해당 앱의 속성(Property)값을 할당한다.
		     * @param {cpr.core.AppInstance} app - 앱인스턴스 객체
			 * @param {String | Object} propertyName App 속성
			 * @param {String | Object} value App 속성값
			 * @param {boolean} pbEvent value-change 이벤트 발생여부  (default : true)
			 * @return void
		     */
		    setAppProperty : function (app, propertyName, value, pbEvent) { 
		    	pbEvent = pbEvent == null ? true : pbEvent;
		    	
		        /** @type cpr.core.AppInstance */
		        var _app = app;
		        var hostApp = _app.getHostAppInstance();
		        var property = _app.getAppProperty(propertyName);
		        if(hostApp && hostApp.lookup(property) && hostApp.lookup(property) instanceof cpr.controls.UIControl){
		        	if(pbEvent){
		        		hostApp.lookup(property).value = value;
		        	}else{
		        		hostApp.lookup(property).putValue(value);
		        	}
		        }else{
		        	_app.setAppProperty(propertyName, value);
		        }
		    },
		    
		    /**
		     * UDC 컨트롤에 대해 value 앱 속성에 바인딩된 컨트롤 객체를 반환한다.
		     * @param {cpr.controls.UIControl} poCtrl
		     */
		    getUDCBindValueControl : function(poCtrl){
		    	var vcBindCtrl = poCtrl;
		    	var embApp = poCtrl.getEmbeddedAppInstance();
				embApp.getContainer().getChildren().some(function(embCtrl){
					if(embCtrl.type == "container"){
						embCtrl.getChildren().some(function(subembCtrl){
							if(subembCtrl.getBindInfo("value") && subembCtrl.getBindInfo("value").property == "value"){
								vcBindCtrl = subembCtrl;
								return true;
							}
						});
					}else{
						if(embCtrl.getBindInfo("value") && embCtrl.getBindInfo("value").property == "value"){
							vcBindCtrl = embCtrl;
							return true;
						}
					}
				});
				
				return vcBindCtrl;
		    }
		 };
		
		/**
		 * @class ValueUtil Value 체크 및 형 변환
		 */
		ValueUtil = {
		    /**
		     * 해당 값이 Null인지 여부를 체크하여 반환한다.
			 * @param {String | Object} puValue		값
			 * @return {Boolean} Null 여부
		     */
		    isNull : function (puValue) {
		        return (this.fixNull(puValue) == "");
		    },
		
		    /**
		     * 해당 값이 숫자(Number) 타입인지 여부를 반환한다.
			 * @param {Number | String} puValue		값
			 * @example ValueUtil.isNumber("1234.56") == true
			 * @return {Boolean} Number인지 여부
		     */
		    isNumber : function (puValue) {
		        var vnNum = Number(puValue);
		        return isNaN(vnNum) == false;
		    },
		
		    /**
		     * 해당 값에 대한 문자열을 반환한다. 
		     *       만약 해당값이 null이거나 정의되지 않은 경우, 공백("") 문자열을 반환한다.
			 * @param {String | Object} puValue		값
			 * @return {String} 문자열 String
		     */
		    fixNull : function (puValue) {
		        var vsType = typeof(puValue);
		        if (vsType == "string" || (vsType == "object" && puValue instanceof String)) {
					puValue = this.trim(puValue);
		        }
				
		        return (puValue == null || puValue == "null" || puValue == "undefined") ? "" : String(puValue);
		    },
		
		     /**
		     * 해당 값을 불리언(Boolean) 타입으로 변환한다.
			 * @param {Boolean | Object} puValue		값
			 * @return {Boolean} 불리언 유형으로 반환
		     */
		    fixBoolean : function (puValue) {
		        if (typeof(puValue) == "boolean" || puValue instanceof Boolean) {
		            return puValue;
		        }
		        if (typeof(puValue) == "number" || puValue instanceof Number) {
		            return puValue != 0;
		        }
		        return (this.fixNull(puValue).toUpperCase() == "TRUE");
		    },
		
		    /**
		     * 해당 값을 숫자(Number) 타입으로 변환한다.
			 * @param {Object} puValue		값
			 * @return {Number} 숫자 타입으로 반환
		     */
		    fixNumber : function (puValue) {
		        if (typeof(puValue) == "number" || puValue instanceof Number) {
		            return puValue;
		        }
		        var vnNum = Number(this.fixNull(puValue));
		        return isNaN(vnNum) ? 0 : vnNum;
		    },
		    
		    /**
		     * 해당 값을 숫자(Float) 타입으로 변환한다.
			 * @param {Object} puValue		값
			 * @return {Float} 소수점이 있는 숫자 타입으로 반환
		     */
		    fixFloat : function (puValue) {
		        if (typeof(puValue) == "number" || puValue instanceof Number) {
		            return puValue;
		        }
		        var vnFloat = parseFloat(this.fixNull(puValue));
		        return isNaN(vnFloat) ? 0 : vnFloat;
		    },
		    
		    /**
		     * 해당 값의 앞/뒤 공백을 제거한 문자열을 반환한다.
			 * @param {String} psValue		값
			 * @return {String} 공백 제거된 문자열
		     */
		    trim : function (psValue) {
		        return psValue == null ? psValue : psValue.replace(/(^\s*)|(\s*$)/g, "");
		    },
		    
		    /**
		     * 문자열을 split한 배열을 반환한다.
			 * @param {String} psValue		split 대상 문자열
			 * @param {String} psDelemeter  구분문자 (ex: 콤마(,))
			 * @return {Array} 문자열 배열
		     */
		    split : function (psValue, psDelemeter) {
		    	psValue = this.fixNull(psValue);
		        var vaValues = new Array();
		        var vaTemp = psValue.split(psDelemeter);
		        var _this = this;
		        vaTemp.forEach(function(/* eachType */ item){
		        	vaValues.push(_this.trim(item));
		        });
		        
		        return vaValues;
		    },
		    
		    /**
		     * 문자열 데이터의 길이(length)를 반환한다.
			 * @param {String} value		값
			 * @param {String} unit (Optional) 단위<br/>
		     * [char] : 문자의 길이.<br/>
		 	 * [utf8] : utf8 기준의 문자 byte size.<br/>
		 	 * [ascii] : ascii 기준의 문자 byte size.
			 * @return {Number} 문자열 길이
		     */
		    getLength : function(value, unit) {
		    	if(!unit) unit = "char";
		    	
				var length = 0;
				switch(unit) {
					case "utf8":{
		//				for(var i = 0, c; c = value.charAt(i++); length += (c >> 11 ? 3 : c >> 7 ? 2 : 1));
						for(var i=0, len=value.length; i<len; i++) {
						    if(escape(value.charAt(i)).length >= 4)
						        length += 3;
						    else if(escape(value.charAt(i)) == "%A7")
						        length += 3;
						    else if(escape(value.charAt(i)) != "%0D")
						        length++;
						    else length++;
						}
						break;
					}
					case "ascii":{
						for(var i = 0, c; c = value.charAt(i++); length += c >> 7 ? 2 : 1);
						break;
					}
					default : {
						length = value.length;
					}
				}
				
				return length;
		    },
		    getByteLength: function(/*String*/_str){
		    	var stringByteLength = 0;
		    	stringByteLength = (function(s,b,i,c){
				    for(b=i=0;c=s.charCodeAt(i++);b+=c>>11?2:c>>7?2:1);
				    return b
				})(_str);
		
				return stringByteLength;
		    }
		 };
		
		/**
		 * @class 날짜 유틸 클래스
		 */
		DateUtil = {
		
		    /**
		     * 날짜를 지정한 패턴의 문자열로 반환한다.
			 * @param {Date} poDate			날짜
			 * @param {String} psPattern	포맷 문자열(ex: YYYYMMDD)
			 * @return {String} 날짜 문자열
		     */
		    format : function (poDate, psPattern) { // dateValue As Date, strPattern As String
		        var CAL_INITIAL = {
				    MONTH_IN_YEAR :         ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
				    SHORT_MONTH_IN_YEAR :   ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
				    DAY_IN_WEEK :           ["Sunday", "Monday", "Tuesday", "Wednesday","Thursday", "Friday", "Saturday"],
				    SHORT_DAY_IN_WEEK :     ["Sun", "Mon", "Tue", "Wed","Thu", "Fri", "Sat"]
				};
		        
		        var year      = poDate.getFullYear();
			    var month     = poDate.getMonth() + 1;
			    var day       = poDate.getDate();
			    var dayInWeek = poDate.getDay();
			    var hour24    = poDate.getHours();
			    var ampm      = (hour24 < 12) ? "AM" : "PM";
			    var hour12    = (hour24 > 12) ? (hour24 - 12) : hour24;
			    var min       = poDate.getMinutes();
			    var sec       = poDate.getSeconds();
			
			    var YYYY = "" + year;
			    var YY   = YYYY.substr(2);
			    var MM   = (("" + month).length == 1) ? "0" + month : "" + month;
			    var MON  = CAL_INITIAL.MONTH_IN_YEAR[month-1];
			    var mon  = CAL_INITIAL.SHORT_MONTH_IN_YEAR[month-1];
			    var DD   = (("" + day).length == 1) ? "0" + day : "" + day;
			    var DAY  = CAL_INITIAL.DAY_IN_WEEK[dayInWeek];
			    var day  = CAL_INITIAL.SHORT_DAY_IN_WEEK[dayInWeek];
			    var HH   = (("" + hour24).length == 1) ? "0" + hour24 : "" + hour24;
			    var hh   = (("" + hour12).length == 1) ? "0" + hour12 : "" + hour12;
			    var mm   = (("" + min).length == 1) ? "0" + min : "" + min;
			    var ss   = (("" + sec).length == 1) ? "0" + sec : "" + sec;
			    var SS   = "" + poDate.getMilliseconds();
				
			    var dateStr;
			    var index = -1;
			    if (typeof(psPattern) == "undefined") {
			        dateStr = "YYYYMMDD";
			    } else {
			        dateStr = psPattern;
			    }
			
			    dateStr = dateStr.replace(/YYYY/g, YYYY);
			    dateStr = dateStr.replace(/yyyy/g, YYYY);
			    dateStr = dateStr.replace(/YY/g,   YY);
			    dateStr = dateStr.replace(/MM/g,   MM);
			    dateStr = dateStr.replace(/MON/g,  MON);
			    dateStr = dateStr.replace(/mon/g,  mon);
			    dateStr = dateStr.replace(/DD/g,   DD);
			    dateStr = dateStr.replace(/dd/g,   DD);
			    dateStr = dateStr.replace(/day/g,  day);
			    dateStr = dateStr.replace(/DAY/g,  DAY);
			    dateStr = dateStr.replace(/hh/g,   hh);
			    dateStr = dateStr.replace(/HH/g,   HH);
			    dateStr = dateStr.replace(/mm/g,   mm);
			    dateStr = dateStr.replace(/ss/g,   ss);
			    dateStr = dateStr.replace(/(\s+)a/g, "$1" + ampm);
			
			    return dateStr;
		    },
		
		    /**
		     * 올바른 날짜인지를 체크한다.
			 * @param {Number | String} puYear			년도
			 * @param {Number | String} puMonth			월
			 * @param {Number | String} puDay			일
			 * @return {Boolean} 유효한 날짜인지 여부
		    */
		    isValid : function (puYear, puMonth, puDay) {
		    	var pnYear = Number(puYear);
		    	var pnMonth = Number(puMonth);
		    	var pnDay = Number(puDay);
		        var vdDate = new Date(pnYear, pnMonth-1, pnDay);
		        return vdDate.getFullYear() == pnYear      &&
		               vdDate.getMonth   () == pnMonth - 1 &&
		               vdDate.getDate    () == pnDay;
		    },
		
		    /**
		     * 현재 날짜에 해당 날짜만큼 더한 날짜를 반환한다.
			 * @param {String} psDate			날짜 문자열(ex: 20180101)
			 * @param {Number} pnDayTerm		추가 일수
			 * @return {String} 날짜 문자열
		    */
		    addDate : function (psDate, pnDayTerm) { 
		    	var pnYear 	= Number(psDate.substring(0,4));
		    	var pnMonth = Number(psDate.substring(4,6));
		    	var pnDay 	= Number(psDate.substring(6,8));
		
		    	if (this.isValid(pnYear, pnMonth, pnDay)) {
			    	var vdDate = new Date(pnYear, pnMonth-1, pnDay);
			    	var vnOneDay = 1*24*60*60*1000 ; /* 1day,24hour,60minute,60seconds,1000ms */
			    	
			    	var psTime = vdDate.getTime() + (Number(pnDayTerm)*Number(vnOneDay));
			    	vdDate.setTime(psTime);
			    	
			        return this.format(vdDate,"YYYYMMDD");
		    	}else{
		    		return psDate;
		    	}
		    },
		    
		    /**
		     * 날짜 문자열을 Date형으로 변환하여 반환한다.
		     * <pre><code>
		     * DateUtil.toDate("2007-02-09","YYYY-MM-DD");
		 	 * </code></pre>
			 * @param {Date} psDateTime			날짜
			 * @param {String} psPattern	포맷 문자열(ex: YYYY-MM-DD)
			 * @example DateUtil.toDate("2007-02-09","YYYY-MM-DD")
			 * @return {Date} 날짜(Date) 객체
		     */ 
		    toDate : function (psDateTime, psPattern) {
		        var vdDate = new Date();
		        var vnIdx, vnCnt;
		
		        var vsaFmt = ["Y", "M", "D", "H", "m", "s", "S"];
		        var vnFmtLen = vsaFmt.length;
		        var vnPtnLen = psPattern.length;
		        var vnaNums = [vdDate.getFullYear(), vdDate.getMonth()+1, vdDate.getDate(), vdDate.getHours(), vdDate.getMinutes(), vdDate.getSeconds(), vdDate.getMilliseconds()];
		
		        for (var i = 0; i < vnFmtLen; i++) {
		            vnIdx = psPattern.indexOf(vsaFmt[i]);
		            if (vnIdx != -1) {
		                vnCnt = 1;
		                for (var j=vnIdx+1; j < vnPtnLen; j++) {
		                    if (psPattern.charAt(j) != vsaFmt[i]) { break; }
		                    vnCnt++;
		                }
		                vnaNums[i] = Number(psDateTime.substring(vnIdx, vnIdx+vnCnt));
		            } else {
		                if(i==0) vnaNums[0] = 1900;
		                else if(i==2) vnaNums[2] = 01;
		            }
		        }
		
		        if (vnaNums[0] < 1900) { // 년도는 검증
		            if (vnaNums[0] <= vdDate.getFullYear() % 100) {
		                vnaNums[0] += vdDate.getFullYear() - (vdDate.getFullYear() % 100);
		            } else if (vnaNums[0] < 100) {
		                vnaNums[0] += 1900;
		            } else {
		                vnaNums[0] = 1900;
		            }
		        }
		
		        return new Date(vnaNums[0], vnaNums[1]-1, vnaNums[2], vnaNums[3], vnaNums[4], vnaNums[5], vnaNums[6]);
		    },
		
		    /**
		     * 해당월의 마지막 일자를 반환한다.
		     * <pre><code>
		     * DateUti.getMonthLastDay("20180201");
		     * 또는
		     * DateUti.getMonthLastDay("20180301", -1);
		 	 * </code></pre>
			 * @param {String} psDate	년월 문자열(ex: 201802, 20180201)
			 * @param {Number} pnAdd (Optional)   +/- 월 수
			 * @return {Number} 일(Day)
		     */ 
		    getMonthLastDay : function (psDate, pnAdd) {
		    	var pnYear 	= Number(psDate.substring(0,4));
		    	var pnMonth = Number(psDate.substring(4,6));
		        var vdDate = new Date(pnYear, pnMonth, 0, 1, 0, 0);
		        if(pnAdd == null){
		        	return vdDate.getDate();
		        }else{
		        	var vdDate2 = new Date(vdDate.getFullYear(), vdDate.getMonth()+1+pnAdd, 0, 1, 0, 0);
		        	return vdDate2.getDate();
		        }
		    },
		
		    /**
		     * 두 날짜간의 일(Day)수를 반환한다.
			 * @param {String} psDate1st	년월 문자열(ex: 20180201)
			 * @param {String} psDate2nd    년월 문자열(ex: 20170201)
			 * @return {Number} 일수(Day)
		     */
		    getDiffDay : function (psDate1st, psDate2nd) {
		    	var date1 = this.toDate(psDate1st, "YYYYMMDD");
		    	var date2 = this.toDate(psDate2nd, "YYYYMMDD");
		        
		        return parseInt((date2 - date1)/(1000*60*60*24));
		    },
		    
		    /**
		     * @param {String} psHHmm 특정분을 더할 시분 값
			 * @param {String} pnAddMinutes 더할 분
			 * @return {String} 시분(HHmm)
			 * 
			 * ex) DateUtil.addMinutes("0900", 50)
		     */
		    addMinutes : function (psHHmm, pnAddMinutes) {
		    	var vdDate = DateUtil.toDate(psHHmm, "HHmm");
				vdDate.setMinutes(vdDate.getMinutes() + pnAddMinutes);
				
				var vnHours = vdDate.getHours();
				var vnMinutes = vdDate.getMinutes();
				
				var vsHours = "";
				var vsMinutes = "";
				
				if(vnHours < 10){
					vsHours = "0" + vnHours;
				}else{
					vsHours = vnHours + "";
				}
				
				if(vnMinutes < 10){
					vsMinutes = "0" + vnMinutes;
				}else{
					vsMinutes = vnMinutes + "";
				}
				
				return vsHours + vsMinutes;
		    },
		    
		    getCurrentTime : function() {
		    	return new Date().getTime();
		    },
		    
		    /**
		     * 입력한 일자에 해당되는 한글 요일을 반환한다.
		     * <pre><code>
		     * DateUti.getDayOfWeek("20191120");
		 	 * </code></pre>
			 * @param {String} psDate 일자 문자열(ex:20191120)
			 * @return {String} 한글 요일
		     */ 
		    getDayOfWeek : function (psDate) {
		    	
		    	var vsYear 	= psDate.substring(0,4);
		    	var vsMonth = psDate.substring(4,6);
		    	var vsDay 	= psDate.substring(6,8);
		    	var vaWeek  = ['일', '월', '화', '수', '목', '금', '토'];
		    	
				return vaWeek[new Date(vsYear + "-" + vsMonth + "-" + vsDay).getDay()];
		    }
		};
		
		/**
		 * @class 파일 유틸 클래스
		 */
		FileUtil = {
			//업로드 가능한 파일 확장자 목록반환
			getPemitedFileExts : function(){
				var vaFileExt = [
								'JPG' , 'PNG' , 'GIF', 'BMP'
								, 'TIF', 'TIFF', 'JFIF'
								, 'TXT', 'CSV', 'HWP', 'DOCX', 'GNG'
								, 'DOC', 'DOCM', 'PPT', 'PPTX'
								, 'PPTM', 'PPS', 'PPSX', 'XLS'
								, 'XLSX', 'XLSM', 'XLAM', 'XLA'
								, 'PSD', 'PDF', 'ODS', 'OGG', 'ZIP', 'EGG'
								, 'MP4', 'AVI', 'WMV'
								, 'RAR', 'TAR', '7Z', 'TBZ', 'TGZ'
								, 'LZH', 'GZ', 'AI'
							]; 
				return vaFileExt;
			},
			
			//업로드 불가한 파일 확장자 목록반환
			getLimitedFileExts : function(){
				// 파일 선택 제한 확장자.
				var vaFileExt = new Array(
					 'A6P'     //Authorware 6 Program
					,'AC'      //Autoconfig Script
					,'AS'      //Adobe Flash ActionScript File
					,'ACR'     //ACRobot Script
					,'ACTION'  //Automator Action
					,'AIR'     //Adobe AIR Installation Package
					,'APP'     //FoxPro Generated Application
					,'APP'     //Symbian OS Application
					,'ASP'	   //Active Server Page
					,'ASPX'	   //Extended Active Server Page
					,'AWK'     //AWK Script
					,'BAT'     //Batch File
					,'CGI'     //Common Gateway Interface Script
					,'CMD'     //Windows Command
					,'COM'     //DOS Command File
					,'CSH'     //C Shell Script
					,'DEK'     //Eavesdropper Batch File
					,'DLD'     //EdLog Compiled Program
					,'DS'      //TWAIN Data Source
					,'EBM'     //EXTRA! Basic Macro
					,'ESH'     //Extended Shell Batch File
					,'EXE'     //Windows Executable File
					,'EZS'     //EZ-R Stats Batch Script
					,'FKY'     //FoxPro Macro
					,'FRS'     //Flash Renamer Script
					,'FXP'     //FoxPro Compiled Source
					,'GADGET'  //Windows Gadget
					,'HMS'     //HostMonitor Script File
					,'HTA'     //HTML Application
					,'ICD'     //SafeDisc Encrypted Program
					,'INX'     //Compiled Script
					,'IPF'     //SMS Installer Script
					,'ISU'     //InstallShield Uninstaller Script
					,'JAR'     //Java Archive File
					,'JS'      //JScript Executable Script
					,'JSE'     //JScript Encoded File
					,'JSP'	   //JavaServer Pages
					,'JSX'     //ExtendScript Script File
					,'KIX'     //KiXtart Script File
					,'LUA'     //Lua Scripting File
					,'MCR'     //3ds Max Macroscript File
					,'MEM'     //Macro Editor Macro
					,'MPX'     //FoxPro Compiled Menu Program
					,'MS'      //3ds Max Script File
					,'MST'     //Windows SDK Setup Transform Script
					,'OBS'     //ObjectScript Script File
					,'PAF'     //Portable Application Installer File
					,'PEX'     //ProBoard Executable File
					,'PHP'	   //Hypertext Preprocessor
					,'PIF'     //Program Information File
					,'PL'	   //Perl Script
					,'PRC'     //Palm Resource Code File
					,'PRG'     //Generica Program File
					,'PVD'     //Instalit Script
					,'PWC'     //PictureTaker File
					,'PY'      //Python Script
					,'PYC'     //Python Compiled File
					,'PYO'     //Python Optimized Code
					,'QPX'     //FoxPro Compiled Query Program
					,'RBX'     //Rembo-C Compiled Script
					,'RGS'     //Registry Script
					,'ROX'     //Actuate Report Object Executable File
					,'RPJ'     //Real Pac Batch Job File
					,'SCAR'    //SCAR Script
					,'SCR'     //Script File
					,'SCRIPT'  //Generic Script File
					,'SCT'     //Windows Script Component
					,'SH'	   //Shell Script
					,'SHB'     //Windows Shortcut into a Document
					,'SHS'     //Shell Scrap Object File
					,'SPR'     //FoxPro Generated Screen File
					,'TLB'     //OLE Type Library
					,'TMS'     //Telemate Script
					,'U3P'     //U3 Smart Application
					,'UDF'     //Excel User Defined Function
					,'VB'      //VBScript File
					,'VBE'     //VBScript Encoded Script File
					,'VBS'     //VBScript File
					,'VBSCRIPT'//Visual Basic Script
					,'WCM'     //WordPerfect Macro
					,'WPK'     //WordPerfect Macro
					,'WS'      //Windows Script
					,'WSF'     //Windows Script File
					,'XQT'     //SuperCalc Macro File
				);
				
				return vaFileExt;
			},
			
			/**
			 * 해당 파일이 업로드 가능한 파일 유형인지 체크한다.
			 * @param {Object} poUtil - 유틸 클래스 객체 
			 * @param {String} psFileNm - 파일명
			 * @param {String} psLimitFileExt - 업로드 가능제한 파일 확장자
			 */
			checkFileType : function(poUtil, psFileNm, psLimitFileExt){
				var vaCheckFileExt = null;
				if (!ValueUtil.isNull(psLimitFileExt)) {
					psLimitFileExt = psLimitFileExt.replace(/\./ig, "");
					vaCheckFileExt = ValueUtil.split(psLimitFileExt, ",");
				}
				
				if(vaCheckFileExt == null || vaCheckFileExt.length < 1){
					vaCheckFileExt = FileUtil.getPemitedFileExts();
				}
				
				var isValid = false;
				var arrStr = psFileNm.split(".");
				var extStr = arrStr [arrStr.length - 1].toUpperCase();
				for(var j=0, jlen=vaCheckFileExt.length; j<jlen; j++){
					if (extStr == vaCheckFileExt[j].toUpperCase()) {
						isValid = true;
						break;
					}
				}
				if(!isValid){
					//{0} 유형의 파일은 업로드 불가합니다.
					poUtil.Msg.alert("WRN-M024", [extStr]);
					return isValid;
				}
				
				var vaLimitedFileExt = FileUtil.getLimitedFileExts();
				for(var i=0, len=vaLimitedFileExt.length; i<len; i++) {
					if (extStr == vaLimitedFileExt[i]) {
						//{0} 유형의 파일은 업로드 불가합니다.
						poUtil.Msg.alert("WRN-M024", [extStr]);
						isValid = false;
						break;
					}
				}
				
				return isValid;
			}
		};
	});
})();
/// end - module/util
/// start - module/validation
/*
 * Module URI: module/validation
 * SRC: module/validation.module.js
 *
 * This file was generated by eXbuilder6 compiler, Don't edit manually.
 */
(function(){
	cpr.core.Module.define("module/validation", function(exports, globals, module){
		
		//exports.id = "validation.module.js";
		
		// 의존 모듈 선언.
		module.depends("module/common");
		
		/**
		 * 공통 Validator Class
		 */
		Validator = function(appKit) {
			/** @type AppKit */
			this._appKit = appKit;
		};
		
		/**
		 * 해당 값이 'E-mail' 유형인지 여부를 반환한다.
		 * @param {String} value - 값  문자열
		 * @return {Boolean} true/false
		 */
		Validator.prototype.isEmail = function(value){ 
			if(!value) return true;
			
			if(/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(value)) {
				return true;
			}
			
			return false;
		}
		
		/**
		 * 해당 값이 'URL' 형식에 맞는 문자열인지 여부를 반환한다.
		 * @param {String} value - 값  문자열
		 * @return {Boolean} true/false
		 */
		Validator.prototype.isURL = function(value){
			if(!value) return true;
			
			// w3resource.com
			var regexp = /^(?:(?:https?|ftp):\/\/)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?$/;
			if(regexp.test(value)) {
				return true;
			}
			
			return false;
		}
		
		/**
		 * 해당 값이 '사업자 번호' 형식에 맞는 문자열인지 여부를 반환한다.
		 * @param {String} value - 값  문자열
		 * @return {Boolean} true/false
		 */
		Validator.prototype.isBizCSN = function(value){
			if(!value) return true;
			
			// 넘어온 값의 정수만 추츨하여 문자열의 배열로 만들고 10자리 숫자인지 확인합니다.
			if ((value = (value + '').match(/\d{1}/g)).length != 10) {
				return false;
			}
		
			// 합 / 체크키
			var sum = 0, key = [1, 3, 7, 1, 3, 7, 1, 3, 5];
		
			// 0 ~ 8 까지 9개의 숫자를 체크키와 곱하여 합에 더합니다.
			for (var i = 0 ; i < 9 ; i++) { sum += (key[i] * Number(value[i])); }
		
			// 각 8번배열의 값을 곱한 후 10으로 나누고 내림하여 기존 합에 더합니다.
			// 다시 10의 나머지를 구한후 그 값을 10에서 빼면 이것이 검증번호 이며 기존 검증번호와 비교하면됩니다.
			return (10 - ((sum + Math.floor(key[8] * Number(value[8]) / 10)) % 10)) == Number(value[9]);
		}
		
		/**
		 * 해당 값이 '주민등록번호' 형식에 맞는지 여부를 반환한다.
		 * 주민등록번호 체크 (전자정부프레임워크 : http://www.egovframe.go.kr/wiki/doku.php?id=egovframework:rte:ptl:validation:add_rules_in_commons_validator)
		 * @param {String} value - 값  문자열
		 * @return {Boolean} true/false
		 */
		Validator.prototype.isSSN = function(value){
			if(!value) return true;
			value = value.replace(/[\-]/g, "");
			
			
			var fmt = /^\d{6}[1234]\d{6}$/;
			if(!fmt.test(value)){
				return false;
			}
		
			var birthYear = (value.charAt(7) <= "2") ? "19" : "20";
			birthYear += value.substr(0, 2);
			var birthMonth = value.substr(2, 2) - 1;
			var birthDate = value.substr(4, 2);
			var birth = new Date(birthYear, birthMonth, birthDate);
		
			if( birth.getYear() % 100 != value.substr(0, 2) ||
			    birth.getMonth() != birthMonth ||
			    birth.getDate() != birthDate) {
			    return false;
			}
		
			var arrDivide = [2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5];            	
			var checkdigit = 0;            	
			for(var i = 0; i < value.length - 1; i++) {
				checkdigit += parseInt(value.charAt(i)) * parseInt(arrDivide[i]);
			}
			checkdigit = (11 - (checkdigit % 11)) % 10;
			if(checkdigit != value.charAt(12)){
				return false;
			} else {
				return true;
			}
		}
		
		/**
		 * 해당 값이 일반 '전화번호' 형식에 맞는 문자열인지 여부를 반환한다.
		 * @param {String} value - 값  문자열
		 * @return {Boolean} true/false
		 */
		Validator.prototype.isTelNo = function(value){
			if(!value) return true;
			if(/^\d{2,3}[\)\-\. ]?\d{3,4}[\-\. ]?\d{4}$/.test(value)){
				return true;
			}
			
			return false;
		}
		
		/**
		 * 해당 값이 '핸드폰번호' 형식에 맞는 문자열인지 여부를 반환한다.
		 * @param {String} value - 값  문자열
		 * @return {Boolean} true/false
		 */
		Validator.prototype.isTelMobile = function(value){
			if(!value) return true;
			if(/^01([0|1|6|7|8|9]?)[\-\. ]?([0-9]{3,4})[\-\. ]?([0-9]{4})$/.test(value)) {
				return true;
			}
			
			return false;
		}
		
		/**
		 * 해당 값이 'function' 유형인지 여부를 반환한다.
		 * @param {Function} poFunc  
		 * @return {Boolean} true/false
		 */
		Validator.prototype.isFunc = function(poFunc){
			if (poFunc != null && (typeof poFunc == "function")) {
				return true;
			}else return false;
		}
		
		/**
		 * 
		 * @param {cpr.controls.UIControl} ctrl
		 * @param {String} ctrlValue
		 * @param {cpr.controls.UIControl} poParentCtl
		 * @param {Number} pnIdx
		 * @param {Number} pnCellIdx
		 */
		Validator.prototype.validate = function(ctrl, ctrlValue, poParentCtl, pnIdx, pnCellIdx, poRow, poMstCtrl) {
			if(!ctrl) return true;
			if(ctrl.type == "output" || ctrl.type == "img" || ctrl.type == "button") return true;
			
			//바인딩 및 헤더컬럼으로 수정 필요
			var vsFieldLabel = ctrl.fieldLabel;
			if(ValueUtil.isNull(vsFieldLabel) && ctrl.getHost){
				vsFieldLabel = ctrl.getHost().fieldLabel;
			}
			function getGridFieldLabel(poParentCtl, psFieldLabel){
		//		if(poParentCtl instanceof cpr.controls.Grid && ValueUtil.isNull(psFieldLabel)){
				if(poParentCtl instanceof cpr.controls.Grid){
					var vcDetailCtl = poParentCtl.detail.getColumn(pnCellIdx);
					var vaHeaderCtl = poParentCtl.header.getColumnByColIndex(vcDetailCtl.colIndex, vcDetailCtl.colSpan);
					if(vaHeaderCtl.length > 0){
						var vcHeaderCtl = vaHeaderCtl[0];
						if(vcHeaderCtl){
							psFieldLabel = vcHeaderCtl.getText();
						}
					}
				}
				return psFieldLabel;
			}
			
			var _appKit = this._appKit;
			function parentValidMsg(psMsg, poParentCtl, pnIdx){
				//그리드 내 컨트롤
				if(poParentCtl instanceof cpr.controls.Grid){
					var vsMsg = "";
					var vsDsRefKey = "EX6_REF_KEY_";
					vsMsg = "";
					vsMsg = _appKit.Msg.getMsg("WRN-M002", [poParentCtl.fieldLabel, Number(pnIdx)+1]);
					psMsg = vsMsg + " " + psMsg;
				}
				_appKit.Msg.alert(psMsg, "WARNING");
			}
			
			// 필수 입력 체크
			{
				var notnull = "";
				if(poParentCtl instanceof cpr.controls.Grid && ctrl instanceof cpr.controls.UDCBase){
					notnull = ctrl.getAppProperty("required") === true || ctrl.getAppProperty("required") === "Y" ? "Y" : "";
				}else{
					notnull = ctrl.userAttr("required");
				}
				if(notnull === "Y") {
					if(ctrlValue == null || new String(ctrlValue) == "") {
						vsFieldLabel = getGridFieldLabel(poParentCtl, vsFieldLabel);
						//{0}은(는) 필수 입력 항목입니다.
						var vsMsg = this._appKit.Msg.getMsg("WRN-M001", [vsFieldLabel]);
						parentValidMsg(vsMsg, poParentCtl, pnIdx);
						
						return false;
					}
				}
			}
			
			
			
			// 지정된 컬럼중 하나 이상 필수 입력 체크
			// 그리드일경우 columnname, 그룹 및 일반컨트롤일 경우 id
			{
				var xorNull = ctrl.userAttr("xorRequired");
				if(xorNull) {
					var vaXorNull = ValueUtil.split(xorNull.replace(/\[|\]/g,""), ",");
					var vsName = "";
					//그리드 내 컨트롤
					
					var vbStatus = false;
					if(poParentCtl instanceof cpr.controls.Grid){
						for (var j = 0; j < vaXorNull.length; j++) {
							var vsValue = poRow != null ? poRow.getValue(vaXorNull[j]) : poParentCtl.getCellValue(pnIdx, vaXorNull[j]);
							if(!ValueUtil.isNull(vsValue)){
								vbStatus = true;
								break;
							}
							var vaDetailCell = poParentCtl.detail.getColumnByName(vaXorNull[j]);
							vaDetailCell.some(function(vcCell){
								var vcHeaderCtl = poParentCtl.header.getColumn(vcCell.colIndex).control;
								if(vcHeaderCtl)
									vsName += vcHeaderCtl.getText() + " ,";
								//vsName += vcCell.control.userattr("name") + " ,";
							});
						}
						if(!vbStatus){
							//{0}중 하나는 필수 입력 항목입니다.
							var vsMsg = this._appKit.Msg.getMsg("WRN-M003", [vsName.substring(0, vsName.length -1)]);
							parentValidMsg(vsMsg, poParentCtl, pnIdx);
							return false;
						}
					}else{
						for (var j = 0; j < vaXorNull.length; j++) {
							var vcCtl = ctrl.getAppInstance().lookup(vaXorNull[j]);
							var vsValue = vcCtl.value;
							if(!ValueUtil.isNull(vsValue)){
								vbStatus = true;
								break;
							}
							vsName += vcCtl.fieldLabel + " ,";
						}
						
						if(!vbStatus){
							//{0}중 하나는 필수 입력 항목입니다.
							var vsMsg = this._appKit.Msg.getMsg("WRN-M003", [vsName.substring(0, vsName.length -1)]);
							parentValidMsg(vsMsg, poParentCtl, pnIdx);
							return false;
						}
					}
				}
			}
			
			// 나머지 항목은 값이 있을 때만 체크
			if(ctrlValue == null || ctrlValue == "") return true;
			
			// type check
			{
				var type = ctrl.userAttr("columnType");
				if(type) {
					var isChk = true;
					if(type == "email"){
						isChk = this.isEmail(ctrlValue);
					}else if(type == "ssn"){
						isChk = this.isSSN(ctrlValue);
					}else if(type == "bizno"){
						isChk = this.isBizCSN(ctrlValue);
					}else if(type == "phone"){
						isChk = this.isTelMobile(ctrlValue);
					}else if(type == "tel"){
						isChk = this.isTelNo(ctrlValue);
					}else if(type == "url"){
						isChk = this.isURL(ctrlValue);
					}
					if(isChk == false) {
						vsFieldLabel = getGridFieldLabel(poParentCtl, vsFieldLabel);
						//{0}은(는) 유효하지 않은 형식입니다.
						var vsMsg = this._appKit.Msg.getMsg("WRN-M004", [vsFieldLabel]);
						parentValidMsg(vsMsg, poParentCtl, pnIdx);
						return false;
					}
				}
			}
			
			// minlength
			{
				var minlength = ctrl.userAttr("minlength");
				if(minlength) {
					var minlengthNum = Number(minlength);
					var length = ValueUtil.getLength(ctrlValue, ctrl.lengthUnit);
					if(length < minlength) {
						vsFieldLabel = getGridFieldLabel(poParentCtl, vsFieldLabel);
						//{0}은(는) {1}자 이상으로 입력하십시오.
						var vsMsg = this._appKit.Msg.getMsg("WRN-M005", [vsFieldLabel, minlength]);
						parentValidMsg(vsMsg, poParentCtl, pnIdx);
						return false;
					}
				}
			}
			
			// fixlength
			{
				var fixlength = ctrl.userAttr("fixlength");
				if(fixlength) {
					var fixlength = Number(fixlength);
					var length = ValueUtil.getLength(ctrlValue, ctrl.lengthUnit);
					if(length != fixlength) {
						vsFieldLabel = getGridFieldLabel(poParentCtl, vsFieldLabel);
						//{0}은(는) {1} 자리수만큼 입력하십시오.
						var vsMsg = this._appKit.Msg.getMsg("WRN-M006", [vsFieldLabel, fixlength]);
						parentValidMsg(vsMsg, poParentCtl, pnIdx);
						return false;
					}
				}
			}
			
			{
				//두 값을 비교
				//그리드 일경우 컬럼명, 일반 컨트롤일 경우 컨트롤 id
				var compare = ctrl.userAttr("compare");
				if(!ValueUtil.isNull(compare)) {
					var compareCol = compare.substring(0, compare.indexOf(","));
					var compareType = compare.substr(compare.indexOf(",") + 1).trim();
					//그리드 내 컨트롤
					var vbStatus = false;
					var vsCompareColValue;
		        	var vsCompareColLable;
		        	var value = ctrlValue;
					if(poParentCtl instanceof cpr.controls.Grid){
						vsCompareColValue = poRow != null ? poRow.getValue(compareCol) : poParentCtl.getCellValue(pnIdx, compareCol);
						var vcDetailColumn = poParentCtl.detail.getColumnByName(compareCol)[0];
						var vaHeaderCol = poParentCtl.header.getColumnByColIndex(vcDetailColumn.colIndex, vcDetailColumn.colSpan);
						if(vaHeaderCol.length > 0){
							var vcHeaderCtl = vaHeaderCol[0];
							if(vcHeaderCtl){
								vsCompareColLable = vcHeaderCtl.getText();
							}
						}
					}else{
						vsCompareColValue = ctrl.getAppInstance().lookup(compareCol).value;
						vsCompareColLable = ctrl.getAppInstance().lookup(compareCol).fieldLabel
					}
					
					if(!ValueUtil.isNull(value) && !ValueUtil.isNull(vsCompareColValue)){
						var vbReturn = false;
						if (ValueUtil.isNumber(value) && ValueUtil.isNumber(vsCompareColValue)) {
							vbReturn = eval(value + compareType + vsCompareColValue);
						}else{
							vbReturn = eval("'"+value+"'" + compareType + "'"+vsCompareColValue+"'");
						}
				            
			            if (!vbReturn) {
			            	 vsFieldLabel = getGridFieldLabel(poParentCtl, vsFieldLabel);
			            	 var vsMsg = "";
			            	if(compareType == "<=" || compareType == "<" ){
			            		//{0}은(는) {1}보다 클 수 없습니다.
			            		vsMsg = this._appKit.Msg.getMsg("WRN-M009", [vsFieldLabel, vsCompareColLable]);
			            	}else if (compareType == ">=" || compareType == ">" ){
			            		//{0}은(는) {1}보다 작을수 없습니다.
			            		vsMsg = this._appKit.Msg.getMsg("WRN-M010", [vsFieldLabel, vsCompareColLable]);
			            	}else if (compareType == "==" || compareType == "="){
			            		//{0}은(는) {1}와 같아야 합니다.
			            		vsMsg = this._appKit.Msg.getMsg("WRN-M011", [vsFieldLabel, vsCompareColLable]);
			            	}else{
			            		
			            	}
			            	parentValidMsg(vsMsg, poParentCtl, pnIdx);
			                return false;
			            }
					}
				}
			}
			return true;
		}
		
	});
})();
/// end - module/validation
