ezinc 교육 자료
Search posts...

Categories

  • All Posts16
  • 3rabbitz1
  • 클립리포트6
  • Ezworks3
  • Git5
  • 문서 작성 가이드1
© 2026 Git Static Doc Server. All rights reserved.
Open Source Licenses
General•2026년 5월 12일

Nexacro + EZWORKS 화면 개발 가이드

기존 화면을 복사해 새 업무 화면을 만들 때 자주 확인하는 EZWORKS 기능을 정리한 교육용 가이드이다. 공통환경, 프레임 배치처럼 프로젝트에서 거의 고정된 부분보다는 실제 화면을 고칠 때 반복해서 쓰는 함수와 작업 순서를 중심으로 다룬다.

NOTE

화면명은 문서를 읽을 때 이해하기 위한 정보이고, 괄호 안의 화면 ID는 프로젝트에서 직접 찾아보기 위한 정보이다.


1. 복사 개발 순서

유사 화면을 복사했다면 화면을 실행하기 전에 아래 순서로 먼저 확인한다.

  1. 화면 파일명, Form id, 화면 주석, Titlebar text를 새 메뉴 기준으로 바꾼다.
  2. 검색조건, 상세 Div, Sub Grid, 버튼 같은 화면 디자인 요소를 먼저 정리한다.
  3. SQL을 작성하고 Dataset Source Editor로 Dataset 컬럼을 맞춘다.
  4. Grid Source Editor로 Grid 컬럼, 폭, 정렬, displaytype, edittype을 맞춘다.
  5. 디자인 작업이 끝나면 스크립트를 위에서 아래로 정리한다.
  6. form_onload, 콤보 초기화, Validator, Titlebar 전처리, 트랜잭션, 사용자 함수, 이벤트 순서로 확인한다.
  7. sqlId, inDs, outDs, param, targetGrid, conditionDiv가 새 업무 기준인지 확인한다.
  8. 팝업, 파일, 리포트가 있으면 this.pop.popup, EzFile, EzReport, $f.mkArg를 마지막에 따로 점검한다.

기본 흐름은 상벌코드관리 [hhs_0101_u01.xfdl]에서 보기 좋다. 조회, 저장, 공통콤보, Validation, Titlebar 구성이 단순하다. 학사콤보는 학사콤보테스트 [sample_hcmb.xfdl]를 같이 보면 된다.

1.1. 예제: 전공배정관리에서 다중전공관리 만들기

이 예제는 전공배정관리 [hhj_0601_u01.xfdl]를 복사해서 다중전공관리 [hhj_0602_u01.xfdl]를 만드는 흐름이다. 두 화면은 대학, 학과, 전공, 학적상태, 학년, 전공구분, 학번/성명 검색조건이 비슷해서 학사콤보, 공통콤보, 학생검색 팝업, 조회 파라미터를 재사용하기 좋다.

NOTE

[hhj_0601_u01.xfdl]는 메뉴 타입에 따라 전공배정관리와 다중전공소속변경에서 같이 쓰이는 화면이다. 복사할 때는 현재 만들 메뉴의 업무 기준으로 제목, SQL, 버튼, 저장 로직을 다시 맞춘다.

1단계. 화면 기본 정보 변경

복사한 화면을 열면 먼저 화면 자체의 이름부터 바꾼다. 파일명은 [hhj_0602_u01.xfdl]로 맞추고, Form id, 화면 상단 주석, titletext, EzTitlebar의 text, excelExportName을 새 메뉴명 기준으로 바꾼다.

전공배정관리의 tit_main은 text="학생 목록"이고 하단에 tit_sub, grd_sub가 있다. 다중전공관리에서는 tit_main을 text="다중전공학생목록"으로 바꾸고, tit_sub, grd_sub, 상세 영역을 유지할지 삭제할지 먼저 정한다. 이 예제에서는 grd_main 하나를 중심으로 정리하는 방향으로 잡는다.

2단계. 조회조건 컴포넌트 정리

먼저 남길 조회조건을 정한다. 이 예제에서는 div_search 안의 cmb_univCd, cmb_dpmjCd, cmb_majrCd, cmb_scrgSttus, cmb_grde, cmb_majrDvcd, sch_sno를 유지한다. 다중전공관리에는 연도/학기와 전체조회가 필요하므로 chk_allYn, spn_yy, cmb_smr를 검색조건 영역에 추가한다.

필요 없는 조회조건을 지울 때는 화면에서 컴포넌트를 클릭한 뒤 Ctrl + Shift + C로 경로를 복사한다. 예를 들어 복사된 경로가 this.div_search.form.cmb_xxx라면 스크립트에서 그 문자열을 검색해 새 화면에서 쓰지 않을 참조와 이벤트 함수를 같이 삭제한다. 이때 삭제하는 것은 컴포넌트와 직접 연결된 코드이고, 트랜잭션, Validator, 저장 로직처럼 흐름을 다시 맞춰야 하는 부분은 디자인 작업이 끝난 뒤 6단계에서 위에서 아래로 정리한다.

삭제 순서는 다음처럼 잡는다. 조회조건뿐 아니라 상세 Div, 숨김 Div, Sub Grid, Titlebar, Splitter처럼 화면에서 제거하는 UI 요소는 같은 방식으로 처리한다.

  1. 화면에서 삭제 대상 컴포넌트 경로를 복사한다.
  2. 스크립트에서 해당 경로를 검색해 새 화면에서 안 쓰는 참조를 삭제한다.
  3. onclick, onitemchanged, onsearched 같은 이벤트 연결 함수도 삭제한다.
  4. 화면 컴포넌트를 삭제한다.
  5. 남은 컴포넌트의 위치와 간격을 조정한다.

추가할 컴포넌트는 기존 화면에 같은 유형이 있으면 복사해서 쓰는 편이 빠르다. Spin, Combo, CheckBox, EzSearcher는 cssclass, taborder, 높이, 이벤트 패턴이 이미 맞춰진 경우가 많다. 기존 화면에 없으면 새로 추가하고, id는 프로젝트 규칙에 맞게 정한다.

컴포넌트 id가 애매할 때는 Data Source Generator를 참고한다. 메뉴는 시스템관리 > 개발 > 개발도구 > Data Source Generator이고, DML & Element ID 생성 탭의 ELEMENT 탭에서 테이블 컬럼 기준으로 stt_, edt_, cmb_, spn_, cal_ 형태의 camelCase id를 바로 확인할 수 있다.

3단계. 상세 Div와 Sub Grid 삭제 또는 재구성

복사 기준 화면에는 전공 변경용 상세 영역과 변경 목록이 있다. 전공배정관리의 div_detail에는 cmb_majrChngDvcd, cal_chngYmd, spn_yy, cmb_smr, edt_chngReason 같은 전공 변경 정보가 있고, 하단에는 tit_sub, grd_sub, ds_sub가 연결되어 있다. 다중전공관리에서는 이 구조가 그대로 필요하지 않으므로 먼저 제거 범위를 정한다.

Div를 삭제할 때도 조회조건과 같은 방식으로 처리한다. div_detail을 클릭하고 Ctrl + Shift + C로 경로를 복사한 뒤 스크립트에서 새 화면에 남기지 않을 참조와 이벤트를 삭제한다. Div 안의 컴포넌트는 보통 this.div_detail.form...처럼 부모 Div를 거쳐 접근하므로 this.div_detail만 검색해도 관련 참조를 넓게 찾을 수 있다.

전공배정관리에서 다중전공관리로 바꿀 때 디자인에서 삭제하는 대표 항목은 tit_sub, grd_sub, 변경 목록 영역, 전공 변경 정보 영역이다. div_detail은 전공 변경 정보 영역으로 쓰지 않고, 다중전공관리에서는 포기사유 입력용으로 edt_gvupReason만 둔 작은 영역으로 다시 구성한다.

삭제가 끝나면 tit_main, grd_main, div_detail의 위치와 높이를 다시 맞춘다. 하단 Sub Grid를 없애면 grd_main의 bottom을 화면 하단 기준으로 다시 잡고, EzSplitter 같은 분할 컴포넌트가 남아 있지 않은지 확인한다.

