PMS9 (과제 관리 시스템 - Project Management System)은

Spring 4 + MyBatis 3 + MariaDB (Maven)를 기반으로,

웹 사이트 개발을 쉽게 할 수 있다는 것을 보여주는 예제로 제작하였으며,

이중에서 몇 가지 주요 기능에 대한 개발 과정

따라하면서 개발할 수 있도록 간단하게(?) 정리하였다.




기존에 공개한 과제 관리 시스템 (PMS9)에 대한 간단한 설명소스

간단한 설명을 읽고 다음 내용을 읽으면 도움이 될 것이다.


이중에서 간단한 예제 몇가지를 중심으로 개발 방법을 정리한

과제 관리 시스템 (PMS9_making)에 대한 소스는

Github에서 확인할 수 있다.

쉽게 개발하는 방법은 다음과 같다.

  1. Project9과 같은 웹사이트 제작에 필요한 주요 기능은 미리 구현
  2. 특별한 기능은 가급적 라이브러리 사용
  3. 타이핑은 가급적 하지 않고 CPR (Copy, Paste, Replace)만 사용

이 내용을 다시 풀어서 작성하면


1 번은 솔루션 SI를 의미한다.

솔루션을 활용하는 업체들은 보유한 제품에

거의 모든 기능이 구현되어 있기 때문에

현재 개발하려는 웹사이트에서 필요로 하는 기능(프로세스)만 추가하는 식으로 개발한다.

여기에서는 Spring 4 + MyBatis 3 + MariaDB (Maven) 기반으로

필요한 기능을 미리 구현해 놓은 것이 Project9이다.

Project9을 토대로 필요한 기능을 구현하는 식으로 개발한다.


두번째로 웹 사이트마다

고유의 특수한 기능(treegrid/calendar)들이 필요할 경우가 많다.

특히, 자바스크립트나 CSS를 이용하여 보기 좋게 구성하는 경우가 많은데

웬만한 것은 모두 인터넷에서 상용이나 무료로 구할 수 있다.

거의 없는 경우는 없으니 만들려 하지 말고

다른 사람에 의해 검증된 라이브러리,

특히 오픈 소스를 활용하는 것이 좋다.

(제작한 경우 웹 브라우저 호환성 등 고려해야 할 것도 많고 시간이 지난 후 문제가 된 경우가 많다.)


3 번은 1 번의 의미와 통하는 것으로

자가 복제를 의미한다.

현재 작성하는 페이지가 기존에 없는 것 같아도

CRUD (Create, Read, Update, Delete)에서 벗어나지 않는다.

따라서, 비슷한 기능이나 코드를 복사(Copy)해서

활용하는(Paste)하는 것이 좋다.

이 경우에도 전체적인 룰을 유지해 주면

타이핑 없이 문자열 바꾸기(Replace)로 작성 가능하다.


이상의 내용은 개인적인 경험과 견해를 바탕으로 하는 것이다.

그리고, 이러한 3가지 룰을 지켜서 개발하면

웬만한 웹 사이트는 소수의 인원과 시간으로 개발할 수 있었고

가장 중요한 사항인 유지 보수에 큰 문제가 생기지 않았다.

또, 초보자와 같이 해도 기본 틀을 보면서 따라가기 때문에

문제 발생이 적어지는 장점도 있다.


샘플로 제작한 PMS9의 경우

화면설계서와 ERD 작성에 약 5시간,

기본적인 개발을 진행하는 데

약 10시간 정도가(정확하지 않음) 소요되었다.

이러한 쉬운 개발이 되기 위해서는

앞서 정리한 3가지 조건이 성립되고

개발하는 업무에 대한 분석/설계가

잘 되어 있어야 한다는 전제 조건이 있다.

이것은 개발할 것에 대하여 잘 알고 있어야 한다는 의미로,

준비가 잘 되어 있다면

쉽고 즐거운 개발이 될 수 있다.


PMS9을 개발하는 순서는

먼저, 프로젝트에 대한 CRUD를 만드는 것이다.

다음으로, 하나의 프로젝트에 대하여

개별 작업을 할당하고 (treegrid)

이 개별 작업을 손쉽게 확인할 수 있도록

일자별로 보거나 (calendar)

작업자별등으로 볼 수 있게 구현한다.


기존에 공개한 과제 관리 시스템은 간단한 시스템으로

이외에 사이트 개발에 필요한

로그인, 회원관리, 공지사항(게시판), 조직도 등의 기능은

미리 구현한 Project9을 그대로 사용한다.

즉, 앞서 언급한 기능만 개발하기 때문에

(너무나 당연한 결과이겠지만 )

쉽고 빠르게 개발을 완료하게 된다.



과제 관리 시스템인 PMS9을 개발하기 전에

필요한 준비사항을 정리한다.

Project9, easyUI, fullcalendar를

다운로드 받아서 설치한다.


♡ Project9 설치

Project9은 Spring 4 + MyBatis 3 + MariaDB (Maven) 기반으로 제작한 웹 프로젝트 템플릿이다.

개발 중에 많이 사용되는

회원관리(로그인, 로그아웃 포함), 부서관리 (조직도),

게시판 (설정), 무한 댓글 등의 기능을 미리 구현해 두었고,

S/W 개발에 따라 필수적인 기능을 샘플로 구현해 두었기 때문에

복사/붙여 넣기를 하는 방법으로 활용할 것이다.

Project9 설치 방법은 여기를 참고하면 된다.

보다 상세한 설명은 여기를 읽어보길 바란다.

설치 방법대로 설치를 한 후 다음과 같은 화면이 실행되어야 한다.

로그인 창에서 admin / admin으로 로그인하면

다음과 같은 메인 화면을 볼 수 있다.

Project9이 잘 실행되었으면

편의상 Project9을 PMS9으로 바꾸어서 진행한다.

(안해도 됨)


먼저, github와의 연결을 해제한다.

Eclipse에서 Project9을 선택하고,

마우스 오른쪽 버튼을 클릭해서 다음 그림과 같이 실행한다.

Team 메뉴 하위에 있는 Disconnect를 실행하면 된다.

연결이 잘 해제 되면

위 그림에서 보듯이

Project Explorer나 Navigator 뷰에서

project9 [project9 master]로 되어 있던것이

project9만 남아 있게 된다.

다음으로 Project Explorer나 Navigator 뷰에서

project9을 선택하고

F2 버튼을 눌러서 프로젝트 명을 바꾸어 준다.

각자 원하는 이름을 입력하면 되고

여기서는 PMS9으로 입력한다.


다음 그림과 같이

Project Explorer나 Navigator 뷰와

톰캣 서버에서 프로젝트명이

PMS9으로 변경된 것을 확인할 수 있다.

이제 부터는 웹 브라우저에서

http://localhost:8080/pms9으로 접속한다.


다음으로, 관련 테이블들을 생성한다.

PMS9과 관련된 테이블은 4개로

상세한 정보는 여기에 있으며

다음 그림(ERD)에서 왼쪽에 있는 테이블들이다.


프로젝트에 대한 정보를 저장하는 테이블 (PRJ_PROJECT),

프로젝트의 개별 작업 정보를 저장하는 테이블 (PRJ_TASK),

프로젝트의 개별 작업 담당자 정보 테이블(PRJ_TASKUSER),

담당자가 진행 상황을 입력하면서

첨부 파일이 있을 경우,

이 파일에 대한 정보를 저장하는 테이블(PRJ_TASKFILE)이 있다.

완성된 PMS9의 Github에서

Amateras로 제작된 pms9.erd파일을 받아서

ERD를 확인하고, SQL문을 생성해도 되고

다음 SQL문을 복사해서

HeidiSQL이나 WorkBench에서 실행한다.


마지막으로

다음 기초 코드 데이터를 실행한다.

INSERT INTO `com_code` (`CLASSNO`, `CODECD`, `CODENM`) VALUES
    (2, '0', '진행중'),
    (2, '1', '종료');

이것은 프로젝트 상태를 나타내는 것으로

프로젝트가 진행중이면 codecd가 0

종료되었으면 1로 지정하여

차후 프로젝트 관리에서 사용할 것이다.


♡ easyUI 다운로드

easyUI는 jquery 기반의 오픈 소스 라이브러리로,

다양한 컴포넌트를 제공하고 있다.

이 중에서 treeGrid를 이용하여

PMS9의 프로젝트 작업들을 계층형으로

생성하고 관리하는데 이용할 것이다.


easyUI의 다운로드 페이지에 접속을 한다.


다운로드 페이지에서 하단으로 내려보면

easyUI의 두가지 옵션을 확인 할 수 있다.

구매할 수 없으니

Freeware Edition에 있는 [Download] 버튼을 눌러서 다운받는다.


다운로드 받은 파일[jquery-easyui-1.5.1.zip]을 압축해제 한다.


easyUI의폴더 하위에 있는 데모들을 확인해 보고

demo > treegrid 폴더의

basic.html이나 editable.html등을 더블클릭해서 실행한다.

이 treegrid를 사용할 것이다.

easyUI는 기능이 다양하고 많지만

무료라 Chrome에서는 실행되지 않는 문제가 있다.


♡ Full Calendar 다운로드

Full Calendar는 달력 라이브러리로,

프로젝트별 작업에 대한 확인을

일정 중심으로 보기 위해 사용할 라이브러리이다.


Full Calendar의 다운로드 페이지에 접속 한다.

가장 최신 버전(Latest) 버전인

fullcalendar-3.3.1.zip을 선택해서 다운 받는다.


압축을 해제하고

demos 폴더에 있는 내용들을 실행해서 확인해 본다.


이것으로 과제 관리 시스템인 PMS9을 개발할 준비를 마쳤다.

이 외에 화면 설계서와 ERD는

이전에 공유 했으므로 여기에서 다시 정리하지 않으니

읽어보기를 바란다.




과제 관리 시스템을 개발하기 위해

가장 먼저 과제에 대한 기본 기능(CRUD)을 개발한다.

과제(이하 프로젝트)의 기본 기능은

프로젝트 전체 리스트 (조회 - Read),

개별 프로젝트에 대한 읽기 (Read)

개별 프로젝트 생성(Create)과 수정(Update),

그리고 삭제(Delete)로 구성된다.

이것은 거의 모든 데이터 베이스 기반 프로그램에서

동일하게 구현 되는 것으로,

가장 기초가 되는 것이 게시판이니

잘 모르는 경우 여기를 참조하면 된다.


코드에 대한 설명은 정리하지 않는다.

개발에 대해 어느 정도 알고 있다는 전제하에

일종의 개발 요령만을 정리한다.


요령으로 단순 게시판의 소스를 가져와서

코드만 조금 고치면 프로젝트 기능을 구현할 수 있다.

다만, project9에 있는 게시판은 

기능이 많고 복잡해서 사용하기 어렵다.

이 게시판을 별도의 간단한 샘플 코드로 제작해서

CRUD라는 이름으로 개발해 두었다.

그림의 왼쪽 메뉴 중에서 샘플 메뉴 하위에서 실행해 볼 수 있다.

따라서, 다음 내용을 쉽게 이해하기 위해서

단순 게시판에 대해서 잘 알고 있어야 한다.

관련 내용을 설명하지 않기 때문에

모른 다면, 본 블로그의 관련 내용(최소한 게시판 1, 2)을 읽어보기 바란다.


