자바스크립트/Ext JS2014. 1. 4. 00:35


이번 강좌에서는 게시판 애플리케이션의 주요 로직을 구현해 보도록 하자. 게시판의 로직을 구현하기 위해 Store와 모델에 의해 데이터를 로드하고 입력, 수정, 삭제가 가능하도록 코딩한다.


연재순서

1회 | 2013.09 | ExtJS4 클래스 시스템의 이해

2회 | 2013.10 | ExtJS4 MVC 아키텍처의 이해

3회 | 2013.11 | 시스템 뼈대 구현하기

4회 | 2013.12 | ExtJS4 실전코딩 : 멀티 게시판 애플리케이션 구현(1)

5회 | 2014.01 | ExtJS4 실전코딩 : 멀티 게시판 애플리케이션 구현(2)


이전 시간에 우리는 메뉴를 통해 동일한 형태의 게시판이지만 관리아이디가 다른 게시판 애플리케이션을 중복으로 실행하여 멀티게시판의 형태를 구성하였다. 이제 게시판의 구체적인 기능을 구현하도록 한다.


게시판 리스트가 데이터를 불러올 수 있도록 BoardList클래스를 <리스트 1>과 같이 render된 시점에 Store를 로드하도록 수정하자.


<리스트 1> BoardList클래스 렌더시 데이터 로드

me.on('render', function () {

        me.store.proxy.extraParams = {

            brd_category_cd: this.brd_category_cd

        }

        me.store.load();

});



Store클래스인 MyMvc.store.Boards를 통해 리스트를 제공받는다. 프록시의 read에 해당하는 url인 board.do?boards에 의해 전달받는다. 이는 아래의 json결과를 반환하고 BoardList클래스에 표현된다.


<리스트2> board.do?board에 제공되는 json데이터

{

    success: true,

    totalCount: "2",

    entitys: [

        {

            brd_title: "테스트..",

            brd_seq: 11,

            brd_input_user_nm: "홍길동",

            brd_read_cnt: 10,

            brd_input_date: "2013-09-04 07:31:30",

            brd_content: "test"

        },

        ..

    ]

}


<그림1>은 그리드에 출력된 데이터를 보여주고 있다.

<그림 1> 게시물 리스트


다음은 그리드를 클릭 할 경우와 “글쓰기” 버튼을 클릭 할 경우를 구현하자. <리스트 3>와 Board 컨트롤러 코드를 수정하자.


<리스트 3> MyMvc.controller.Board.js

Ext.define('MyMvc.controller.Board', {

    init : function(app) {

        this.control({

            'boardlist button[action=board.create]' : {             // #1

                click: this.newBoard                                          // #2

            },

'boardlist' : {                                                         // #3

                itemclick : this.onBoardSelect                              // #4

            }

        });

    },


    onBoardSelect : function(grid, record, index) {

        var fp = grid.up('boardmain').down('boardview');        // #5

        if (fp.collapsed) {                                                          // #6

            fp.expand();                                                            // #7

        }

        fp.fireEvent('setFormData', record);                            // #8

    },


    newBoard: function(button){

        var fp = button.up('boardmain').down('boardview');  // #9

        if (fp.collapsed) {

            fp.expand();

        }

        fp.fireEvent('setFormData', Ext.create('MyMvc.model.Board'));   // #10

    }

});


코드를 상세히 설명하기로 하자.

 #1. boardlist는 게시물 리스트를 보여주는 그리드패널이다. 이 패널 내부의 버튼 중 action속성이 create인 버튼이 클릭(#2)이벤트를 발생 시킬 경우를 리스닝한다.

 #2. 클릭 이벤트가 발생하면 컨트롤러 내부의 newBoard메소드를 실행시킨다.

 #3. 게시물 리스트

 #4. 게시물 리스트에서 itemclick이벤트가 발생하면 onBoardSelect메소드를 실행 시킨다.

 #5. 게시물 상세내역을 보여주는 패널을 변수에 담는다.

 #6. 패널이 접혀 있다면 #7과 같이 펼쳐지도록 한다.

 #8. 게시물 상세내역 패널에 이벤트를 발생시키고 인자로 그리드의 클릭 된 모델객체를 전달한다.

 #9. 새로운 게시물을 등록하는 메소드로 게시물 입력 패널(상세내역패널과 동일)을 변수에 담는다.

 #10. setFormData이벤트를 발생시키고 비어있는 Board모델 객체를 전달한다.



이제 게시물 정보를 입력하고 보여주는 BoardView클래스에 setFormData이벤트를 추가하고 저장기능과, 삭제 기능을 처리할 메소드를 추가하도록 하자.


<리스트 4> BoardView.js

Ext.define('MyMvc.view.board.BoardView', {

    ..

    initComponent: function () {

        var me = this;

        Ext.apply(this, {

            items: [

                {

                    xtype: 'form',

                    bodyPadding: 10,

                    itemId: 'board.form',

                    layout: 'anchor',

                    defaults: {

                        anchor: '100%'

                    },

                    buttons: [

                        {

                            xtype: 'statusbar',

                            defaultText: '&nbsp;',

                            flex: 1

                        },

                        {

                            action: 'board.save',

                            text: 'Save',

                            scope: me,

                            handler: function () {

                                this.onSave();    // #17

                            }

                        },

                        {

                            action: 'board.remove',

                            text: 'Del ',

                            scope: me,

                            handler: function () {

                                this.onRemove();  // #18

                            }

                        } ,

                        ..

            ],


        .. 중략 ..

            }]

        });

        this.callParent(arguments);

        this.on('setFormData', function (rec) {                     // #1

            me.down('form[itemId=board.form]').getForm().reset();   // #2

            me.down('form').loadRecord(rec);                        // #3

        });

    },

    onSave: function () {                                           // #4

        var me = this;

        var record = this.down('form').getForm().getRecord();       // #5

        this.down('form').getForm().updateRecord(record);           // #6

        record.save({                                               // #7

            success: function (a, b) {                             // #8

                var pers = Ext.ModelMgr.getModel('MyMvc.model.Board');// #9

                pers.load(record.get('brd_seq'), {                      // #10

                    success: function (record, operation) {             // #11

                        me.down('form').loadRecord(record);             // #12

                        me.up('boardmain').down('boardlist').insertRecord(record.data);// #13

                    }

                });

            }

        });

    },

    onRemove: function () {                                     // #14

        var me = this, form = this.down('form').getForm(),

            record = form.getRecord();

        form.updateRecord(record);

        me.collapse();                                          // #15

        record.destroy();                                       // #16

    }

});


