본문 바로가기
JavaScript & TypeScript

IE 7,8에서 모던하게 개발하기 #5 - backbone.js (3)

by 향로 (기억보단 기록을) 2016. 10. 18.
반응형

현재까지 관련된 포스팅과 코드는 아래를 참고!

IE 7,8에서 모던하게 개발하기 #1 (npm/grunt)

IE 7,8에서 모던하게 개발하기 #2 (require.js)

IE 7,8에서 모던하게 개발하기 #3 (backbone.js -1)

IE 7,8에서 모던하게 개발하기 #4 (backbone.js -2)

IE 7,8에서 모던하게 개발하기 #5 (backbone.js -3)

IE 7,8에서 모던하게 개발하기 #6 (배포환경 구축)

IE 7,8에서 모던하게 개발하기 #7 (Handlebars.js 적용)

프로젝트 및 코드

backbone.js 사용 (3)

이번 시간은 backbone의 꽃! rest api와의 연동을 진행할 예정이다.
backbone의 경우 특히나 구분 없이, 중복적으로 사용하는 Ajax 처리에 큰 강점을 가지고 있기에 (조규태님의 발표자료 참고) jquery만으로 프론트엔드를 진행하고 있다면 아주 좋은 시간이 될 것 같다.

자 그럼 본격적으로 내용을 진행하자.
오늘의 예제는 회원 관리 리스트이다. 기본적인 회원 전체 리스트를 조회하고, 추가를 할 수 있다.
해당 회원 정보는 실시간으로 서버와 통신하며 반영되어야 한다는 조건이 있다.
실제 DB까지 구축할 필요는 없으니 내부 변수로 회원 리스트를 선언하는 방식으로 진행하겠다.

제일 먼저 서버 코드를 수정해보자.

// Application.java
@SpringBootApplication
@Controller
public class Application {

    private static List<Member> members = new ArrayList<>();

    public static void main(String[] args) {
    //DB를 대신해서 사용
        members.add(new Member(0, "jojoldu", "jojoldu@gmail.com"));
        members.add(new Member(1, "github", "github@github.com"));
        members.add(new Member(2, "okky", "okky@okky.com"));

        SpringApplication.run(Application.class, args);
    }

    //@RequestMapping(value = "/", method = RequestMethod.GET)가 GetMapping("/") 가 됨
    @GetMapping("/")
    public String index(){
        return "index";
    }

    @GetMapping("/member")
    @ResponseBody
    public List<Member> getMembers() {
        return members;
    }

    @PostMapping("/member")
    @ResponseBody
    public boolean addMember(@RequestBody Member member) {
        member.setIdx(members.size());
        members.add(member);
        return true;
    }

}

// Member.java 생성
// lombok을 사용하였다. 참고 : https://blogs.idincu.com/dev/?p=17
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Member {

    @Getter @Setter
    private long idx;

    @Getter @Setter
    private String name;

    @Getter @Setter
    private String email;
}

Application.java에 Rest API 2개를 추가, DB를 대신할 members라는 ArrayList를 추가하였다.
여기서 사용하는 Member type는 앞으로 backbone에서 넘겨줄 데이터 타입으로 봐도 무방하다.
2개 코드가 추가되었으니 간단하게 기능 확인을 먼저 해보겠다. 프로젝트를 재실행하여 localhost:8080/member를 호출해보자.
3개의 member가 호출되는 것을 확인할 수 있다.



main 메소드에서 저장한 3개의 객체가 올바르게 출력되는 것을 확인할 수 있다.
자 그럼 간단하게 서버코드 작업은 끝이 났으니 프론트로 넘어가보자.
프론트는 크게 3가지를 수정할 예정이다. 


1. json2.js 라이브러리 추가 

 - IE7에서는 JSON 객체가 기본으로 포함되어 있지 않다. JSON.parse, JSON.stringify등을 사용해야 하므로 등록하자 

2. 회원 리스트를 보여줄 수 있도록 index.ftl 수정 

3. Member와 관련된 처리를 담당할 MemberView.js, MemberModel.js, MemberCollection.js 을 추가


먼저 Gruntfile.js를 수정하자.
json2 모듈을 받아야하므로 npm install json2 --save를 입력하자. 그러면 package.json에 json2가 추가된 것을 확인할 수 있다.



받은 json2 모듈을 grunt의 copy 대상으로 추가하자.

