오랫만에 ExtJS 강좌 포스팅을 등록해보도록 하겠습니다.


지난 시간까지 ExtJS MVC 구조에 대하여 잡아보았습니다.



2014/11/26 - [웹개발강좌/ExtJS] - ExtJS 강좌 - ExtJS5 MVC 구조 잡아보기(View+Controller 연동)


2014/11/26 - [웹개발강좌/ExtJS] - ExtJS 강좌 - ExtJS5 MVC 구조 잡아보기(View/Controller + Model+Store 추가)



이번장에는 기존 연동되어있는 MVC 구조를 이용하여 DB와 연동하여


C :  DB INSERT

R  : DB SELECT

U : DB UPDATE

D : DB DELETE


작업을 수행해보도록 하겠습니다.


DB 연동에 대해서는 본인이 작업하는 환경에 맞추어 미리 연동을 해놓으시기 바라겠습니다.


저같은경우는 JSP 기반 SPRING Framework 를 이용하여 DB 연동을 하였습니다 


DBMS는 MySQL을 이용하여 다음과 같은 구조의 테이블을 생성하였습니다.



1
2
3
4
5
6
CREATE TABLE `grid` (
  `idx` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(100) DEFAULT NULL,
  `name` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`idx`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8



Oracle 혹은 MSSQL등 MySQL이 아닌 다른 DBMS에는 

컬럼타입과 컬럼명만 맞추기만 하여 테이블 생성하시면 되겠습니다.


이어서 임의로 가상의 데이터 몇가지를 insert 해보도록 하겠습니다.



1
2
3
4
5
6
INSERT INTO grid(title,NAME)
VALUES('제목1','이름1');
INSERT INTO grid(title,NAME)
VALUES('제목2','이름2');
INSERT INTO grid(title,NAME)
VALUES('제목3','이름3');


이어서 각 MVC 폴더구조는 다음과 같습니다.







그럼 각 MVC 파일들에 대한 코드를 간단하게 작성 및 설명을 해보도록 하겠습니다.



index.jsp


1
2
3
4
5
6
7
<!-- ExtJS 연동 관련 INCLUDE 파일들 -->
<link rel="stylesheet" type="text/css" href="/resource/extjs/ext-theme-crisp-touch/build/resources/ext-theme-crisp-touch-all.css"/>
<script type="text/javascript" src="/resource/extjs/ext-all.js"></script>
<script type="text/javascript" src="/resource/extjs/ext-theme-crisp-touch/build/ext-theme-crisp-touch.js"></script>
<script type="text/javascript" src="/resource/extjs/ext-theme-crisp-touch/build/ext-theme-crisp-touch.js"></script>
<!-- ExtJS MVC 연동 파일  -->
<script type="text/javascript" src="/resource/mvc.js"></script>



별도의 HTML코드는 필요없습니다. 

<head></head> 태그 사이에 include 해주도록 합니다.




/resource/mvc.js


1
2
3
4
5
6
7
8
Ext.application({
    name: 'mvc_study',
    appFolder : '/resource/mvc_study',
    controllers: [
       'MvcController'
    ],
    autoCreateViewport: true
});



MVC 선언 시


appFolder : mvc 구조를 잡기위한 최상단 루트폴더지정 없을시 "/app" 이 default

controllers : 컨트롤러 파일 설정을 위한 설정 



/resource/mvc_study/view/Viewport.js


1
2
3
4
5
6
7
8
9
10
11
Ext.define('mvc_study.view.Viewport', {
    extend: 'Ext.container.Viewport',
    layout : 'border',
    items : [{
        region : 'center',
        xtype : 'customGrid'
    }],
    initComponent: function() {
        this.callParent();
    }
});



MVC에서 최초 LOAD의 시작파일

※ 기존 Ext.create와 같은 구조입니다. 

다른점이 있다면, renderTo를 별도로 해주실 필요가 없으며 

Viewport 클래스를 상속받는 것 외에는 없습니다.

구조는 이전에 했던 방식과 동일합니다. 


이어서 xtype이 customGrid인 컴포넌트를 지정을 하였는데 

컴포넌트는 기본으로 제공하는 컴포넌트가 아닌 사용자지정 컴포넌트입니다.


그럼 customGrid에 대하여 정의를 해보도록 합니다.



/resource/mvc_study/view/grid/CustomGrid.js


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
Ext.define('mvc_study.view.grid.CustomGrid', {
    extend: 'Ext.grid.Panel',
    //xtype에 들어가는 컴포넌트 명칭이다.
    //widget. << 이부분을 제외한 나머지명칭을 xtype에 선언 해주면 됨
    alias : 'widget.customGrid',
    title : '그리드 CRUD',
    //로우에디터를 사용하기 위한 플러그인 설정
    plugins: [Ext.create('Ext.grid.plugin.RowEditing', {
        clicksToMoveEditor: 1,
        autoCancel: false
    })],
    columns : [
    //idx 컬럼은 수정이불가하므로 editor 속성을 제거
    //그외 title,name에만 editor 속성 허용
    {
        text : '번호',
        flex : 1,
        dataIndex : 'idx'
    },{
        text : '제목',
        flex : 1,
        dataIndex : 'title',
        editor: {
             xtype: 'textfield'
        }
    },{
        text : '이름',
        flex : 1,
        dataIndex : 'name',
        editor: {
            xtype: 'textfield'
        }
    }],
    store : 'GridStore',
    fbar : [{
        xtype : 'button',
        text : '추가',
        handler : function(btn){
            //GridModel
            var rec = Ext.create('mvc_study.model.GridModel',{
                title: '',
                name : ''
            });
            //로우 추가
            btn.up("grid").getStore().insert(0,rec);
            //에디트 포커스
            btn.up("grid").plugins[0].startEdit(0, 0);
        }
    },{
        xtype : 'button',
        text : '삭제',
        handler : function(btn){
            //선택된 로우 삭제
            btn.up("grid").getStore().remove(btn.up("grid").getSelectionModel().getSelection());
        }
    },{
        xtype : 'button',
        text : '적용',
        handler : function(btn){
            //각 row 마다 싱크를 맞춰주면서
            btn.up("grid").getStore().sync({
                //성공시
                success: function( response ) {
                        //그리드 select
                         btn.up("grid").getStore().load();
                }
            });
        }
    }],
    initComponent: function() {
        this.callParent();
    }
});


이어서 customGrid의 config속성중 

store에 선언되어있는 

GridStore에 대하여 정의를 해보도록 합니다.



/resource/mvc_study/store/GridStore.js


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
Ext.define('mvc_study.store.GridStore', {
    extend: 'Ext.data.Store',
    model: 'mvc_study.model.GridModel',
    //페이지 로드시  자동으로 READ 조회
    autoLoad : true,
    proxy: {
        type: 'ajax',
        api: {
            //등록페이지
            create : '/insertData',
            //조회페이지
            read: '/listData',
            //수정페이지
            update : '/updateData',
            //삭제페이지
            destroy : '/deleteData'
        },
        //select 옵션
        reader: {
            type: 'json',
            successProperty: 'success',
            //응답받는 json object의 최상단 key값(SELECT 구문)
            //{
            //   data : 'value'
            //}
            rootProperty: 'items'
        },
       //create,update,delete 옵션
       writer: {
            type: 'json',
            //그리드 row의 모든값 전송
            writeAllFields: true,
            //필수속성값
            encode : true,
            //문자열로 넘어가는 서버 parameter값 json object의 최상단 key값(INSERT/UPDATE/DELETE 구문)
            //java 기준 : request.getParameter("data");
            rootProperty: 'data'
        }
    }
});



store는 기존포스팅을 따라오셨던 분들이시라면 

크게 변한 부분은 없을 것입니다.


변경된 점이 있다면 

proxy에 api부분에 (create/read/update/destroy) 속성과

writer 속성이 추가된것이 전부입니다.

주석으로 설명 대체를 하도록 하겠습니다 ^^ㅋㅋ


다음은 store에 설정한 

model 속성에 정의된 GridModel에 대한 코드를 작성해보도록 합니다.



/resource/mvc_study/model/GridModel.js


1
2
3
4
Ext.define('mvc_study.model.GridModel', {
    extend: 'Ext.data.Model',
    fields: ['idx','title','name']
});



모델을 생략하고 fields 속성을 데이터스토어에 정의를 해주셔도 무관하나 

MVC에 관련된 내용이므로 MODEL에 fields 속성을 정의하였습니다.


화면에 대한 정의는 완료되었으나 

뷰를 화면에 표출을 시키기 위하여 컨트롤러 파일을 다음과 같이 작성합니다.



/resource/mvc_study/controller/MvcController.js


1
2
3
4
5
6
7
8
Ext.define('mvc_study.controller.MvcController', {
    extend: 'Ext.app.Controller',
    stores : ['GridStore'],
    views: ['grid.CustomGrid'],
    init: function() {
        this.control();
    }
});


실질적으로 그리드 툴바들에 대한 버튼 컨트롤을 위하여 

각 refs 라는 속성내에 버튼을 정의후 클릭이벤트에 대하여 기능을 구현하여야 하겠지만 

이부분에 대해서는 다음 장에 그리드 페이징 및 컨트롤러 사용에 대하여 설명을 진행하겠습니다.


마지막으로 데이터 스토어에서 정의된 각각의 데이터통신을 하는 URL에 대한 샘플 코드를 작성하도록 하겠습니다.


서버페이지는 Spring MVC + Mybatis 를 이용하여 출력하며 

JSON 파싱부분은 Jackson JSON을 이용하였습니다.


PHP 및 ASP 를 사용하시는 분들은 별도로 코드에 대한 설명을 파악 후 

JSON REQUEST/RESPONSE 구조만 맞춰주시면 동일한 결과를 얻으실 수 있습니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/**
 * response json 결과값은 다음처럼 나와야 합니다.
 * {"items":[
 *      {"title":"제목3","idx":24,"name":"이름3"},
 *      {"title":"제목2","idx":23,"name":"이름2"},
 *      {"title":"제목1","idx":22,"name":"이름1"}],
 * "success":true}
 */
//list logic
@RequestMapping("/listData")
public @ResponseBody Map<String , Object> listData(HttpServletRequest request) throws SQLException {
    Map<String, Object> map = new HashMap<String, Object>();
    //DB 목록 조회
    ArrayList<Map<String, Object>> list = dao.listData();
    //데이터스토어에 reader -> successPropery값과 맞춰줌 (true/false)
    map.put("success", true);
    //데이터스토어에 reader -> rootProperty값과 맞춰줌
    //DB목록값에 대한 JSON KEY값은 items가 되어야 한다
    map.put("items", list);
    return map;
}
 
/**
 * CREATE / UPDATE / DELETE 에 대한 JSON REQUEST 및 RESPONSE에 대한 구조는 동일
 * 다른점은 DB통신하는 부분만 변경해주면 됨 
 * REQUEST 구조
 * jsp = request.getParameter("data");
 * php = $_REQUEST['data']
 * asp = <%=request("data") %>
 * 위와같이 하면 JSON문자열 데이터로 넘어옵니다.
 * 넘어온 문자열을 각 언어에 맞게끔 string to object로 변환하는 방식을 이용합니다.
 * 제가 사용한 Jackson JSON라이브러리는 단일 object, 배열 object별로 구분을 해주어야 하므로
 * 넘어온 json 문자열에 맨 앞 표시에 따라 구분을 하였습니다.
 *
 * RESPONSE 구조
 * DB가 정상적으로 등록/수정/삭제 되었다 가정하고
 * {success : true}
 * 라는 JSON OBJECT를 RESPONSE 해줍니다.
 */
//insert logic
@RequestMapping("/insertData")
public @ResponseBody Map<String , Object> insertData(HttpServletRequest request) throws JsonParseException, JsonMappingException, IOException, SQLException {
    Map<String, Object> hmap = new HashMap<String, Object>();
    hmap.put("success", true);
    String data = request.getParameter("data");
    ObjectMapper mapper = new ObjectMapper();
    if(data.substring(0,1).equals("{")) {
        Map<String, Object> map = mapper.readValue(data, Map.class);
        dao.insertData(map);
    } else {
        ArrayList<Map<String, Object>> list = (ArrayList<Map<String, Object>>) mapper.readValue(data, List.class);
        if(list != null && list.size() > 0) {
            //처음부터 등록시 마지막 등록한게 최우선 등록하게 되므로 거꾸로 for문을 돌림
            for(int i= list.size() -1 ; i >= 0; i--) {
                dao.insertData(list.get(i));
            }
        }
    }
    return hmap;
}
//update logic
@RequestMapping("/updateData")
public @ResponseBody Map<String , Object> updateData(HttpServletRequest request) throws JsonParseException, JsonMappingException, IOException, SQLException {
    Map<String, Object> hmap = new HashMap<String, Object>();
    hmap.put("success", true);
    String data = request.getParameter("data");
    ObjectMapper mapper = new ObjectMapper();
    if(data.substring(0,1).equals("{")) {
        Map<String, Object> map = mapper.readValue(data, Map.class);
        dao.updateData(map);
    } else {
        ArrayList<Map<String, Object>> list = (ArrayList<Map<String, Object>>) mapper.readValue(data, List.class);
        if(list != null && list.size() > 0) {
            //upate 순서는 상관없으므로
            for(Map<String, Object> map : list) {
                dao.updateData(map);
            }
        }
    }
    return hmap;
}
//delete logic
@RequestMapping("/deleteData")
public @ResponseBody Map<String , Object> deleteData(HttpServletRequest request) throws JsonParseException, JsonMappingException, IOException, SQLException {
    Map<String, Object> hmap = new HashMap<String, Object>();
    hmap.put("success", true);
    String data = request.getParameter("data");
    ObjectMapper mapper = new ObjectMapper();
    if(data.substring(0,1).equals("{")) {
        Map<String, Object> map = mapper.readValue(data, Map.class);
        dao.deleteData(map);
    } else {
        ArrayList<Map<String, Object>> list = (ArrayList<Map<String, Object>>) mapper.readValue(data, List.class);
        if(list != null && list.size() > 0) {
            //delete 순서는 상관없으므로
            for(Map<String, Object> map : list) {
                dao.deleteData(map);
            }
        }
    }
    return hmap;
}



DB호출하는 쿼리로직은 다음과 같습니다.




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<select id="listData" resultType="java.util.Map">
    SELECT idx,title, name
      FROM grid
     ORDER BY idx DESC
</select>
 
<insert id="insertData" parameterType="java.util.Map">
    INSERT INTO grid(title,name) VALUES(#{title},#{name})
</insert>
 
<update id="updateData" parameterType="java.util.Map">
    UPDATE grid
    SET title = #{title}, name = #{name}
    WHERE idx = #{idx}
</update>
 
<delete id="deleteData" parameterType="java.util.Map">
    DELETE FROM grid WHERE idx = #{idx}
</delete>



이제 ExtJS MVC를 이용한 "GRID CRUD" 관련 코드작성은 완료되었습니다.


이제 실행을 해보도록 하겠습니다.



최초 그리드 목록조회






위 데이터 3개는 임의로 테이블 생성 후

 insert한 데이터에 대한 값을 출력한 결과입니다.



그리드 row 추가하여 데이터 입력






추가 버튼 클릭 후 제목과 이름에 textfield 로 원하는 값을 입력 후

 UPDATE 버튼을 클릭합니다.


※ update 버튼 클릭한다고 데이터 통신이 되지 않습니다.

임의로 화면상에 쌓아두고 있는 상태입니다.



그리드 기존 그리드데이터 더블클릭하여 데이터 수정하기






추가버튼을 이용하여 가상의 데이터 2개를 삽입 후 

기존 데이터를 선택하여 각 컬럼을 수정하였습니다.



그리드 기존 데이터 삭제 전 - 후






기존 데이터를 선택 후 삭제버튼을 클릭한 최종 화면입니다.


화면에 보시다시피 기존 데이터를 제외한 나머지에 각 컬럼 좌측상단을 보시면 

빨칸화살표같은 것이 있는데 이부분은 임의로 등록이 되었거나 수정이 되었다는 뜻입니다.


그럼 이제 적용 버튼을 클릭하여 데이터 통신이 일어난 후의 결과 화면을 보도록 하겠습니다.






신규로 넣었던 데이터에 대한 번호시퀀스 값이 자동으로 적용되며 

수정된 값도 정상적으로 반영되어 최종값이 로드되었습니다.


다음장은 기존 소스에 이어 그리드 페이징 및 각 버튼에 대한 기능들을 

컨트롤러에 담아보도록 하겠습니다. 



by 개발로짜