코드를 자세히 설명하도록 하자.

 #1. setFormData 이벤트가 이 클래스에서 발생할 경우 리스닝하도록 했다.

 #2. 내부 폼패널을 사용하기 전에 reset한다.

 #3. setFormData 이벤트는 인자로 데이터가 담긴 모델객체를 전달받는다. 이 전달받은 모델을 loadRecord메소드를 통해 폼에 세팅한다.

 #4. 폼패널을 저장하는 메소드이다.

 #5. 폼패널에 입력된 값을 갖는 모델객체를 변수에 담는다.

 #6. updateRecord메소드에 입력된 값을 갖는 모델객체를 전달하여 폼패널에 세팅하고 #5의 모델데이터의 수정을 완료한다.(클라이언트에서의 완료를 의미함) updateRecord메소드를 꼭 호출해야함.

 #7. 모델의 프록시 설정을 통해 서버에 저장을 요청한다.

 #8. 저장 완료 후 호출된다.

 #9. 저장한 데이터를 다시 불러오기 위해 모델 객체를 생성한다.

 #10. 변경 된 조회수와, 수정일 등을 알아오기 위해 모델의 load메소드에 현재 게시물의 아이디를 전달해 최종 데이터를 불러온다.

 #11. 모델의 load메소드의 호출이 성공하면 호출된다.

 #12. loadRecord메소드를 통해 폼패널에 데이터를 다시 전달한다.

 #13. 게시물리스트 그리드 패널에도 수정, 입력된 데이터를 반영하도록 한다.

 #14. 게시물 삭제 처리용 메소드이다.

 #15. 게시물 상세보기화면을 접도록 한다.

 #16. 모델객체에 접근하여 destroy메소드를 호출 서버에서 삭제처리 한다.

 #17. 저장 버튼을 클릭 할 경우 onSave메소드를 호출한다.

 #18. 삭제 버튼을 클릭 할 경우 onRemove메소드를 호출한다.


게시물 리스트 클래스인 BoardList에 insertRecord메소드를 구현하자. 이 메소드는 게시물의 저장버튼에 반응하여 수정, 신규저장 된 폼패널의 내용을 좌측 그리드패널에 반영되도록 하는 역할을 한다.

insertRecord: function (record) {

    index = this.store.findExact('brd_seq', record.brd_seq);    // #1

    if (index != -1) {                                          // #2

        var rs = this.store.getAt(index);                       // #3

        rs.set(record);                                         // #4

        return;                                                 // #5

    }

    this.store.insert(0, record);                               // #6

}


 #1. findExact메소드는 Store내부에서 원하는 필드에 값을 가진 데이터가 존재하는지 확인하는 메소드이다. 이 메소드에 인자로 전달받은 데이터의 brd_seq값을 넘겨 index변수에 저장한다.

 #2. 검색된 결과가 존재하지 않으면 -1을 반환하므로 존재한다는 조건식은 if(index != -1)이고 조건식에 맞는 결과가 있다면 index변수에 결과에 맞는 모델객체는 Store내부에 몇 번째인지 index변수에 전달한다.

 #3. index변수는 검색결과에 매칭되는 데이터의 순번을 가지므로 해당 순번으로 Store내부의 모델객체를 rs변수에 전달한다.

 #4. 외부에서 전달한 데이터를 Store내부 모델객체에 세팅한다. 이렇게 하여 외부의 새로운 값을 Store에 반영하는 것이다.

 #5. 여기까지는 수정에 대한 처리이다. Store내부에 외부에서 전달한 데이터의 brd_seq값이 존재하였으므로 수정행위로 보고 이후에 this.store.insert 로직이 실행되지 않도록 한다.

 #6. index변수에 -1이 전달될 경우 즉 외부에서 전달한 데이터의 brd_set값이 Store내부에 존재하지 않으면 Store의 첫 번째로 신규 입력한다.


이제 코드를 실행하고 테스트를 진행하자.

게시물 리스트를 클릭하면 Board컨트롤러가 itemclick이벤트를 감지하여 onBoardSelect메소드를 호출하고 이 메소드는 BoardView클래스에 클릭한 모델데이터를 전달하여 폼패널에 데이터를 보여지도록 하였다.



<그림 2> 게시물 선택


“글쓰기” 버튼을 클릭하자. 글쓰기 버튼이 클릭되면 setFormData이벤트를 호출하고 인자로 비어 있는 모델객체(Ext.create('MyMvc.model.Board'))를 전달한다. 폼패널에 이 빈 모델 객체가 loadRecord메소드에 세팅되면 폼은 빈 상태로 사용자의 입력을 기다리게 된다.


데이터를 모두 채우고 ‘Save’버튼을 클릭하여 게시물을 저장하자. ‘Save’버튼이 클릭 되면 onSave메소드가 호출되고 이 메소드는 폼패널에서 모델객체를 추출하여 record.save() 메소드를 실행한다. 여기서 폼패널의 데이터가 입력이든 수정이든 record.save()메소드가 실행되는 것은 동일하다. 그렇다면 모델의 api설정 중 craete와 update 중 하나를 실행하여 입력과 수정작업이 이루어지는데 무엇으로 판단하는 것일까? 모델의 save메소드는 모델내부의 idProperty의 필드 값의 존재 유무에 따라, 즉 brd_seq값이 있다면 update이고 없다면 craete로 실행되는 것이다.


좌측 게시물을 선택하여 해당 모델객체를 우측 패널에 로드 할 경우 당연히 brd_seq값이 존재할 것이고 “글쓰기”버튼을 클릭하여 빈 모델객체를 우측 패널에 로드 할 경우 brd_seq값은 비어 있게 되므로 이러한 특성을 이용하여 입력과 수정을 한가지 로직으로 처리하게 된다.


<그림 3>은 데이터를 수정할 경우 Ajax호출 모습이다. record.save()메소드는 수정로직을 처리한 이후 수정한 데이터를 다시 로드 한 뒤 폼패널에 세팅하는 과정을 거쳐 서버에서 최종 업데이트 된 내용을 클라이언트에서 재확인 시켜준다.


<그림 3> 게시물 수정작업 결과