이 소스를 복사해서 CPR(Copy, Paste, Replace)로 구현할 것이다.


MVC(Model–View–Controller) 개발에서

하나의 CRUD를 복사해서 구현하기 위해서는

Java에서는 컨트롤(Control), VO (Value Object), 서비스(Service)를 복사하고,

SQL문을 저장한 XML 파일도 있어야 하고

화면에 보여주는 JSP(HTML) 파일등

5개 이상의 파일이 있어야 한다.

(리스트, 읽기, 쓰기폼의 jsp파일이 있어야 하니 실제로는 7개의 파일)


그림에서 보는 것 처럼

Java 파일은 java > gu > crud 폴더에 있고

sql 파일은 resources > sql 폴더에 crud.xml 파일

JSP 파일은 webapp > WEB-INF > jsp > crud 폴더에 있다.


지금부터 이 파일들을 복사한 뒤 (Copy, Paste)

각각의 이름을 project로 바꾸어서 (Replace)

프로젝트 CRUD가 만들어 지도록 한다.


먼저, java > gu > crud 폴더를 선택하고 복사(Ctrl + C)를 한다.

java > gu 폴더를 선택하고 붙여넣기(Ctrl +P)를 한 뒤,

다음과 같이 project라는 폴더명을 입력한다.


java > gu > project 폴더에 있는

CrudCtr.java, CrudSvc.java, CrudVO.java

파일을 선택하고 F2키를 눌러서

PrjoectCtr.java, PrjoectSvc.java, PrjoectVO.java로

각각 파일명을 수정해 준다 [다음 그림 왼쪽 참조].


각 파일을 열어 보면 패키지(package) 이름 gu.crud와

클래스 이름(Crud*)에 오류가 표시 되어 있는 것을 볼 수 있다.

아직 오류가 나지 않았지만

컨트롤명인 crudList등 crud란 단어가 포함된 것은

crud 폴더에서 복사해 왔기 때문에

모두 잠재적으로 오류를 발생시킨다.


별도의 프로젝트 기능을 구현하고

오류를 해결하기 위해 crud (또는 Crud)로 된 글자를

project(Project)로 모두 바꾸어 주면 된다.

각자 재미로 하나 하나 crud를 project로 수정해서 완성해도 되고

여기서는 바꾸기(replace) 기능으로 간단하게 해결한다.


Eclipse 각 파일 편집창에서 찾기(Ctrl + F)를 실행하고

찾을 문자에 crud

바꿀 문자에 project를 입력하고

옵션에 대소문자 구분(Case sensitive)를 체크하고

[Replace All] 버튼을 클릭한다.

Java에서 클래스는 대문자로 시작하고

인스턴스는 소문자로 시작하는 명명 규칙이 있어서

그냥 바꾸기를 하면 문제가 생길 수 있기 때문에

대소문자를 구분(Case sensitive)해서 실행해야 한다.

즉, crud와 Crud를 project와 Project로 두번 실행해야 한다.


그림과 같이 PrjoectCtr.java, PrjoectSvc.java파일 등에

빨간 표시들이 사라진 것을 확인할 수 있다.

또, 컨트롤명, 서비스의 메소드명, 호출한 sql id,

컨트롤에서 반환할 jsp 파일명등이 모두 바뀌어 있다.

이러한 작업을 고려해서

처음 crud 샘플을 만들때 동일한 명명 규칙으로 제작하였다.

다음 그림에서 도형안에 있는 영어는

컨트롤명이자 jsp 파일 명이 된다.

도형 밖에 있는 insert, delete 등은

컨트롤에서 사용하는 서비스의 메소드명,

서비스에서 호출하는 SQL id를 나타낸다.

서비스의 메소드명과 SQL id는 같은 이름을 사용한다.

이렇게 이름에 기능과 테이블 명(crud)을 조합하여 부여하고

테이블명만 교체해서

기본기능(crud)을 간단하게 작성한다.


Java 파일을 수정 한 후,

JSP (HTML) 관련 파일들을 수정한다.

Java에서 처리한 것과 같이

webapp > WEB-INF > jsp > crud 폴더를 복사해서

webapp > WEB-INF > jsp 에서 붙여넣기 하여

project 폴더를 생성한다.

다음 그림의 왼쪽 그림과 같이

Crud로 시작하는 jsp 파일을

모두 Project로 시작하도록 수정한다.


ProjectForm.jsp, ProjectList.jsp, ProjectRead.jsp로 생성하고

3개의 파일을 모두 열어서

앞서와 같이

crud를 project로 모두 바꾸어 준다.


다만 기억해야 할 것이

위 그림에 중앙에 표시한 것처럼

다국어 처리를 위해 사용된 변수명인 crud가 있다.

이것은 앞서 언급한 java, jsp, xml(sql)외에

추가로 수정해야 할 파일이 있다는 것을 의미한다.


resources > message 폴더에 있는

message.properties 파일을 열어서 다음과 같이 작성한다.

이 내용들은

프로젝트 관련 해서 사용할 문자열들로

개발 중에 필요에 따라서 추가해서 사용하지만

여기서는 편의상

다음 문장을 그대로 복사해서 사용하면 된다.

project.title=프로젝트
project.prtitle=프로젝트명
project.prdate=작성일자
project.usernm=등록자
project.prstartdate=시작일자
project.prenddate=종료일자
project.prendreal=종료일자(실제)
project.prstatus=상태
project.prstatus0=진행중
project.prstatus1=종료   
project.new=새프로젝트
project.term=프로젝트 기간
project.child=자식
project.rate=진행율
project.worker=작업자
project.task=작업
project.taskterm=작업 기간
project.taskMgr=작업
project.calendar=일정
project.taskWorker=작업자
project.taskMine=내것만
project.gray=미시작
project.red=종료일자를 지났음
project.green=좋습니다.
project.yellow=일정에 신경써야 합니다.
project.orange=종료일자 이후에 완료.
project.attach=첨부파일(산출물)        
project.copy=다른 프로젝트에서 작업 항목 복사


마지막으로 가장 어렵고

처리가 많은 SQL문을 수정한다.

다음 그림과 같이

resources > sql 폴더에 있는

crud.xml 파일을 복사해서

project.xml을 생성하고,

XML 파일 내용에서 crud, Crud를

각각 project, Project로 모두 바꾸어 준다.

이 상태에서 톰캣을 가동하고 서버를 실행해도 된다.

다국어 처리 리소스가 맞지 않아 오류가 생기고,

수정 후 실행해도

아직 사용 테이블(TBL_CRUD)을 바꾸지 않아서

의미 없는 화면이 출력된다.


지금까지의 작업 내용을 정리해 보면

Java 파일 복사 해서 crud를 project로

Jsp 파일 복사 해서 crud를 project로

Jsp(XML) 파일 복사 해서 crud를 project로

바꾸어 주었다.

이 것은 단순한 CPR(Copy, Paste, Replace)의 반복이었다.

이번에는 테이블을 바꾸면서 발생하는

조금 복잡한 CPR처리를 진행한다.


다음 표에서 보듯이

기본 CRUD는 TBL_CRUD 테이블을 사용하고

프로젝트는 PRJ_PROJECT 테이블을 사용한다.

필드 구조가 다른 것 같지만 제법 많이 비슷하다.

TBL_CRUDPRJ_PROJECT
번호CRNO프로젝트 번호PRNO
제목CRTITLE프로젝트 제목PRTITLE
내용CRMEMO

작성일자CRDATE작성일자PRDATE
삭제 여부CRDELETEFLAG삭제DELETEFLAG
사용자번호USERNO사용자번호USERNO


상태PRSTATUS


시작일자PRSTARTDATE


종료일자PRENDDATE

이렇게 정리한 이유는

현재 표로 매칭한 것처럼 바꾸기를 진행할 것이기 때문이다.


바꾸기 대상은 주로 테이블을 사용하는 SQL 문과

화면에 출력(jsp)하기 위해 데이터(값) 오프젝트를 사용하는 부분이다.


먼저 SQL 파일을 열어서

테이블 명을 TBL_CRUD에서 PRJ_PROJECT로 바꾸어 준다.

다음으로

표에서 정리한 것 처럼 각 필드를 바꾸어 준다.

다만, SQL문 작성 규칙이

SQL문은 대문자로

mybatis에서 사용하는 파라메타는 소문자로 작성했다.

예로, 그림의 53 라인을 보면

필드명인 [글 번호]는 CRNO이고

조건으로 값을 넘기기 위해 사용한 변수는 #{crno}이다.

즉, 변환할때 모두 대문자(CRNO)로 한번,

모두 소문자(crno)로 한번해서 두번 실행해야 한다.


TBL_CRUD를 기준으로 바꾸고 나면

내용(CRMEMO)은 비슷한 것이 없어서 남게 되는데,

지우거나 대충 비슷한 것으로 바꾸어 주면 된다.

여기에서는 프로젝트의 상태(PRSTATUS) 필드로 바꾸어 주었다.

프로젝트 테이블(PRJ_PROJECT)에서 처리하지 못한

시작일자(PRSTARTDATE), 종료일자(PRENDDATE)는