// Gruntfile.js
'use strict';
module.exports = function(grunt) {

    grunt.initConfig({
        pkg : grunt.file.readJSON('package.json'),

        //jquery와 requirejs, underscorejs, backbonejs, json2를 copy하도록 지정
        copy : {
            jquery : {
                src : 'node_modules/jquery.1/node_modules/jquery/dist/jquery.min.js',
                dest : 'src/main/resources/static/js/lib/jquery.min.js'
            },
            require : {
                src : 'node_modules/requirejs/require.js',
                dest : 'src/main/resources/static/js/lib/require.js'
            },
            underscore : {
                src : 'node_modules/backbone/node_modules/underscore/underscore-min.js',
                dest : 'src/main/resources/static/js/lib/underscore-min.js'
            },
            backbone : {
                src : 'node_modules/backbone/backbone-min.js',
                dest : 'src/main/resources/static/js/lib/backbone-min.js'
            },
            //json2 추가
            json2 : {
                src : 'node_modules/json2/lib/jSON2/static/json2.js',
                dest : 'src/main/resources/static/js/lib/json2.js'
            }
        }
    });

    // 플러그인 load
    grunt.loadNpmTasks('grunt-contrib-copy');

    // Default task(s) : 즉, grunt 명령어로 실행할 작업
    grunt.registerTask('default', ['copy']);
};

정상적으로 copy가 되는지 확인을 위해 터미널 혹은 CMD에서 npm start를 입력하자.



/js/lib 폴더에 옮겨진 것을 확인하였다. 이후에는 index.ftl에 json2.js를 추가해주기만 하면 된다.

이제 index.ftl을 수정하자.

//index.ftl에 아래 코드 추가

<h1>Member List</h1>
<div id="member">

    <div class="inputs">
        이름 : <input type="text" id="name">
        email : <input type="text" id="email">
        <button name="button" type="button" id="addMember">회원 추가 </button>
    </div>

    <h5>회원 Collection 리스트</h5>
    <ul id="memberList" class="list">
    </ul>

    <script id="collectionTemplate" type="text/template">
        <li><span><%= name %> : <%= email %> </span></li>
    </script>
</div>


<script type="text/javascript" src="/js/lib/json2.js"></script>

위 코드의 위치를 모르겠다면, Github 코드를 참고하자.
index.ftl에는 크게 어려운 것이 없다. 이전시간에 진행했던 것처럼 화면 템플릿용으로 text/template type의 코드가 추가 되고, 입력화면이 구성되었다.
자 다음으로는 Member와 관련된 backbone 모듈 생성이다.



각각의 코드는 아래와 같다.
MemberModel.js

define([],
function () {
    return Backbone.Model.extend({
        defaults: {
            idx : null,
            name : null,
            email : null
        }
    });
});

MemberModel.js는 회원 하나하나를 나타내는 모듈이 된다.
이전 시간에는 Model이 데이터를 전부 담당하였지만, 여기서는 Model을 데이터를 다루는 단위로서 사용하고 있다.
이유는 backbone에서는 단일 단위들의 집합체를 나타내기 위해 Collection이라는 타입을 지원하기 때문이다.
우리가 Java에서 Member class로 만들어진 인스턴스들의 집합을 관리하기 위해 List, Map 등의 Collection을 사용하는 것과 유사하다고 보면 된다.
backbone은 단일 객체는 Model로, Model의 집합은 Collection으로 관리한다고 생각하면 편할 것이다.
다음은 MemberCollection.js를 생성해보자

MemberCollection.js

define(['member/MemberModel'],
function (MemberModel) {
    return Backbone.Collection.extend({
        model : MemberModel,
        url : '/member'
    });
});

위 코드를 보면 MemberCollection.js는 자신이 어떤 타입을 관리할지 선언해야 한다.
여기선 MemberModel을 관리해야하므로 MemberModel을 model에 지정하였다.
그리고 추가로 url을 지정하였는데, 이게 collection의 코드를 간소화하는 핵심이다.
backbone의 collection은 기본적으로 RESTFul API를 기준으로 한다. 그래서 서버쪽이 RESTFul API를 지원한다는 가정하에 url 속성에 지정한 값 (여기서는 /member)으로 RequestMethod에 따라 아래와 같은 기능을 자동 지원한다.


  • 전체리스트 조회 : GET, "/member"
  • 단일 조회 : GET, "/member/key값"
  • 등록 : POST, "/member"
  • 수정 : PUT, "/member"
  • 삭제 : DELETE, "/member/key값"

RequestMethod가 용도에 맞게 지정되면 동일 url로 CRUD (Create, Read, Update, Delete)가 가능하기 때문에 backbone에서는 url을 단일값으로 지정하도록 되어있다. 우리도 그래서 Application.java의 2개 메소드가 동일한 url에 mapping되도록 하였다.

다음은 MemberView.js이다.

MemberView.js