이번에는 입력처리에 대한 결과를 확인하자. 입력처리도 수정과 마찬가지 과정을 거친다. record.save()메소드를 실행하면 ExtJS는 모델객체 내부에 brd_seq값이 존재하지 않으므로 api의 create가 실행되고 입력작업을 서버에 요청한다. 이후 요청이 성공하면  입력된 데이터를 다시 로드하여 폼패널에 세팅하고 게시판리스트에 신규 데이터를 추가하도록 하였다.


<그림4> 게시물 입력작업 결과


마지막으로 삭제 기능에 대해 설명하자. 삭제 기능 또한 모델클래스의 destroy메소드를 이용하여 처리하였다. destroy메소드는 아래 <그림 5>와 같이 모델 api설정 중 destroy가 실행되고 모델객체의 데이터가 서버에 전송되므로 서버에서는 brd_seq값으로 해당 테이블의 데이터를 삭제 처리하면 되겠다. destory메소드가 호출되면 데이터베이스에 실제 데이터가 정상적으로 삭제 처리되면 이후 좌측 게시물리스트 그리드에서도 해당 데이터가 삭제되는 것을 확인 할 수 있다.


<그림 5> 삭제 처리 시 Ajax전송 처리 결과


정리하며

지금까지 총 5회에 걸쳐 ExtJS를 이용하여 멀티 게시판을 구현해 보았다. 강좌에서 핵심적으로 봐야 할 부분은 UI를 확장한 어떤 클래스도 메뉴를 통해 중앙영역에 추가할 수 있는 구조와, 동일한 애플리케이션 클래스를 여러 개 실행하여 각기 다른 데이터를 다루도록 하는 것이다.

우리는 이러한 모든 것을 클래스화 하였기에 가능하다는 것을 알게 되었다. 클래스의 개념은 ExtJS가 가지는 가장 중요한 개념이고 복잡하고 큰 애플리케이션을 자바스크립트로 개발할 수 있게되었다. 



Posted by 베니94
자바스크립트/Ext JS2013. 11. 20. 15:23

이번 시간에는 게시판 애플리케이션의 UI를 구성하고 데이터를 채울 준비를 해본다. 간략히 게시판 애플리케이션에 대해 설명하면, 좌우로 나눠진 게시판 애플리케이션에서 좌측에 게시판 리스트를 그리드로 구성하고 해당 그리드에서 게시물을 클릭하면 우측 패널에 해당 글의 내용이 보여지는 구조다.

 

---------------------------------------

연재순서

1회 | 2013. 9 | ExtJS4 클래스 시스템의 이해

2회 | 2013. 10 | ExtJS4 MVC 아키텍처의 이해

3회 | 2013. 11 | 시스템 뼈대 구현하기

4회 | 2013. 12 | ExtJS4 실전코딩 : 멀티 게시판 애플리케이션 구현 (1)

5회 | 2014. 1 | ExtJS4 실전코딩 : 멀티 게시판 애플리케이션 구현 (2)


이전시간에 우리는 애플리케이션의 뼈대를 구성하고 UI를 가지고 있는 클래스라면 어떤 형태로도 보여지는 구조를 구현해 보았다. 이런 구조의 뼈대는 게시판과 같이 동일한 구조를 반복적으로 표현하는 애플리케이션뿐만 아니라 전혀 다른 여러가지 형태의 애플리케이션 클래스를 실행할 수 있는 좋은 구조다.

이제 게시판 애플리케이션의 UI를 구성해 보자. <표 1>은 게시판을 구성할 클래스들에 대한 설명이다.

 타입

 클래스명

 기능

비고 

 모델

 MyMvc.model.Board

 게시판 데이터 모델 클래스

 

 뷰

MyMvc.view.BoardMain 

 한 개의 게시판 대표 클래스

 

MyMvc.view.BoardList 

 게시판 리스트를 표현할 그리드 클래스

 

 MyMvc.view.BoardView

 한 개의 게시물의 내용을 보여줄 클래스

 

 컨트롤러

 MyMvc.controller.Board

 게시판 관리 컨트롤러 클래스

 

 스토어

MyMvc.store.Boards 

 게시판 데이터 집합

 

<표 1> 클래스 정의

 

UI 클래스를 구현하기 앞서 <리스트 1>과 같이 실행할 프로그램의 클래스명을 수정하도록 하자. 게시판 애플리케이션의 메인 클래스는 MyMvc.view.board.BoardMain이다. 최초 애플리케이션이 실행되면 첫번째 데이터가 선택되고 BoardMain 클래스가 실행되도록 한다는 내용이다.

 

<리스트 1> /json/programlist.json

{"entitys": [
    {
        "pgm_syscd": "F001",
        "pgm_class": "MyMvc.view.board.BoardMain",
        "pgm_icon": "grid",
        "pgm_nm": "ExtJS",
        "brd_number": "001"
    },
    {
        "pgm_syscd": "F001",
        "pgm_class": "MyMvc.view.board.BoardMain",
        "pgm_icon": "grid",
        "pgm_nm": "Sencha Touch",
        "brd_number": "002"
    },
    {
        "pgm_syscd": "F002",
        "pgm_class": "MyMvc.view.board.BoardMain",
        "pgm_icon": "grid",
        "pgm_nm": "구인_구직",
        "brd_number": "003"
    }
],
    "errMsg": "", "errTitle": "검색결과", "message": "", "success": true, "totalCount": "2"}

 

app/view 폴더 아래에 board 폴더를 생성하고 <리스트 2>와 같이 BoardMain.js 클래스를 구현하자. 이 클래스는 board 레이아웃을 사용해 좌측에 그리드와 우측에 패널을 가지고 있고, 게시판 아이디로 사용할 brd_number를 config 정보로 가지고 있다.

 

<리스트 2> BoardMain 클래스

Ext.define('MyMvc.view.board.BoardMain', {
    extend: 'Ext.container.Container',
    alias: 'widget.boardmain',
    layout : 'border',

    config: {
      brd_number : ''
    },
    initComponent: function () {
        var me = this;
        me.initConfig();

        Ext.apply(me, {
            items: [
                {
                    region: 'center',
                    xtype: 'boardlist',
                    brd_category_cd : me.getBrd_number()
                },
                {
                    xtype: 'boardview',
                    title: '게시물',
                    width : 500,
                    region: 'east',
                    collapsible: true,
                    collapsed : true,
                    split: true
                }
            ]
        });
        me.callParent(arguments);
    }
});

