자바스크립트/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