/********************************************************************************** * modular_model.js * * Modular 프레임워크의 모델과 관련된 부분이 포함되어 있는 스크립트 파일 * 'Modular.model' 이라는 네임스페이스를 사용하고 있음 * * @author 김선엽(sunyoupk@udapsoft.co.kr) **********************************************************************************/ /*============================================================= * * SearchOperator 구현 * =============================================================*/ /** * 조회 연산자를 포함하고 있는 객체 */ Modular.model.SearchOperator = { GREATER : ">", GREATER_EQUAL : ">=", LESS : "<", LESS_EQUAL : "<=", LIKE_BOTH : "LIKE_BOTH", LIKE_NONE : "LIKE_NONE", LIKE_LEFT : "LIKE_LEFT", LIKE_RIGHT : "LIKE_RIGHT", BETWEEN : "BETWEEN", EQUALS : "=", IN : "IN", IS_NULL : "IS NULL", NOT_LIKE_BOTH : "NOT LIKE_BOTH", NOT_LIKE_NONE : "NOT LIKE_NONE", NOT_LIKE_LEFT : "NOT LIKE_LEFT", NOT_LIKE_RIGHT : "NOT LIKE_RIGHT", NOT_BETWEEN : "NOT BETWEEN", NOT_EQUALS : "<>", NOT_IN : "NOT IN", IS_NOT_NULL : "IS NOT NULL", /** * 비교 연산자 배열 */ compareOperator: new Array(), /** * 기간 연산자 배열 */ rangeOperator: new Array(), /** * 복수 연산자 배열 */ multiOperator: new Array(), /** * 연산자 유형 별로 분류 작업을 수행하는 초기화 함수 */ initialize : function() { this.compareOperator.push(this.GREATER); this.compareOperator.push(this.GREATER_EQUAL); this.compareOperator.push(this.LESS); this.compareOperator.push(this.LESS_EQUAL); this.compareOperator.push(this.LIKE_BOTH); this.compareOperator.push(this.LIKE_NONE); this.compareOperator.push(this.LIKE_LEFT); this.compareOperator.push(this.LIKE_RIGHT); this.compareOperator.push(this.EQUALS); this.compareOperator.push(this.IS_NULL); this.compareOperator.push(this.NOT_LIKE_BOTH); this.compareOperator.push(this.NOT_LIKE_NONE); this.compareOperator.push(this.NOT_LIKE_LEFT); this.compareOperator.push(this.NOT_LIKE_RIGHT); this.compareOperator.push(this.NOT_EQUALS); this.compareOperator.push(this.IS_NOT_NULL); this.rangeOperator.push(this.BETWEEN); this.rangeOperator.push(this.NOT_BETWEEN); this.multiOperator.push(this.IN); this.multiOperator.push(this.NOT_IN); }, /** * 주어진 연산자가 비교 연산자인지를 리턴합니다. * * @param op {Modular.model.SearchOperator} 연산자 * @return {boolean} 비교 연산자 여부 */ isCompareOperator : function(op) { if (Modular.Utils.hasText(op)) { for (var i =0; i < this.compareOperator.length; i++) { if (this.compareOperator[i].toLowerCase() == op.toLowerCase()) { return true; }//end if }//end for }//end if return false; }, /** * 주어진 연산자가 기간 연산자인지를 리턴합니다. * * @param op {Modular.model.SearchOperator} 연산자 * @return {boolean} 기간 연산자 여부 */ isRangeOperator : function(op) { if (Modular.Utils.hasText(op)) { for (var i =0; i < this.rangeOperator.length; i++) { if (this.rangeOperator[i].toLowerCase() == op.toLowerCase()) { return true; }//end if }//end for }//end if return false; }, /** * 주어진 연산자가 복수 연산자인지를 리턴합니다. * * @param op {Modular.model.SearchOperator} 연산자 * @return {boolean} 복수 연산자 여부 */ isMultiOperator : function(op) { if (Modular.Utils.hasText(op)) { for (var i =0; i < this.multiOperator.length; i++) { if (this.multiOperator[i].toLowerCase() == op.toLowerCase()) { return true; }//end if }//end for }//end if return false; } }; Modular.model.SearchOperator.initialize(); /*============================================================= * * Condition 구현 * =============================================================*/ /** * Condition 생성자 함수 * * @param key {String} 조회조건 키 * @param operator {Modular.model.SearchOperator} 조회조건 연산자 * @param value {String} 조회조건 값 * @param mapping {String} 조회조건 자동 매핑 여부 */ Modular.model.Condition = function (key, operator, value, mapping) { this.key = key; this.operator = Modular.Utils.hasText(operator) ? operator : Modular.model.SearchOperator.EQUALS; this.value = value || ""; this.type = "normal"; this.mapping = mapping; if (!Modular.model.SearchOperator.isCompareOperator(this.operator)) { throw new Error( "[" + operator + "]는 지원하지 않는 연산자 입니다." ); }//end if };//end of constructor /*============================================================= * * RangeCondition 구현 * =============================================================*/ /** * RangeCondition 생성자 함수 * * @param key {String} 조회조건 키 * @param operator {Modular.model.SearchOperator} 조회조건 연산자 * @param fKey {String} 조회조건 시작 값의 키 * @param tKey {String} 조회조건 종료 값의 키 * @param mapping {String} 조회조건 자동 매핑 여부 */ Modular.model.RangeCondition = function (key, operator, fKey, tKey, mapping) { this.key = key; this.operator = Modular.Utils.hasText(operator) ? operator : Modular.model.SearchOperator.BETWEEN; this.fKey = fKey; this.tKey = tKey; this.fValue = ""; this.tValue = ""; this.type = "range"; this.mapping = mapping; if (!Modular.model.SearchOperator.isRangeOperator(this.operator)) { throw new Error( "[" + operator + "]는 지원하지 않는 연산자 입니다."); }//end if };//end of constructor /*============================================================= * * MultiCondition 구현 * =============================================================*/ /** * MultiCondition 생성자 함수 * * @param key {String} 조회조건 키 * @param operator {Modular.model.SearchOperator} 조회조건 연산자 * @param mapping {String} 조회조건 자동 매핑 여부 */ Modular.model.MultiCondition = function (key, operator, mapping) { this.key = key; this.operator = Modular.Utils.hasText(operator) ? operator : Modular.model.SearchOperator.IN; this.value = []; this.type = "multi"; this.mapping = mapping; if (!Modular.model.SearchOperator.isMultiOperator(this.operator)) { throw new Error("[" + operator + "]는 지원하지 않는 연산자 입니다."); }//end if };//end of constructor /*============================================================= * * SearchCondition 구현 * =============================================================*/ /** * SearchCondition 생성자 함수 * * @param id {String} 조회조건 ID */ Modular.model.SearchCondition = function (id) { this.id = id; this.sessionKey = Modular.model.PageContext.SESSION_KEY_PAGE_SEARCH_CONDITION_BACKUP_KEY_PREFIX + Modular.model.PageContext.REQUEST_URI + ":" + this.id; this.conditions = {}; };//end of constructor /** * 주어진 Condition을 추가합니다. * * @param condition {Modular.model.Condition} Condition 객체 */ Modular.model.SearchCondition.prototype.addCondition = function (condition) { this.conditions[condition.key] = condition; }; /** * 주어진 id에 해당하는 Condition 객체를 리턴합니다. * * @param key {String} Condition의 키 값 */ Modular.model.SearchCondition.prototype.getCondition = function (key) { return this.conditions[key]; }; /** * 주어진 key에 해당하는 Condition이 존재하는지 여부를 리턴합니다. * * @param key {String} Condition의 키 값 * @return {boolean} Condition 존재 여부 */ Modular.model.SearchCondition.prototype.hasCondition = function (key) { if (this.conditions[key]) { return true; } else { return false; }//end if else }; /** * 주어진 key에 해당하는 Condition에 주어진 값을 설정합니다. * * @param key {String} Condition의 키 값 * @param value {Object} Condition의 값 */ Modular.model.SearchCondition.prototype.setCondition = function (key, value) { this.conditions[key].value = value; }; /*============================================================= * * Column 구현 * =============================================================*/ /** * 컬럼 타입을 나타내는 상수 */ Modular.model.ColumnType = { /** * 문자열 타입 */ STRING : "string", /** * 날짜 타입 */ DATE : "date", /** * 숫자 타입 */ NUMBER : "number", /** * boolean 타입 */ BOOLEAN : "boolean" }; /** * Column 생성자 함수 * * @param id {String} 컬럼 ID * @param type {Modular.model.ColumnType} 컬럼 타입 */ Modular.model.Column = function (id, type) { this.id = id; this.type = type; };//end of constructor /*============================================================= * * Record 구현 * =============================================================*/ /** * 레코드 상태를 나타내는 상수 * * TODO 서버의 상수와 동기화를 위해 JSP 형태로 변경하는 것을 고려 * * 2008.11.21 - Helexis */ Modular.model.RecordStatus = { /** * 레코드 상태 : 정상 - 아무일도 일어나지 않았음 */ NORMAL : "N", /** * 레코드 상태 : 추가 - 신규 추가된 레코드 임 */ INSERTED : "I", /** * 레코드 상태 : 수정 - 수정된 레코드 임 */ UPDATED : "U", /** * 레코드 상태 : 삭제 - 삭제된 레코드 임 */ DELETED : "D" }; /** * Record 생성자 함수 * * @param id {String} 레코드 ID * @param value {Array} 레코드 값. Array 형태임. * @param status {Modular.model.RecordStatus} 레코드 상태 값 */ Modular.model.Record = function (id, value, status) { this.id = id; this.cell = value || []; this.status = status || Modular.model.RecordStatus.NORMAL; };//end of constructor /** * 주어진 인덱스에 해당하는 컬럼 값을 리턴합니다. * * @param id {Number} 컬럼 인덱스 * @return 컬럼 값 */ Modular.model.Record.prototype.getColumn = function (id) { if (Modular.Utils.hasText(id)) { var num = new Number(id); if (isNaN(num)) { return null; } else { // 인덱스라면, 그냥 컬럼 배열에서 꺼내준다. return this.cell[id]; }//end if else } else { return null; }//end if }; /** * 주어진 인덱스와 값을 지정된 컬럼에 설정합니다. * * @param idx 컬럼 인덱스 * @param value 컬럼 값 */ Modular.model.Record.prototype.setColumn = function (idx, value) { var num = new Number(idx); if (isNaN(num)) { // do nothing... } else { // 인덱스라면, 그냥 컬럼을 찾아서 설정한다. this.cell[idx] = value; }//end if else }; /** * 레코드가 가지는 컬럼 값을 객체의 형태로 만들어서 리턴합니다. * * @param columns {Array} 컬럼 배열 * @return {Object}레코드의 ID와 값을 포함하는 객체 */ Modular.model.Record.getDataRow = function (columns, cell) { var row = {}; jQuery.each(columns, function(idx, column) { row[column.name] = cell[idx]; }); return row; }; /*============================================================= * * RecordSet 구현 * =============================================================*/ /** * RecordSet 생성자 함수 * * 파라미터로 받는 객체 metadata는 아래와 같은 프로퍼티를 포함할 수 있습니다. * * - bindingClass {Object} 서버의 DTO 바인딩을 위한 클래스 * - columns {Array} 레코드의 컬럼 메타 정보 배열 * - volumePerPage {Number} 페이지 당 게시물 수 * - currentPage {Number} 현재 페이지 * - totalRecordSize {Number} 전체 게시물 수 * * @param id {String} 레코드 셋 ID * @param metadata {Object} 레코드의 메타 정보를 포함하는 객체 * @param records {Array} 레코드의 객체의 Array */ Modular.model.RecordSet = function (id, metadata, records) { this.id = id; this.metadata = jQuery.extend({ /** * 레코드 컬럼 정보 */ columns : [], /** * 레코드 셋이 바인딩되는 DTO 클래스 : 기본 값은 java.util.HashMap * * 기본 값을 java.util.Map으로 하는 이유는, DTO 없이 개발이 가능하도록 하기 위함임. */ bindingClass : "java.util.HashMap", /** * 페이지 당 게시물 수 : 기본 값은 10 */ volumePerPage : 10, /** * 현재 페이지 : 기본 값은 1 */ currentPage : 1, /** * 전체 게시물 수 */ totalRecordCount : 0, /** * 테이블 내부의 페이징이나 정렬 기능을 사용할 경우, 호출할 조회 컨트롤 ID */ searchControlId : "", /** * 정렬 대상 컬럼 */ sortColumnId : "", /** * 정렬 대상 컬럼 정렬순서 */ sortOrder : true, /** * 응답을 처리하기 위한 핸들러 */ responseHandler : false, /** * 레코드 셋의 처리 결과를 담는 객체 */ processResult : {} }, metadata || {}); this.records = records || []; };//end of constructor /** * 주어진 ID 혹은 인덱스에 해당하는 레코드를 리턴합니다. * * @param id {String} or {Number} 레코드 ID 혹은 인덱스 * @return {Modular.model.Record} Record 객체 */ Modular.model.RecordSet.prototype.getRecord = function (id) { if (Modular.Utils.hasText(id)) { var num = new Number(id); if (isNaN(num)) { // TODO 성능 개선 필요 for (var i = 0; i < this.records.length; i++) { if (this.records[i].id == id) { return this.records[i]; }//end if }//end for return null; } else { // 인덱스라면, 그냥 레코드 배열에서 꺼내준다. return this.records[id]; }//end if else } else { if (isNaN(id)) { return null; }//end if return this.records[id]; }//end if }; /** * 레코드를 추가합니다. * * @param id {String} 레코드 ID * @param value {Array} 레코드 값. Array 형태임. * @param status {Modular.model.RecordStatus} 레코드 상태 값 * @return {Modular.model.Record} 추가된 레코드 객체 */ Modular.model.RecordSet.prototype.addRecord = function (id, value, status) { if (this.getRecord(id)) { /* * TODO 비즈니스 로직적으로 그냥 오류를 발생시키고 * 말아도 되는지 확인해 보도록! * * 2008.11.11 - Helexis */ throw new Error("[" + id + "]에 해당하는 레코드가 이미 존재합니다."); }//end if var stat = status || Modular.model.RecordStatus.INSERTED; var record = new Modular.model.Record(id, value, stat); this.records.push(record); return record; }; /** * 주어진 ID에 해당하는 레코드를 삭제합니다. * * @param id {String} 레코드 ID */ Modular.model.RecordSet.prototype.deleteRecord = function (id) { this.getRecord(id).status = Modular.model.RecordStatus.DELETED; }; /** * 레코드 셋이 가지는 모든 레코드를 객체의 배열 형태로 만들어서 리턴합니다. * * @param recordSet {Modular.model.RecordSet} 레코드 셋 객체 * @return {Array} 레코드의 ID와 값을 포함하는 객체 배열 */ Modular.model.RecordSet.getDataRows = function (recordSet) { var rows = []; var columns = recordSet.metadata.columns; var records = recordSet.records; jQuery.each(records, function (index, record) { rows.push(Modular.model.Record.getDataRow(columns, record.cell)); }); return rows; }; /*============================================================= * * PageContext 구현 * =============================================================*/ /** * 페이지 내에서 존재하는 모델을 포함하는 PageContext 객체를 생성합니다. */ Modular.model.PageContext = { /** * 컨텍스트 ROOT 경로를 나타내는 상수 */ CONTEXT_ROOT : "", /** * 페이지 요청 URI를 나타내는 상수 */ REQUEST_URI : "", /** * 페이지 내부의 초기화 함수 목록 */ initFunctions : [], /** * 페이지 내부에서 사용하는 코드 목록 */ codeContext : {}, /** * 페이지 내부에서 사용하는 validator 목록 */ validators : [], /** * 페이지 내부에서 사용하는 그리드에 열 추가 시 * 임시로 사용할 레코드의 시퀀스 */ gridTempRecordSequence : 0, /** * 페이지 내부의 SearchCondition 목록 */ searchConditions : {}, /** * 페이지 내부의 RecordSet 목록 */ recordSets : {}, /** * 페이지 내부의 Control 목록 */ controls : {}, /** * 페이지 네비게이션 히스토리 목록 */ navigationHistoryFunctions : [], /** * 페이지 내부의 파일 업로드 컴포넌트 목록 */ fileSets : {}, /** * 페이지 내부의 파일 업로드 컴포넌트 뷰 목록 */ fileSetViews : {}, /** * 조회조건에 대한 폼 값 바인딩 여부 */ popSearchForm : true, /** * 페이지 네비게이션 설정 데이터 */ navigation : {}, /** * 주어진 ID에 해당하는 SearchCondition 객체를 리턴합니다. * * @param id SearchCondition ID * @return SearchCondition 객체 */ getSearchCondition : function (id) { return Modular.model.PageContext.searchConditions[id]; }, /** * 주어진 ID에 해당하는 RecordSet 객체를 리턴합니다. * * @param id RecordSet ID * @return RecordSet 객체 */ getRecordSet : function (id) { return Modular.model.PageContext.recordSets[id].recordSet; }, /** * 주어진 ID에 해당하는 RecordSet 객체의 초기화 함수 목록를 리턴합니다. * * @param id RecordSet ID * @return 초기화 함수 목록 */ getRecordSetInitFunctions : function (id) { return Modular.model.PageContext.recordSets[id].initFunctions; }, /** * 주어진 ID에 해당하는 Control 객체를 리턴합니다. * * @param id Control ID * @return Control 객체 */ getControl : function (id) { return Modular.model.PageContext.controls[id]; }, /** * 주어진 객체를 PageContext 에 추가합니다. * * @param t {Object} PageContext 객체에 추가할 SearchCondition/RecordSet/Control 객체 */ add : function (t) { if (t instanceof Modular.model.SearchCondition) { Modular.model.PageContext.searchConditions[t.id] = t; } else if (t instanceof Modular.model.RecordSet) { Modular.model.PageContext.recordSets[t.id] = { recordSet : t, initFunctions : [] }; } else if (t instanceof Modular.model.Control) { Modular.model.PageContext.controls[t.id] = t; }//end if else } }; /*============================================================= * * Control 구현 * =============================================================*/ /** * 컨트롤을 생성하기 위한 생성자 * * 옵션으로 다음과 같은 내용이 포함될 수 있습니다. * - id 컨트롤 ID * - eventHandler 컨트롤의 이벤트 핸들러 함수 * - nextControl 다음 실행 컨트롤. 주로, 저장 후 재 조회 시 사용. * * @param o {Object} 옵션 내역을 포함하는 객체 */ Modular.model.Control = function (o) { var option = o || {}; this.id = option.id; this.chainControl = option.chainControl; this.eventHandler = null; this.url = ""; this.successMessage = option.successMessage; this.commands = []; }; /** * 컨트롤에 커맨드를 추가합니다. * * @param c 커맨드 */ Modular.model.Control.prototype.add = function (c) { if (c && c instanceof Modular.model.Command) { this.commands.push(c); }//end if }; /*============================================================= * * Command 구현 * =============================================================*/ /** * 커맨드 타입을 나타내는 상수 */ Modular.model.CommandType = { /** * 조회 타입 */ SEARCH : "search", /** * 추가 타입 */ INSERT : "insert", /** * 삭제 타입 */ DELETE : "delete", /** * 수정 타입 */ UPDATE : "update", /** * 저장 타입 */ SAVE : "save" }; /** * 커맨드를 생성합니다. * * @param {Object} 옵션 객체 */ Modular.model.Command = function (option) { this.id = option.id; this.type = option.type; this.i = option.i; this.o = option.o; this.preHandle = option.preHandle; this.searchCondition = null; this.recordSets = {}; }; /** * 커맨드 전송 전 준비 작업을 수행합니다. */ Modular.model.Command.prototype.prepare = function () { var command = this; switch (this.type) { case Modular.model.CommandType.SEARCH : /* * 조회일 경우에는 불필요하게 RecordSet 내부의 데이터가 전송되지 않도록, * RecordSet의 메타 정보만 복사하여 전송한다. */ Modular.view.SearchView.setAllConditions(this.i); this.searchCondition = Modular.model.PageContext.getSearchCondition(this.i); jQuery.each(this.o, function (n, val) { var rs = Modular.model.PageContext.getRecordSet(val); command.recordSets[rs.id] = new Modular.model.RecordSet(rs.id, rs.metadata); }); break; default : var rs = Modular.model.PageContext.getRecordSet(this.i); this.recordSets[rs.id] = rs; }//end switch case }; /*============================================================= * * ServiceExecutionContext 구현 * =============================================================*/ /** * 컨트롤 객체를 사용하여 ServiceExecutionContext 객체를 초기화 합니다. * * @param control 컨트롤 객체 */ Modular.model.ServiceExecutionContext = function (control, flag) { jQuery.each(control.commands, function (n, command) { if (!flag) { command.preHandle.call(this); }//end if command.prepare(); }); this.commands = control.commands; };