좌측 그리드 클래스를 작성하자. 클래스명은 BoardList이고 Grid 클래스를 상속받으며 boarlist이라는 위젯명을 사용한다. <리스트 3>에서 스토어는 Ext.create를 사용해 매번 생성하도록 했다. 컨트롤러에 스토어를 등록하고 store: 'Board'로 사용할 수도 있겠으나, 이렇게 할 경우 모든 게시판이 하나의 스토어를 공유해 사용하게 되므로 각 게시판별 게시물을 읽어오기 어려워진다.

 

<리스트 3> BoardList 클래스 코드

Ext.define('MyMvc.view.board.BoardList', {
    extend: 'Ext.grid.Panel',
    alias: 'widget.boardlist',
    columnLines: true,
    initComponent: function () {
        var me = this;
        this.store = Ext.create('MyMvc.store.Boards');
        Ext.apply(this, {
            tbar: [
                {
                    xtype: 'button',
                    text: '글쓰기 ',
                    action: 'board.create'
                }
            ],
            columns: [
                {
                    text: '게시글번호 ',
                    width: 80,
                    dataIndex: 'brd_seq',
                    field: {
                        allowBlank: false
                    }
                },
                {
                    text: '제목 ',
                    flex: 1,
                    dataIndex: 'brd_title',
                    field: {
                        allowBlank: false
                    }
                },
                {
                    text: '입력자 ',
                    width: 70,
                    dataIndex: 'brd_input_user_nm',
                    field: {
                        allowBlank: false
                    }
                },
                {
                    text: 'Input Date ',
                    width: 150,
                    align: 'center',
                    dataIndex: 'brd_input_date',
                    field: {
                        allowBlank: false
                    }
                },
                {
                    text: 'Read Cnt ',
                    width: 70,
                    dataIndex: 'brd_read_cnt',
                    field: {
                        allowBlank: false
                    }
                }
            ],
            dockedItems: [
                {
                    xtype: 'pagingtoolbar',
                    store: this.store,
                    dock: 'bottom',
                    displayInfo: true
                }
            ]
        });

        me.callParent(arguments);
    }
});

 

우측 패널 클래스를 작성한다. 우측 패널 클래스는 내부에 게시물 내용을 확인하는 폼 패널과 함께 조작을 위한 버튼을 가지고 있다.

 

<리스트 4> BoardView 클래스 코드

Ext.define('MyMvc.view.board.BoardView', {
    extend: 'Ext.panel.Panel',
    alias: 'widget.boardview',
    bodyPadding: 5,
    autoScroll: true,
    collapsed: true,

    initComponent: function () {
        var me = this;
        Ext.apply(this, {
            items: [
                {
                    xtype: 'form',
                    bodyPadding: 10,
                    itemId: 'board.form',
                    layout: 'anchor',
                    defaults: {
                        anchor: '100%'
                    },
                    buttons: [
                        {
                            xtype: 'statusbar',
                            defaultText: ' ',
                            flex: 1
                        },
                        {
                            action: 'board.save',
                            text: 'Save',
                            scope: me,
                            handler: function () {
                                this.onSave();
                            }
                        },
                        {
                            action: 'board.remove',
                            text: 'Del ',
                            listeners: {
                            }
                        } ,
                        {
                            action: 'board.close',
                            text: 'Close ',
                            listeners: {
                            }
                        }
                    ],

                    items: [
                        {    xtype: 'hiddenfield', name: 'brd_seq'    },
                        {    xtype: 'hiddenfield', name: 'brd_category_cd'    },
                        {
                            xtype: 'textfield',
                            name: 'brd_title',
                            allowBlank: false,
                            fieldLabel: '제 목',
                            emptyText: 'Title'
                        },
                        {
                            xtype: 'textareafield',
                            grow: true,
                            growMin: 150,
                            growMax: 300,
                            allowBlank: false,
                            name: 'brd_content',
                            fieldLabel: '내 용 '
                        },
                        {
                            xtype: 'textfield',
                            name: 'brd_input_user_nm',
                            allowBlank: false,
                            fieldLabel: '등 록 자'
                        },
                        {
                            xtype: 'displayfield',
                            fieldLabel: '입 력 일',
                            name: 'brd_input_date'
                        },
                        {
                            xtype: 'displayfield',
                            fieldLabel: '수 정 일',
                            name: 'brd_update_date'
                        }
                    ]
                }
            ]
        });
        this.callParent(arguments);
    }
});

 

좌측 그리드에서 사용할 Store 클래스를 작성하자. Store 클래스는 CRUD가 모두 가능하도록 플록시를 설정하자. actionMethod의 경우 서버쪽 코드에 따라 변경될 수 있으므로 필요 시 수정하도록 한다.

 

<리스트 5> Boards Store 클래스 코드

Ext.define('MyMvc.store.Boards', {
    extend: 'Ext.data.Store',
    autoLoad: false,
    model: 'MyMvc.model.Board',
    proxy: {
        type: 'ajax',
        actionMethods: {
            read: 'GET',
            create: 'POST',
            update: 'PUT',
            destroy: 'DELETE'
        },
        api: {
            read: '/board.do?boards',
            create: '/board.do?create',
            update: '/board.do?update',
            destroy: '/board.do?remove'
        },
        reader: {
            type: 'json',
            root: 'entitys',
            totalProperty: 'totalCount',
            messageProperty: 'message'
        },
        listeners: {
            exception: function (proxy, response, operation) {

            }
        }
    }
});

 

<리스트 5>의 Store 클래스에서 사용하는 모델 클래스를 작성하자. 이 클래스 또한 자체적으로 CRUD가 모두 가능하도록 <리스트 5>의 플록시 설정을 그대로 사용하도록 하자. 일반적으로 스토어에 설정된 플록시 정보는 게시물의 리스트를 저장하는 용도로 사용하고, 모델에 설정된 플록시 정보는 폼 정보와 연관지어 입력, 수정, 삭제 등을 수행하는 데 사용한다.

 

<리스트 6> Board 모델 클래스 코드