4단계. SQL 작성과 Dataset 준비

디자인 정리가 끝나면 SQL과 Dataset을 맞춘다. Data Source Generator의 DML & Element ID 생성 탭은 테이블명을 넣으면 SELECT, INSERT, UPDATE, DELETE 기본 문장을 만들어준다. 컬럼 alias, 주석, #{PARAM} 형태의 조건도 같이 나오므로 XML 쿼리를 처음 잡을 때 참고한다.

첨부파일 컬럼을 가진 화면이면 FILE_KEY, FILE_NM 탭도 확인한다. 복합키로 FILE_KEY를 만드는 방식과 SF_CSB_GET_FILE_INFO(...)로 파일명을 조회하는 식을 참고할 수 있다. 파일이 있는 화면은 SQL을 만들 때 이 부분을 같이 챙겨야 Grid에서 파일명 표시나 파일 컴포넌트 연결이 자연스럽다.

XML 파일을 만들고 sqlId가 정해지면 데이터소스생성 탭에서 SQL_ID를 입력해 Dataset 소스를 생성한다. 생성된 <ColumnInfo>를 Nexacro의 Dataset Source Editor에 붙여 넣으면 Dataset 컬럼을 손으로 하나씩 만들지 않아도 된다.

다중전공관리의 조회는 hhj_0602_u01.s01, 저장은 hhj_0602_u01.p01, 포기는 hhj_0602_u01.p02를 사용한다. Dataset 컬럼은 SQL 결과 컬럼, Grid bind: 컬럼, Validator 컬럼명과 반드시 같은 기준으로 맞춘다.

5단계. Grid 생성과 컬럼 속성 조정

Dataset이 준비되면 Data Source Generator의 그리드 탭에서 SQL 컬럼 기준의 Grid XML을 대략 생성한다. 여기서 컬럼명칭, 컬럼크기, 컬럼정렬, displaytype, edittype을 먼저 잡고 생성된 XML을 Nexacro Grid Editor에 붙여 넣는다.

생성된 Grid XML은 Nexacro의 Grid Source Editor에 붙여 넣는다. 붙여 넣은 뒤에는 화면에서 직접 열어 컬럼 폭, 정렬, 콤보 표시, 체크박스 표시가 의도대로 보이는지 한 번 더 조정한다.

Grid를 붙인 뒤에는 binddataset, bind: 컬럼, 콤보 컬럼의 displaytype, edittype, checkbox 컬럼, expandup 컬럼을 다시 확인한다. 다중전공관리에서는 grd_main 하나를 중심으로 ds_main을 연결하고, tit_main의 targetGrid="grd_main", conditionDiv="div_search"도 같이 맞춘다.

6단계. 스크립트는 위에서 아래로 정리

디자인, Dataset, Grid 작업이 끝나면 Script 탭으로 넘어간다. 이때는 삭제할 컴포넌트를 따라 여기저기 뛰기보다 파일 위에서부터 아래로 내려가며 정리한다. 앞 단계에서 Ctrl + Shift + C로 찾은 참조 위치는 이 단계에서 같이 처리한다.

1. form_onload와 초기화 호출 정리

복사 기준 화면의 초기화 호출 중 새 화면에 필요 없는 것을 먼저 뺀다. 전공배정관리에서 쓰던 전공 변경 정보 Dataset, 상세 Div 바인딩, 변경 목록 재검증 흐름은 다중전공관리에서 제거 대상이다.

this.fn_initMajrInfo();                 // 전공 변경 정보 초기 세팅
$comp.setDetail(this.div_detail, this.ds_majrChange, this);
$f.revalidateAllRow(this.mainValidator);

다중전공관리에서는 기본적으로 콤보 초기화와 Validator 초기화만 남긴다.

this.form_onload = async function(obj:nexacro.Form,e:nexacro.LoadEventInfo) {
    await this.fn_initCombo();
    this.fn_initValidator();
};

2. 콤보 초기화 정리

fn_initCombo에서는 디자인에서 남긴 영역만 대상으로 잡는다. 전공배정관리 원본은 div : [this.div_search, this.div_detail], grid : this.grd_sub 형태지만, 다중전공관리에서는 검색조건과 메인 그리드만 쓰므로 아래처럼 정리한다.

await $f.getHCmb(this).init({
    div  : this.div_search,
    grid : this.grd_main,
    smrMode : ["n", "n"]
});

fn_setCombo에서는 삭제한 grd_sub, 전공 변경 상세 Div, 변경구분 콤보를 제거한다. 다중전공관리에서는 div_search와 grd_main에 필요한 공통코드만 남기고, 전공구분은 다중전공 대상만 나오도록 CLA_CD='1540' SUPLMT_INFO_5='1' 조건을 사용한다.

3. Validator 대상 변경

전공배정관리 원본은 ds_majrChange를 대상으로 UNIV_CD, DPMJ_CD, MAJR_CHNG_DVCD, CHNG_YMD, CHNG_REASON, YY, SMR를 검사한다. 다중전공관리에서는 grd_main을 대상으로 SNO, MAJR_DVCD, ORG_CD를 검사한다.

this.mainValidator = new EzValidator(this.grd_main, {
    SNO : {
        required : { value : true, msg : "학번은 필수입력 항목입니다." }
    },
    MAJR_DVCD : {
        required : { value : true, msg : "전공구분은 필수선택 항목입니다." }
    },
    ORG_CD : {
        required : { value : true, msg : "조직코드는 필수입력 항목입니다." }
    }
}, this);

this.mainValidator.markingCells();

4. Titlebar 공통 함수 정리

Titlebar 전처리 영역에서는 조회, 저장, 신규, 삭제 가능 여부를 먼저 맞춘다. 삭제한 ds_sub 기준 확인은 제거하고, 다중전공관리에서는 ds_main 기준으로 확인한다.

this.fn_cansearch = async function() {
    return $mpt.confirmSearch(this.ds_main);
};

this.fn_cansave = function() {
    return $mpt.confirmSave(this.ds_main, this.mainValidator);
};

신규 행은 검색조건의 연도/학기를 기본값으로 세팅한다. 직접 신규 행을 만드는 fn_add 방식이면 addRow()를 호출한다.

this.fn_add = function() {
    let nRow = this.ds_main.addRow();

    this.ds_main.setColumn(nRow, "YY", this.div_search.form.spn_yy.value);
    this.ds_main.setColumn(nRow, "SMR", this.div_search.form.cmb_smr.value);
};

반대로 EzTitlebar의 onadded 이벤트에서 후처리하는 화면은 Titlebar가 이미 행을 추가한 뒤 onadded를 호출한다. 이때는 다시 addRow()를 하지 않고 e.row를 사용한다. 이 패턴은 사유분류코드/명 [hhj_0102_u01.xfdl]의 tit_main_onadded와 fn_addRow에서 확인할 수 있다.

this.tit_main_onadded = function(obj:nexacro.EzTitlebar, e:nexacro.EzTitlebarEventInfo) {
    this.fn_addRow(this.ds_main, e.row);
};

this.fn_addRow = (obj, nRow) => {
    obj.setColumn(nRow, "SCRG_VARB_CD", "자동채번");
    let upCd = '';
    if (obj.id == "ds_main") {
        return true;
    } else if (obj.id == "ds_sub") {
        upCd = this.ds_main.getColumn(this.ds_main.rowposition, "SCRG_VARB_CD");
    } else if (obj.id == "ds_detail") {
        upCd = this.ds_sub.getColumn(this.ds_sub.rowposition, "SCRG_VARB_CD");
    }
    
    obj.setColumn(nRow, "UP_SCRG_VARB_CD", upCd);
    obj.setColumn(nRow, "FCLT_USE_YN", "1");
    obj.setColumn(nRow, "GRSC_USE_YN", "1");
};

WARNING

fn_add처럼 직접 신규 행을 만드는 함수에서는 this.ds_main.addRow()를 호출한다. 반대로 tit_main_onadded처럼 Titlebar Add 후처리에서는 이미 추가된 행이 있으므로 addRow()를 다시 호출하지 않는다. 두 방식을 섞으면 신규 행이 두 줄 생기거나 기본값이 엉뚱한 row에 들어갈 수 있다.