다음과 같이 수작업으로 추가한다.

    <select id="selectProjectList" resultType="gu.project.ProjectVO" parameterType="gu.common.SearchVO">
        SELECT PRNO, PRTITLE, TC.USERNO, USERNM, PRSTATUS, DATE_FORMAT(PRDATE,'%Y-%m-%d') PRDATE
                , PRSTARTDATE, PRENDDATE
          FROM PRJ_PROJECT TC
         INNER JOIN COM_USER CU ON TC.USERNO=CU.USERNO
         <include refid="includeProject"/>
         ORDER BY PRNO DESC
         <if test="rowStart != null">
             LIMIT ${rowStart-1}, 10
         </if>
    </select>
       
    <insert id="insertProject" parameterType="gu.project.ProjectVO" >
        INSERT INTO PRJ_PROJECT(PRTITLE, USERNO, PRSTATUS, PRDATE, DELETEFLAG, PRSTARTDATE, PRENDDATE)
        VALUES (#{prtitle}, #{userno}, #{prstatus}, NOW(), 'N', #{prstartdate}, #{prenddate})
    </insert>
   
    <update id="updateProject" parameterType="gu.project.ProjectVO">
        UPDATE PRJ_PROJECT
           SET PRTITLE=#{prtitle}, PRSTATUS=#{prstatus}
              , PRSTARTDATE=#{prstartdate}, PRENDDATE=#{prenddate}
         WHERE PRNO=#{prno}
    </update>
       
    <select id="selectProjectOne" parameterType="gu.project.ProjectVO" resultType="gu.project.ProjectVO">
        SELECT PRNO, PRTITLE, TC.USERNO, USERNM, PRSTATUS, DATE_FORMAT(PRDATE,'%Y-%m-%d') PRDATE
                , PRSTARTDATE, PRENDDATE
          FROM PRJ_PROJECT TC
         INNER JOIN COM_USER CU ON TC.USERNO=CU.USERNO
         WHERE DELETEFLAG='N' AND PRNO=#{prno}
    </select>

이 상태에서 실행하면

DELETEFLAG필드가 중복된다고 오류가 난다.

DELETEFLAG필드는 프로젝트 테이블(PRJ_PROJECT)에도 있고

작성자 정보를 가져오기 위해 조인하는

사용자 테이블(COM_USER)에도 있다.

따라서, 필드 사용시 테이블을 명시해야 한다.

바꾸기 기능을 이용해 DELETEFLAG를 TC.DELETEFLAG로 바꾸어 준다.

조건절에 사용된 2개만 바꾸면 되니 잘 보고 수정한다.

앞서 TBL_CRUD를 TC로 별칭(Aliase)을 사용했다.

TC를 PP(PRJ_PROJECT)로 바꾸기 해 준다.

(하지 않아도 되지만 코딩 규칙 준수를 위해서 처리)


SQL에서 소문자 변환시

ProjectForm.jsp, ProjectList.jsp, ProjectRead.jsp파일도

같이 변환하면 빠르게 작업 할 수 있다.

그림에서 보는 것과 같이

jsp 파일에서도 동일한 명칭을 소문자로 사용하기 때문이다.


마지막으로 다음 그림과 같이

ProjectVO 파일을 열어서 (왼쪽그림)

모두 지우고 오른쪽 그림과 같이 작성한다.

프로젝트 테이블(PRJ_PROJECT)구조를 그대로 작성한 것으로

Amateras에서 제공되는 코드를 수정하여 작성한 것이다.

작성자 이름(usernm)은 프로젝트에서도 사용하므로 그대로 뒀다.

노란 밑줄이 생긴 변수를 선택하고

마우스 오른쪽 버튼을 클릭해서 메뉴를 실행한다.

source > Generate Getters and Setters를 선택한다.

(실행이 안될 경우, 편집창을 닫았다가 다시 열어서 ...)

우측에 있는 [Select All] 버튼을 클릭해서

모든 변수를 선택하고 [OK]버튼을 클릭하여

그림과 같이 VO클래스를 완성한다.

ProjectVO를 저장하면 위 그림에서 보듯이

ProjectCtr과 ProjectSvc 파일에 오류가 생긴다.

이 오류는 삭제된 메소드를 호출해서 생긴 것으로

Crno를 찾는 것이다.

(PK값으로 수정과 쓰기를 구분하는 것으로 자세한 설명은 게시판 참조)

표에서 crno는 prno(Prno)로 바꾸었기 때문에

다시 바꾸기 기능을 이용해 바꾸어 주면 된다.


지금까지의 내용은

테이블 필드를 기준으로 바꾸기 기능을 이용하였다.

테이블 필드명, 데이터 오프젝트(Data Object, Value Object-VO)의

변수 이름을 동일하게 구성하고

이들을 참조하는 파일들(Java, JSP)도 같은 이름을 사용하게 되어

한번에 변환하는 과정을 정리하였다.


웹 브라우저에서 다음 주소로 접속해서 실행 결과를 확인한다.

http://localhost:8080/pms9/projectList

프로젝트 CRUD 제작의 마지막 작업으로

적절하게 화면(JSP)을 보강한다.

실행 화면은 프로젝트 리스트 화면으로

프로젝트에서 중요하게 여기는 정보가 빠져있다.

앞서, SQL 수정에서도 다루었지만

CRUD 샘플 테이블에는 프로젝트 정보 테이블의

상태, 시작일자, 종료일자 필드가 없어서 추가했다.

화면에서도 마찬가지로 이 세가지 필드가 없어서 추가해야 한다.

리스트에서는 작성일자 필드보다

시작일자와 종료일자를 보여 주는 것이 더 좋을 것이다.

다음의 화면설계서와 같이

프로젝트 상태도 보여주는 것이 좋다.

프로젝트 읽기 (개별 정보 출력)와 등록도 동일하다.

CRUD 샘플에서 사용된 글 내용은

프로젝트 관리 기능에서는 필요 없다.

따라서, 앞서 SQL문 작성에서는

글 내용(crmemo)을 프로젝트 상태(prstatus)로 바꾸어 주었다.

화면에서도 동일하게 처리한다.

또, 글 리스트에서만 사용한 작성일자(prdate)도

굳이 필요가 없어서 시작일자(prstartdate)로 바꾸어 준다.

(작성일자, 삭제 필드는 보이지 않고 데이터 관리를 위해 내부적으로만 사용)

마지막으로, 종료일자(prenddate)를 추가할 것이다.


ProjectList.jsp 파일을 열어서

다음과 같이 (빨강색) 작성한다.

<div class="listHead">

    <div class="listHiddenField pull-left field60"><s:message code="board.no"/></div>
    <div class="listHiddenField pull-right field100"><s:message code="project.prstatus"/></div>
    <div class="listHiddenField pull-right field100"><s:message code="project.prenddate"/></div>
    <div class="listHiddenField pull-right field100"><s:message code="project.prstartdate"/></div>
    <div class="listHiddenField pull-right field100"><s:message code="project.usernm"/></div>
    <div class="listTitle"><s:message code="project.prtitle"/></div>
</div>

<c:if test="${listview.size()==0}">
    <div class="listBody height200">
    </div>
</c:if>

<c:forEach var="listview" items="${listview}" varStatus="status">
    <c:url var="link" value="projectRead">
        <c:param name="prno" value="${listview.prno}" />
    </c:url>

    <div class="listBody">
        <div class="listHiddenField pull-left field60 textCenter"><c:out value="${searchVO.totRow-((searchVO.page-1)*searchVO.displayRowCount + status.index)}"/></div>
        <div class="listHiddenField pull-right field100 textCenter"><c:out value="${listview.prstatus}"/></div>
        <div class="listHiddenField pull-right field100 textCenter"><c:out value="${listview.prenddate}"/></div>
        <div class="listHiddenField pull-right field100 textCenter"><c:out value="${listview.prstartdate}"/></div>
        <div class="listHiddenField pull-right field100 textCenter"><c:out value="${listview.usernm}"/></div>
        <div class="listTitle" title="<c:out value="${listview.prtitle}"/>">
            <a href="${link}"><c:out value="${listview.prtitle}"/></a>
        </div>
    </div>
</c:forEach>

기존에 있던 prdate를 지우고 위와 같이 작성한다.

처음부터 타이핑하지 말고

한줄 복사해서 붙여넣기 한 후

굵게(bold)표시된 글자만 수정한다.


(foreach) 앞에 추가한 것들은 (listHead)

프로젝트 리스트의 헤드 부분으로

출력할 데이터들의 필드명(상태, 시작일자, 종료일자)을 의미한다.


뒤에 작성한 것들은 (listBody)

화면에 출력해줄 필드들을 추가한 것이다.



이번에는 프로젝트 등록 화면을 수정한다.

실행화면에서 글쓰기를 선택하면

(각자가 알아서 [새프로젝트]로 바꾸어준다.)

다음과 같은 화면이 실행된다.



프로젝트 생성 폼(projectForm.jsp)파일을 열어서 다음과 같이 수정한다.

데이터를 서버로 전송하기 전에 (fn_formSubmit)

입력 값 확인 부분은 적절하게 수정 추가하면 된다.

여기서는 편의상 주석(삭제)으로 처리했다.

<script>
function fn_formSubmit(){
    if ( ! chkInputValue("#prtitle", "<s:message code="project.prtitle"/>")) return false;
    //if ( ! chkInputValue("#prstatus", "<s:message code="project.prstatus"/>")) return false;
   
    $("#form1").submit();
}
</script>

~~ 생략 ~~

<div class="panel-body">
    <div class="row form-group">
        <label class="col-lg-2"><s:message code="project.prtitle"/></label>
        <div class="col-lg-8">
            <input type="text" class="form-control" id="prtitle" name="prtitle" maxlength="255"
            value="<c:out value="${projectInfo.prtitle}"/>">
        </div>
    </div>
    <div class="row form-group">
        <label class="col-lg-2"><s:message code="project.term"/></label>                             
         <div class="col-lg-2">
            <input class="form-control" size="16" id="prstartdate" name="prstartdate" type="text" value="<c:out value="${projectInfo.prstartdate}"/>" readonly>
         </div>
         <div class="col-lg-2">
            <input class="form-control" size="16" id="prenddate" name="prenddate" type="text" value="<c:out value="${projectInfo.prenddate}"/>" readonly>
         </div>
    </div>
    <div class="row form-group">
        <label class="col-lg-2"><s:message code="project.prstatus"/></label>
        <div class="col-lg-8">
            <!-- textarea class="form-control" id="prstatus" name="prstatus"><c:out value="${projectInfo.prstatus}"/></textarea -->
            <label class="radio-inline"><input type="radio" name="prstatus" value="0"                    
            <c:if test="${projectInfo.prstatus==0}">checked</c:if>><s:message code="project.prstatus0"/></label>
            <label class="radio-inline"><input type="radio" name="prstatus" value="1"
            <c:if test="${projectInfo.prstatus==1}">checked</c:if>><s:message code="project.prstatus1"/></label>
        </div>
    </div>
</div>

시작일자(prstartdate), 종료일자(prenddate)는

HTML 입력상자(text)로 작성하면 된다 .

상태(prstatus)는 textarea로 작성되어 있는 것을

진행중과 종료를 선택할 수 도록 radio 테그로 작성했다 .

이 부분은 타이핑을 쳐야한다.


이렇게 실행해도 되지만

시작일자, 종료일자는 달력을 사용하는 것이 더 좋다.

따라서, Project 9 실행 페이지에서 샘플 > 샘플 2: 날짜 선택을 실행하면,

(또는 http://localhost:8080/pms9/sample2 실행)

시작일자, 종료일자와 같이 기간을 선택하는 페이지가 실행된다.

우측의 [코드 보기]를 선택한다.

그림에서

위에 있는 박스는 달력 라이브러리를 사용하는데

필요한 자바 스크립트 코드들이고

아래 박스에 있는 코드들은 HTML 입력 상자(text) 코드이다.

HTML 입력 상자(text)는 달력을 지정할 필드로

시작일자, 종료일자에 적용할 것이다 .

즉, HTML 입력 상자(text)는 앞서 작성했기 때문에

위에 있는 박스의 코드만 복사해서 사용하면 된다.

위에 있는 박스의 자바 스크립트 코드는 다음과 같다.

<link href="js/datepicker/datepicker.css" rel="stylesheet" type="text/css">
<script src="js/datepicker/bootstrap-datepicker.js"></script>


<script>
window.onload = function() {
    $('#term1').datepicker().on('changeDate', function(ev) {
        if (ev.viewMode=="days"){
            $('#term1').datepicker('hide');
        }
    });
    $('#term2').datepicker().on('changeDate', function(ev) {
        if (ev.viewMode=="days"){
            $('#term2').datepicker('hide');
        }
    });
}
</script>     

윗 두줄은 (link, script)은 

projectForm.jsp파일에서 head 부분에 넣어주고

코드에서 사용된 단어중

term1은 prstartdate로,

term2는 prenddate로 수정한다.

실행해서 데이터를 넣어 보면 잘 되는 것을 알 수 있다.


이렇게 사용하기 위해 5개의 샘플을 제작해 둔 것이다.


다음으로 프로젝트 리스트에서 프로젝트 하나를 선택하여

그림과 같이 프로젝트 읽기(projectRead)를 실행하면

남아 있는 작업을 알 수 있다.

앞서 글내용(crmemo)을 프로젝트 상태(prstatus)로 바꾸었기 때문에

글 내용 부분에 0 (또는 1)이 출력되어 있다.


ProjectRead.jsp 파일을 열어서

다음과 같이 수정해 준다.

<div class="panel panel-default">
    <div class="panel-heading">
        <c:out value="${projectInfo.prtitle}"/> (<c:out value="${projectInfo.prstartdate}"/> ~ <c:out value="${projectInfo.prenddate}"/>)    
    </div>
    <!-- div class="panel-body">                      
        <c:out value="${projectInfo.prstatus}"/>
    </div -->
</div>

작성자 위치에 프로젝트 시작일자와 종료일자를 넣었다 .

프로젝트 상태는 사용하지 않을 것이기 때문에 없애버렸다 .

이 둘은 작업자 마음이니 각자 편한데로 하면 된다.


마지막으로 projectList를 왼쪽에 있는 메뉴에 등록한다.

common폴더에 있는 navigation.jsp 파일을 열어서

적당한 위치에 다음과 같은 코드를 추가해 준다.

<li>
    <a href="boardList"><i class="fa fa-files-o fa-fw"></i> <s:message code="board.boardName"/></a>
</li>
<li>
    <a href="projectList"><i class="fa fa-files-o fa-fw"></i> <s:message code="project.title"/></a>
</li>
<li>
    <a href="#"><i class="fa fa-music fa-fw"></i> 샘플<span class="fa arrow"></span></a>
    <ul class="nav nav-second-level">
        <li>
            <a href="sample1">샘플 1: 조직도/사용자</a>
        </li>
        <li>
            <a href="sample2">샘플 2: 날짜 선택 </a>
        </li>
        <li>
            <a href="sample3">샘플 3: 챠트</a>
        </li>
        <li>
            <a href="sample4">샘플 4: List & Excel</a>
        </li>
        <li>
            <a href="crudList">샘플 5: CRUD</a>
        </li>
    </ul>                           
</li>

게시판 메뉴를 복사해서

URL(projectList)과 메뉴 글자를 수정해서 작성했다.



이상과 같이

게시판 입출력 예제(CRUD)를 이용하여

프로젝트 관리 페이지를 제작하였다.

일부 타이핑한 것을 제외하면

모두 복사/붙여넣기와 바꾸기 (CPR)로 작성하였다.

이렇게 작성하면 유사한 페이지들은

천천이 해도 약 30분 이내에 기능을 구현 할 수 있다.


지금까지 진행한 것이 복잡하게 느껴질 수 있는데

다시 정리하면

1. crud 샘플을 복사해서 project 페이지를 제작한다.

2. 두 페이지간에 차이가 나는 필드를

삭제하거나 추가한다 (SQL, JSP).

3. 라이브러리(달력)가 필요한 경우

준비된 샘플의 코드를 복사해서 사용한다.


이렇게 간단한 작업으로 그럴듯한 기능을 구현했다.

이렇게 간단하게 개발하기 위해서는

생략된 기본 개념들을 모두 알고 있어야 한다는 전제가 있으니

Spring MVC, 게시판 등의 개념들을 잘 익혀두어야 한다.



과제 관리 시스템(Project Management System)에서

하나의 프로젝트를 생성하면

해당 프로젝트를 수행하는 세부 과정을 등록한다.


예로, 어떤 개발 프로젝트를 진행한다고 하면

세부 과정은 분석/설계, 구현, 테스트 등으로 구성될 것이다.

다시 각 과정은 더 세분화된 과정으로 나눌 수 잇다.

분석/설계는 시스템 분석, 업무 분석, 화면설계, DB설계 등이 될 것이다.

이러한 세부화된 과정을 작업(Task)이라고 하고

이 작업은 계층형의 트리 구조를 가지게 된다.

따라서 작업 할당에서는

이러한 계층 데이터를 표시하는데 유용한 treegrid를 이용한다.


본격적인 작업전에 필요 기술에 대해서 정리한다.

각 개별 작업을 관리(등록, 수정, 삭제)하는 방법으로

easyui에서 제공하는 treegrid를 활용할 것이다.

따라서, 이 treegrid를 사용하는 방법을 익히고

쉽게 개발하기 위한 사전 준비 작업을 진행한다.


다운로드 받은 easyui 라이브러리 파일에서

jquery-easyui-1.5.1\demo\treegrid 폴더에 있는

editable.html 파일을 웹 브라우저에서 실행한다.

전체 코드

실행 결과가 화면 설계서와 비슷한 것을 알 수 있다.

화면 설계서 작성시 고려한 것도 있고,

작업(task)을 할당하는 업무 자체가 비슷한 것도 있다.

easyui의 예제를 실행해보면

화면 설계서와 가장 유사한 것이 editable.html인데,

두 가지 보완이 필요하다.

  1. 이 예제에는 작업을 추가하고, 삭제할 수 있는 기능이 없다.
  2. 데이터 설정이 같은 스크립트(파일) 내에서 지정되어야 하는데 treegrid_data2.json 파일로 외부에 있다.


이 두가지 기능을 보완하는 작업을 먼저 진행한다.

먼저 추가/삭제 기능은 contextmenu.html 예제에 있다.

contextmenu.html예제에서

append()와 removeIt() 함수가 추가하고 삭제하는 함수이므로

복사해서 editable.html파일에 넣어준다.

var idIndex = 100;
function append(){
    idIndex++;
    var d1 = new Date();
    var d2 = new Date();
    d2.setMonth(d2.getMonth()+1);
    var node = $('#tg').treegrid('getSelected');
    $('#tg').treegrid('append',{
        parent: node.id,
        data: [{
            id: idIndex,
            name: 'New Task'+idIndex,
            persons: parseInt(Math.random()*10),
            begin: $.fn.datebox.defaults.formatter(d1),
            end: $.fn.datebox.defaults.formatter(d2),
            progress: parseInt(Math.random()*100)
        }]
    })
}
function removeIt(){
    var node = $('#tg').treegrid('getSelected');
    if (node){
        $('#tg').treegrid('remove', node.id);
    }
}


각 작업마다 식별을 위해 고유 번호가 필요한데

idIndex 변수가 이 역할을 한다.

따라서, idIndex, append(),removeIt()를

복사해서 editable.html파일에 넣어준다.

(수정된 editable.html 내용은 다음과 같다.)

그리고, 이 기능을 수행할 버튼(link)을 추가해 주고 [라인 110, 111]

Click 이벤트에서 이 함수를 호출하도록 한다 .


두 번째로 HTML table 태그(treegrid 배치 정보 제공)에서

data-options 속성에 지정된

외부 데이터 지정 정보(url: 'treegrid_data2.json')를 지운다.

대신 웹 페이지 로딩이 완료된 후에 [라인 12]

Treegrid를 실행하면서 데이터를 읽어오도록 한다 [라인 25].

treegrid_data2.json 파일에 있는 Json 데이터를 복사해서

editable.html파일에 넣어주는 데

다음 코드의 13 번째 라인과 같이 변수를(dataSet) 지정한다 [라인 13].

Treegrid를 실행시키면서 이 변수를 파라메타로 넘겨주면 된다 [라인 26].


이제 실행해서

추가, 삭제, 수정이 잘되는지 확인한다.

코드의 양이 많아서 어렵게 느껴질 수 있는데,

editable.html, contextmenu.html, treegrid_data2.json을

적당하게 잘 합쳐주는 작업을 진행한 것이다.


조금더 상세하게

Treegrid의 data-options에 대해서 정리한다.

먼저, 데이터 구조(필드, Json 키, dataSet)가

id, name, begin, end, progress, iconCls로 되어 있다.

각각 키 필드(id), 작업 이름(name), 시작일자(begin),

종료일자(end), 진행율(progress), 상태(state),

아이콘 CSS 클래스명(iconCls)이다 [라인 14~].

이 이름(Json 키)들이 데이터를 조작할 때 사용하는 이름이 된다.


Treegrid의 data-options은 두 가지로 사용된다.

Table 태그에서 사용되는 것은 [라인 114]

전체적인 옵션으로 용어(영어)를 세심하게 보면

기능을 쉽게 알 수 있어서 넘어가고

idField(id)와 treeField(name)에 대해서만 정리한다.

idField는 고유키 값을 가진 데이터 필드를 의미하고 [라인 121]

앞서 Json으로 구성된 키 필드(id)를 사용하는 것으로 지정하였다.

treeField는 트리처럼 계층형(들여쓰기)으로

보여줄 필드를 지정하는 것으로

작업 이름(name) 필드를 지정하였다 [라인 122].

실행 화면에서 작업 이름에 트리가 구성된 것을 볼 수 있다.


Table의 th 태그에서도 data-options을 사용한다 [라인 127].

Treegrid의 각 필드를 데이터(Json, dataset)의 필드와 매칭시켜주고

각 필드의 속성을 지정하는 것이다.

예로 첫 번째 th는 작업 이름(name) 필드(field)의 내용을 보여주고 [라인 127]

크기(width)는 180px,

값 입력(editor)은 텍스트 박스(text)를 이용해서 입력 받는다.

옵션의 필드(field)는 앞서 선언하고

treegrid 생성시 데이터 변수로 지정한

dataSet에서 사용된 필드 이름이다 [라인 14].

값 입력(editor) 방식은

일반적인 텍스트 박스(text),

날자 입력에 사용하는 달력(datebox),

숫자만 입력할 때 사용하는 텍스트 박스(numberbox)가 있다.

진행율과 같이 입력 받은 값을

다양한 형식으로 출력하기 위한 formatter 속성이 있다 [라인 131].

formatter 속성에 사용자 함수(formatProgress)를 지정해서

다양하게 형식을 지정할 수 있는데

예제에서는 사용자가 입력한 숫자(진행율)를

바 챠트로 그려서(div 생성) 보여주도록 작성했다.


Treegrid로 지정한 Table 태그에서 title 속성값을 지우면

실행 화면에서 Treegrid 타이틀에 있는

Editable TreeGrid를 제거할 수 있다 [라인 113].


앞서 보강할 두가지 외에도

편리한 사용을 위해 Treegrid 기능을 조금더 보강한다.


하나의 행을 선택하고,

수정 버튼을 누르는 것이 불편하다.

조금 더 편하게 사용하고

TreeGrid 사용법도 익힐 겸

수정하려는 행을 마우스로

두 번 클릭하면 수정되게 작성해 본다.


다음과 같이 코드를 추가하여

셀(TD)을 더블 클릭하면 (onDblClickCell)

현재 선택된 행이 수정되게 구성할 수 있다.

            $('#tg').treegrid({
                data: dataSet,
                onDblClickCell : function(field, row) {
                        edit();
                }                   
            });

Treegrid 생성시 옵션으로

마우스 이벤트 등을 지정해서 사용할 수 있다.

Treegrid 제공되는 옵션이나 이벤트 명은

일반적인 JS에서 제공되는 이름과 유사하지만

조금씩 차이가 있기 때문에 보다 자세한 정보를

다음 사이트를 참고 하거나 인터넷으로 검색해 보길 바란다.

마지막 기능 보강으로

행(노드)을 추가하는 것을 처리한다.

현재 구현된 추가(append) 기능은

선택된 행의 하위만 자식 행을 추가한다.

즉, 선택된 행이 있어야 한다.

예제는 데이터가 주어진 상태이지만

다음과 같이 특정 프로젝트에서 작업을 처음 할당할 경우

데이터가 없기 때문에 선택된 행이 없다.

    var dataSet = {"total":0,"rows":[]}

따라서, 버그가 발생하게 된다.

            var parentid = null;
            var node = $('#tg').treegrid('getSelected');
            if (node) parentid=node.id;
            $('#tg').treegrid('append',{
                parent: parentid,
                data: [{
                    id: idIndex,
                    name: 'New Task'+idIndex,
                    persons: parseInt(Math.random()*10),
                    begin: $.fn.datebox.defaults.formatter(d1),
                    end: $.fn.datebox.defaults.formatter(d2),
                    progress: parseInt(Math.random()*100)
                }

이것은 Treegrid에 행을 추가할 때,

부모행(parent)을 지정해서 해결한다.

기존 코드를 보면 선택된 행(node)의 키 값(node.id)을

지정하게 되어 있다 [라인 84].

이것을 변수(parented) 처리해서

부모가 없으면(선택하지 않았으면) Null ,

있으면 부모 키 값(node.id)을 가지도록 수정한다.

부모가 Null 이면 최상위에 노드가 생성된다.


여기서는 구현 하지 않지만

트리를 구성할 경우에는 자식도 추가하지만

형제를 추가할 수도 있다.

현재는 형제를 추가할 경우

부모를 선택해서 자식을 추가해야 한다.

(현재는 최상위 부모의 형제를 추가하는 것이 어렵다.)

이 기능 구현은 공유한 과제관리시스템에 있으니

확인해보길 권하고 여기서는 넘어간다.


행 추가 방법은

앞서 Treegrid의 초기 데이터 설정에서 한 것처럼

키 필드(id), 작업 이름(name), 시작일자(begin),

종료일자(end), 진행율(progress)에 값을 지정하고

추가(append) 메소드를 지정해 주면 된다.

최종 전체 코드

이외에도 삭제시 자식 노드 처리,

진행율에 따른 바 차트의 색깔 처리 등의

구현해야 될 기능은 더 많이 있다 (화면설계서 참조).

기본적으로 필요한 기능은 공유한 과제관리시스템을 참고하길 바라고

여기서는 개발 방법만을 정리하기 때문에 넘어간다.


이렇게 HTML로 기본 기능을 구현하였다.

이 코드를 사용하려는 jsp 파일에 복사해서 붙여넣고,

Java와 잘 연동시켜서

데이터베이스에 저장하면 작업(Task)할당 기능이 구현된다.






작업 테이블(TBL_TASK)은 다음과 같이 구성되었다.

CREATE TABLE PRJ_TASK(
    PRNO             INT          NULL  COMMENT '프로젝트 번호',
    TSNO             BIGINT       NOT NULL AUTO_INCREMENT COMMENT '업무번호',
    TSPARENT       BIGINT       NULL  COMMENT '부모업무번호',
    TSSORT          MEDIUMINT(10) NULL  COMMENT '정렬',
    TSTITLE          VARCHAR(100) NULL  COMMENT '업무 제목',
    TSSTARTDATE   VARCHAR(10)  NULL  COMMENT '시작일자',
    TSENDDATE      VARCHAR(10)  NULL  COMMENT '종료일자',
    TSRATE          SMALLINT     NULL  COMMENT '진행율',
    DELETEFLAG     CHAR(1)      NULL  COMMENT '삭제'
) COMMENT='프로젝트 업무';


테이블 구조와

treegrid 예제에서 사용된 필드 구조를 비교하면

이름이 조금씩 다를 뿐

구조는 같다는 것을 알 수 있다.
treegrid 예제PRJ_TASK
키 필드id작업 번호TSNO
이름name작업 명TSTITLE
시작일자begin시작일자TSSTARTDATE
종료일자end종료일자TSENDDATE
진행율progress진행율TSRATE
부모parentid부모TSPARENT
작업자persons별도 테이블PRJ_TASKUSER

따라서, 매칭되는 이름으로 바꾸기를 진행한다.

바꾸기는 소문자로 한번씩만 진행하면 된다.

다만, treegrid에서 사용된 필드가 한단어 영어로

id, name등은 HTML에서 사용하는 문자이다.

따라서 replaceAll로 한번에 수정하면 안된다.

하나씩 찾아서 바꾸어 준다.

(변수명등은 한단어 영어로 작성하지 않는것이 좋다.)


하나씩 바꿀때 좋은 에디터가 Notepad++이다.

문자열을 선택하면 파일내에 있는 같은 문자열을 표시해줘서

쉽게 식별할 수 있다.

이방법을 쓰거나 하나씩 replace해서 표와 같이 바꾸어 준다.

수정 부분은 데이터 지정부분(dataset), 추가(append) 부분과

treegred설정 부분(table), treegred 출력 부분(table태그의 th)이다.

treegred의 필드명은 알맞게 수정하거나 그대로 사용한다.

다국어 처리를 하는 것이 좋겠지만

개발 방법과 관련되지 않고 어렵지 않아 넘어간다.

이렇게 구성한 코드를

포함할 외부 파일, 자바스크립트, HTML의

3가지로 구분해서

프로젝트 읽기 파일에(ProjectRead.jsp) 복사한다.

프로젝트 읽기 파일에서

개별 작업을 추가하고 삭제하는 등의 관리를 할 것이다.


먼저, 가지고올 CSS와 JS 등의 외부 파일 정보를

ProjectRead.jsp에 추가한다.

<link rel="stylesheet" type="text/css" href="../../themes/default/easyui.css">
<link rel="stylesheet" type="text/css" href="../../themes/icon.css">
<link rel="stylesheet" type="text/css" href="../demo.css">
<script type="text/javascript" src="../../jquery.min.js"></script>
<script type="text/javascript" src="../../jquery.easyui.min.js"></script>

외부 파일의 경로를 보면 모두 ".." 으로 상대 경로를 지정하고 있다.

현재 개발하고 있는 과제 관리 시스템에서는 이와 같은 경로가 없다.

jquery.min.js파일을 제외하고

나머지 4개 파일을 코드에서 작성한 디렉토리에서 찾아서

과제관리 시스템 프로젝트 웹 폴더에 넣어준다.

여기에서는 webapp 하위에 js 폴더에

자바스크립트 파일을 모아 두었다.

그림과 같이 js 폴더 하위에 easyui 폴더를 생성해서 복사한다.


복사한뒤, 다음 코드와 같이 수정한다.

과제 관리 시스템(실제로는 Project9)에서

JQuery(jquery-2.2.3.min.js)를 사용하고 있기 때문에

jquery.min.js는 제외한다.

    <link rel="stylesheet" type="text/css" href="js/easyui/easyui.css">
    <link rel="stylesheet" type="text/css" href="js/easyui/icon.css">
    <link rel="stylesheet" type="text/css" href="js/easyui/demo.css">
    <script type="text/javascript" src="js/easyui/jquery.easyui.min.js"></script>

생성한 easyui 폴더에

다운로드 받은 easyui파일 중에서

jquery-easyui-1.5.1\themes\default 경로에 있는

images 폴더를 복사해서 붙여 넣어야 한다 [위 그림 참조].

easyui(icon).css에서 참조하는 이미지이다.


다음으로,

자바스크립트 코드로 작성된 부분을 복사해서

ProjectRead.jsp의 HEAD 태그 안에 붙여 넣는다.


마지막으로 HTML 코드를 복사해서

ProjectRead.jsp에서 적당한 위치에 붙여넣기 한다.

다만, 과제 관리 시스템은

시각적이 부분을 bootstrap을 기반으로 하기 때문에

다음과 같이 div 태그에 row클래스를 넣어서 사용해야 한다.

bootstrap에서 한 행을 사용한다는 의미이다.

            <div class="row">
                <div style="margin:20px 0;">
                    <a href="javascript:void(0)" class="easyui-linkbutton" onclick="edit()">Edit</a>
                    <a href="javascript:void(0)" class="easyui-linkbutton" onclick="save()">Save</a>
                    <a href="javascript:void(0)" class="easyui-linkbutton" onclick="cancel()">Cancel</a>
                    <a href="javascript:void(0)" class="easyui-linkbutton" onclick="append()">append</a>
                    <a href="javascript:void(0)" class="easyui-linkbutton" onclick="removeIt()">remove</a>
                </div>
                <table id="tg" class="easyui-treegrid" style="width:700px;height:250px"
~~ 생략 ~~
                    <thead>
                        <tr>
                            <th data-options="field:'tstitle',width:180,editor:'text'">Task Name</th>
                            <th data-options="field:'persons',width:60,align:'right',editor:'numberbox'">Persons</th>
                            <th data-options="field:'tsstartdate',width:80,editor:'datebox'">Begin Date</th>
                            <th data-options="field:'tsenddate',width:80,editor:'datebox'">End Date</th>
                            <th data-options="field:'tsrate',width:120,formatter:formatProgress,editor:'numberbox'">Progress</th>
                        </tr>
                    </thead>
                </table>           
            </div>

웹 브라우저에서 다음 주소로 접속해서

잘 되는지 확인한다.

                http://localhost:8080/pms9/projectRead?prno=과제번호

다음 그림을 보면

윗 부분은 지정된 프로젝트에 대한 정보와 관리할 수 있는 링크가 있고,

아랫 부분은 작업을 관리할 수 있는 기능이 구현되는 것을 볼 수 있다.

공개한 PMS9에서는 이 버튼들을 좀더 보기 좋게 정리했지만

여기서는 개발 방법을 정리하는 것이라 그냥 넘어 간다.


실행에 문제가 없으면

몇가지 추가 작업을 진행한다.

먼저, 해당 작업에 대한 담당자를 지정해야 한다.

담당자는 각 작업에 대한 진행 상황(progress, tsrate)을 입력한다.

여기서 구현하지 않지만 산출물(첨부파일)이나

진행 상황에 대한 메모도 등록할 수 있다.


두번째는 이렇게 작성된 작업 정보를

서버로 보내서 저장(삭제)해야 한다.

모든 할당을 완료한 후 저장하면

구현이 복잡해지기 때문에

여기서는 하나의 작업(행)을 완료하면 (save버튼 클릭)

Ajax로 데이터를 서버로 전송해서 저장한다.


먼저, 각 업무에 대한 담당자를 지정하도록 구현한다.

담당자 지정은 treegrid에서 담당자 셀(Persons)를

마우스로 두 번 클릭하면 (DblClickCell)

직원들을 선택할 수 있는 팝업 화면이 실행되고 (그림 참조)

담당 직원들을 선택하면

Treegrid의 담당자 셀(Persons)에 이름이 나열되도록 구현한다.

treegrid 예제에서

담당자는 persons라는 필드로 사용되고

담당자 수를 의미한다.

과제 관리에서는

Porject9 샘플에서 작성한 것과 같이

담당자 이름이 출력된다.

더욱이 화면 출력은 이름이지만

데이터 베이스에 저장할 때는

각 담당자(사용자)의 고유번호(userno)로 저장한다.


따라서

Treegrid의 persons는 담당자 이름(usernm)으로 바꾸고,

데이터 베이스 저장을 위해

담당자고유번호(userno)를 숨겨진 필드로 가지고 있는다.

Treegrid의 숨겨진 필드는

데이터(dataSet)에는 값을 가지고 있지만

출력 부분(Table 태그)에는 없는 필드이다.


persons을 usernm으로 모두 바꾸어 준다.

save()함수에 사용된 persons은

입력 인원수를 더해서

treegrid footer에 보여주기 위한 코드로

삭제하고,

append()와 table 태그에서 지정하는 두 부분만 수정하면 된다.

function append(){
    idIndex++;
    var d1 = new Date();
    var d2 = new Date();
    d2.setMonth(d2.getMonth()+1);
    var parentid = null;
    var node = $('#tg').treegrid('getSelected');
    if (node) parentid=node.tsno;
    $('#tg').treegrid('append',{
        parent: parentid,
        data: [{
            tsno: idIndex,
            tstitle: 'New Task'+idIndex,
            usernm: "",
            userno: "",

            tsstartdate: $.fn.datebox.defaults.formatter(d1),
            tsenddate: $.fn.datebox.defaults.formatter(d2),
            tsrate: parseInt(Math.random()*100)
        }]
    })
}
</script>  
</head>

<table id="tg" class="easyui-treegrid" style="width:700px;height:250px"
    <thead>
        <tr>
            <th data-options="field:'tstitle',width:180,editor:'text'">Task Name</th>
            <th data-options="field:'usernm',width:60,align:'right'">Persons</th>
            <th data-options="field:'tsstartdate',width:80,editor:'datebox'">Begin Date</th>
            <th data-options="field:'tsenddate',width:80,editor:'datebox'">End Date</th>
            <th data-options="field:'tsrate',width:120,formatter:formatProgress,editor:'numberbox'">Progress</th>
        </tr>
    </thead>
</table>           

table 태그에서 담당자(usernm) 지정시

입력 방식으로 지정된 editor:'numberbox'는 제거한다.

인원수를 입력하지 않고

마우스로 두번 클릭하면

앞서의 사용자 선택 팝업에서 선택하도록 구현할 것이다.


Project9에서 제공하는

샘플 1의 3번째 예제인 [코드보기]를 실행한다.

3개의 블럭으로 구성되어 있다.

2번째 블럭 코드는 HTML(text, hidden) 태그를 이용하여

사용자 선택 값을 주고 받는 코드로

여기에서는 treegrid를 사용할 것이기 때문에 사용하지 않는다.

첫 번째 블럭 코드는 다음과 같다.

<link href="js/dynatree/ui.dynatree.css" rel="stylesheet"/>
<script src="js/jquery-2.2.3.min.js"></script>
<script src="js/jquery-ui.js"></script>
<script src="js/dynatree/jquery.dynatree.js"></script>

<script>                       
function fn_searchUsers(){
    $.ajax({
        url: "popupUsers",
        type: "post"       
    }).success(function(result){
                $("#popupUsers").html(result);
                if ($("#usernos").val()!==""){
                    set_Users($("#usernos").val(), $("#usernms").val());
                }
        }           
    );
    $("#popupUsers").modal("show");
}
function deptTreeInUsersActivate(node) {
    if (node==null || node.data.key==0) return;
   
    $.ajax({
        url: "popupUsers4Users",
        type:"post",
        data: { deptno : node.data.key }       
    }).success(function(result){
                $("#userlist4Users").html(result);
        }           
    );
}

function fn_selectUsers(usernos, usernms) {
    $("#usernos").val(usernos);
    $("#usernms").val(usernms);
    $("#popupUsers").modal("hide");
}
</script>

이 코드는 부서 조직도를 보여 주기 위해 사용한

dynatree 용 코드와

사용자 명(usernms)과 코드(usernos)를

주고 받기 위한 코드로 구성되어 있다.

usernms, usernos의 s를 모두 지운다.

treegrid와 테이블(PRJ_TASKUSER)에서는

usernm, userno로 사용하기 때문이다.

(의미상 그냥 사용해도 되지만 혼동을 줄이기 위해 수정한다.)


Project9의 샘플 코드는

사용자 명은 text, 사용자 코드는 hidden 태그를 사용했다.

여기서는 사용자 명은 treegrid 필드,

사용자 코드는 숨겨진 필드로 구현한다.

(dataset 변수에 지정되었지만 treegrid에 지정되지 않은면 숨겨진 필드가 된다)

따라서 위 코드를 다음과 같이 수정한다.

function fn_searchUsers(row){                                  
    $.ajax({
        url: "popupUsers",
        type: "post"      
    }).success(function(result){
                $("#popupUsers").html(result);
                   if (row.userno){
                       set_Users(row.userno, row.usernm);
                   }
        }          
    );
    $("#popupUsers").modal("show");
}
~~ 생략 ~~
function fn_selectUsers(userno, usernm) {                      
    var node = $('#tg').treegrid('getSelected');
   
    $('#tg').treegrid('update', {
        id : node.tsno,
        row : {userno : userno, usernm:usernm}
    });
    $('#tg').treegrid('endEdit', editingId);
    $('#tg').treegrid('beginEdit', editingId);
    $("#popupUsers").modal("hide");
}

treegrid 사용에 대한 상세한 설명은 정리하지 않으니 찾아보길 바란다.

① fn_searchUsers()는 treegrid에서

담당자(persons)필드를 두번 클릭하면 호출하는 함수로

사용자 선택 팝업을 실행하기 전에 기존에 선택된 값을 넘겨준다 (set_Users).

② fn_selectUsers()는 팝업에서 사용자를 선택하고

[확인]을 누르면

선택된 값을 treegrid에 넣어주는(update)는 역할을 한다.


이렇게 수정한 코드를 ProjectRead.jsp에 넣어준다.


앞서의 원본 코드에서 앞에 있는 link 와 script 를 복사해서

ProjectRead.jsp 파일의 head 태그에 붙여 넣는다.

jquery-2.2.3.min.js는 이미 사용중이니 제외한다.


[코드 보기] 팝업에서

마지막에 있는 div를 복사해서 ProjectRead.jsp의 마지막에 붙여 넣는다.

<div id="popupUsers" class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel"></div>

이 코드는 팝업 내용을 출력하기 위한

Bootstrap 코드이다.


담당자 지정과 관련해서 마지막 작업으로

treegrid 생성부분에 다음 코드를 추가 한다.

$(function() {
    var dataSet = {"total":0,"rows":[]}

    $('#tg').treegrid({
        data: dataSet,
        onDblClickCell : function(field, row) {
            edit();
            if (field==="usernm") {
                fn_searchUsers(row);
            }
        }
    });
});

하나의 행을 두번 클릭하면

데이터를 수정하는 코드이다.

다만, 두번 클릭한 필드가 담당자 필드이면

fn_searchUsers()를 호출해서

사용자 선택 팝업을 실행시킨다.

파라메터로 row를 사용하는 것은

treegrid에서 주는 값으로

하나의 행에 대한 모든 값을 Json으로 가지고 있기 때문이다.

row.tsno, row.usernm, row.userno, row.tstitle 등으로

이용할 수 있다.

실행해서

그림과 같이 담당자를 지정한다.



하나의 프로젝트에

개별 작업(Task)들을 구성하는 마지막 처리는

treegrid에서 작성한 내용을

데이터 베이스에 저장하고 관리하는 것이다.


작성한 내용을 데이터 베이스에 저장하는 처리는

개별 작업(Task)을 작성하고 [append(), edit() 호출]

treegrid에 저장할 때[save()] 데이터 베이스에도 저장한다.

또, treegrid에서 하나의 작업을 선택하고

삭제할 때 (removeIt)

데이터베이스에서도 삭제하면 된다.

이러한 처리는 Ajax로 데이터(Json)만 주고 받도록 한다.


이렇게 저장한 작업 데이터는

하나의 프로젝트 정보를 보여줄 때

같이 보여주도록 해야 한다.

따라서, 작업에 대한 조회(select),

저장(insert/update), 삭제(delete)로 구성된

CRUD를 제작한다.

다만, 앞서 작성한 프로젝트의 CRUD와

개념은 같지만 처리 방법이 다르다.


먼저, 지정한 작업(Task)을 PRJ_TASK 테이블에 저장한다.

하나의 작업에 대한 저장은 save() 함수에서 처리된다.

본격적인 작업전에

먼저 if 문 사용을 바꾸어 준다.

if 문 블럭내에 코드가 많을 경우

다음 코드의 왼쪽 보다는 오른쪽이 조금더 가독성이 좋다.

if (editingId != undefined){

     처리

}

 if (editingId === undefined){

     return

}

처리

블럭 내에 긴 코드를 작성하면

블럭의 시작과 끝을 알기 어렵기 때문이다.

그리고, 비교 연산자는 !=, == 보다 !==, === 사용이 추천된다.


작업자 수(persons)를 합산하는 코드는 제거한다.

따라서 save()와 removeIt()를 다음과 같이 수정한다.

function save(){
    if (editingId === undefined){return;}
    var t = $('#tg');
    t.treegrid('endEdit', editingId);
    // 현재 선택된 행
    var node = t.treegrid('getSelected');                             
     t.treegrid('endEdit', editingId);
     // 부모 노드 정보 추출
     var parentId=null;
     var parent = t.treegrid('getParent', node.tsno);                 
     if (parent) {
         parentId = parent.id;
     }   
     node.tsparent=parentId;
     node.prno='<c:out value="${projectInfo.prno}" />';
     // 데이터 전송
     $.ajax({                                                         
         url : "taskSave",
         type: "post",
         dataType: "json",
         data: node
     }).done(function(data){
         $('#tg').treegrid('update', {                                  
             id : "N",
            row : {id: data, tsno: data}
         });       
     });
    
     editingId = undefined;       
}

function removeIt(){
    var node = $('#tg').treegrid('getSelected');
    if (!node) {return;}
   
    $.ajax({
        url : "taskDelete",
        cache : false,
        dataType : "json",
        data : {tsno:node.tsno}
    }).done(function(data){
        $('#tg').treegrid('remove', node.tsno);                         
    });
   
}

저장/수정과 삭제 모두 Ajax를 이용하여

데이터를 서버로 전송한다.


삭제는 삭제할 데이터(현재 선택한 행의 tsno)를 서버로 전송하여

데이터 베이스에서 삭제하고(delete),

treegrid에서도 삭제해(remove) 주면 된다 ⑤.


저장시 전송할 데이터는 현재 선택된 행을 찾아서(node)

해당 행의 정보를 전송한다 ①.

getSelected로 반환된 개체(node)는 Json 데이터를 반환하는데

행의 모든 필드 정보를 가지고 있지만

부모 노드에 대한 정보가 없어서 별도의 처리를 했다 ②.

taskSave 컨트롤을 호출해서 ③

해당 작업을 저장하고

작업번호(tsno)을 반환 받아서

treegrid의 해당 행을 수정(update)해 준다 ④.

수정일 경우에는 작업번호(tsno)가 있지만

신규일 경우에는 작업번호(tsno)에 N 문자를 넣어서 전송한다.

서버에서는 이 값을 기준으로 insert를 실행 할지

update를 실행할지를 결정한다.

treegrid 버그인지 tsno를 키로 지정했지만

id를 키로 사용하기 때문에 두 개의 필드 값을 수정(update)해 준다.

이 처리를 좀더 정확하게 수행하기 위해

append() 함수도 다음과 같이 수정한다.

function append(){
    ~~ 생략 ~~
    $('#tg').treegrid('append',{
        parent: parentid,
        data: [{
            id: 'N',
            tsno: 'N',
            tstitle: 'New Task'+idIndex,
            usernm: "",
            userno: "",
            tsstartdate: $.fn.datebox.defaults.formatter(d1),
            tsenddate: $.fn.datebox.defaults.formatter(d2),
            tsrate: parseInt(Math.random()*100)
        }]
    });
    editingId = 'N';
    $('#tg').treegrid('select', editingId);
    $('#tg').treegrid('beginEdit', editingId);   
}

추가시 신규와 수정을 구분하기 위해 id와 tsno에 N값을 넣어준다.

추가후 반드시 저장하도록 하기 위해

현재 추가한 행(select)을 수정모드(beginEdit)로 설정한다.


이렇게 클라이언트쪽 작업을 마치고

서버쪽 작업을 진행한다.


resource > sql 폴더에

projectTask.xml 파일을 생성하고 다음 코드를 작성한다.

insertCrud, updateCrud, deleteCrud를 복사해서 수정(replace)해도 되고,

그냥 작성한다 (CPR추천).

    <insert id="insertTask" parameterType="gu.project.TaskVO" useGeneratedKeys="true" keyProperty="tsno">
        INSERT INTO PRJ_TASK(PRNO, TSPARENT, TSSORT, TSTITLE,  TSSTARTDATE, TSENDDATE, TSRATE, DELETEFLAG)
        VALUES (#{prno}, #{tsparent}, #{tssort}, #{tstitle}, #{ tsstartdate}, #{tsenddate}, #{tsrate}, 'N')
    </insert>
   
    <update id="updateTask" parameterType="gu.project.TaskVO">
        UPDATE PRJ_TASK
           SET TSTITLE=#{tstitle}
             , TSSTARTDATE=#{tsstartdate}
             , TSENDDATE=#{tsenddate}
             , TSRATE=#{tsrate}
         WHERE DELETEFLAG='N'
           AND TSNO=#{tsno}
    </update>
   
    <delete id="deleteTask" parameterType="String">
        UPDATE PRJ_TASK
           SET DELETEFLAG='Y'
         WHERE TSNO=#{tsno}
    </delete>
    <insert id="insertTaskUser" parameterType="gu.common.Field3VO" >
        INSERT INTO PRJ_TASKUSER(TSNO, USERNO)
        VALUES (#{field1}, #{field2})
    </insert>
    <delete id="deleteTaskUser" parameterType="String" >
        DELETE FROM PRJ_TASKUSER WHERE TSNO = #{tsno}
    </delete>

다음으로

gu > project 폴더에

TaskVO.java 파일을 생성하고

PRJ_TASK 테이블의 필드에 해당하는 변수들을 선언한다.


TaskCtr.java 파일을 생성해서

다음과 같이 작성한다.

    @RequestMapping(value = "/taskSave")
    public void taskSave(HttpServletRequest request, HttpServletResponse response, TaskVO taskInfo) {
       
        taskSvc.insertTask(taskInfo);
       
        UtilEtc.responseJsonValue(response, taskInfo.getTsno());
    }
   
    @RequestMapping(value = "/taskDelete")
    public void taskDelete(HttpServletRequest request, HttpServletResponse response) {
        String tsno = request.getParameter("tsno");

        taskSvc.deleteTask(tsno);
       
        UtilEtc.responseJsonValue(response, "OK");
    }

컨트롤 파일에서는

데이터 베이스에 저장할 수 있도록 서비스(TaskSvc)를 호출하고

Ajax 호출에 따른 결과를 반환한다.

Ajax로 값을 반환하는 코드는

UtilEtc파일에 responseJsonValue() 함수로 미리 구현되어 있어서

함수만 호출해 주면 된다.


마지막으로 TaskSvc.java을 생성하고

다음과 같이 코드를 작성한다.

    public void insertTask(TaskVO param) {
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        TransactionStatus status = txManager.getTransaction(def);
       
        try {
            if (param.getTsno() == null || "N".equals(param.getTsno())) {               
                if ("".equals(param.getTsparent())) {
                    param.setTsparent(null);
                }
                sqlSession.insert("insertTask", param);
            } else {
                sqlSession.update("updateTask", param);
                sqlSession.delete("deleteTaskUser", param.getTsno());                 
            }
            String userno = param.getUserno();
            if (userno!=null) {                                                             
                Field3VO fld = new Field3VO(param.getTsno(), null, null);
                String[] usernos = userno.split(",");
                for (int i=0; i< usernos.length; i++){
                    if ("".equals(usernos[i])) {continue;}
                    fld.setField2(usernos[i]);
                    sqlSession.update("insertTaskUser", fld);
                }
            }
            txManager.commit(status);
        } catch (TransactionException ex) {
            txManager.rollback(status);
            LOGGER.error("insertTask");
        } 
    }
 
    public void deleteTask(String param) {
        sqlSession.delete("deleteTask", param);
    }

코드가 복잡해 보이는데

CRUD 샘플의 내용과 비슷한다.

작업 키 필드(tsno)의 값이 없거나 N이면 ①

신규로 insert를 실행하고

아니면(값이 있으면) 수정으로 보고 Update를 실행한다.


CRUD 샘플과 다른 점은

해당 작업에 대한 담당자 처리 부분이다.

담당자정보(userno)는 여러 명일 수 있기 때문에

콤마(,)로 묶여서 하나의 변수(userno)에 넣어져 전송된다.

이것을 분리해서(split)

개수만큼 저장하게 처리한다 ③.

다만, 수정시 누가 어떻게 바뀌는지 알 수 없기 때문에

모두 지운후 ②

새로 저장하는 방식으로 구현한다.


여기까지 작업한후 실행 결과를 확인해도 되지만

제대로 저장/수정/삭제 되었는지 확인하려면

데이터 베이스에 접속해서 확인해야 한다.

웹 페이지를 새로고침(F5) 해보면 데이터가 사라지는 것을 볼 수 있다.


데이터 베이스에 접속하지 않고

프로젝트 정보를 출력할 때 출력하도록 한다.

projectTask.xml에 다음 코드를 추가한다.

(project.xml 에 추가해도 된다.)

<select id="selectTaskList" resultType="gu.project.TaskVO" parameterType="String">
    SELECT PRNO, TSNO, TSPARENT, TSSORT, TSTITLE, TSSTARTDATE, TSENDDATE, TSRATE
         , (SELECT GROUP_CONCAT(USERNO) FROM PRJ_TASKUSER WHERE TSNO=PT.TSNO) USERNO
         , (SELECT GROUP_CONCAT(USERNM) FROM PRJ_TASKUSER PTU, COM_USER CU WHERE TSNO=PT.TSNO AND PTU.USERNO=CU.USERNO) USERNM
      FROM PRJ_TASK PT
     WHERE PT.DELETEFLAG='N' AND PRNO=#{pjno}
     ORDER BY TSNO
</select>

PRJ_TASK 테이블에서 필요한 정보를 가져오는 SQL문으로

담당자 정보(USERNO, USERNM)에서

subquery로 GROUP_CONCAT을 사용했다.

앞서의 코드에서 여러 명의 담당자가 PRJ_TASKUSER에 각각 저장되게 하고,

정보를 출력할 때는

콤마(,)를 이용하여 다시 하나의 값으로 묶어서 처리한다.

TaskSvc에 이 SQL을 실행하는 함수를 다음과 같이 작성한다.

    public List<?> selectTaskList(String param) {
        return sqlSession.selectList("selectTaskList", param);
    }


이 서비스를 호출하는 다음 코드를

ProjectCtrprojectRead 컨트롤에 추가한다.

@RequestMapping(value = "/projectRead")
public String projectRead(HttpServletRequest request, ProjectVO projectVO, ModelMap modelMap) {
    ~~ 생략 ~~   
    ProjectVO projectInfo = projectSvc.selectProjectOne(projectVO);

    List<?> listview  = taskSvc.selectTaskList(projectVO.getPrno());
   
    modelMap.addAttribute("projectInfo", projectInfo);
    modelMap.addAttribute("listview", listview);
   
    return "project/ProjectRead";
}

데이터 베이스에서 작업 리스트를 가지고 와서

ProjectRead.jsp에 작업 리스트를 넘겨 주는 코드이다.


다른 서비스(taskSvc)를 사용하기 때문에

클래스 앞부분에 (projectSvc 근처)에 다음 코드를 추가해야 한다.

    @Autowired
    private TaskSvc taskSvc;

컨트롤(ProjectCtr)에서 반환된 listview의 데이터를

JSP 파일에서 treegrid에 맞는 데이터(Json)로

변환하는 다음 코드를 추가한다.

var dataSet = {"total":<c:out value="${listview.size()}" />,"rows":[
    <c:forEach var="listview" items="${listview}" varStatus="status">
        {"id":'<c:out value="${listview.tsno}" />', "tsno":'<c:out value="${listview.tsno}" />'
        ,"tstitle":'<c:out value="${listview.tstitle}" />',"tsstartdate":"<c:out value="${listview.tsstartdate}" />"
        ,"tsenddate":"<c:out value="${listview.tsenddate}" />", "tsrate":"<c:out value="${listview.tsrate}" />"
        ,"userno": "<c:out value="${listview.userno}" />", "usernm": "<c:out value="${listview.usernm}" />"
        <c:if test="${listview.tsparent!=null}">,"_parentId":"<c:out value="${listview.tsparent}" />"</c:if> } <c:if test="${!status.last}">,</c:if>
    </c:forEach>
]};

$('#tg').treegrid({ 
    data: dataSet,
    onDblClickCell : function(field, row) {
        edit();
        if (field==="usernm") {
            fn_searchUsers(row);
        }
    }
});


데이터 베이스에서 작업 리스트(listview)를 가지고 와서 (select)

id, tsno, tstitle 등으로 구성된

Json 데이터를 동적으로 생성한다.


다음 그림과 같이 실행 결과를 확인한다.

append 버튼을 눌러서 작업을 추가하고

Save버튼으로 저장한다.

행을 선택하고 Edit 버튼으로 수정하고

Save버튼으로 저장한다.

행을 선택하고 remove 버튼으로 삭제 한다.


이렇게 작업한 후

새로고침(F5)해서 제대로 서버에 저장되었는지 확인한다.


지금까지 teegrid를 이용하여

세부 작업을 관리하는 간단한 기능을 구현하였다.

디자인도 정리, 다국어 처리,

작성자만 수정하도록 하는 등의

많은 기능들이 더 구현되어야 하지만

간단하게 개발하는 방법을 정리하는 것이 목적이라

작업 할당에서는 여기까지만 정리한다.

단순한 기능만으로도

제법 많은 코딩을 추가하고

복잡한 처리를 구현 한 것 같지만

Spring과 웹 개발을 알고 있다면

크게 어려운 것이 없는 내용이다.


정리하면

Treegrid 샘플을

과제 관리 시스템에 맞게끔 수정하고

작업을 추가/수정한 뒤 저장(Save)하거나

삭제할 때 Ajax로 데이터를 전송하여

데이터 베이스에 저장하였다.

이렇게 작성한 작업들을

프로젝트 정보가 출력될 때 같이 출력되도록

Treegrid의 기본 데이터로 지정하였다.







프로젝트별 작업 내역을 월별로 보여주는 기능(달력)은

앞서 다운받은 fullCalendar 라이브러리를

그대로 복사해서 사용하면 된다.

fullCalendar은 구글의 캘린더와 비슷한 인터페이스를

쉽게 사용할 수 있도록 해주는 라이브러리이다.


작성방식은 treegrid와 비슷하지만

저장과 삭제가 없이

조회만 구현하기 때문에

라이브러리 사용법만 알면 간단하게 구현할 수 있다.


fullCalendar를 다운받아서 압축 해제한 폴더 중에서

데모(fullcalendar-3.3.1\fullcalendar-3.3.1\demos)에

파일들을 확인해서 맘에 드는 것을 사용하면 된다.

데모들은 fullCalendar의 다양한 옵션을 지정하여 제작되었다.

여기에서는 basic-views.html 파일에 있는 내용을 복사해서 사용한다.


이상의 코드는 모두 fullCalendar에 대한 기본 설정이므로

자바 스크립트를 그대로 복사해서 사용하면 된다.


옵션들을 간단하게 정리하면,

옵션들은 Json으로 지정한다.

그 중에서 Header는 실행된 달력 화면에서 [라인 15]

이전달(prev), 다음달(), 오늘(이번달)로 이동하는 기능, [라인 16]

현재 출력된 월(title)에 대한 정보 출력 [라인 17],

날짜를 보는 기간(월, 주, 일)을 지정하는 옵션이다 [라인 18].

이러한 버튼을 왼쪽(left), 중앙(center), 오른쪽(right)에 지정할 수 있다.


defaultDate는 fullCalendar가 [라인 20]

처음 실행되면서 보여줄 일자(월)을 의미한다.

예제는 특정 일자를 지정했는데

오늘 (PC에서 지정된 일자)을 보여주도록

날짜 값 대신에 new Date()로 수정해서 사용한다.

navLinks는 월/주별 달력에서 일자를 클릭하면 [라인 21]

일별 보기로 전환하는 기능을 사용하지 여부를 결정한다.

editable는 실행된 달력에서 일정(event)을 표시한 바(bar)를

마우스로 이동할 수 있게 하는 것으로

여기서는 사용하지 않는다(false) [라인 22].

마우스로 날짜를 바꾸면

저장된 데이터도 바꾸어 줘야 하는 어려움이 있기 때문이다.

eventLimit는 하루에 일정(event)을 3개만 보여주고

그 이상은 more로 처리할지 여부를 결정한다 [라인 23].


마지막으로 events 속성이 있다 [라인 24].

Events는 표현할 일정들로 일정 데이터를 의미한다.

즉, 작성 일정을 넣어주면 된다.

일정은 많기 때문에 배열로 지정하고

배열의 원소는 Json으로 지정한다.

즉, 하나의 일정을 Json으로 지정하는 것이고

이것은 일정명(작업명-title), 시작일자(start), 종료일자(end),

데이터 식별(id)로 구성되어 있다.

데이터 식별(id) 속성은 생략해도 되지만

향후 구현에서 일정을 클릭하면

상세한 작업 정보를 팝업으로 제공하기 위해서 사용할 것이다.


보다 자세한 정보는 fullCalendar에서 제공하는

문서를 확인하면 된다.


해당 프로젝트에 대한 작업 내역을

일정별로 보는 기능을 구현한다.

작업 내역을 관리하는 페이지나

일정별로 보는 페이지는

프로젝트 상세 정보 페이지(읽기 - ProjectRead.jsp)에서 구현된다.


이외에도 화면설계서에서는

작업자별로 보거나 내것만(내 작업) 보는 기능도

프로젝트 상세 정보 페이지에서 구현된다.

즉 작업, 일정, 작업자, 내것만이라는 탭으로 디자인하고

각각을 누르면

각각의 컨트롤을 호출하도록 구현한다.

따라서 다음 코드를

프로젝트 관리 버튼과 과제 관리 버튼 사이에 작성한다.

<p>&nbsp;</p>
<div class="row">
    <div class="col-lg-5">
        <ul class="nav nav-pills">
             <li class="active"><a href="projectRead?prno=<c:out value="${projectInfo.prno}"/>"><i class="fa fa-tasks fa-fw"></i>작업</a></li>
             <li><a href="taskCalendar?prno=<c:out value="${projectInfo.prno}"/>"><i class="fa fa-calendar  fa-fw"></i>일정</a></li>
             <li><a href="taskWorker?prno=<c:out value="${projectInfo.prno}"/>"><i class="fa fa-user fa-fw"></i>작업자</a></li>
             <li><a href="taskMine?prno=<c:out value="${projectInfo.prno}"/>">내것만</a></li>
         </ul>
    </div>
</div>  

bootstrap 코드로 탭 기능을 구현한 코드이다.

li 테그에 active 클래스를 주면

탭이 활성화 된 것처럼 보이게 된다.

일정별 보기는 taskCalendar로 명명하였다.

이 파일을 복사해서

taskCalendar.jsp 파일을 생성한다.

taskCalendar.jsp 파일에서 treegrid 관련 코드를 모두 지운다.

treegrid와 조직도를 위해 사용한 dynatree용

외부 파일(script, style)을 지우고,

<script>태그안에 있는 모든 내용을 지우면 된다.

작업 관리를 위한 버튼과

treegrid용 Table 태그도 지운다

(하나의 bootstrap row만 지우면 됨). 

프로젝트 읽기 기능과 탭만 남기고 모두 지운 것이다.


다음으로 앞서 정리한  basic-views.html 파일의 내용을

taskCalendar.jsp 파일에 넣어주면 된다.

먼저, fullCalendar 라이브러리를 위한

1개의 style과 2개의 자바스트립트 파일을

Eclipse에서 프로젝트에 복사한다.

fullcalendar.print.min.css는 복사해도 되지만

출력하지 않을 것이기 때문에 제외하고,

jquery.min.js 파일은 기존에 사용하는

Jquery가 있기 때문에 제외한다.


1개의 CSS 파일과 2개의 자바스트립트 파일을

복사한 뒤에 다음과 같이 경로를 수정하고

taskCalendar.jsp 파일 head에 작성한다.

<link href='js/fullcalendar/fullcalendar.min.css' rel='stylesheet' />
<script src='js/fullcalendar/moment.min.js'></script>
<script src='js/fullcalendar/fullcalendar.min.js'></script>

앞서 정리한 스트립트를 복사해서 붙여넣기 한 후,

다음과 같이 수정한다.

앞서 정리한 스트립트에 추가된 내용은

작업 리스트(listview)를 가져와서

출력할 데이터(dataset)를 구성하는 것 뿐이다.

var dataset = [
    <c:forEach var="listview" items="${listview}" varStatus="status">
        <c:if test="${listview.tsstartdate != ''}">
            {"id":'<c:out value="${listview.tsno}" />'
            ,"title":'<c:out value="${listview.tstitle}" />'
            ,"start":"<c:out value="${listview.tsstartdate}" />"
            <c:if test="${listview.tsenddate != ''}">
                ,"end":"<c:out value="${listview.tsenddate}" />"
            </c:if>
            } <c:if test="${!status.last}">,</c:if>
        </c:if>
    </c:forEach>
];

$(document).ready(function() {
    $('#calendar').fullCalendar({
        header: {
            left: 'prev,next today',
            center: 'title',
            right: 'month,basicWeek,basicDay'
        },
        defaultDate: new Date(),
        navLinks: true,
        editable: false,
        eventLimit: true,
        events: dataset
    });
});

이 과정은 앞서 작업 관리에서

treegrid를 대상으로 진행한 것과 동일한 방식이다.


달력을 배치할 적당한 위치(탭 밑)에 다음 코드를 작성한다.

<div class="row">
    <div id='calendar' style="width: 90%"></div>
</div>

ProjectCtr에서

projectRead를 복사해서 다음과 같이 작성한다.

projectRead를taskCalendar로 바꾸어 준것이다.

    @RequestMapping(value = "/taskCalendar")
    public String taskCalendar(HttpServletRequest request, ProjectVO projectVO, ModelMap modelMap) {
        // 페이지 공통: alert
        String userno = request.getSession().getAttribute("userno").toString();
       
        Integer alertcount = etcSvc.selectAlertCount(userno);
        modelMap.addAttribute("alertcount", alertcount);
       
        ProjectVO projectInfo = projectSvc.selectProjectOne(projectVO);

        List<?> listview  = taskSvc.selectTaskList(projectVO.getPrno());
       
        modelMap.addAttribute("projectInfo", projectInfo);
        modelMap.addAttribute("listview", listview);
       
        return "project/taskCalendar";
    }  

작업 관리의 리스트와

일정 중심의 리스트는 기본적으로 같은 방식으로 구현된다.

둘다 지정된 프로젝트 정보와 작업 데이터를 가져오는 것이기 때문이다.

보여주는 방식(jsp)이 다른 것이기 때문에

위 코드와 같이 taskCalendar.jsp 파일을 지정하고

컨트롤 이름만 수정하면 된다.


PMS9에서는 좀더 복잡하게 구현했지만

이 글은 개발 개념을 잡기 위한 것으로 간단하게 구현했다.


작업자별 보기와 내것만 보기는 구현하지 않는다.

projectRead, taskCalendar 컨트롤과 구현 방식이 동일하고

SQL문의 조건만 바꾸어 주면

작성 할 수 있기 때문에 정리하지 않는다.


이상으로 작업과 일정을 중심으로

treegrid와 fullCanlendar를 사용하여 구현하였다.

작업과 일정을하나로 구현한 간트(gantt) 챠트를 이용하여

MS-Project 처럼 구현해 보는 것도

실력향상에 도움이 되고 재미도 있을 것이다.

PMS9에서는 구현하지 않았지만

직접 구현해 보길 바란다 (의외로 간단하다).

http://taitems.github.io/jQuery.Gantt/
http://jsgantt.com/
https://developers.google.com/chart/interactive/docs/gallery/ganttchart
https://frappe.github.io/gantt/
https://github.com/robicch/jQueryGantt


지금까지 crud 샘플을 복사해서

프로젝트 마스터 정보를 생성/관리하는 웹 페이지를 구현하였고,

treegrid를 이용하여 과제를 생성/관리하는 페이지를

fullcalendar를 이용하여 과제를 일정별로

확인하는 페이지를 구현하였다.

추가적으로 기간 선택(datePicker)과

사용자 선택에 Project9의 샘플을 복사하여 사용하였다.


작업을 생성하고 관리하는 부분에서 다소 복잡한 코드가 사용되었지만

대부분의 코드는 복사/붙여넣기/바꾸기를 이용하여 작성하였다.

(실제로 PMS9 개발시 거의 타이핑은 하지 않았다.)

이글의 핵심이자

과제 관리 시스템(PMS9)의 핵심,

쉽고 빠르게 개발하는 방법의 핵심은

업무에 대하여 정확하게 알고

사용할 기술에 대하여 정리되어 있으면

복사/붙여넣기/바꾸기(CPR)로 쉽게 개발할 수 있다는 것이다.




+ Recent posts