Ext.define('MyMvc.model.Board', {
    extend: 'Ext.data.Model',
    idProperty: 'brd_seq',
    fields: [
        {name: 'brd_seq', type: 'int'},
        'brd_title',
        'brd_content',
        'brd_category_cd',
        'brd_input_user_nm',
        'brd_input_date',
        {name: 'brd_read_cnt', type: 'int'},
        'brd_update_date'
    ],
    proxy: {
        type: 'ajax',
        actionMethods: {
            read: 'GET',
            create: 'POST',
            update: 'PUT',
            destroy: 'DELETE'
        },
        api: {
            read: '/board.do?boards',
            create: '/board.do?create',
            update: '/board.do?update',
            destroy: '/board.do?remove'
        },
        reader: {
            type: 'json',
            root: 'entitys'
        }
    }
});

게시판 클래스들이 모두 동적으로 로딩될 수 있도록 컨트롤러 클래스를 만들고 앞서 작성한 모든 클래스를 등록하자. 이를 구체적으로 구현하는 방법에 대해서는 다음 시간에 설명하기로 한다.

 

<리스트 7> Board 컨트롤러 클래스 코드

Ext.define('MyMvc.controller.Board', {
    extend : 'Ext.app.Controller',

    views : [ 'board.BoardMain', 'board.BoardList', 'board.BoardView' ],
    models: ['Board'],
    stores : ['Boards'],

    init : function(app) {
        this.control({
        });
    }
});

 

이제 Board 컨트롤러 클래스를 application.js 클래스에 등록해 전체 애플리케이션이 Board컨트롤러 클래스를 인식하도록 변경해준다.

 

<리스트 8> application.js 내부 컨트롤러 추가

Ext.define('MyMvc.Application', {
	name : 'MyMvc',

	extend : 'Ext.app.Application',

	views : [
	// TODO: add views here
	],

	controllers : [ 'Frame' , 'Board'],

	stores : [
	// TODO: add stores here
	]
});

 

애플리케이션을 재실행하면 <그림 1>과 같이 게시판 리스트와 게시물 보기 화면을 확인할 수 있다.

<그림 1> 게시판 애플리케이션 UI 구성

 

좌측 메뉴에서 선택된 게시판은 BoardMain 클래스에 의해 우측 중앙 영역에 보여지게 된다. 표면적으로는 각 메뉴에서 선택된 게시판은 동일하나 각각의 게시판 구분은 /json/programlist.json의 brd_number에 의해 달라지게 된다.

이는 <리스트 9>와 같이 BoardMain 클래스를 수정하면, 좌측 게시판 리스트를 클릭할 때 마다 게시판 아이디가 다르다는 것을 확인할 수 있다. 이 게시판 아이디에 의해 데이터베이스의 게시물을 가져오거나 입력할 수 있다.

 

<리스트 9> BoardMain 클래스 수정

Ext.define('MyMvc.view.board.BoardMain', {
    extend: 'Ext.container.Container',
    alias: 'widget.boardmain',
    layout : 'border',

    config: {
      brd_number : ''
    },
    initComponent: function () {
        var me = this;
        me.initConfig();

        .. 중략 ..
        me.callParent(arguments);
        me.on('render', function(){
           console.log("현재 게시판의 게시판 아이디는 " + me.getBrd_number());
        });
    }
})

 

<그림 2> 게시판 아이디 확인 결과

 

정리하며

이번 시간에는 게시판 애플리케이션의 UI를 구현해 봤다. 동일한 게시판 프로그램을 개발하고 해당 프로그램이 각기 다른 데이터를 표현하도록 하는 것이 핵심이다. 다음 시간에는 게시판의 CRUD 기능을 구현해보겠다. 지면 관계상 생략된 내용은 필자의 블로그를 통해 확인하자.

Posted by 베니94
자바스크립트/Ext JS2013. 10. 13. 15:09

시스템 뼈대 구현하기

 

이번 강좌부터 3회에 걸쳐 ExtJS MVC를 통해 게시판을 구현해보기로 하자. 금번 강좌에서는 게시판이 들어갈 애플리케이션의 뼈대를 만들고 이를 통해 MVC를 적용하는 방법을 같이 배우게 된다. 굳이 게시판을 만드는 이유는 웹애플리케이션에서 게시판이 가지고 있는 의미가 CRUD성격을 모두 지니고 있고 모든 웹개발자들이 만들어봐야 하는 기본 애플리케이션으로 주제에 대한 공통적인 이슈가 모두 공유되어 있다고 믿기 때문이다.

이전 시간에 우리는 Sencha CMD를 통해 애플리케이션을 생성하고 빌드하는 방법을 배웠다. 또한 내장된 웹서버를 이용 개발된 애플리케이션을 실행해보았다. 우리가 만들 게시판은 서버코드가 필요하고 데이터베이스가 필요하므로 개발툴을 에디터에서 이클립스로 바꿀 필요가 있다. 이번 강좌에서는 이전시간에 만들었던 MyMvc애플리케이션을 이클립스의 Dynamic Web Project와 함께 묶어 차후에 있을 서버코드 구현에 대비하도록 하자.

우선 그림1과 같이 이클립스 Dynamic Web Project를 생성하고 프로젝트 설정에서 Text file encoding을 EUC_KR에서 UTF-8로 변경하자.

<그림1> 이클립스 프로젝트 설정

 

필자는 MasoExtJSLecture라는 이름의 프로젝트를 생성하였다. 이제 Sencha CMD를 통해 Extjs 애플리케이션을 생성하자. 이클립스 프로젝트와 맞추려면 WebContent폴더에 ExtJS애플리케이션을 생성하도록 하자. 애플리케이션 생성 코드는 아래와 같다.

sencha -sdk C:\Sencha\ext-4.2.1.883 generate app MyMvc C:\이클립스프로젝트\WebContent

 

정상적으로 애플리케이션이 생성되면 이클립스에서 해당 프로젝트를 refresh하고 이클립스 톰캣플러그인으로 웹서버를 실행한다. 브라우저를 통해 실행하면 이전 강의에서 보았듯이 기본 어플리케이션이 그림2와 같이 실행되는 것을 볼 수 있다.


<그림2> Sencha CMD를 통해 생성된 기본 애플리케이션

 

게시판 애플리케이션은 <그림3>과 같이 상단영역과 좌측 메뉴영역 우측 중앙의 애플리케이션영역으로 나누어 진다.


<그림3> 애플리케이션인 MockUp

 

표1은 뼈대를 이룰 클래스들에 대한 설명이다.

 

클래스명

기능

Model

MyMvc.model.Program

게시판 또는 우측패널에 표시될 애플리케이션의 속성

View

MyMvc.view.WestMenuPanel