Titlebar의 Save 버튼은 기본적으로 Titlebar 속성에 등록된 insertSqlId, updateSqlId, deleteSqlId, targetGrid, targetDataset 등을 기준으로 저장할 수 있다. 다중전공관리처럼 화면 공통 저장 함수에서 업무 저장 함수를 직접 호출하는 경우도 있다.

this.fn_save = async function() {
    await this.fn_saveListMain();
};

업무 로직이 들어가는 학사 화면은 Titlebar의 onsave를 오버라이딩해서 저장 함수를 직접 연결하기도 한다. 이 패턴은 사유분류코드/명 [hhj_0102_u01.xfdl]에서 확인할 수 있다.

this.fn_initEzTitlebarTxFunc = () => {
    this.tit_main.onsave = this.fn_saveMain;
    this.tit_main.onsearch = this.fn_searchMain;
    this.tit_sub.onsave = this.fn_saveSub;
    this.tit_sub.onsearch = this.fn_searchSub;
    this.tit_detail.onsave = this.fn_saveDetail;
    this.tit_detail.onsearch = this.fn_searchDetail;
};

WARNING

cansave는 저장 전에 확인과 Validation을 수행하는 전처리이고, onsave는 실제 저장 동작이다. tit_main.onsave = this.fn_saveMain처럼 오버라이딩하면 Titlebar의 기본 저장 흐름을 직접 만든 함수로 바꾸는 것이므로, sqlId, inDs, param, 저장 후 재조회까지 새 화면 기준으로 다시 확인한다.

5. 트랜잭션 함수 정리

트랜잭션 영역에서는 조회, 저장, 프로시저 호출 순서로 새 SQL ID를 맞춘다. 조회는 검색조건 Div 기준으로 파라미터를 만들고 hhj_0602_u01.s01을 호출한다.

this.fn_searchMain = async () => {
    var sParam = $comp.makeParamStr(this.div_search);

    await this.tx.search({
        action : "basic",
        svcId  : "fn_searchMain",
        sqlId  : "hhj_0602_u01.s01",
        outDs  : "ds_main",
        param  : sParam
    });
};

저장은 hhj_0602_u01.p01을 호출하고, 저장 후 재조회한다.

this.fn_saveListMain = async () => {
    await this.tx.save({
        action : "basic",
        svcId  : "fn_save",
        sqlId  : "hhj_0602_u01.p01,,",
        inDs   : "ds_main"
    });

    $mpt.alertSaved();
    await this.fn_searchMain();
};

다중전공 포기 버튼은 hhj_0602_u01.p02를 호출한다. 체크된 행과 포기사유를 먼저 확인하고, GVUP_REASON을 param으로 넘긴다.

this.fn_execGvup = async () => {
    let chkBox = this.tit_main.getCheckBox();

    if (!chkBox.hasCheckedRow) {
        this.pop.alert("체크된 행이 없습니다.");
        return false;
    }

    if ($util.isNull(this.div_detail.form.edt_gvupReason.value)) {
        this.pop.alert("포기사유를 입력하세요.");
        return false;
    }

    await this.tx.save({
        action : "basic",
        svcId  : "fn_execGvup",
        sqlId  : ",hhj_0602_u01.p02,",
        inDs   : "ds_main",
        param  : "GVUP_REASON='" + this.div_detail.form.edt_gvupReason.value + "'"
    });
};

6. 사용자 정의 함수와 컴포넌트 이벤트 정리

마지막 영역에서는 버튼, 검색 팝업, 체크박스, 그리드 이벤트를 맞춘다. 전공배정관리에서 가져온 fn_searchSub, tit_sub_cansave, fn_initMajrInfo, fn_execMajrChange, ds_main_onrowposchanged의 fn_searchSub() 호출은 이 단계에서 제거한다.

다중전공 포기 버튼은 tit_main의 extra button으로 연결한다.

this.tit_main_onextrabuttonclick = async function(obj:nexacro.EzTitlebar, e:nexacro.ClickEventInfo) {
    if (e.fromobject.id == "btn_gvup") {
        this.fn_execGvup();
    }
};

전체조회 체크박스는 연도/학기 조건을 잠그는 이벤트를 둔다.

this.div_search_chk_allYn_onclick = function(obj:nexacro.CheckBox,e:nexacro.ClickEventInfo) {
    let readonly = obj.value == 1;

    this.div_search.form.spn_yy.readonly = readonly;
    this.div_search.form.cmb_smr.readonly = readonly;
};

Grid 확장 버튼, 학생검색 팝업, 조직검색 팝업처럼 화면 조작에 필요한 이벤트도 마지막에 맞춘다. 다중전공관리에서는 grd_main_onexpandup에서 학번 컬럼은 학생검색 팝업, 조직코드 컬럼은 조직검색 팝업을 열고, 반환값을 ds_main의 SNO, NAME, SCRG_STTUS, GRDE, ORG_CD, UNIV_CD, DPMJ_CD, MAJR_CD 등에 세팅한다.


2. 자주 쓰는 공통 함수

분류 함수/컴포넌트 주요 역할
트랜잭션 this.tx.search, this.tx.save, this.tx.exec, this.tx.load 조회, 저장, 프로시저 실행, 별도 URL 호출
콤보 this.combo.setCombo, this.combo.createComboChain, this.combo.setFilter 일반 공통코드 콤보와 그리드 콤보 세팅
학사콤보 $f.getHCmb(this).init 연도, 학기, 대학, 학과, 전공 등 학사 공통 조건 세팅
메시지 $mpt.confirmSearch, $mpt.confirmSave, $mpt.confirmRemove, $mpt.alertSaved 변경 데이터 확인, 저장 확인, 완료 메시지
Validation new EzValidator, markingCells, markingComps, check 필수값, 길이, 날짜, 형식 검증
파라미터 $f.mkArg, $comp.makeParamStr, $ds.rowToParam, $util.nvl, $util.isNull 검색조건, Dataset row, 팝업/리포트 인자 생성
Dataset $ds.isUpdated, $ds.rowToParam, $ds.getRowData, $ds.saveJSON 변경 여부, 현재 행 데이터 추출
Grid $grid.setComboDataset, $grid.getCellBind, $grid.getHeadText 그리드 콤보와 컬럼 정보 처리
팝업 EzSearcher, this.pop.popup, this.pop.close 검색 팝업, 일반 팝업 호출과 반환
부가 컴포넌트 EzTitlebar, EzFile, EzReport, EzSplitter, EzSpacer 버튼, 파일, 리포트, 화면 배치 보조

TIP

복사 개발에서는 새 함수를 먼저 찾기보다 기존 화면의 fn_initCombo, fn_initValidator, fn_search, fn_save 흐름을 따라가는 편이 빠르다.


3. 트랜잭션 사용법

화면 데이터 처리는 대부분 this.tx로 한다. this.tx는 화면에서 직접 new 하지 않고 사용할 수 있는 EZWORKS 트랜잭션 객체다.

3.1. 조회

await this.tx.search({
    action : "basic",
    svcId  : "fn_search",
    sqlId  : "hhs_0101_u01.s01",
    outDs  : "ds_main",
    param  : sParam
});

3.2. 저장

await this.tx.save({
    action : "basic",
    svcId  : "fn_save",
    sqlId  : "hhs_0101_u01.i01,hhs_0101_u01.u01,hhs_0101_u01.d01",
    inDs   : "ds_main"
});

save는 sqlId의 순서가 중요하다. 일반적으로 insert,update,delete 순서로 comma를 맞춘다. update SQL이 없다면 sample.i01,,sample.d01처럼 빈 자리를 남기는 방식도 사용할 수 있다.

3.3. this.tx.load

this.tx.load는 기본 /basic 트랜잭션이 아닌 특정 서버 URL로 직접 요청할 때 사용한다. 파일 임시복사, 엑셀 업로드 검증, 개발도구성 조회, 별도 컨트롤러 호출에서 자주 보인다.