define(['member/MemberCollection'],
function(MemberCollection){
    return Backbone.View.extend({
        collection : null,
        template : null,
        $memberList : null,
        events : {
            'click #addMember' : 'save'
        },

        initialize: function () {
            this.collection = new MemberCollection();
            var html = this.$el.find('#collectionTemplate').html();
            this.template = _.template(html);
            this.$memberList = this.$el.find('#memberList');

            this.collection.fetch();

            //collection.reset 이벤트 발생시 view.rednerAll 이벤트 실행
            this.listenTo(this.collection, 'reset', this.renderAll);

            //collection.add 이벤트 발생시 view.render 이벤트 실행
            this.listenTo(this.collection, 'add', this.render);
        },

        render : function(member){
            this.$memberList.append(this.template(member.toJSON()));
        },

        renderAll : function() {
            /*
             forEach의 내부 function에서는 this가 MemberView가 아니다.
             MemberView를 사용하기 위해 this를 self로 변수할당 후 사용한다.
             */
            var self = this;
            self.collection.forEach(function(member){ //member는 MemberModel 객체이다.
                self.render(member);
            });
        },

        save : function() {
            var name = this.$el.find('#name').val(),
                email = this.$el.find('#email').val();

            this.collection.create({name : name, email: email});
        }
    });
});

MemberView.js는 AddView.js와 크게 다른건 없지만 몇가지 차이점이 존재한다.


  • Model 대신에 Collection (MemberCollection)에 의존성을 두고 있다.
    • Model 관리는 Collection에게 위임하였다.
    • 대신 Collection에 Model이 추가되는 경우 반응하도록 Listener를 등록하였다.
  • 1가지 렌더링 타입을 확장해서 사용한다.
    • render : Model하나만을 대상으로 진행한다.
    • renderAll : collection 내부에 있는 모든 Model을 대상으로 진행한다.
    • 즉, 이전처럼 전체를 위한 렌더링 하나, 단일 대상을 위한 렌더링 하나 이렇게 구현하는 것이 아니라, 전체라는 것이 결국은 단일 대상이 모여서 구성된 것이므로 단일 대상만을 위한 렌더링만 구현하여 이를 확장해서 사용하도록 하였다.
  • 이벤트 대상에 change는 없다.
    • collection은 이벤트 대상에 change가 없다.
    • 대신 reset (전체 초기화 혹은 갱신), add(단일 대상 추가), remove(단일 대상 제거)등이 있다.
    • 해당 이벤트에 맞춰 렌더링이 발생하도록 지정하였다.
    • 즉, fetch를 통해 전체 대상 갱신을 하여 renderAll이 발생하고, 회원 추가 버튼으로 회원이 추가되면 collection에 model이 추가되어 render 이벤트가 자동으로 발생하도록 지정한 것이다.

자 그럼 여기까지 진행후 전체 기능 확인을 진행해보자.
처음 로딩을 하게 될 경우 collection.fetch가 발생하여 빈 화면에 서버에서 받은 회원 리스트가 출력된다.



위 화면처럼 네트워크상에 member가 호출되어 값을 전달 받고 화면에 출력되는 것을 확인할 수 있다.
여기서 input box를 통해 회원을 추가해보겠다.



화면상에 신규 회원이 추가되고, 네트워크상으로 post로 회원정보가 전송된 것을 확인할 수 있다.
실제로 서버에서 잘 전달 받았는지 확인하기 위해 브라우저의 주소를 localhost:8080/member로 변경해보자


(짜잔!)


3개였던 서버 회원 정보가 4개가 된 것을 확인할 수 있다.

드디어 길고 길었던 backbone 시간이 끝이 났다!!
backbone만으로 3챕터를 진행할것이라 생각못했지만... 그래도 기본적인 backbone의 기능은 볼수 있었던것 같다.
예제는 get/post만 진행하였지만 좀 더 backbone을 공부하고 싶다면 put/delete도 직접 구현하여 수정/삭제 기능을 완성해보는 것도 좋은 경험이 될 것 같다.
국내에는 backbone의 한글로 된 교재가 하나밖에 없는걸로 알고 있다. 


제대로 배우는 Backbone.js 프로그래밍
국내도서
저자 : 애디 오스마니(Addy Osmani) / 이지훈역
출판 : 비제이퍼블릭 2014.06.23
상세보기

그래도 처음 튜토리얼로 진행하기에 굉장히 좋은 책이니 backbone을 시작해봐야겠다는 마음이 있으면 큰맘먹고 진행해보는 것도 좋을것 같다.
다음 시간은 grunt를 사용하여 배포용 프로젝트 전환을 진행해보겠다.


(다음에 또 만나요!)


반응형