좌측에 표시할 게시판그룹

MyMvc.view.WestMenuDataViewPanel

게시판그룹에 해당되는 게시판리스트

MyMvc.view.Header

애플리케이션 상단영역

Controller

MyMvc.controller.Frame

뼈대를 제어할 Controller 클래스

Store

MyMvc.store.Programs

좌측 메뉴에 표시 할 Store

<표1> 클래스 정의

 

MyMvc.view.Viewport.js를 구현하자. Viewport에서는 애플리케이션의 기본 구조를 구성한다.

<리스트1> MyMvc.view.Viewport.js

Ext.define('MyMvc.view.Viewport', {

extend : 'Ext.container.Viewport',

requires : [],

layout : 'border',

items : [ {

    region : 'north',

    xtype : 'container',

    html : '상단영역',

    height : 100,

    style : {

     borderColor : '#000000',

     borderStyle : 'solid',

     borderWidth : '1px'

    }

}, {

    region : 'center',

    xtype : 'tabpanel',

    title : '게시판 프로그램 영역',

    style : {

        borderColor : '#000000',

        borderStyle : 'solid',

        borderWidth : '1px'

    }

}, {

    region : 'west',

    xtype : 'container',

    html : '메뉴 영역',

    width : 200,

    style : {

        borderColor : '#000000',

        borderStyle : 'solid',

        borderWidth : '1px'

    }

} ]

});

 

브라우저를 통해 실행하면 좌측, 상단, 우측으로 영역이 나눠지는 것을 확인 할 것이다.

이제 상단 영역을 구현하자. 상단영역에 해당되는 클래스를 <리스트2>와 같이 코딩하고

Viewport.js의 상단영역을 <리스트2>의 Header클래스로 대체하자.

 

<리스트2> 상단 Header 클래스 및 Viewport클래스 수정

Ext.define('MyMvc.view.Header', {

extend: 'Ext.container.Container',

alias: 'widget.mymvcheader',

id: 'app-header',

height: 52,

layout: {

type: 'hbox',

align: 'middle'

},

initComponent: function() {

this.items = [{

xtype: 'component',

id: 'app-header-title',

html: 'ExtJS MVC를 활용한 게시판 구현하기',

flex: 1

}];

this.callParent();

}

});

// Viewport클래스 기존 상단영역 수정