화면 용도
가상계좌일괄업로드 [bul_0110_u01.xfdl] 엑셀 업로드 데이터 검증, /datavalidator/check 호출
화면설계서용 [dev_0201_v01.xfdl] devtool/tableinfo 호출로 SQL 테이블 정보 조회
사회봉사실적신청팝업 [hhs_0502_p01.xfdl] 첨부파일 조회/복사 계열 처리
자료정보관리 [bui_0103_u01.xfdl] 첨부파일 조회/복사 계열 처리
부서출석인정 관리 [hse_0713_u01.xfdl] 엑셀/파일 관련 Dataset 전달 처리

파일 삭제 예제는 부서출석인정 관리 [hse_0713_u01.xfdl]에서 확인할 수 있다. 삭제 대상 파일 Dataset을 JSON으로 만든 뒤 /ezFile/basic/del URL로 전달한다.

this.fn_delFile = async function() {
    if(this.ds_fileDel.getRowCount() > 0) {
        var sdelFile = $ds.saveJSON(this.ds_fileDel);
        
        await this.tx.load({
            action : "basic",
            svcId : "fileDel",
            param : "UUID_LIST=" + JSON.stringify(sdelFile),
        }, '/ezFile/basic/del')
    };
};

load는 두 번째 인자로 URL을 받을 수 있다. 해당 URL의 컨트롤러는 MapDTO를 받아 Dataset을 만들거나 가공한 뒤 outDs로 반환한다.

3.4. Dataset 업로드 검증

가상계좌일괄업로드 [bul_0110_u01.xfdl]은 /datavalidator/check와 ERR_TEXT를 함께 사용하는 예제다. 엑셀로 업로드한 가상계좌번호를 서버에서 검증하고, 오류가 있는 행은 ERR_TEXT에 메시지를 받아 그리드 상단으로 정렬한다.

호출 전에는 업로드 데이터가 있는지 확인하고, 그리드와 Titlebar 상태를 업로드 확인용으로 맞춘다.

if (this.ds_main.getRowCount() == 0) {
    this.pop.alert("입력된 데이터가 없습니다.");
    return false;
}

this.grd_main.formatid = "excel";
this.tit_main.checkboxBodyCellIndex = -1;
this.tit_main.checkboxHeadCellIndex = -1;
this.tit_main.statCellIndex = -1;
this.tit_main.checkboxBodyCellIndex = 0;
this.tit_main.checkboxHeadCellIndex = 0;
this.tit_main.statCellIndex = 1;
this.ds_main.updatecontrol = false;

검증 호출은 /datavalidator/check로 보낸다. inDs와 outDs가 모두 ds_main이므로 업로드 Dataset을 서버로 보낸 뒤 SQL 검증 결과를 같은 Dataset으로 다시 받는다.

await this.tx.load({
    svcId : "test",
    sqlId : "bul_0110_u01.s02",
    inDs  : "ds_main",
    outDs : "ds_main"
}, "/datavalidator/check");

서버에서는 DataValidatorServiceImpl.java에서 입력 Dataset을 꺼내 각 row를 순회한다. row 값과 param을 합친 뒤 SQL을 실행하고, 결과를 output Dataset에 붙여 반환한다.

DataTable inDs = mapDTO.getInputTables().get(0);

inDs.forEach(row -> {
    Map<String, Object> args = new HashMap<>();
    args.putAll(param);
    args.putAll(row);
    DataTable selectResult = ezworksDefaultService.getList(sqlId, args);

    if (selectResult != null && !selectResult.isEmpty()) {
        outDs.addAll(selectResult);
    }
});

mapDTO.appendOutputTable(outDs);

호출 후에는 반환된 row를 수정 상태로 바꾸고 화면 검색조건의 캠퍼스/은행 코드를 다시 세팅한다. 이후 fn_checkDataValid(i)에서 같은 SQL을 row 단위로 한 번 더 조회해 필요한 컬럼을 현재 row에 반영한다.

for (var i = 0; i < this.ds_main.rowcount; i++) {
    this.ds_main.setRowType(i, 2);
    this.ds_main.setColumn(i, "CAMP_DVCD", this.div_search.form.cmb_campDvcd.value);
    this.ds_main.setColumn(i, "BANK_CD", this.div_search.form.cmb_bankCd.value);

    this.fn_checkDataValid(i);
}

this.ds_main.updatecontrol = true;
this.ds_main.set_keystring("S:ERR_TEXT");
this.ds_main.set_rowposition(this.ds_main.findRowExpr("ERR_TEXT!=null"));

실제 SQL은 bul_0110_u01.xml의 s02이다. ERR_TEXT는 Java에서 자동 생성되는 값이 아니라 SQL의 CASE 결과로 내려온다.