Ext.define('MyMvc.view.Viewport', {

    extend : 'Ext.container.Viewport',

    requires : [ 'Ext.layout.container.Border', 'MyMvc.view.Header',

            'Ext.tab.Panel' ],

    layout : 'border',

    items : [ {

        region : 'north',

        xtype : 'mymvcheader' // 기존 container

 

상단영역에서 필요한 css를 index.html에 추가하자.

 

<리스트3> 상단영역에서 사용할 index.html 내부 css

<link rel="stylesheet" href="bootstrap.css">

<style type="text/css">

#app-header {

background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #8fc33a), color-stop(100%, #739b2e));

background-image: -webkit-linear-gradient(top, #8fc33a, #739b2e);

background-image: -moz-linear-gradient(top, #8fc33a, #739b2e);             background-image: -o-linear-gradient(top, #8fc33a, #739b2e);

background-image: linear-gradient(top, #8fc33a, #739b2e);

border-bottom: 1px solid #567422;

}

#app-header-title {

padding: 15px 10px 10px 31px;

color: white;

font-size: 18px;

font-weight: bold;

text-shadow: 0 1px 0 #4e691f;

}

</style>

<script src="ext/ext-dev.js"></script>

 

실행하면 <그림4>와 같은 결과를 확인할 수 있다.

 

<그림4> 상단영역을 구현한 결과

 

좌측 메뉴영역을 구현하자. 좌측 메뉴는 2depth로 이루어진다. 게시판그룹을 accordion레이아웃으로 노출하고 클릭 시 세부게시판 리스트를 출력하도록 한다.

게시판그룹을 나타낼 WestMenuPanel클래스를 <리스트4>와 같이 정의하자.

 

<리스트4> 게시판 그룹 클래스

Ext.define('MyMvc.view.WestMenuPanel',{

extend : 'Ext.panel.Panel',

alias : 'widget.westmenupanel',

layout:'accordion',    // ① 레이아웃 설정

collapsible: true,

split:true,

title : 'Forum List',

initComponent: function() {

var me = this;

this.callParent(arguments);

this.on('render', this.setSubMenu, this); // ② render이벤트 발생시 함수 호출

},

// ③ 스토어 호출 후 게시판 그룹을 추가.

setSubMenu: function(){

    var me = this;

    var store = Ext.create('MyMvc.store.Systems');

    store.load(function(record, b, c){                

     store.each(function(rec){

        me.add({

          xtype:'panel',

          title:rec.get('pgm_sysnm'),

          pgm_syscd:rec.get('pgm_syscd'),

          iconCls:rec.get('pgm_sysicon')

         });

     });

    });

}

});

 

주요코드를 설명하자. ①은 게시판그룹을 추가할 때 accordion형태로 추가되도록 레이아웃을 설정한다. ②는 현 클래스에서 render이벤트가 발생시 게시판그룹을 추가할 메소드를 호출한다. ③함수는 게시판그룹 store를 호출하고 자식으로 추가한다.

 

게시판 그룹에 사용할 Model과 Store를 정의하자.

 

<리스트5> MyMvc.model.Program.js

Ext.define('MyMvc.model.Program', {

extend : 'Ext.data.Model',

fields : [ 'pgm_id', 'pgm_nm', 'pgm_syscd', 'pgm_sysnm', 'pgm_class',

    'pgm_icon', 'pgm_sysicon', 'group_id', 'group_nm',

    'group_status_cd', 'group_status_nm', 'group_pgm_status_nm',

    'title', 'brd_number']

});

 

<리스트6> MyMvc.store.Systems.js

Ext.define('MyMvc.store.Systems', {

extend: 'Ext.data.Store',    // 당연히 store상속

autoLoad : false,        // 자동 로드는 꺼놓자.

model: 'MyMvc.model.Program',    // 모델 세팅

proxy: {

type: 'ajax',

// json폴더를 루트에 만들고 systemlist.json이라는 파일를 통해

// 시스템 리스트를 받을 수 있다.

url: '/json/systemlist.json',    

reader: {

    type: 'json',

    root: 'entitys',

    totalProperty: 'totalCount',

    messageProperty: 'message'

},

listeners: {

exception: function(proxy, response, operation){}

    }

}

});

 

<리스트7>/json/systemlist.json

{

    "entitys":[{

        "pgm_syscd":"F001",

        "pgm_sysicon":"grid",

        "pgm_sysnm":"Sencha",

        "title":"Sencha"

    }, {

        "pgm_syscd":"F002",

        "pgm_sysicon":"grid",

        "pgm_sysnm":"사는애기 ",

        "title":"사는애기"

    }, {

        "pgm_syscd":"F003",

        "pgm_sysicon":"grid",

        "pgm_sysnm":"Front End",

        "title":"Front End"

    }],

    "errMsg":"",

    "errTitle":"검색결과",

    "message":"",

    "success":true,

    "totalCount":""

}

 

Viewport.js를 수정하여 <리스트5~7> 코드가 적용되도록 하자.

<리스트8>Viewport.js수정

…중략..

requires : [ 'Ext.layout.container.Border', 'MyMvc.view.Header',

            'Ext.tab.Panel', 'MyMvc.view.WestMenuPanel' ],

..중략..

}, {

    region : 'west',

    xtype : 'westmenupanel',

    width : 200

} ]

결과를 실행하면 <그림5>와 같이 게시판그룹이 출력된다.

<그림5> 좌측 게시판 그룹 구현

 

<그림5>는 accordion레이아웃을 이용하여 panel에 panel을 배치한 것이다. 이제 각 게시판그룹 이하에 중앙패널에 표시할 게시판리스트가 나오도록 구현해 보자.

 

<리스트9> Controller를 Application클래스에 등록

Ext.define('MyMvc.Application', {

    ..중략..

    controllers : [ 'Frame' ],

    ..중략..

});

 

<리스트10> MyMvc.controller.Frame.js 생성

Ext.define('MyMvc.controller.Frame', {

extend: 'Ext.app.Controller',

views: ['MyMvc.view.WestMenuDataViewPanel'],

stores : ['Programs'],

init : function(app) {

    this.control({

    });

}

});

 

<리스트11> MyMvc.store.Programs.js 생성

Ext.define('MyMvc.store.Programs', {

extend : 'Ext.data.Store',

autoLoad : true,

model : 'MyMvc.model.Program',

proxy : {

    type : 'ajax',

    url : '/json/programlist.json',

    reader : {

     type : 'json',

     root : 'entitys',

     totalProperty : 'totalCount',

     messageProperty : 'message'

    },

    listeners : {

     exception : function(proxy, response, operation) {

     }

    }

},

filterUsersByDepartment : function(pgm_syscd) {

    this.clearFilter();

    this.filter([ {

        property : 'pgm_syscd',

        value : pgm_syscd

    } ]);

},

refresh : function() {

    this.clearFilter();

}

});

 

<리스트12> /json/programlist.json

{"entitys":[

{    "pgm_syscd":"F001","pgm_class":"Ext.panel.Panel","pgm_icon":"grid","pgm_nm":"ExtJS","brd_number":"001" },

{    "pgm_syscd":"F001","pgm_class":"Ext.panel.Panel","pgm_icon":"grid","pgm_nm":"Sencha Touch","brd_number":"002" },

{    "pgm_syscd":"F002","pgm_class":"Ext.panel.Panel","pgm_icon":"grid","pgm_nm":"구인_구직","brd_number":"003" },

{    "pgm_syscd":"F002","pgm_class":"Ext.panel.Panel","pgm_icon":"grid","pgm_nm":"요즘","brd_number":"004" },

{    "pgm_syscd":"F003","pgm_class":"Ext.panel.Panel","pgm_icon":"grid","pgm_nm":"jQuery","brd_number":"005" },

{    "pgm_syscd":"F003","pgm_class":"Ext.panel.Panel","pgm_icon":"grid","pgm_nm":"Javascript","brd_number":"006" }

],

"errMsg":"","errTitle":"검색결과","message":"","success":true,"totalCount":"2"}

 

<리스트13> MyMvc.view.WestMenuDataViewPanel.js

Ext.define('MyMvc.view.WestMenuDataViewPanel', {

extend : 'Ext.panel.Panel',

alias : 'widget.menuboard',

animCollapse : true,

collapsible : true,

collapsed : true,

useArrows : true,

rootVisible : false,

multiSelect : false,

header : {

    toolFirst : true

},

initComponent : function() {

    var me = this;

    this.initConfig(); // 초기화 실행

    Ext.apply(this, {

     items: [{

        xtype: 'dataview',

        store : 'Programs',

        trackOver: true,

        cls: 'feed-list',

        itemSelector: '.feed-list-item',

        overItemCls: 'feed-list-item-hover',

        tpl: '<tpl for="."><div class="feed-list-item {pgm_icon}">{pgm_nm}</div></tpl><p>'

     }]

    });

    this.callParent(arguments);

}

});

 

<리스트13>이 호출 될 수 있도록 게시판그룹을 표현한 WestMenuPanel의 setSubMenu 메소드를 아래와 같이 수정하자.

변경전 : xtype: 'panel' -> 변경후 xtype : 'menuboard'

 

실행 결과를 확인하면 <그림6>와 같이 좌측 게시판 그룹에 게시판 리스트가 추가된 것을 볼 수 있다.

 

<그림6> 좌측 메뉴에 게시판 리스트 추가.

 

각각의 게시판그룹을 클릭해보자. 우리가 구현하고자 하는 것은 게시판 그룹을 클릭 후 해당 게시판 그룹코드(pgm_syscd)와 동일한 코드를 가지고 있는 게시판만 출력되도록 하는 것이다. 현재는 모든 게시판이 보여지고 있으므로 해당그룹에 맞는 게시판을 필터링 하도록 구현해보자.

<리스트14>와 같이 Frame Controller를 수정하여 게시판그룹 패널과 내부 게시판리스트 패널의 이벤트를 리스닝 할 수 있도록 하자.

 

<리스트14> Frame Controller수정

Ext.define('MyMvc.controller.Frame', {

extend : 'Ext.app.Controller',

views : [ 'MyMvc.view.WestMenuDataViewPanel', 'MyMvc.view.WestMenuPanel' ],

stores : [ 'Programs' ],

init : function(app) {

    this.control({

     'westmenupanel > menuboard' : {    // ① 이벤트 리스닝

        afterrender : function(panel){    // ② 최초 발생

         panel.firstSelectDataView();

        },

        expand : function(panel){

         panel.onItemClicked();    // ③ 패널이 열릴때 발생

        }

     }

    });

}

});

 

<리스트14>를 자세히 설명하자. ①은 Controller의 주요 역할인 View의 이벤트를 리스닝하고 있다. westmenupanel(게시판그룹패널) 하위에 존재하는 menuboard(게시판리스트패널)에서 발생하는 이벤트를 리스닝한다.(westmenupane과 menuboard는 위젯명임을 명심하자.)

②는 render가 끝난 후 호출된다. 인자로 menuboard패널이 넘어온다. afterrender이벤트가 발생하면 firstSelectDataView()함수를 호출한다. ③은 패널이 expand(열릴 때) 될 때 onItemClicked()함수를 호출한다.

②, ③에 해당되는 WestMenuDataViewPanel클래스의 메소드를 <리스트15>와 같이 구현하자.

 

<리스트15> WestMenuDataViewPanel의 로직구현

Ext.define('MyMvc.view.WestMenuDataViewPanel', {

.. 중략 ..

// dataview상에 출력된 리스트 중에 맨처음 프로그램이

//          선택되어지도록 한다.

firstSelectDataView : function() {

    var me = this;

    if (me.collapsed)

     return;

 

    var store = this.onItemClicked();

    if (store) {

     var task = new Ext.util.DelayedTask(function() {

        me.down('dataview').getSelectionModel().select(store.getAt(0));

     });

     task.delay(1000);

    }

},

// 시스템 패널을 클릭 할 때 마다 expand이벤트가 호출 된다.

// 이 때 클릭되어 expand된 패널 위에 하위 프로그램을 출력하면 된다.

onItemClicked : function() {

    var me = this;

    if (me.collapsed)

     return // 패널이 접히지 않은 패널을 찾는다.

            

    me.down('dataview').store

    .filterUsersByDepartment(this.pgm_syscd);

    return me.down('dataview').store;

}

});

 

수정된 코드를 실행하고 게시판 그룹을 클릭하면 해당 그룹에 맞는 게시판 리스트가 필터링 되어 보여지는 것을 볼 수 있다. 이는 WestMenuDataViewPanel에 구현된 두 개의 메소드에 의해 작동된다.

firstSelectDataView() 메소드는 게시판리스트를 담고 있는 store의 첫번째 데이터가 select되어지도록 하는 코드이다. onItemClicked() 메소드는 게시판 리스트를 출력하는 dataview가 가지는 store를 통해 해당 게시판 그룹에 맞는 게시판을 필터링 하는 역할을 한다.

메뉴에 들어갈 css를 index.html에 <리스트16>과 같이 추가하고 실행하자.

 

<리스트16> 좌측 메뉴용 css

.feed-list {

padding: 0 3px 0 3px;

}

.feed-list-item {

margin-top: 3px;

font-size: 11px;

line-height: 20px;

cursor: pointer;

border: 1px solid #fff;

}

.feed-list .x-item-selected {

font-weight: bold;

color: #15428B;

background-color: #DFE8F6;

border: 1px dotted #A3BAE9;

}

 

<그림7은> 게시판 그룹별 게시판리스트를 필터링하여 보여주고 있다.

 

<그림7> 게시판 그룹별 필터링 적용 후

 

이번 강좌의 마지막 작업이자 가장 핵심적인 작업이 남아있다. 좌측 메뉴를 구현했고 최초 로딩 시 첫번째 게시판 그룹의 첫번째 게시판이 자동으로 선택되어지게 하였다. 이때 선택되어진 또는 사용자가 선택할 경우 우측에 해당 게시판 애플리케이션을 출력되도록 해야 한다.

 

<리스트17>과 같이 Frame Controller에 코드를 추가하자.

Ext.define('MyMvc.controller.Frame', {

..중략..

Init : function(app){

this.control({

.. 중략 ..

     'menuboard > dataview' : { // 게시판 리스트의 dataview에서 발생한 이벤트

     select : this.onProgramSelect

     }

});

},

onProgramSelect : function(dataview, record) {

    var pgm_class = record.get('pgm_class'), pgm_nm = record.get('pgm_nm');

    var centerpanel = Ext.ComponentQuery

                .query('viewport container[region="center"]')[0];

    var tab = centerpanel.down('[tabuniqid=' + pgm_class + pgm_nm + ']');

    if (!tab) {

     tab = Ext.create(pgm_class, {

        brd_number : record.get('brd_number')

     });

     tab.title = pgm_nm+ "("+record.get('brd_number')+")";

     tab.tabuniqid = pgm_class + pgm_nm;

     centerpanel.add(tab);

    }

    centerpanel.setActiveTab(tab);

}

});

 

<리스트17>에 대해 자세히 설명하겠다. Frame Controller에 새로운 이벤트를 리스닝하도록 코드를 추가하였다. 이 코드는 게시판리스트 패널(WestMenuDataViewPanel) 내부의 dataview에서 select이벤트가 발생하면 onProgramSelect()메소드를 호출하도록 했다. onProgramSelect()메소드는 select이벤트 발생시 전달받은 게시판정보를 이용해 애플리케이션 중앙의 탭패널에 자식으로 게시판 클래스 인스턴스를 생성하여 추가하는 로직이다.

programlist.json파일을 상기시켜보자 아래와 같이 pgm_class에 일괄적으로 Ext.panel.Panel클래스가 들어 가있다. 이것은 아직 구현되지 않은 게시판 클래스 대신 Panel클래스를 넣어둔 것이다. 이러한 형태는 이후 게시판 뿐만 아니라 새로 개발된 어떤 클래스도 중앙패널에 추가할 수 있다는 것을 의미한다.

"pgm_syscd":"F001","pgm_class":"Ext.panel.Panel","pgm_icon":"grid","pgm_nm":"ExtJS","brd_number":"001" },

 

최종 실행하면 <그림8>과 같은 결과를 볼 수 있을 것이다.

<그림8> 최종 구현된 뼈대의 실행결과

 

정리하며

이번 시간에는 ExtJS의 MVC를 적용하여 애플리케이션의 뼈대를 구현해보았다. 이러한 뼈대는 향후 게시판 뿐만 아니라 어떤 어플리케이션도 배치할 수 있는 좋은 구조라 할 수 있겠다.

Posted by 베니94