<select id="s02" resultType="map">
    SELECT #{CAMP_DVCD} AS CAMP_DVCD
         , #{VIRT_ACC_NO} AS VIRT_ACC_NO
         , NVL(B.BANK_CD, #{BANK_CD}) AS BANK_CD
         , SF_CSB_COMN_CD(NVL(B.BANK_CD, #{BANK_CD})) AS BANK_NM
         , CASE
             WHEN REGEXP_LIKE(REPLACE(#{VIRT_ACC_NO}, '-', ''), '[^0-9]')
             THEN '계좌번호는 숫자만 입력 가능합니다.'
             WHEN B.VIRT_ACC_NO IS NOT NULL
             THEN '이미 존재하는 자료입니다.'
             ELSE NULL
           END AS ERR_TEXT
      FROM DUAL A
           LEFT OUTER JOIN BUL0150 B
             ON REPLACE(B.VIRT_ACC_NO, '-', '') = REPLACE(#{VIRT_ACC_NO}, '-', '')
</select>

IMPORTANT

/datavalidator/check는 SQL 결과를 그대로 Dataset에 담아 돌려준다. ERR_TEXT뿐 아니라 SQL에서 조회한 다른 컬럼도 함께 받을 수 있다.


4. 일반 콤보

일반 공통코드 콤보는 this.combo.setCombo를 사용한다. 컴포넌트 콤보와 그리드 콤보를 같은 comps 배열 안에서 같이 세팅할 수 있다.

await this.combo.setCombo({
    action : "basic",
    svcId  : "combo",
    comps  : [{
        comp    : this.div_search.form.cmb_renpCd,
        sqlId   : "@comcd.s01",
        mode    : "a",
        param   : "CLA_CD=1753",
        mapping : { code: "CD", data: "CD_NM" }
    }, {
        comp    : this.grd_main,
        colNm   : "RENP_DVCD",
        sqlId   : "@comcd.s01",
        mode    : "s",
        param   : "CLA_CD=1753",
        mapping : { code: "CD", data: "CD_NM" }
    }]
});

comp가 Grid면 colNm이 필수다. mode는 콤보 앞에 붙일 기본 항목을 정한다.

mode 의미
a 전체
s 선택
n 또는 빈 값 추가 항목 없음

param에는 CLA_CD만 넘기는 경우가 많지만 추가 조건을 같이 넘길 수도 있다.

{
    comp    : this.grd_main,
    colNm   : "EVL_GROUP_DVCD",
    mode    : "s",
    sqlId   : "@comcd.s01",
    param   : "CLA_CD='1434' SUPLMT_INFO_10='1'",
    mapping : { code : "CD", data : "CD_NM" }
}

4.1. 콤보 Dataset 이름 지정

comboDsId를 지정하면 콤보 조회 결과 Dataset 이름을 개발자가 정할 수 있다. 동일한 콤보 데이터를 다른 처리에서 참조하거나 콤보 체인 필터의 원본 데이터로 사용할 때 유용하다.

평가항목관리 [aga_0103_u01.xfdl]에서는 평가코드 콤보 데이터를 ds_evlDvcd라는 Dataset 이름으로 생성한다.

await this.combo.setCombo({
    action : "basic",
    svcId  : "combo",
    comps  : [{
        comp    : this.grd_main,
        colNm   : "EVL_TERR_DVCD",
        sqlId   : "@comcd.s01",
        mode    : "s",
        param   : "CLA_CD=1390",
        mapping : { code : "CD", data : "CD_NM" }
    }, {
        comp      : this.grd_main,
        colNm     : "EVL_DVCD",
        sqlId     : "aga_com.s03",
        mode      : "s",
        param     : "",
        mapping   : { code : "CD", data : "CD_NM" },
        comboDsId : "ds_evlDvcd"
    }]
});

이후 createComboChain으로 상위 콤보와 하위 콤보를 연결한다. 아래 예시는 평가영역 EVL_TERR_DVCD를 선택하면 평가코드 EVL_DVCD 목록 중 SUPLMT_INFO_1이 상위 코드와 일치하는 항목만 보이도록 필터링한다.

var comboChain = await this.combo.createComboChain({
    comp  : this.grd_main,
    colNm : "EVL_TERR_DVCD"
}).setNext({
    comp   : this.grd_main,
    colNm  : "EVL_DVCD",
    filter : "pre.CD==SUPLMT_INFO_1"
});

comboChain.refreshRoot();

setNext filter의 pre 값

filter에서 사용하는 pre는 화면에 선언한 Dataset 이름이 아니다. 질문에서 prd라고 부르는 값은 실제 예제와 구현에서는 pre로 표현된다. pre는 EzComboChain 내부에서 상위 체인의 현재 선택 행을 객체로 만든 값이다. 상위 콤보 값이 바뀌면 상위 콤보 Dataset에서 현재 코드에 해당하는 row를 찾고, 그 row를 getRowData(row)로 꺼내 하위 체인 필터에 넘긴다.

그리드 콤보 기준 흐름은 다음과 같다.

  1. 상위 컬럼 EVL_TERR_DVCD 값이 바뀐다.
  2. 상위 콤보 Dataset에서 cur.code 컬럼으로 해당 값을 가진 row를 찾는다.
  3. mapping : { code : "CD", data : "CD_NM" }이므로 이 예시에서 cur.code는 CD다.
  4. 찾은 row를 getRowData(row)로 객체화한다.
  5. 하위 필터 pre.CD==SUPLMT_INFO_1의 pre.CD를 상위 row 객체의 CD 값으로 치환한다.
  6. 하위 콤보 Dataset에서 SUPLMT_INFO_1이 상위 CD와 같은 row만 선택된다.

형식은 Dataset row를 컬럼명 기준으로 꺼낸 일반 객체에 가깝다. aga_com.s03은 CD, CD_NM, SUPLMT_INFO_1 등 컬럼을 조회하므로 실행 시 개념적으로는 아래처럼 사용된다.

// 상위 평가영역 콤보의 선택 row를 객체로 꺼낸 값
var preRowMap = {
    CD             : "13900001",
    CD_NM          : "평가영역명",
    SUPLMT_INFO_1  : "...",
    SUPLMT_INFO_10 : "..."
};

// filter : "pre.CD==SUPLMT_INFO_1"
// 내부 변환 후 개념
"'13900001'==SUPLMT_INFO_1"

체인 관계 자체는 하위 EzComboChain의 pre 변수에 상위 체인 객체로 보관된다. 반면 필터 문자열에서 쓰는 pre.CD는 그 상위 체인의 현재 선택 row 데이터다. 따라서 상위 콤보의 CD인지 확인하려면 상위 콤보 Dataset의 CD 컬럼과 현재 선택 row를 보면 된다. 디버그 로그가 켜져 있으면 구현부에서 pre rowMap 로그로 이 객체가 출력된다.

NOTE

pre는 필터 문자열에서 상위 콤보 row를 가리키는 접두어처럼 사용한다. pre.CD, pre.SUPLMT_INFO_3, pre.UNIV_CD처럼 상위 콤보 Dataset 컬럼명을 붙이고, 오른쪽에는 하위 콤보 Dataset 컬럼명을 적는다.

단일 콤보에 직접 필터를 거는 경우는 this.combo.setFilter를 사용한다. 멘티 검색 [bui_0004_p01.xfdl]에서는 캠퍼스 콤보를 세팅한 뒤 특정 코드만 남기도록 필터링한다.

this.combo.setFilter({
    comp   : this.div_search.form.cmb_campDvcd,
    filter : "CD=='13580001'"
});

TIP

그리드 셀에 displaytype="combotext"와 edittype="combo"가 있는데 값이 코드로만 보이면 colNm, mapping, param, comboDsId를 먼저 확인한다.


5. 학사공통콤보

학사 업무 화면에서는 $f.getHCmb(this).init을 자주 사용한다. 이름 때문에 대학, 학과, 전공 콤보 전용처럼 보일 수 있지만 연도, 학기만 쓰는 화면에서도 사용한다. div나 grid에 넘긴 영역 안에서 학사 공통 컴포넌트를 찾아 화면에 있는 항목만 세팅해준다.

$f.getHCmb(this).init({
    div       : [this.div_search, this.div_detail],
    orgMode   : ["a", "s"],
    smrMode   : ["a"],
    smrFilter : ["RS"],
    grid      : [this.grd_main]
});
옵션 설명
div 검색조건 Div나 상세 Div 내부의 학사 공통 컴포넌트를 찾아 세팅한다.
grid 그리드 컬럼의 대학, 학과, 전공 콤보까지 같이 세팅한다.
orgMode, smrMode 조직 콤보와 학기 콤보의 전체/선택 항목을 조정한다.
smrFilter R 정규학기, S 계절학기, RS/SR 정규+계절학기. 없으면 업무구분 기준을 따른다.
targtCompNm 화면 컴포넌트 id가 기본 규칙과 다를 때 직접 이름 규칙을 지정한다.

연도/학기의 기본값은 $hcmb.s01에서 조회한다. 기준은 아래 순서로 결정된다.

  1. 메뉴에 연결된 일정이 있고 현재 적용 가능한 일정 데이터가 있으면 APLY_YY, APLY_SMR를 사용한다.
  2. 일정 값이 없으면 메뉴의 업무구분(TASK_DTL_CD)에 등록된 기준연도/학기(CSB_BASS_YY_SMR_M)를 사용한다.
  3. 둘 다 없으면 DB 서버의 현재일자(SYSDATE) 기준으로 계산한다. 1월~2월은 전년도 2학기, 3월~8월은 당해연도 1학기, 9월~12월은 당해연도 2학기다.

이 우선순위는 h_combo.xml의 COALESCE(C.APLY_YY, B.YY, D.YY)와 COALESCE(C.APLY_SMR, B.SMR, D.SMR) 구조를 기준으로 한다.

연도/학기만 따로 쓰는 화면도 학사콤보 초기화 대상에 포함할 수 있다. 아래 예시는 숨겨진 기준연도/학기 Div까지 함께 초기화해서 이후 로직에서 값을 참조하는 형태다.

this.fn_setDivSearchHCombo = () => {
    return $f.getHCmb(this).init({
        div: [this.div_search, this.div_detail, this.div_bassYySmr],
        grid: [this.grd_main],
        orgGridMode: ["n"]
    });
};

개설강좌관리 [hse_0319_u01.xfdl]에서는 컴포넌트명이 기본 규칙과 달라 targtCompNm으로 대상 이름을 지정한다.

$f.getHCmb(this).init({
    div  : this.div_search,
    grid : this.grd_main,
    targtCompNm: {
        yyCompNm   : /^spn_aplyYy$/,
        smrCompNm  : /^cmb_aplySmr$/,
        univCompNm : /^cmb_testUnivCd$/,
        dpmjCompNm : /^cmb_hakgwaCd$/,
        univColNm  : "CUSTOM_1",
        dpmjColNm  : "CUSTOM_2"
    }
});

6. Validation

Validation은 new EzValidator로 만든다. Grid 또는 Dataset을 대상으로 만들 수 있고, 보통 저장 전 $mpt.confirmSave(ds, validator)와 같이 사용한다.

this.validator = new EzValidator(comp, option, doFinding);
인자 설명
comp 검증 대상. nexacro.Grid 또는 nexacro.Dataset을 넘긴다.
option 컬럼별 검증 옵션. { 컬럼명: { 검증속성: { value, msg } } } 형태로 작성한다.
doFinding 검증 실패 시 해당 컴포넌트나 그리드 셀로 포커스를 이동할지 여부. 생략하면 true다.

기본 예제:

this.validator = new EzValidator(this.grd_main, {
    SNO : {
        required : { value: true, msg: "학번은 필수입력입니다." }
    },
    TRNN_INSTI_NM : {
        required : { value: true, msg: "연수기관은 필수입력입니다." },
        maxByte  : { value: 4000, msg: "연수기관은 4000 Byte 이하여야 합니다." }
    }
});

this.validator.markingCells();
this.validator.markingComps();

markingCells는 그리드 필수값 표시, markingComps는 Div 안의 입력 컴포넌트 필수값 표시다. 필수 표시는 essential, CellEssential, headerE 스타일로 적용된다.

자주 사용하는 검증 속성은 다음과 같다.

속성 value 기본 유형 용도
required boolean 필수값 확인
nonEmpty boolean 공백 문자열까지 비어 있는 값으로 보고 확인
min, max number 숫자 최소값, 최대값 확인
minDate, maxDate string yyyyMMdd 기준 날짜 하한, 상한 확인
minLength, maxLength number 문자열 길이 확인
minByte, maxByte number byte 길이 확인
jumin, foreign, rrn boolean 주민등록번호, 외국인등록번호 형식 확인
biznum, corpnum boolean 사업자번호, 법인번호 형식 확인
mobile, tel, email boolean 휴대전화, 전화번호, 이메일 형식 확인

조건부 필수나 컬럼 간 관계 검증은 value에 함수를 넣어서 처리한다. 함수는 rowData, row, colNm을 받으며 true를 반환하면 통과하고 false를 반환하면 실패한다.

개설강좌관리 [hse_0319_u01.xfdl]의 Dataset 검증 예제:

this.validatorDetail = new EzValidator(this.ds_main, {
    SUBJT_NM : {
        required : { value: true, msg: "교과목코드를 검색해서 입력해주세요." }
    },
    custom : {
        limit : {
            value : function(rowData, row, colNm) {
                var left = rowData["COCT_BEGIN_WEKS_DVCD"];
                var right = rowData["COCT_END_WEKS_DVCD"];
                return !(left && right) || left <= right;
            },
            msg : "[집중이수유형] 시작주차보다 종료주차가 커야합니다."
        }
    }
});

저장 전에는 보통 메시지 확인과 Validator가 같이 사용된다.

const { data } = await $mpt.confirmSave(this.ds_main, this.validator);

if (!data) {
    return;
}

await this.fn_saveMain();
return $mpt.alertSaved();

WARNING

화면을 복사한 뒤 Grid bind: 컬럼명은 바꿨는데 EzValidator 컬럼명을 그대로 두면 저장 전 검증이 엉뚱한 컬럼을 보거나 동작하지 않는다.


7. 저장 전 확인과 메시지

저장, 삭제, 조회 전 확인은 $mpt 계열을 사용한다.

const { data } = await $mpt.confirmSave(this.ds_main, this.validator);

if (!data) {
    return;
}

await this.tx.save({
    svcId : "fn_save",
    sqlId : "sample.i01,sample.u01,sample.d01",
    inDs  : "ds_main"
});

return $mpt.alertSaved();

자주 쓰는 메시지 함수는 다음과 같다.

함수 용도
$mpt.confirmSearch(ds) 변경 데이터가 있을 때 조회 계속 여부 확인
$mpt.confirmSave(ds, validator) 저장 대상과 유효성 검증 확인
$mpt.confirmRemove(ds) 삭제 전 확인
$mpt.alertSaved() 저장 완료 메시지
$mpt.alertDeleted() 삭제 완료 메시지

8. 파라미터 만들기

트랜잭션의 param에는 보통 컬럼='값' 형태의 문자열을 넘긴다. 직접 문자열을 이어 붙여도 되고, $f.mkArg를 사용해도 된다. $f.mkArg는 객체처럼 작성하지만 결과값은 문자열이다.

const sParam = "YY='" + this.div_search.form.spn_yy.value + "' "
             + "SMR='" + this.div_search.form.cmb_smr.value + "' "
             + "USE_YN='Y'";

위 코드는 아래처럼 쓸 수 있다.

const sParam = $f.mkArg({
    YY     : this.div_search.form.spn_yy.value,
    SMR    : this.div_search.form.cmb_smr.value,
    USE_YN : "Y"
});

결과 문자열은 같다.

"YY='2026' SMR='10540010' USE_YN='Y'"

$f.mkArg의 장점은 검색조건 Div를 자동으로 파라미터화할 수 있다는 점이다.

const sParam = $f.mkArg({
    $div   : this.div_search,
    USE_YN : "Y"
});

예를 들어 div_search 안에 spn_yy, cmb_smr, edt_sno가 있으면 아래와 같은 문자열이 만들어진다.

"YY='2026' SMR='10540010' SNO='20240001' USE_YN='Y'"

$comp.makeParamStr(this.div_search)도 Div 안의 컴포넌트를 문자열 파라미터로 바꿔준다. 단순히 Div 값만 넘길 때는 이 함수를 써도 되고, Div 값에 추가 조건을 섞어야 할 때는 $f.mkArg가 더 읽기 좋다.

const sParam = $comp.makeParamStr(this.div_search);

await this.tx.search({
    svcId : "fn_search",
    sqlId : "sample.s01",
    outDs : "ds_main",
    param : sParam
});

현재 Dataset row를 객체로 꺼낼 때는 $ds.getRowData를 사용한다.

const rowData = $ds.getRowData(this.ds_main, 0);

// 결과 예시
{
    YY      : "2026",
    SMR     : "10540010",
    SNO     : "20240001",
    KORN_NM : "홍길동",
    PAY_AMT : 15000
}

현재 Dataset row를 바로 문자열 파라미터로 만들 때는 $ds.rowToParam을 사용한다.

const sParam = $ds.rowToParam(this.ds_main, 0);

// 결과 예시
"YY='2026' SMR='10540010' SNO='20240001' KORN_NM='홍길동' PAY_AMT='15000'"

$ds.getRowData는 객체가 필요할 때, $ds.rowToParam은 바로 this.tx.search의 param에 넣을 문자열이 필요할 때 사용한다. rowToParam은 값이 null인 컬럼은 문자열에 포함하지 않는다.


9. 리스트 조건 전달용 opInDs

문자열 조건은 보통 param으로 넘긴다. 여러 건의 값을 조건으로 넘겨야 할 때는 opInDs로 Dataset을 같이 보낼 수 있다. 서버 SQL에서는 Dataset의 특정 컬럼을 리스트처럼 꺼내 <foreach> 조건에 사용할 수 있다.

교수회의시간조회 [hse_0408_v01.xfdl]에서는 교수 검색을 여러 명 선택하면 ds_enos에 PRFS_CD를 쌓고, 조회할 때 opInDs: "ds_enos"로 서버에 같이 보낸다.

await this.tx.search({
    action : "basic",
    svcId  : "fn_search",
    sqlId  : "hse_0408_v01.s01",
    outDs  : "ds_main",
    param  : sParam,
    opInDs : "ds_enos"
});

SQL에서는 ds_enos.PRFS_CD 목록을 사용한다.

<if test="ds_enos != null">
    AND A.PRFS_CD IN
    <foreach collection="ds_enos.PRFS_CD" item="item" open="(" separator="," close=")">
        #{item}
    </foreach>
</if>

opInDs는 파일 관련 조회에서도 사용된다. ds_schFile 같은 Dataset을 만들어 UUID 목록을 넘기고, 서버 SQL에서 해당 파일 목록을 조회하는 식이다.


10. Dataset과 Grid 보조 함수

Dataset 보조 함수:

함수 용도
$ds.isUpdated(ds) Dataset 변경 여부 확인
$ds.getRowData(ds, row) 특정 row를 객체로 추출
$ds.rowToParam(ds, row) 특정 row를 파라미터 문자열로 변환
$ds.saveJSON(ds) Dataset 내용을 JSON 형태로 저장/전달

Grid 보조 함수:

함수 용도
$grid.getCellBind(grid, cell) Grid cell의 bind 컬럼 확인
$grid.getHeadText(grid, cell) Grid 헤더 텍스트 확인
$grid.setComboDataset(grid, colNm, ds) 특정 컬럼에 콤보 Dataset 연결
$grid.setComboCodeColumn(grid, colNm, codeCol) 그리드 콤보 code 컬럼 지정
$grid.setComboDataColumn(grid, colNm, dataCol) 그리드 콤보 data 컬럼 지정

11. 팝업과 검색 팝업

검색 팝업은 EzSearcher 또는 업무별 팝업 호출로 처리한다. 팝업을 복사할 때는 넘기는 인자와 반환 컬럼명을 함께 확인한다. 아래 예제는 다중전공관리 [hhj_0602_u01.xfdl]의 grd_main_onexpandup에서 확인할 수 있다.

this.grd_main_onexpandup = async function(obj:nexacro.Grid,e:nexacro.GridMouseEventInfo)
{
    let colNm = obj.getCellProperty("head", e.cell, "text");
    let title = "";
    let url = "";
    let args = {};

    if (colNm == "학번") {
        title = "학생검색팝업";
        url = "WORK::HHJ/HHJ00/hhj_0001_p01.xfdl";
    } else if (colNm == "조직코드") {
        title = "조직검색팝업";
        url = "WORK::HHJ/HHJ00/hhj_0002_p01.xfdl";
        args.checkboxYn = "0";

        if (!this.ds_main.getColumn(this.ds_main.rowposition, "MAJR_DVCD")) {
            this.pop.alert("전공구분을 선택하세요.");
            return false;
        }
    }

    let result = await this.pop.popup({
        id : "grd_main_onexpandup",
        title : title,
        url : url,
        arg : args,
    });

    if (result.data) {
        if (colNm == "학번") {
            this.ds_main.setColumn(this.ds_main.rowposition, "SNO", result.data.SNO);
            this.ds_main.setColumn(this.ds_main.rowposition, "NAME", result.data.NAME);
            this.ds_main.setColumn(this.ds_main.rowposition, "SCRG_STTUS", result.data.SCRG_STTUS);
            this.ds_main.setColumn(this.ds_main.rowposition, "GRDE", result.data.GRDE);
        } else if (colNm == "조직코드") {
            this.ds_main.setColumn(this.ds_main.rowposition, "ORG_CD", result.data.ORG_CD);
            this.ds_main.setColumn(this.ds_main.rowposition, "UNIV_CD", result.data.UNIV_CD);
            this.ds_main.setColumn(this.ds_main.rowposition, "DPMJ_CD", result.data.DPMJ_CD);
            this.ds_main.setColumn(this.ds_main.rowposition, "MAJR_CD", result.data.MAJR_CD);
            this.ds_main.setColumn(this.ds_main.rowposition, "UNIV_NM", result.data.UNIV_NM);
            this.ds_main.setColumn(this.ds_main.rowposition, "DPMJ_NM", result.data.DPMJ_NM);
            this.ds_main.setColumn(this.ds_main.rowposition, "MAJR_NM", result.data.MAJR_NM);
        }
    }
};

멘티 검색 [bui_0004_p01.xfdl]은 검색조건, 콤보 필터, 팝업 반환 구조를 같이 확인하기 좋은 화면이다.

11.1. EzSearcher 업무별 config 설정

EzSearcher는 검색 팝업을 직접 매번 만들지 않고, type에 등록된 설정을 가져와서 사용한다. 공통 설정은 [ui/CONFIG/searcher_config.xjs]에서 로드되고, 업무별 설정은 [ui/CONFIG/searcherExtend_config.xjs]를 통해 searcher_hhj_config.xjs, searcher_hse_config.xjs 같은 파일이 추가 로드된다.

기본 구조는 아래처럼 type 하나에 조회 SQL, 팝업 URL, 팝업 제목, 반환 코드 컬럼, 반환 명칭 컬럼을 묶어 등록하는 방식이다.

nexacro.EzSearcher.types.append("stud", new nexacro.EzSearchItem(
    "basic",
    "hhj_0001_p01.s01",
    "WORK::HHJ/HHJ00/hhj_0001_p01.xfdl",
    "학생 검색",
    1085,
    500,
    false,
    "SNO",
    "NAME",
    "",
    ""
));

화면에서는 EzSearcher의 type만 맞추면 등록된 팝업을 그대로 사용할 수 있다. 예를 들어 학생 검색은 type="stud", 교과목 검색은 type="subjt", 개설강좌 검색은 type="lect"를 사용한다.

구분 type 설정 파일 주요 반환값 사용 예
학생 검색 stud searcher_hhj_config.xjs SNO, NAME 학번/성명 검색
학교 검색 schAll searcher_hhj_config.xjs SCH_CD, SCH_NM 출신학교 검색
조직 검색 schSearch searcher_hhj_config.xjs ORG_CD, ORG_NM 대학/학과/전공 조직 검색
지도/외부교수 검색 prfsStaf searcher_hhj_config.xjs ENO, NAME 지도교수, 외부교수 검색
교수 검색 prfs searcher_hse_config.xjs PRFS_CD, PRFS_NM 수업 담당교수 검색
강의실 검색 lecm searcher_hse_config.xjs LECM_CD, LECM_NM 강의실 검색
개설강좌 검색 lect searcher_hse_config.xjs SUBJT_CD, SUBJT_NM 연도/학기 기준 개설강좌 검색
교과목 검색 subjt searcher_hgg_config.xjs SUBJT_CD, SUBJT_NM 교과목코드/명 검색
교직원 검색 staf searcher_csb_config.xjs ENO, NAME 교직원 검색
봉사기관 검색 srve searcher_hhs_config.xjs INSTI_CD, INSTI_NM 사회봉사기관 검색
우편번호 검색 postnum searcher_config.xjs postcd, address 주소/우편번호 검색

화면에서 복사할 때 먼저 확인할 속성은 type, subediterwidth, readonly, autoselect, cansearch, onsearched이다.

<EzSearcher id="sch_sno"
    type="stud"
    cansearch="EzSearcher_cansearch"
    onsearched="EzSearcher_onsearched"
    subediterwidth="120"
    maxlength="48"/>

cansearch는 팝업이 열리기 전 조건을 세팅하는 이벤트다. 검색 팝업이 업무 조건을 받아야 하면 이 이벤트에서 popupparam을 넣는다. 학적변동관리 [hhj_0301_u01.xfdl]는 검색조건 Div 값과 학생 검색 옵션을 문자열 파라미터로 넘긴다.

this.EzSearcher_cansearch = function(obj:nexacro.EzSearcher, e:nexacro.EzSearcherCanSearchEventInfo) {
    switch(obj.name) {
        case "sch_sno" :
            const sParam = $f.mkArg({
                $div: this.div_search,
                IS_LOCK_SCRG_STTUS: false,
                IS_FOREIGNER_ONLY: false,
            });

            this.div_search.form.sch_sno.popupparam = sParam;
            break;
    }
};

popupparam은 문자열로도 넘기고 객체로도 넘긴다. 휴보강처리 [hse_0805_v01.xfdl]처럼 개설강좌 검색은 연도, 학기, 데이터 권한 여부를 객체로 넘긴다.

this.div_search_sch_openLect_cansearch = function(obj:nexacro.EzSearcher, e:nexacro.EzSearcherCanSearchEventInfo)
{
    this.div_search.form.sch_openLect.popupparam = { 
        YY : this.div_search.form.spn_yy.value,
        SMR : this.div_search.form.cmb_smr.value,
        USE_DATA_AUTH : "1",
    }
};

onsearched는 검색 결과 선택 후처리다. 단순 조회조건이면 선택 후 바로 fn_search()를 호출하는 경우가 많다. 결과 데이터에서 추가 컬럼을 꺼내 다른 컴포넌트나 Dataset에 세팅해야 하면 e.data를 사용한다.

this.div_search_sch_openLect_onsearched = function(obj:nexacro.EzSearcher, e:nexacro.EzSearcherOnSearchedEventInfo)
{
    var data = e.data;
    var clas = data.CLAS;
    if(!$util.isNull(clas))
    {
        this.div_search.form.edt_clas.value = clas;
        this.fn_search();
    }
};

검색어를 다시 입력하면 이전 선택값과 보조값을 지워야 하는 화면도 있다. 이때는 onmainediterinput에서 관련 컴포넌트를 초기화한다.

this.div_search_sch_openLect_onmainediterinput = function(obj:nexacro.EzSearcher, e:nexacro.InputEventInfo)
{
    this.div_search.form.edt_clas.value = null;
};

복사한 화면에서 EzSearcher를 바꿀 때는 아래 순서로 확인한다.

  1. 새 업무에 맞는 type이 이미 searcher_업무_config.xjs에 있는지 확인한다.
  2. 없으면 업무별 searcher_업무_config.xjs에 nexacro.EzSearcher.types.append(...)로 추가하고, searcherExtend_config.xjs에서 로드되는지 확인한다.
  3. 화면의 type, cansearch, onsearched 이벤트명을 새 컴포넌트 id 기준으로 맞춘다.
  4. popupparam에 넘기는 조건이 새 화면의 검색조건과 맞는지 확인한다.
  5. onsearched에서 세팅하는 컬럼명과 Dataset 컬럼명이 새 화면 기준인지 확인한다.

12. 복사 개발 체크리스트

  1. 화면 파일명과 Form id가 새 메뉴 기준인지 확인한다.
  2. Titlebar의 targetGrid와 버튼 권한 속성이 맞는지 확인한다.
  3. Dataset 컬럼과 SQL 결과 컬럼이 맞는지 확인한다.
  4. Grid bind: 컬럼과 EzValidator 컬럼명이 맞는지 확인한다.
  5. 일반 콤보의 colNm, param, mapping, comboDsId가 맞는지 확인한다.
  6. 학사콤보 화면이면 div, grid, targtCompNm, smrFilter를 확인한다.
  7. this.tx의 sqlId, inDs, outDs, param, opInDs가 새 업무 기준인지 확인한다.
  8. 저장 전 $mpt.confirmSave와 저장 후 $mpt.alertSaved 흐름이 있는지 확인한다.
  9. 팝업을 복사했다면 반환 컬럼명이 새 Dataset 컬럼과 맞는지 확인한다.
  10. 파일/리포트가 있으면 URL과 Dataset 이름이 기존 업무에 묶여 있지 않은지 확인한다.

13. 참고 화면

주제 참고 화면
기본 조회/저장/공통콤보 상벌코드관리 [hhs_0101_u01.xfdl]
학사콤보 샘플 학사콤보테스트 [sample_hcmb.xfdl]
학사콤보 실제 화면 해외어학연수 신청관리 [bui_0234_u01.xfdl]
targtCompNm 예제 개설강좌관리 [hse_0319_u01.xfdl]
콤보 Dataset/체인 필터 평가항목관리 [aga_0103_u01.xfdl]
콤보 직접 필터 멘티 검색 [bui_0004_p01.xfdl]
/datavalidator/check 가상계좌일괄업로드 [bul_0110_u01.xfdl]
opInDs 교수회의시간조회 [hse_0408_v01.xfdl]
파일 임시복사 파일 샘플 [sample_file_copyToTemp.xfdl]

14. 복사용 대표 화면

새 화면을 만들 때는 업무가 완전히 같은 화면보다 레이아웃과 스크립트 흐름이 비슷한 화면을 복사하는 편이 빠르다. 아래 화면들은 기본 주석 양식이 있고, 스크립트가 비교적 단순해서 복사 기준으로 보기 좋다.

만들 화면 형태 복사하기 좋은 화면 구조 복사 포인트
조회조건 1줄 + 메인 Titlebar + 메인 Grid 상벌코드관리 [hhs_0101_u01.xfdl] div_search 높이 49, tit_main, grd_main 가장 기본적인 CRUD 화면이다. 공통콤보, EzValidator, fn_searchMain, fn_saveMain, Titlebar 전처리 흐름을 그대로 보기 좋다.
조회조건 2줄 + 메인 Titlebar + 메인 Grid 세부전공관리 [hhj_0505_u01.xfdl] div_search 높이 80, tit_main, grd_main 연도/학기, 학사콤보, 학생검색 조건이 들어가는 2줄 조회 화면을 만들 때 기준으로 잡기 좋다.
조회조건 3줄 + 메인 Titlebar + 메인 Grid 사회과목관리 [hgg_0202_u01.xfdl] div_search 높이 111, tit_main, grd_main 조회조건이 많은 단일 Grid 화면의 기준으로 쓰기 좋다. div_regi와 extra button 로직은 새 화면에 필요 없으면 제거한다.
조회조건 1줄 + 메인/서브 Grid 상하 배치 출석인정조회 [hse_0709_u01.xfdl] 위쪽 grd_main, 아래쪽 grd_sub 마스터 Grid row 변경 시 하단 서브 목록을 조회하는 형태다. ds_main_onrowposchanged, fn_searchSub 흐름을 확인한다.
조회조건 2줄 + 메인/서브 Grid 상하 배치 합반관리 [hse_0601_u01.xfdl] div_search 높이 80, 위쪽 grd_main, 아래쪽 grd_sub 상하 2단 목록이면서 스크립트가 짧다. 조회용 화면을 복사하기 좋고, 저장 화면이면 Titlebar 버튼 속성과 저장 함수를 추가한다.
조회조건 1줄 + 메인/서브 Grid 좌우 배치 학과별분야관리 [hgg_0401_u01.xfdl] 왼쪽 grd_main, 오른쪽 grd_sub, EzSplitter 좌측 목록을 선택하고 우측 상세 목록을 관리하는 구조다. tit_sub의 add/save/remove 전처리와 fn_searchSub를 같이 보면 된다.
조회조건 2줄 + 메인/서브 Grid 좌우 배치 폐반관리 [hse_0603_u01.xfdl] div_search 높이 80, 좌우 grd_main, grd_sub 좌우 Grid가 필요하지만 저장 로직이 많지 않은 화면을 만들 때 참고하기 좋다. 화면 상단의 div_batch는 업무에 맞게 유지하거나 삭제한다.
Grid 없이 Div와 Button만 있는 처리 화면 중간수업평가통계처리 [hse_1012_u01.xfdl] 중앙 div_search, 연도/학기, 실행 버튼 목록 없이 조건을 입력하고 tx.exec만 호출하는 처리 화면 기준으로 좋다. Titlebar 흐름이 없으므로 버튼 이벤트와 실행 함수 중심으로 본다.
메인 Grid + 우측 상세 Div 학적관리 [hhj_0401_u01.xfdl] 왼쪽 grd_main, 오른쪽 div_detail 목록을 선택하면 우측 상세 입력 영역이 같이 바뀌는 구조다. $comp.setDetail(this.div_detail, this.ds_main, this) 사용 예제로 보기 좋다.
메인 Grid + 하단 상세 Div 핵심역량(교양) [hgg_0211_u01.xfdl] 위쪽 grd_main, 아래쪽 div_mainDetail Grid 아래에 작은 상세/부가 입력 영역이 붙는 형태다. fn_setDetail, tit_mainDetail.onsave, fn_saveDetail 흐름을 확인한다.

복사 기준을 고를 때는 먼저 검색조건 줄 수를 맞춘다. div_search 높이가 49이면 보통 한 줄, 80이면 두 줄, 111이면 세 줄 구성이다. 그 다음 Grid가 하나인지, 상하 2단인지, 좌우 2단인지, 상세 Div가 붙는지를 본다.

화면을 복사한 뒤에는 추천 화면의 업무 전용 기능을 먼저 제거한다. 예를 들어 extra button, div_batch, 파일/리포트, 특정 업무 프로시저, ds_main_onrowposchanged의 하위 조회는 새 화면에서도 필요한지 확인하고 남긴다.

← Back to all posts