블로그 이미지
윤영식
Full Stacker, Application Architecter, KnowHow Dispenser and Bike Rider

Publication

Category

Recent Post

2013. 3. 30. 16:23 Backbone.js

EmberJS를 익히는 방법에 대한 개념을 잡아보자



1) 개념잡기 

  - GitHub 메뉴얼 보기 

    + Binding : 프로퍼티 명칭뒤에 Binding을 붙여주면 변경에 대하여 자동 반영된다 

    + 계산된 속성 : 함수를 속성으로 다룰 수 있다 

    + 자동 업데이트 템플릿 : 자동 렌더링 in View


  - 홈페이지의 Guide 보기 

  - 가장 간단한 코드 


2) 기타 튜토리얼

  - ember.js 실습1


  - ember.js 실습2


  - ember.js 실습3



posted by 윤영식
2013. 3. 26. 18:29 Backbone.js

마리오네트는 엔터프라이즈 애플리케이션 개발시 필수적으로 갖추어야할 부분에 대한 고려사항이 반영되어 있으며 백본을 기본으로 확장하여 기능을 추가하였다. 마리오네트 공연을 보자



1) 마리오네트 장점

  - 모듈, 이벤트 지향 아키텍쳐의 확장

  - View에 대한 rendering 반복코드 줄여줌 

  - Region and Layout 개념을 통한 화면 통합

  - 메모리 관리 및 좀비 화면면/리젼/레이아웃등 관리

  - EventBinder 통한 이벤트 누수방지 (clean-up)

  - EventAggreagator 통한 이벤트 지향 아키텍쳐

  - 필요한 부분만 선택적으로 적용 가능



2) 반복 렌더링 코드

  - 백본 + 언더스코어 코드

    + View 에 대한 일반적인 define -> build -> render -> display == boilerplate code 라고 한다 

    + 이러한 코드가 애플리케이션 내에서 계속해서 반복된다 

//////////////////

// template file

<script type="text/html" id="my-view-template">

  <div class="row">

    <label>Name:</label>

    <span><%= name %></span>

  </div>

</script>


//////////////////

// 백본 View 코드 

var MyView = Backbone.View.extend({

  template: $('#my-view-template').html(),


  render: function(){

    // compile the Underscore.js template

    var compiledTemplate = _.template(this.template);


    // render the template with the model data

    var data = this.model.toJSON();

    var html = compiledTemplate(data);


    // populate the view with the rendered html

    this.$el.html(html);

  }

});


//////////////////

// 백본 App

var Dowon = new Person({

  name: 'Hi YoungSik'

});

var myView = new MyView({

  model: Dowon

});

myView.render();


//템플릿 결과 html 내역을 include

$('#content').html(myView.el)


  - Marionette의 ItemView 사용

    + render 메소드를 내장하고 있다. 즉 render 메소드가 제거되었다

    + Underscore를 기본 사용한다 

////////////////////////

// 마리오네트 View 코드 

var MyView = Marionette.ItemView.extend({

  template: '#my-view-template'

});



3) 뷰의 Render 및 참조 오류 제거

  - View 인스턴스와 Event 핸들러를 메모리 관리 걱정없이 쉽게 다루도록 해준다 

  - 백본 코드

    + 하나의 뷰를 만들고 내부적으로 이벤트 핸들러를 등록한다 

    + 뷰의 변수에 두개의 레퍼런스가 할당되면 첫번째 뷰 객체는 좀비가 되지만 이벤트 핸들러가 2회 반응한다

       즉, View 의 render가 두번 호출되어 alert이 두번 뜸

////////////////////

// 좀비 View 만들기 

var ZombieView = Backbone.View.extend({

  template: '#my-view-template',

  initialize: function(){

    // bind the model change to re-render this view

    this.model.on('change', this.render, this);

  },

  render: function(){

    // This alert is going to demonstrate a problem

    alert('We`re rendering the view');

  }

}); 


///////////////////

// 수행

var Person = Backbone.Model.extend({

  defaults: {

    "name": "hi dowon"

  }

});


var Dowon = new Person({

  name: 'youngsik'

});


// create the first view instance

var zombieView = new ZombieView({

  model: Person

});


// 해결을 위하여 zombieView.stopListening(); 호출이 필요한다


// create a second view instance, re-using

// the same variable name to store it

zombieView = new ZombieView({

  model: Person

});

// set 호출하여 이벤트 trigger 발생

Person.set('name', 'yun youngsik');


  - Marionette 코드 

    + listenTo를 사용하면 중복된 이벤트 핸들러는 제거함 

var ZombieView = Marionette.ItemView.extend({

  template: '#my-view-template',

  initialize: function(){

    // bind the model change to re-render this view

    this.listenTo(this.model, 'change', this.render, this);

  },


  render: function(){

    // This alert is going to demonstrate a problem

    alert('We`re rendering the view');


  }

});



4) Region을 통한 View lifeCycle 자동 관리

  - 템플릿을 해석하고 데이터 맵핑후 jQuery를 이용하여 html()이용하여 템플릿 삽입하는 부분도 boilerplate 코드이다 

    위 예에서 $('#content').html(myView.el); 코드

  - View 메모리 문제처럼 화면에 보이는 부분을 Region 으로 사용하면 개별 View들에 대한 LifeCycle 을 관리해 준다 

  - DOM element를 관리하는 Region 을 생성하고 View의 관리를 Region에게 위탁한다 

    + el 을 지정한다

    + render의 구현은 필요없다

    + view에 대한 stopListening를 호출할 필요가 없고 자동으로 DOM에서 이전 view는 제거된 후 새로운 view가 render 된다다

// create a region instance, telling it which DOM element to manage

var myRegion = new Marionette.Region({

  el: '#content'

});


// show a view in the region

var view1 = new MyView({ /* ... */ });

myRegion.show(view1);


// somewhere else in the code,

// show a different view

var view2 = new MyView({ /* ... */ });

myRegion.show(view2); 



5) Application을 통한 Region 통합 관리

  - Marionette ToDoMVC AMD방식의 GitHub 소스를 통하여 살펴보자 (참조 문서에 없는 자체 분석 결과임)

    + git clone -b marionette https://github.com/derickbailey/todomvc.git  수행하여 clone 할 때 marionette 브랜치로 구성한다

       git clone https://github.com/derickbailey/todomvc.git  후에 git checkout marionette 해도 된다 

    + cd todomvc 디렉토리로 들어가서 static 수행한다 (8080 port)

    + 브라우져에서 바로 호출한다

       http://localhost:8080/labs/dependency-examples/backbone_marionette_require/index.html

    


  - backbone_marionette_require 의 index.html 의 body 태그 중요내역

    + todoapp 섹션안에 #header, #main, #footer selector가 존재

    + Require.js 를 통한 js 폴더 밑의 main.built.js 파일을 수행(Minified 파일) -> main.js 파일 수행함 

<body>

<section id="todoapp">

  <header id="header">

  </header>

  <section id="main">

  </section>

  <footer id="footer">

  </footer>

</section>

<footer id="info">

  <p>Double-click to edit a todo</p>

  <p>Created by <a href="http://github.com/jsoverson">Jarrod Overson</a></p>

  <p><a href="./index-dev.html">View the unbuilt version</a></p>

</footer>

<script src="../../../assets/base.js"></script>

<script data-main="js/main.built" src="./js/lib/require.js"></script>

...

</body> 


  - main.js 파일에서 AMD 모듈 환경설정

    + 마리오네트를 AMD 모듈에 넣는다. 최근 버전은 AMD버전이 있으므로 shim에 설정하지 않아도 됨

    + app.js 파일을 수행한다 

    + baseUrl 을 설정하지 않았으므로 현재 디렉토리가 된다 

require.config({

  paths : {

    underscore : 'lib/underscore',

    backbone   : 'lib/backbone',

    marionette : 'lib/backbone.marionette',

    jquery     : '../../../../assets/jquery.min',

    tpl        : 'lib/tpl'   // underscore micro template 

  },

  shim : {

    'lib/backbone-localStorage' : ['backbone'],

    underscore : {

      exports : '_'

    },

    backbone : {

      exports : 'Backbone',

      deps : ['jquery','underscore']

    },

    marionette : {

      exports : 'Backbone.Marionette',

      deps : ['backbone']

    }

  },

  deps : ['jquery','underscore']

});


require(['app','backbone','routers/index','controllers/index'],function(app,Backbone,Router,Controller){

  "use strict";


  // 애플리케이션 시작

  app.start();


  // 라우터에 컨트롤러를 설정함

  new Router({

    controller : Controller

  });


  // 백본 이력 시작

  Backbone.history.start();

});

 

  - app.js 에서 Application.addRegions를 통하여 화면 Fragment를 등록한다

    + 애플리케이션이 초기화 된후 Fragment의 히스토리를 시작한다 (참조)

    + collections/TodoList.js 파일은 Backbone.Collection 이다 

    + views/Header 형식은 각각 views/Header.js 로 연결되고 다시 templates/header.tmpl 파일과 연결된다 

       underscore micro template (tpl)을 사용하고 있다

// 마리오네트안에 

define(

  ['marionette','vent','collections/TodoList','views/Header','views/TodoListCompositeView','views/Footer'],

  function(marionette, vent, TodoList, Header, TodoListCompositeView, Footer){

    "use strict";


    // SPA 애플리케이션 1개를 생성한다 

    var app = new marionette.Application(),

        todoList = new TodoList();


    app.bindTo(todoList, 'all', function() {

      if (todoList.length === 0) {

        app.main.$el.hide();

        app.footer.$el.hide();

      } else {

        app.main.$el.show();

        app.footer.$el.show();

      }

    });


    // 리젼안에 화면을 담는다 

    app.addRegions({

      header : '#header',

      main   : '#main',

      footer : '#footer'

    });


 ... 중략 ...

});


  - Header.js 를 통한 Todo 입력하기 

    + todo 입력을 수행한다 

    + id="new-todo"   

    

    + 소스 

////////////////////////

// views/Header.js

define(['marionette','templates'], function (Marionette,templates) {

  "use strict";


  // ItemView 을 상속 

  return Marionette.ItemView.extend({

    // templates/header.tmpl 파일을 참조 

    template : templates.header,


    // 템플릿 화면의 new-todo 아이디 

    // jQuery selected object를 가르키는 캐쉬된 속성을 input 이라고 생성한다 

    ui : {

      input : '#new-todo'

    },


    // 이벤트 설정 

    events : {

      'keypress #new-todo': 'onInputKeypress'

    },


    // 이벤트 핸들러 

    // 입력하고 엔터키 쳐지면 값을 컬렉션에 담는다 

    onInputKeypress : function(evt) {

      var ENTER_KEY = 13;

      var todoText = this.ui.input.val().trim();


      if ( evt.which === ENTER_KEY && todoText ) {

        // Collection에서 직접 Model객체를 생성함 

        this.collection.create({

          title : todoText

        });

        this.ui.input.val('');

      }

    }

  });

});


////////////////////////

// templates/header.tmpl 

<h1>todos</h1>

<input id="new-todo" placeholder="What needs to be done?" autofocus>


  - TodoListCompositeView.js 를 통한 TodoList

    + header 밑으로 첨부된 todo list에 대한 태그입니다 

    


    + #todo-list 는 ul 태그안에  컬렉션이 리스팅된다 

    + 한개의 todo를 입력하였을 경우 : <ul id="todo-list> 태그안에  <li class="active">..</li> 내역이 동적으로 생성된다. 이것은 TodoItemView.js 가 된다. 

    + 즉, TodoListCompositeView.js는 TodoItemView.js 목록을 관리한다. itemView 속성에 지정한다

    + 좌측의 checkbox를 클릭하면 <li class="active"> 에서 <li class="completed">  로 변경된다 

    


// TodoListCompositeView.js

define(['marionette','templates','vent','views/TodoItemView'], function (Marionette,templates,vent,ItemView) {

  "use strict";


  return Marionette.CompositeView.extend({

    template : templates.todosCompositeView,

    itemView : ItemView,  // TodoItemView.js 

    itemViewContainer : '#todo-list',


    ui : {

      toggle : '#toggle-all'

    },


    events : {

      'click #toggle-all' : 'onToggleAllClick'

    },


    initialize : function() {

      this.bindTo(this.collection, 'all', this.updateToggleCheckbox, this);

    },


    onRender : function() {

      this.updateToggleCheckbox();

    },


    // 전체 checkbox 토글들에 대한 초기화 

    updateToggleCheckbox : function() {

      function reduceCompleted(left, right) { return left && right.get('completed'); }

      var allCompleted = this.collection.reduce(reduceCompleted,true);

      this.ui.toggle.prop('checked', allCompleted);

    },


    // 전체 todo 에 대한 토글

    onToggleAllClick : function(evt) {

      var isChecked = evt.currentTarget.checked;

      this.collection.each(function(todo){

        todo.save({'completed': isChecked});

      });

    }

  });

});


/////////////////////////////////////////

// templates/todoListCompositeView.tmpl

<input id="toggle-all" type="checkbox">

<label for="toggle-all">Mark all as complete</label>

<ul id="todo-list"></ul>


  - TodoItemView.js 

    + templates/todoItemView.tmpl 사용

    + todo를 더블클릭하면 class="active" 에서 class="active editing" 이 추가된다 

    


define(['marionette','templates'], function (Marionette,templates) {

  "use strict";


  return Marionette.CompositeView.extend({

    // 상단 <li class="active editing"> 태그를 표현 

    tagName : 'li',


    // todoItemView.tmpl 지정 

    template : templates.todoItemView,


    // <input class="edit" value="hi dowon"> 태그에 대한 jQuery selected object 를 가르킴

    ui : {

      edit : '.edit'

    },


    events : {

      'click .destroy' : 'destroy',

      'dblclick label' : 'onEditClick',

      'keypress .edit' : 'onEditKeypress',

      'click .toggle'  : 'toggle'

    },


    initialize : function() {

      this.bindTo(this.model, 'change', this.render, this);

    },


    onRender : function() {

      this.$el.removeClass('active completed');

      if (this.model.get('completed')) this.$el.addClass('completed');

      else this.$el.addClass('active');

    },


    destroy : function() {

      this.model.destroy();

    },


    toggle  : function() {

      this.model.toggle().save();

    },


    // 더블클릭을 하면 editing 중이라는 class가 추가되고 edit input 태그에 포커스가 가서 edit상태가 된다

    onEditClick : function() {

      this.$el.addClass('editing');

      this.ui.edit.focus();

    },


    // input 태그로 focus가 와서 입력후 enter를 치면 값을 넣고 editing class는 제거한다 

    onEditKeypress : function(evt) {

      var ENTER_KEY = 13;

      var todoText = this.ui.edit.val().trim();


      if ( evt.which === ENTER_KEY && todoText ) {

        this.model.set('title', todoText).save();

        this.$el.removeClass('editing');

      }

    }

  });

});


/////////////////////////////

// todoItemView.tmpl

<div class="view">

  <input class="toggle" type="checkbox" <% if (completed) { %>checked<% } %>>

    <label><%= title %></label>

  <button class="destroy"></button>

</div>

<input class="edit" value="<%= title %>">


  - Footer.js 를 통한 Layout 개념이해 

    + Layout은 Region을 가지고 있다 

    + 좌측 완료안된 todo 건수 + 중앙 All, Active, Completed 상태별 todo 필터링 보기 + 우측 Clear Completed로 완료된 todo 제거 

    


    + 소스코드

// Footer.js 

define(['marionette','vent','templates','views/ActiveCount'], function (Marionette,vent,templates,ActiveCount) {

  "use strict";


  return Marionette.Layout.extend({

    // templates/footer.tmpl  

    template : templates.footer,


    // footer.tmpl의 id="todo-count" 밑의 <strong> 태그 

    regions : {

      count : '#todo-count strong'

    },


    // filters의 <a> 태그 

    ui : {

      filters : '#filters a'

    },


    // 제일 우측의 clear completed 

    events : {

      'click #clear-completed' : 'onClearClick'

    },


    initialize : function() {

      this.bindTo(vent, 'todoList:filter', this.updateFilterSelection, this);

    },


    // 리젼에 속한 count에 건수를 측정하는 ActiveCount 클래스에 컬렉션을 넘김

    onRender : function() {

      this.count.show(new ActiveCount({collection : this.collection}));

    },


    // All, Active, Completed <a> 태그의 class="selected"는 선택하는 곳으로 이동을 한다  

    // 또한 최상단의 <sectio id="todoapp" class="filter-active"> 또는 "filter-all", "filter-completed"로 변경됨 

    updateFilterSelection : function(filter) {

      this.ui.filters.removeClass('selected').filter('[href="#/' + filter + '"]').addClass('selected');

    },


    onClearClick : function() {

      vent.trigger('todoList:clear:completed');

    }

  });


});

//////////////////////

// footer.tmpl

<span id="todo-count"><strong></strong> items left</span>

<ul id="filters">

  <li>

    <a href="#/">All</a>

  </li>

  <li>

    <a href="#/active">Active</a>

  </li>

  <li>

    <a href="#/completed">Completed</a>

  </li>

</ul>

<button id="clear-completed">Clear completed</button>


/////////////////////////
// views/ActiveCount.js
define(['marionette'], function (Marionette) {
  "use strict";

  return Marionette.View.extend({
    tagName : 'span',
    // collection에 변경이 있으면 다시 그려준다 
    initialize : function() {
      this.bindTo(this.collection, 'all', this.render, this);
    },
    render : function() {
      this.$el.html(this.collection.getActive().length);
    }
  });
});


쿨럭 목감기로 오늘은 여기까지 아무래도 Model/Collection 그리고 Marionette API 에 대한 분석이 필요해 보인다. 또한 TodoMVC 를 구현함에 있어서 개발 프로세스를 고려하여 분석하는 것이 맞을 듯하다. 다시 분석을 해보도록 한다 


<참조>

  - 원문 : Backbone.Marionette 

posted by 윤영식
2013. 3. 23. 17:12 Backbone.js

Backbone.js MV* 각 요소를 직접 개발해보고 전체 흐름에 대하여 알아보자



1. Backbone.Model

  - initialize : 초기화 model new 생성시 자동 호출 

  - save : 서버로 전송

  - fetch : 서버에서 가져오기

  - sync : 강제로 서버와 값을 맞춤 

  - id : 속성을 통하여 전송 아이디 설정가능 (MongoDB 를 사용하면 "idAttribute: _id" 로 설정한다)

  - defaults : model 객체의 기본값을 지정

  - validate : 데이터 정합성 체크

  - 테스트

    + index.html 준비 : jquery, underscore, backbone 순으로 최신버전을 다운로드 받아서 js 폴더 밑에 놓는다 

<!DOCTYPE html>

  <html>

  <head>

    <meta charset="utf-8">

    <title>hello-backbonejs</title>

  </head>

  <body>


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

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

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


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


  </body>

  </html>


    + 2.js 백본 코딩

(function($) { 

    var UserModel = Backbone.Model.extend({

        url: '/user',

        defaults: {

            name: '',

            email: ''

        }

    });


    var user = new UserModel();

    var userDetails = { name: "dowon", email: "ysyun@yuwin.co.kr"};

    user.save(userDetails, {

    success: function(user) {

    alert(user.toJSON());

    },

    error: function(user){

    alert(user.toJSON());

    }

    });


    var user1 = new UserModel({id: 1});

    user1.fetch({

    success: function(user) {

    alert('fetch user name : ' + user.get('name'));

    },

    error: function(user){

    alert(user.toJSON());

    }

    });


    user1.sync();

})(jQuery);


   + fetch시에 맨밑의 user(빨간색)는 서버에서 가져온 정보가 없다 (404 code)

  


    + save 시의 Request URL 정보 : http://localhost:8080/user

  



2. Backbone.View

  - initialize : render  메소드 호출

  - render : 화면 그리기

  - events : 화면의 이벤트와 핸들러 등록

    + index.html 안에 템플릿 추가 : 위치가 중요

<!DOCTYPE html>

  <html>

  <head>

    <meta charset="utf-8">

    <title>hello-backbonejs</title>

  </head>

  <body>


    <script type="text/template" id="search_template">

      <label><%= name %></label>

      <input type="text" id="search_input" />

      <input type="button" id="search_button" value="Search" />

    </script>

    <div id="search_container"></div>


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

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

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


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

  </body>

  </html>


   + 2.js 안에 View 추가 

    var SearchView = Backbone.View.extend({

        initialize: function(){

            this.render(); // 호출시 화면 그리기. initialize는 new 생성시 자동 호출됨 

        },

        render: function(){

            // 템플릿 과 데이터 결합

            var template = _.template( $("#search_template").html(), {name: 'dowon'} );

            this.$el.html( template );

        },

        events: {

            "click input[type=button]": "doSearch"  // 이벤트 핸들러 등록

        },

        doSearch: function( event ){

            // Button clicked, you can access the element that was clicked with event.currentTarget

            alert( "Search for " + $("#search_input").val() );

        }

    });


    var search_view = new SearchView({ el: $("#search_container") });


    + 브라우져 호출

  



3. Backbone.Router

  - routes : routing 경로와 처리 메소드 등록 (API 호출시 보여질 View 를 결정하여 준다)

    + 기존 호출 : http://localhost:8080 이고 

    + routes설정이 다음과 같으면 

            routes: {          

       "foo/:bar" : "paramtest",

       "*action" : "func"

   },

    + http://localhost:8080/#/foo/dowon 호출하면 paramtest 메소드로 이동

      http://localhost:8080/#/dowonaction 호출하면 func로 메소드로 이동

// sample example

var Router = Backbone.Router.extend({

   routes: {          

       "foo/:bar" : "paramtest",

       "*action" : "func"

   },

   func: function (action) {

       console.log('action> ' + action);

   },

   paramtest:function (p) {

       console.log('p> ' + p);

   }

});

  

  - Backbone.history.start() 호출 : SPA에서 Fragment (# 해쉬로 표현) 들에 대한 브라우져 호출의 히스토리를 관리한다

  - 라우팅 과 Fragment의 관계를 반드시 이해하자 

  

    이미지 출처: http://blog.nodejitsu.com/scaling-isomorphic-javascript-code


  - Dynamic Routing

 var AppRouter = Backbone.Router.extend({

        // # 해쉬로 표현될 Fragment를 등록한다

        routes: {

            "posts/:id": "getPost",

            "*actions": "defaultRoute" // Backbone will try match the route above first

        }

    });


    // Instantiate the router

    var app_router = new AppRouter;

    app_router.on('route:getPost', function (id) {

        // Note the variable in the route definition being passed in here

        alert( "Get post number " + id );   

    });

    app_router.on('route:defaultRoute', function (actions) {

        alert( actions ); 

    });

    // Start Backbone history a necessary step for bookmarkable URL's

    Backbone.history.start();


  - 브라우져 호출 : <url>/spa-page/#/posts/1  호출

  



4. Backbone.Collection

  - model : 속성에 Model 클래스를 지정한다 

  - models : 컬렉션에 등록된 model 객체 배열열

  - 2.js Model -> Collection 에 지정한다 

     var Song = Backbone.Model.extend({

     initialize: function(){

         console.log("Music is the answer");

     }

     });


     var Album = Backbone.Collection.extend({

 model: Song

     });


      var song1 = new Song({ name: "How Bizarre", artist: "OMC" });

      var song2 = new Song({ name: "Sexual Healing", artist: "Marvin Gaye" });

      var song3 = new Song({ name: "Talk It Over In Bed", artist: "OMC" });


      var myAlbum = new Album([song1, song2, song3]);

      console.log( myAlbum.models );

  

  - 브라우져 호출

  



Backbone을 이용하게 되면 좀 더 쉽게 SPA 를 만들 수 있다. Backbone을 사용하여 개발하는 Workflow는 다음과 같다

  - index.html 을 만든다 : SPA 에서는 오로지 <body> 태그는 index.html 에만 존재한다 

  - Backbone.Router를 개발한다 : RESTful 설계서에 따라 업무를 분류하면 될 것이다

  - Backbone.View를 개발한다 : view에 맞는 template 파일을 별도로 가져가면 되겠다

  - Backbone.Collection을 개발한다 : Model을 만들고 Collection을 개발한다

  - RESTful Web Services & Open API : 설계에 따라서 Model에 url 을 설정하면 되겠다  

이미지 출처 : 참조의 동영상중에서 


  

  Backbone의 서버연동 전체흐름도 : Real-time은 화면의 reload가 없이 진행된다. The 'net을 socket.io로 연결하는 것이 중요!



5. 총정리

  - Model, View, Router 이해


    + 상황에 따른 코딩 패턴 소개
    + 코드 컨벤션 및 네이밍 규칙

    + Backbone Pattern에 따라 전체 디렉토리 구조 만들기

    + 시작, 업무클래스의 AMD 사용방법 소개 



<참조> 

  - 원문1 : Hello World Backbone.js Tutorial

  - 원문1의 GitHub 저장소

  - 원문2 비디오 튜토리얼

  - 원문2의 GitHub 저장소

  - Backbone 완벽가이드 문서

  - $(this.el) 과 this.$el 의 차이점  : Backbone.View 의 render 메소드에서 사용함. this.$el 에서 에러 발생하면 최신버전 upgrade

  - Backbone 개발 Workflow 동영상 

  - Backbone Step by Step 튜토리얼 (동영상포함)

  - Backbone/Node.js/MongoDB 페이스북 공개 그룹

posted by 윤영식
2013. 3. 20. 15:09 Backbone.js

기존에 작성한 index.html 파일안에는 템플릿에 대한 내역도 같이 포함되어 있다. 해당 템플릿 내역을 별도의 파일로 때어 내어 AMD  모듈화와 결합하는 방법을 알아본다. 



1) Require.js 플러그인 test.js 설치

  - 텍스트 파일을 로딩하는 text.js 파일을 적절한 위치에 설치 (여기서는 require.js 랑 같은 위치에 설치함)

  - 다운로드 : http://requirejs.org/docs/download.html#text

  - index.html 파일의 requirejs.config 에 의존성 설정 추가
requirejs.config({
    baseUrl: './js/', 
    paths: {
        'jquery': 'jquery',
        'underscore': 'underscore',
        'backbone': 'backbone',
        'text': 'text',
        'jquery.dateformat' : 'jquery-dateformat'
    },
    shim: {
        'underscore': {
            exports: '_'
        },
        'backbone': {
            deps: ['underscore', 'jquery'],
            exports: 'Backbone'
        },
        'jquery.dateformat': {
            deps: ['jquery']
        }
    }
});



2) index.html 에서 템플릿내역 분리하기 

  - 템플릿 영역 분리하여 ride_template.html 명칭으로 index.html 과 같은 위치에 저장

////////////////////////////////

// index.html 내부

<div id="rides">

    <div id="addRide">

        <label for="coverImage">CoverImage: </label><input id="coverImage" type="file" />

        <label for="title">Title: </label><input id="title" type="text" />

        <label for="rider">Rider: </label><input id="rider" type="text" />

        <label for="ridingDate">Riding date: </label><input id="ridingDate" type="text" />

        <label for="keywords">Keywords: </label><input id="keywords" type="text" />

        <button id="add">Add</button>

    </div>

    <!-- 주석처리 사용하지 않음 / 또는 제거함 

        script id="rideTemplate" type="text/template">

        <img src="<%= coverImage %>"/>

        <ul>

            <li><%= title %></li>

            <li><%= rider %></li>

            <li><%= ridingDate %></li>

            <li><%= keywords %></li>

        </ul>

        <button class="delete">Delete</button>

    </script-->

</div>


////////////////////////////////

// ride_template.html 전체 내역

<img src="<%= coverImage %>"/>

<ul>

    <li><%= title %></li>

    <li><%= rider %></li>

    <li><%= ridingDate %></li>

    <li><%= keywords %></li>

</ul>

<button class="delete">Delete</button>



3) ride.js 파일에 템플릿 의존성 주입

  - AMD는 SOLID의 Dependency Inversion Principle과 같은 원리. 즉, 스프링의 Inversion Of Control 과 같은 방식이다. 

  - 따라서 모듈 ride_template.html 을 ride.js 에 주입해 보자 

    +  define([의존관계], 파라미터) : 의존관계에 템플릿 파일 상대 경로 지정

    + 기존 template 프로퍼티의 값을 파라미터로 변경

define(['jquery', 'backbone', 'underscore', 'text!../ride_template.html', 'jquery.dateformat'], function($, Backbone, _, rideTemplate) {

   .. 중략 ..

   var RideView = Backbone.View.extend({

        tagName:"div",

        className:"rideContainer",

        //template: $("#rideTemplate").html(),

        template: rideTemplate,


   render:function () {

        .. 중략 ..



4) 브라우져 호출 

  - 호출후 크롬 개발자 도구의 DOM 구성 : rideContainer div 태그안에 ride_template.html 이 들어가 있다 

   


 ** 전체 소스 

riding.zip



<참조>

  - 백본에 템플릿 텍스트 적용하기

  - text.js 사용법 : prefix로 반드시 text!을 붙여서 정적파일을 로딩한다 


posted by 윤영식
2013. 3. 18. 14:42 Backbone.js

엔터프라이즈급의 SPA 개발을 위해서는 AMD를 필수적으로 사용해야 한다. 기존 "서점 샘플 응용하기 - 02"에서 진행하였던 자바스크립트 라이브러리들과 Backbone 기반 코드인 ride.js 파일을 Require.js를 이용하여 모듈화하고 재설정해 보자 



1) index.html 변경하기 

  - 기존 index.html 내역

    + <%= %> 구문으로 underscore template 사용

    + jquery dateformat plugin 을 html에서 직접 호출

    + <script> 의존 라이브러리를 개발자가 알아서 체크하고 의존성이 있는 것을 더 밑으로 열거함 

       자바스크립트만을 가지고 SPA 를 개발할 때 수 많은 의존관계를 지금의 방식으로 설정하는 것은 한계가 있음 

<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8"/>

    <title>Backbone.js Cycle Riding for Dowon</title>

    <link rel="stylesheet" href="css/screen.css">

</head>

<body>


<div id="rides">

    <div id="addRide">

        <label for="coverImage">CoverImage: </label><input id="coverImage" type="file" />

        <label for="title">Title: </label><input id="title" type="text" />

        <label for="rider">Rider: </label><input id="rider" type="text" />

        <label for="ridingDate">Riding date: </label><input id="ridingDate" type="text" />

        <label for="keywords">Keywords: </label><input id="keywords" type="text" />

        <button id="add">Add</button>

    </div>

    <script id="rideTemplate" type="text/template">

        <img src="<%= coverImage %>"/>

        <ul>

            <li><%= title %></li>

            <li><%= rider %></li>

            <li><%= $.format.date(new Date(ridingDate), 'yyyy/MM/dd') %></li>  <!-- jquery dataformat 직접 호출 --> 

            <li><%= keywords %></li>

        </ul>

        <button class="delete">Delete</button>

    </script>

</div>


<!-- 자바스크립트의 의존관계에 따라 의존성이 있는 라이브러리를 맨밑에 열거한다 --> 

<script src="js/jquery.js"></script>

<script src="js/jquery-dateformat.js"></script>

<script src="js/underscore.js"></script>

<script src="js/backbone.js"></script>

<script src="js/ride.js"></script>

</body>

</html>


  - Require.js 방식으로 index.html 변경 

<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8"/>

    <title>Backbone.js Cycle Riding for Dowon</title>

    <link rel="stylesheet" href="css/screen.css">

</head>

<body>


<div id="rides">

    <div id="addRide">

        <label for="coverImage">CoverImage: </label><input id="coverImage" type="file" />

        <label for="title">Title: </label><input id="title" type="text" />

        <label for="rider">Rider: </label><input id="rider" type="text" />

        <label for="ridingDate">Riding date: </label><input id="ridingDate" type="text" />

        <label for="keywords">Keywords: </label><input id="keywords" type="text" />

        <button id="add">Add</button>

    </div>

    <script id="rideTemplate" type="text/template">

        <img src="<%= coverImage %>"/>

        <ul>

            <li><%= title %></li>

            <li><%= rider %></li>

            <li><%= ridingDate %></li>  <!-- jquery dateformat은 ride.js 에서 포멧팅한다 --> 

            <li><%= keywords %></li>  

        </ul>

        <button class="delete">Delete</button>

    </script>

</div>


<!-- require.js 최상단에 위치 --> 

<script src="js/require.js"></script>

<script>

// 환경설정

requirejs.config({

    // 라이브러리 위치 (index.html 에서 상대경로)

    baseUrl: './js/', 

    // baseUrl+paths 로 실제 라이브러리 파일위치를 지정 (value값에서 .js 생략함)

    paths: {

        jquery: 'jquery',

        underscore: 'underscore',

        backbone: 'backbone',

        'jquery.dateformat' : 'jquery-dateformat'

    },

    // require.js 를 사용할 수 없는 이미 정의된 라이브러리를 require.js 방식으로 만들고자 할 때 설정

    shim: {

        'underscore': {

            exports: '_'

        },

        'backbone': {

            deps: ['underscore', 'jquery'],  // 의존하는 라이브러리 설정

            exports: 'Backbone'  // window객체에서 접근가능한 변수명 설정 

        },

        'jquery.dateformat': {   // jquery.dateformat 을 index.html 에서 사용하지 않고 ride.js 에서 사용하기 위해 설정

            deps: ['jquery']

        }

    }

});


// define 펑션으로 정의한 ride.js 모듈을 사용하기 위하여 require 펑션을 호출한다 

require(['ride'], function(ride) {

    console.log('let\'s go riding');

});

</script>


</body>

</html>



2) ride.js 모듈화 하기 

  - 기존 ride.js 

(function ($) {


    var Ride = Backbone.Model.extend({

    .. 중략 ..


})(jQuery);  // 글로벌 변수 jQuery 를 받아서 사용함 


  - define 펑션을 사용하여 ride.js 모듈화 하기 

// jquery, backbone을 사용한다

// jquery.dateformat은 jquery 플러그인으로 의존관계만 설정해 놓고 파라미터로 받지는 않는다 

// 파라미터로 jquery=$, backbone=Backbone, underscore=_ 로 레퍼런스를 받고 있다 

define(['jquery', 'backbone', 'underscore', 'jquery.dateformat'], function($, Backbone, _) {


    var Ride = Backbone.Model.extend({

    .. 중략 ..


    renderRide: function(item){

            // 화면에 포멧 변경로직을 넣지 않고 model에서 변경하여 준다

            // update ridingDate to YYYY/MM/dd from YYYY-MM-DDT00:00:00.000Z

            var ridingDate = item.get('ridingDate');

            var date = $.format.date(new Date(ridingDate), 'yyyy/MM/dd');

            item.set('ridingDate', date);


            var rideView = new RideView({

                model: item

            });

            this.$el.append(rideView.render().el);

     },

    .. 중략 ..


});



3) 호출하기 

  - 브라우져에서 호출

    + nodemon 으로 node 수행 

$ nodemon server.js

18 Mar 11:40:03 - [nodemon] v0.7.2

18 Mar 11:40:03 - [nodemon] watching: /Users/nulpulum/development/backbone/riding

18 Mar 11:40:03 - [nodemon] starting `node server.js`

Express server listening on port 8080 in development mode

18 Mar 13:28:12 - [nodemon] restarting due to changes...

18 Mar 13:28:12 - [nodemon] /Users/nulpulum/development/backbone/riding/public/js/ride.js


    + http://localhost:8080/  : 하나더 추가해 보았다 

  


<참조>

  - Require.js API

  - backbone.js 소스 주석 한글화

posted by 윤영식
2013. 3. 16. 16:17 Backbone.js

모듈 프로그래밍은 큰사이즈의 애플리케이션을 작은 단위의 관리가능한 블록으로 만드는데 유용하다. 모듈기반 코딩은 유지보수 노력을 줄여주고 재사용을 높여준다. 그러나 모듈간의 의존관계를 관리하는 부분은 어려운 점들이 있다. 이런 의존 관계상의 문제를 해결하기 위하여 RequireJS 프레임워크가 나오게 되었다. 



1) Old Style Loading JavaScript 파일들

  - 큰사이즈 애플리케이션들은 여러 자바스크립트 파일을 <script> 태그로 로딩한다

  - 이때 각 자바스크립트 파일은 어떻게 로딩을 처리하는지 예제로 보자


purchase.js

function purchaseProduct(){

  console.log("Function : purchaseProduct");

  var credits = getCredits();

  if(credits > 0){

    reserveProduct();

    return true;

  }

  return false;

}


products.js

function reserveProduct(){

  console.log("Function : reserveProduct");

  return true;

}


credits.js

function getCredits(){

  console.log("Function : getCredits");

  var credits = "100";

  return credits;

}


  - 만일 main.js에서 이렇게 코딩을 하면 var result = purchaseProduct(); 

  - purchase.js는 credits.jsproducts.js 파일 의존관계가 된다. 따라서 이들은 purchaseProduct()이 호출되지 전에 먼저 로딩되어 져야 한다. 

  - 만약 다음 순서라면 에러가 발생할 것이다. (credits.js 가 먼저 로딩되어야 하는데 맨 뒤에 있으므로)

<script src="products.js"></script>

<script src="purchase.js"></script>

<script src="main.js"></script>

<script src="credits.js"></script>


  

2) RequireJS 소개

  - http://requirejs.org/docs/download.html 에서 모듈을 다운로드하자 

  - 모든 자바스크립트 파일과 함께 require.js 파일을 같은 디렉토리에 둔다

    

  - index.html 안에 하기와 같이 script를 넣는다 

<script data-main="scripts/main" src="scripts/require.js"></script>


  - 코드안에 RequireJS를 사용하여 require code를 넣는다.

  - data-main 어트리뷰트 애플리케이션 최기화 시점으로 이때 RequireJS가 다른 스크립트 파일과 의존관계를 찾기위하여 scripts 디렉토리 밑에 있는 main.js를 사용하고 require.js를 src 값으로 지정한다. main.js가 해당 예에서는 같은 폴더에 모든 파일이 위치하지만 원하는 어느 폴더에 놓아도 무방하다. 


  - main.js 코드를 보자 

require(["purchase"],function(purchase){

  purchase.purchaseProduct();

});


  -  RequireJS는 require() 또는 define() 펑션으로 모든 코드를 감싼다. 펑션의 첫번째 파라미터로 의존관계에 있는 파일명을 넣으면 되고, .js는 생략가능하다. 두번째 파라미터는 무명함수(anonymous function)로 의존파일안의 함수를 호출한다. 

  - 다중의존관계 호출은 다음과 같다

require(["a","b","c"],function(a,b,c){

});



3) Define으로 모듈 생성하기

  - main.js 에서 사용될 모듈들을 define() 펑션을 이용하여 만든다

  - 모듈생성이 펑션널 프로그래밍 단위 즉 Task 단위로 생성이 되는 것이다. 

  - main.js 에서 필요한 모듈 즉 Task 를 로딩하여 사용하게 된다 

  - purchase.js를 바꾸어 보자

define(["credits","products"], function(credits,products) {

  console.log("Function : purchaseProduct");

  return {

    purchaseProduct: function() {

      var credit = credits.getCredits();

      if(credit > 0){

        products.reserveProduct();

        return true;

      }

      return false;

    }

  }

});


  - products.js 도 바꾸어 보자

define(function(products) {

  return {

    reserveProduct: function() {

      console.log("Function : reserveProduct");

      return true;

    }

  }

});


  - credits.js 도 바꾸어 보자 

define(function() {

  console.log("Function : getCredits");

  return {

    getCredits: function() {

      var credits = "100";

      return credits;

    }

  }

});



4) define()으로 모듈 만들고 require()로 필요한 모듈 사용하기 

  - require() : 모듈을 로딩하여 즉시 함수를 수행하는데 사용한다

  - define() : 모듈을 정의하는데 사용한다

  - purchaseProduct() 은 main.js 파일에서 함수를 즉시 실행한 것이다. 그러나 다른 파일은 재사용을 위하여 모듈화 하기 위하여 define()을 사용했다. 



5) 왜 RequireJS가 중요한가?

  - RequireJS는 펑션이 수행되기전에 모든 의존관계 파일이 로딩될 때까지 기다린다. 즉, 하나의 호출에 관여된 의존관계 파일을 로딩한 후에 펑션을 호출해 준다. 필요한 시점에 로딩하여 주고 Asynchronous Module Loading 이다. 

 


6) 의존관계 파일의 우선순위 관리 방법

  - RequireJS는 파일을 로딩할 때 Asynchronous Module Definition(AMD)을 사용한다.

  - Asynch로 로딩되기 때문에 순서적인 로딩을 보장받고 싶다면 shim 환경파일을 작성할 수 있다. 

  - 환경 shim 예

requirejs.config({

  shim: {

    'source1': ['dependency1','dependency2'],

    'source2': ['source1']

  }

});


  - RequireJS에서 제공하는 config() 펑션을 통해 shim 이라 불리우는 파라미터를 정의한다.

  - configuration 가이드는 http://requirejs.org/docs/api.html#config 를 참조한다. (Advanced User로 갈려면 꼭 익히자!)

    + jQuery config

    + Node config

    + Dojo config


  - 예로 하기와 같이 정의하지만 로딩 순서 지정을 위와 같이 shim으로 할 수 있는 것이다. 

define(["dependency1","dependency2","source1","source2"], function() {

);

  

  - source2는 source1을 의존한다. source1의 로딩이 끝나면 source2는 모든 의존파일이 로딩되었다 여긴다. 그러나 dependency1과 dependency2 는 여전히 로딩되고 있을지도 모른다. shim config를 사용하면 이러한 복잡성을 제거하여 로딩 순서를 지정하여 줄 수 있다. 


  - Backbone.js Boilerplate 블로깅의 index.html 파일을 파악해 보자



<참조>

  - 원문 : Understanding RequireJS for Effective JavaScript Module Loading

  - RequireJS 홈페이지

posted by 윤영식
2013. 3. 16. 16:00 Backbone.js

SPA를 작성하게 되면 클라이언트 사이드의 템플릿엔진을 사용하게 된다. 많은 템플릿 엔진이 나와 있고 이중 핸들바에 대해서 알아보자 



1) Handlebar.js 소개 

  - 클라이언트 사이드 템플릿 엔진 성능 비교에서 핸들바의 위치 

  - 핸들바의 동작방식 소개 동영상

  


2) 핸들바 템플릿 만들기

  - 핸들바 스크립트 최신 다운로드 받고 <script> 코드 넣기 (jQuery 기본으로 넣음) 

<html>

<body>

  <script src="jquery.js"></script>

  <script src="handlebar.js"></script>

</body>

</html>


  - 템플릿 코드 작성하기 

    + <script> 테그를 사용하면 화면에 렌더링되지 않음

    + handlebar.js 가 사용하는 type임을 x-handlebars-template 명칭으로 준다

    + 컬렉션의 루프틑 # 으로 시작해서 / 로 끝남 == Code Less

<script id="some-template" type="text/x-handlebars-template">

 <table>

   <thead>

     <th>Username</th>

     <th>Real Name</th>

     <th>Email</th>

   </thead>

   <tbody>

     {{#users}}

       <tr>

         <td>{{username}}</td>

         <td>{{firstName}} {{lastName}}</td>

         <td>{{email}}</td>

       </tr>

     {{/users}}

   </tbody>

 </table>

</script>


  - jQuery를 이용하여 템플릿 핸들링하기 

<script>

          // 템플릿 해석

 var source   = $("#some-template").html();

 var template = Handlebars.compile(source);

    // 데이터 

 var data = { users: [

     {username: "alan", firstName: "Alan", lastName: "Johnson", email: "alan@test.com" },

     {username: "allison", firstName: "Allison", lastName: "House", email: "allison@test.com" },

     {username: "ryan", firstName: "Ryan", lastName: "Carson", email: "ryan@test.com" }

   ]};

          // div id=content-placeholder 에 결과 내역을 렌더링한다 

 $("#content-placeholder").html(template(data));

</script>


  - template 변수가 참조하는 펑션 내용

function (context, options) {

    if (!compiled) {

      compiled = compile();

    }

    return compiled.call(this, context, options);

  } 


  - content-placeholder div 태그 넣기 (<script> 태그보다 위에 놓는다)

<html>

<body>

<div id="content-placeholder"></div>


  <script src="jquery.js"></script>

  <script src="handlebar.js"></script>

.. 중략 ..


  - 전체 소스 코드 

  - 결과 화면 

  



4) 클라이언트 사이드 템플릿 엔진

  - Mustache -> Handlebar -> JsRender 순서로 발전해 왔음 

  - JsRender : MS가 차기 Office 에 공식 채택한 템플릿 엔진



** 테스트 소스 

handlebar.zip


<참조>

  - 원문 : Getting Start With Handlebar.js


posted by 윤영식
2013. 3. 15. 17:49 Backbone.js

서점 샘플 응용하기 - 01 에 이어서 02 에서는 서버-Node.js & MongoDB- 와 붙이는 작업을 한다. 원문이 과거 버전이어서 좀 더 이해하기 쉽도록 재구성하였고, RESTful API 테스트 방법도 새롭게 시도해 보았다



1) RESTful API 와 Node.js 환경 구성하기 

  - 호출하는 API 목록

/api/rides  GET 모든 라이딩정보의 배열을 얻는다.

/api/rides/:id  GET 아이디가 :id인 라이딩정보을 얻는다.

/api/rides  POST  새로운 라이딩정보를 추가하고 아이디가 추가된 그 정보를 반환한다.

/api/rides/:id  PUT 아이디가 :id인 라이딩정보를 수정한다.

/api/rides/:id  DELETE  아이디가 :id인 라이딩정보를 삭제한다.


  - node.js & mongodb 설치되어 있음을 가정하고 node.js 모듈을 설치한다

// 모듈 설치

$ npm install express

$ npm install mongoose


  - public 디렉토리를 생성하여 img, js, css, index.html 파일을 public 디렉토리 밑으로 옮긴다 

  


  - server.js 코딩하기

// 모듈 의존성

var express = require("express"); //웹 프레임워크


// 서버를 생성한다

var app = express();


// 서버를 설정한다

app.configure(function () {

    app.use(express.bodyParser()); //요청 바디를 파싱해서 req.body를 만든다

    app.use(express.methodOverride()); //HTTP 메쏘드를 덮어쓰기 위해서 req.body를 확인한다

    app.use(app.router); //url과 HTTP 메쏘드에 기반한 라우팅을 수행한다

    app.use(express.static(__dirname + '/public')); //정적 자원을 제공하는 곳

    app.use(express.errorHandler({ dumpExceptions:true, showStack:true })); //개발시점에 모든 에러를 보여준다

});


// 서버를 구동한다

app.listen(8080, function () {

    console.log("Express server listening on port %d in %s mode", 8080, app.settings.env);

});


  - nodemon 을 통해서 구동하기 : 수정된 코딩이 있으면 자동으로 restart node 수행 (nodemon 블로깅)

nodemon server.js

15 Mar 14:37:11 - [nodemon] v0.7.2

15 Mar 14:37:11 - [nodemon] watching: ~/backbone/riding

15 Mar 14:37:11 - [nodemon] starting `node server.js`

Express server listening on port 8080 in development mode


  - MongoDB 구동하기 

// mongod 데몬 실행 

$ mongod --dbpath /mongodb/ride_database/

Fri Mar 15 15:33:19 [initandlisten] MongoDB starting : pid=50361 port=27017 dbpath=/Users/nulpulum/mongodb/ride_database/ 64-bit host=mac-42-retina.local

Fri Mar 15 15:33:19 [initandlisten] db version v2.2.3, pdfile version 4.5

... 중략 ..

Fri Mar 15 15:33:19 [websvr] admin web console waiting for connections on port 28017

Fri Mar 15 15:33:19 [initandlisten] waiting for connections on port 27017

// Node.js 서버가 구동 되어 있으면 연결 정보가 출력된다 

Fri Mar 15 15:34:27 [initandlisten] connection accepted from 127.0.0.1:61129 #1 (1 connection now open)

Fri Mar 15 15:34:27 [initandlisten] connection accepted from 127.0.0.1:61130 #2 (2 connections now open)

Fri Mar 15 15:34:27 [initandlisten] connection accepted from 127.0.0.1:61131 #3 (3 connections now open)

Fri Mar 15 15:34:27 [initandlisten] connection accepted from 127.0.0.1:61132 #4 (4 connections now open)



2) Mongoose 통하여 MongoDB 접근하기 

  - biz.js 파일을 생성하고 mongoose 코딩하기

    + Mongoose 용 schema와 model을 만든다 

    + MongoDB 에 접속한다 

    + 모듈패턴으로 업무 callback CRUD function 들을 GET, POST, PUT, DELTE 메소드를 정의한다

  - server.js 안에 RESTful API 추가하기

.. 중략 ..

// 서버를 설정한다

app.configure(function () {

    app.use(express.bodyParser()); //요청 바디를 파싱해서 req.body를 만든다

    app.use(express.methodOverride()); //HTTP 메쏘드를 덮어쓰기 위해서 req.body를 확인한다

    app.use(app.router); //url과 HTTP 메쏘드에 기반한 라우팅을 수행한다

    app.use(express.static(__dirname + '/public')); //정적 자원을 제공하는 곳

    app.use(express.errorHandler({ dumpExceptions:true, showStack:true })); //개발시점에 모든 에러를 보여준다

});


// mongoose 업무모듈인 biz.js 가져오기 

var biz = require("./biz");


// RESTful API에 대한 호출에 대하여 biz.js 모듈의 callback 펑션을 설정함

app.get( '/api/rides', biz.readAll);

app.post('/api/rides', biz.create);

app.get( '/api/rides/:id', biz.read);

app.put( '/api/rides/:id', biz.update);

app.delete('/api/rides/:id', biz.delete);


// 서버를 구동한다

app.listen(8080, function () {

    console.log("Express server listening on port %d in %s mode", 8080, app.settings.env);

});



3) RESTful API 테스트하기 

  - Chrome extention 인 "Advanced REST client" 설치한다 : Chrome Web Store에서 검색하고 설치하면 됨


  - Advanced REST client 실행

    + POST 메소드를 선택 (GET, PUT, DELETE 순으로 실행하여 본다)

    + 하단의 Form 탭을 선택하여 key=value 를 추가한다 

    + "Send" 버튼을 클릭하면 결과가 최하단에 나온다 (200 OK)

          

 - MongoDB에서 결과 조회

$ mongo

MongoDB shell version: 2.2.3

connecting to: test

> show dbs

local (empty)


// POST가 성공하면 데이터베이스가 보인다 

> show dbs

local (empty)

ride_database 0.203125GB

> use ride_database

switched to db ride_database

> show collections

rides

system.indexes

> db.rides.find().toArray();

[

{

"title" : "투어 드 코리아",

"rider" : "윤복자",

"ridingDate" : ISODate("2013-06-01T00:00:00Z"),

"keywords" : "코리아 싸이클링",

"_id" : ObjectId("5142ccbd1b06d36acc000001"),

"__v" : 0

}

]

>


  - PUT 은 URL 뒤에 MongoDB document의 _id 값인  5142ccbd1b06d36acc000001  을 붙여준다. (DELETE 도 동일)

   



4) Backbone.js 와 Node.js 연결하기 

  - 클라이언트 <-> 서버 연결

    + MongoDB사용시 : _id 지정하기 

    + url 설정 : /api/rides

    + fetch() 호출

    + reset callback으로 render 설정

  - ride.js 백본파일을 수정 : 라이딩 정보 목록 가져오기 == 컬렉션 모델에서 url 설정

   var Ride = Backbone.Model.extend({

        defaults:{

            coverImage:"img/my_cycle.png",

            title:"2011년 대관령 대회",

            rider:"Yun YoungSik",

            ridingDate:"2011",

            keywords:"대관령 힐크라이밍 대회"

        },

        idAttribute: "_id"  // MongoDB 사용시 바꾸어줌 

        // idAttribute 와 같은 효과를 같는다 

        /*parse:function (response) {

            console.log(response);

            response.id = response._id;

            return response;

        }*/

    });

.. 중략 ..  

    // rides 

   /* var rides = [{title:"대관령 힐크라이밍 대회", rider:"Yun YoungSik", ridingDate:"2010", keywords:"대관령"},

        {title:"미시령 힐크라이밍 대회", rider:"Yun DoWon", ridingDate:"2011", keywords:"미시령"},

        {title:"투어 드 코리아", rider:"Yun YoungSik", ridingDate:"2012", keywords:"코리아"}];*/

    var rides = [];   // 위의 샘플은 사용하지 않고 빈 배열을 만들어 준다 

  

    ////////////////////

    // Ride Collection

    var Rides = Backbone.Collection.extend({

        model : Ride,

        url : '/api/rides'  // 서버 호출 API

    });


    ///////////////////

    // Collection View

    var RidesView = Backbone.View.extend({

        el: $("#rides"),


        initialize: function(){

            this.collection = new Rides(rides);

            // 서버로 부터 데이터 가져오기 

            this.collection.fetch();

            this.render();

            // 컬렉션 add가 호출되면 renderRide를 trigger 한다 

            this.collection.on("add", this.renderRide, this);

            this.collection.on("remove", this.removeRide, this);

            // fetch()가 비동기적이기 때문에 Backbone이 reset을 호출하면 trigger 될 수 있도록 한다 

            this.collection.on("reset", this.render, this);  

        },

.. 중략 ..


  - 브라우져 호출 : MongoDB 의 데이터를 가져와서 Backbone이 데이터를 reset 하게 되면 입력한 라이딩정보가 출력된다 

   


  -  날짜가 이상하게 나오므로 jQuery를 이용하여 날짜를 포멧팅 해준다 

<script id="rideTemplate" type="text/template">

        <img src="<%= coverImage %>"/>

        <ul>

            <li><%= title %></li>

            <li><%= rider %></li>

            <!-- 년/월/일 만 표현 --> 

            <li><%= $.format.date(new Date(ridingDate), 'yyyy/MM/dd') %></li>

            <li><%= keywords %></li>

        </ul>

        <button class="delete">Delete</button>

    </script>

</div>

<script src="js/jquery.js"></script>

<!-- 다운로드 받아서 js 폴더에 놓는다 -->

<script src="js/jquery-dateformat.js"></script>

<script src="js/underscore.js"></script>

<script src="js/backbone.js"></script>

<script src="js/ride.js"></script>


  - 브라우져 화면에서 MongoDB로 add 하기 : Backbone의 ride.js 에서 addRide 수정

var RidesView = Backbone.View.extend({

        el: $("#rides"),


        initialize: function(){

            this.collection = new Rides(rides);

            // 서버로 부터 데이터 가져오기 

            this.collection.fetch();

            this.render();

            // 컬렉션 add가 호출되면 renderRide를 trigger 한다 

            this.collection.on("add", this.renderRide, this);

            this.collection.on("remove", this.removeRide, this);

            // fetch()가 비동기적이기 때문에 Backbone이 reset을 호출하면 trigger 될 수 있도록 한다 

            this.collection.on("reset", this.render, this);

        },


        render: function(){

            var that = this;

            _.each(this.collection.models, function(item){

                that.renderRide(item);

            }, this);

        },


        renderRide: function(item){

            var rideView = new RideView({

                model: item

            });

            this.$el.append(rideView.render().el);

        },


        addRide: function(e){

            e.preventDefault();


            var formData = {};

            // jQuery의 each로 formData key=value 객체를 만듦

            $("#addRide").children("input").each(function (i, el) {

                if ($(el).val() !== "") {

                    formData[el.id] = $(el).val();

                }

            });


            // rides 배열에 저장

            rides.push(formData);

            // 컬렉션 객체에 저장

            // 서버로 저장하기 기존 add를 create 으로 바꾸면 된다 

            this.collection.create(formData); 

            //this.collection.add(new Ride(formData));

        }, 


  - 브라우져 결과 화면 : "평택 대회" 입력

   


  - MongoDB의 결과값 : 3개의 도큐먼트가 존재한다 

> db.rides.find().toArray();

[

{

"title" : "투어 드 코리아3",

"rider" : "윤영식",

"ridingDate" : ISODate("2012-06-01T00:00:00Z"),

"keywords" : "싸이클링 코리아 우승",

"_id" : ObjectId("5142dc4ae196a916e0000001"),

"__v" : 0

},

{

"title" : "대관령 힐크라이밍 대회",

"rider" : "윤도원",

"ridingDate" : ISODate("2013-07-01T00:00:00Z"),

"keywords" : "강원도 우승목표",

"_id" : ObjectId("5142dd8cba03b734e1000001"),

"__v" : 0

},

{

"title" : "평택 대회",

"rider" : "윤복자",

"ridingDate" : ISODate("2013-03-01T00:00:00Z"),

"keywords" : "고속도로 개통기념",

"_id" : ObjectId("5142de7351b4341ee2000001"),

"__v" : 0

}

]

>


  - 원문에 포함된 날짜 포함하기와 키워드배열로 만들기는 패스~~~ ^^;


** 전체 소스 코드 

riding.zip



<참조>

  - 원문 : Backbone.js Developing 번역글

  - Node.js & Express & Mongoose ToDo 예제

  - Handlebars 홈페이지

  - Tool : Advanced Rest Client

posted by 윤영식
2013. 3. 15. 13:30 Backbone.js

Backbone.js 서점 샘플 예제를 사이클링 샘플로 변경하면서 직접 코딩해 보고 구조를 파악하자. 



1) index.html 파일 준비하기 

  - 이미지 파일 다운로드 하고 img 폴더에 넣는다 (100*152)

   


  - backbone.js, underscore.js, jquery.js 파일 다운로드 받아서 js 폴더에 넣는다 

   


  - index.html 파일을 루트에 생성한다


  - 브라우져 수행 점검 

   


  - css 폴더를 만들고 screen.css 파일을 생성한다 

css body { background-color: #eee; }

.bookContainer { border: 1px solid #aaa; width: 350px; height: 170px; background-color: #fff; float: left; margin: 5px; } .bookContainer img { float: left; margin: 10px; } .bookContainer ul { list-style-type: none; }



2) index.html 확장하기 

  - script 태그를 넣는다 

<script id="rideTemplate" type="text/template">

        <img src="<%= coverImage %>"/>

        <ul>

            <li><%= title %></li>

            <li><%= rider %></li>

            <li><%= ridingDate %></li>

            <li><%= keywords %></li>

        </ul>

    </script>

   Model의 명칭과 template의 <%= 명칭 %> 을 일치시켰다

   


  

3) ride.js 통해 Backbone 코딩하기 

  - js 폴더안에 ride.js파일을 생성하고 Model, View 클래스를 만든다 

(function ($) {

    // model 만들기

    var Ride = Backbone.Model.extend({

        defaults:{

            coverImage:"img/my_cycle.png",

            title:"2011년 대관령 대회",

            rider:"Yun YoungSik",

            ridingDate:"2011",

            keywords:"대관령 힐크라이밍 대회"

        }

    });


    // view 만들기

    var RideView = Backbone.View.extend({

        // el 변수에 html에 있는 DOM 레퍼런스를 만든다. $el은 DOM에 대한 jQuery 객체를 의미 한다  

        // html에 없는 DOM을 만들때는 tagName/className/id 등을 설정한다. 설정하지 않으면 기본 div 태그임

        // el 이란 무엇인가?

        tagName:"div",

        // screen.css 에서 사용할 css의 클래스이름 .rideContainer 

        className:"rideContainer",

        template:$("#rideTemplate").html(),


        render:function () {

            //tmpl은 JSON객체를 받아서 html을 반환하는 함수이다.

            var tmpl = _.template(this.template); 

            //jQuery html() 함수를 사용하기 위해 jQuery객체 $el을 쓴다.

            this.$el.html(tmpl(this.model.toJSON())); 

            return this;

        }

    });


})(jQuery);


  - View를 생성하고 Model를 할당한 후 render()를 호출하여 화면을 그린다 

(function ($) {

    // model 만들기

    var Ride = Backbone.Model.extend({

        defaults:{

            coverImage:"img/my_cycle.png",

            title:"2011년 대관령 대회",

            rider:"Yun YoungSik",

            ridingDate:"2011",

            keywords:"대관령 힐크라이밍 대회"

        }

    });


    // view 만들기

    var RideView = Backbone.View.extend({

        tagName:"div",

        className:"rideContainer",

        template:$("#rideTemplate").html(),


        render:function () {

        //tmpl은 JSON객체를 받아서 html을 반환하는 함수이다.

            var tmpl = _.template(this.template); 

            //this.el은 tagName에 정의된 것이다. jQuery html() 함수를 사용하기 위해서는 $el을 쓴다.

            this.$el.html(tmpl(this.model.toJSON())); 

            return this;

        }

    });


   // model, view 생성하여 view에 model 할당하기 

    var ride = new Ride({

        title:"Some title",

        rider:"Yun DoWon",

        ridingDate:"2011",

        keywords:"대관령 힐크라이밍 대회"

    });


    rideView = new RideView({

        model: ride

    });

    // 템플릿에 값을 맵핑한 DOM 을 최종적으로 넘김 

    $("#rides").html(rideView.render().el);

    

})(jQuery);


  - 브라우져 호출 결과화면 

    



4) Collection을 통해 목록 만들기 

 - Collection 클래스를 상속받아 Collection Model을 만들고 Collection을 제어할 수 있는 View도 만든다 

(function ($) {


    var Ride = Backbone.Model.extend({

        defaults:{

            coverImage:"img/my_cycle.png",

            title:"2011년 대관령 대회",

            rider:"Yun YoungSik",

            ridingDate:"2011",

            keywords:"대관령 힐크라이밍 대회"

        }

    });


    var RideView = Backbone.View.extend({

        tagName:"div",

        className:"rideContainer",

        template:$("#rideTemplate").html(),


        render:function () {

        //tmpl은 JSON객체를 받아서 html을 반환하는 함수이다.

            var tmpl = _.template(this.template); 

            //this.el은 tagName에 정의된 것이다. jQuery html() 함수를 사용하기 위해서는 $el을 쓴다.

            this.$el.html(tmpl(this.model.toJSON())); 

            return this;

        }

    });


    var ride = new Ride({

        title:"Some title",

        rider:"Yun DoWon",

        ridingDate:"2011",

        keywords:"대관령 힐크라이밍 대회"

    });


    var rideView = new RideView({

        model: ride

    });


    $("#rides").html(rideView.render().el);

    

    //////////////////////

    // rides 샘플 3개 생성

    var rides = [{title:"대관령 힐크라이밍 대회", rider:"Yun YoungSik", ridingDate:"2010", keywords:"대관령"},

        {title:"미시령 힐크라이밍 대회", rider:"Yun DoWon", ridingDate:"2011", keywords:"미시령"},

        {title:"투어 드 코리아", rider:"Yun YoungSik", ridingDate:"2012", keywords:"코리아"}];


    //////////////////////

    // Ride Collection 생성

    var Rides = Backbone.Collection.extend({

        model : Ride

    });


    //////////////////////

    // Collection View 생성

    var RidesView = Backbone.View.extend({

        el:$("#rides"),


        initialize:function(){

            this.collection = new Rides(rides);

            this.render();

        },


        // 컬렉션의 내역을 루핑을 돌고, 

        // each 메소드내에서서 함수 그자체가 this가 된다. 따라서 this 참조 안하게 that 을 설정한다 (블로깅)

        render: function(){

            var that = this;

            _.each(this.collection.models, function(item){

                that.renderRide(item);

            }, this);

        },


        // RideView 를 통해 개별적으로 렌더링한다 

        renderRide:function(item){

            var rideView = new RideView({

                model: item

            });

            this.$el.append(rideView.render().el);

        }

    });


    // new 를 하면  initialize() 가 자동 호출된다 

    var ridesView = new RidesView();


})(jQuery);


  - 호출한 결과 화면 : 기존 1개와 샘플 데이터 4개로 총 4개의 화면이 나옴 

   



5) 컬렉션에 모델을 추가하기 

  - 옷 신제품 발표 컬렉션에 옷입은 모델들을 추가하는 느낌이랄까 어감이 비슷하네요. ^^

  - 모델을 추가하기 위해서 index.html에 input tag 를 추가한다 

.. 중략 ..

<div id="rides">

    <!-- default 로 들어 있던 div 태그 제거 --> 

    <!--div class="rideContainer">

        <img src="img/my_cycle.png"/>

        <ul>

            <li>Title</li>

            <li>Rider</li>

            <li>Riding date</li>

            <li>Keywords</li>

        </ul>

    </div-->


    <!-- 새로운 카드를 넣을 수 있는 input tags --> 

    <div id="addRide">

        <label for="coverImage">CoverImage: </label><input id="coverImage" type="file" />

        <label for="title">Title: </label><input id="title" type="text" />

        <label for="rider">Rider: </label><input id="rider" type="text" />

        <label for="ridingDate">Riding date: </label><input id="ridingDate" type="text" />

        <label for="keywords">Keywords: </label><input id="keywords" type="text" />

        <button id="add">Add</button>

    </div>


    <!-- 하단의 template script 만 놓는다 --> 

    <script id="rideTemplate" type="text/template">

.. 중략 ..


  - screen.css 파일에 하기 내역을 추가한다 

css body { background-color: #eee; }


.rideContainer { border: 1px solid #aaa; width: 350px; height: 170px; background-color: #fff; float: left; margin: 5px; } .rideContainer img { float: left; margin: 10px; } .rideContainer ul { list-style-type: none; }


#addRide label {

    width:100px;

    margin-right:10px;

    text-align:right;

    line-height:25px;

}


#addRide label, #addRide input {

    display:block;

    margin-bottom:10px;

    float:left;

}


#addRide label[for="title"], #addRide label[for="ridingDate"] {

    clear:both;

}


#addRide button {

    display:block;

    margin:5px 20px 10px 10px;

    float: right;

    clear: both;

}


#addRide div {

    width: 550px;

}


#addRide div:after {

    content:"";

    display:block;

    height:0;

    visibility:hidden;

    clear:both;

    font-size:0;

    line-height:0;

}


  - ride.js 파일에서 RidesView 안의 renderRide 메소드 밑으로 addRide 메소드를 추가한다

// 테스트로 한개 넣었던 코드에 대해서 주석처리한다.

/*  var ride = new Ride({

        title:"No title",

        rider:"Unknown",

        ridingDate:"Unknown",

        keywords:"empty"

    });


    var rideView = new RideView({

        model: ride

    });


    $("#rides").html(rideView.render().el);  */

    

.. 중략 ..

        renderRide:function(item){

            var rideView = new RideView({

                model: item

            });

            this.$el.append(rideView.render().el);

        },


        addRide: function(e){

      e.preventDefault();


            var formData = {};

      // jQuery의 each로 formData key=value 객체를 만듦

             $("#addRide").children("input").each(function (i, el) {

                if ($(el).val() !== "") {

                    formData[el.id] = $(el).val();

                }

            });


            // rides 배열에 저장

            //rides.push(formData);

            // 컬렉션 객체에 저장 

            this.collection.add(new Ride(formData));

        }, 

        

        // add버튼 누를때 이벤트 발생하여 addRide 메소드 호출

        events:{

            "click #add": "addRide"

        }


    });


  - 컬렉션에 모델이 추가되는 add 메소드 호출시 이벤트 발생토록 함

var RidesView = Backbone.View.extend({

        el:$("#rides"),


        initialize:function(){

            this.collection = new Rides(rides);

            this.render();


// initailize 초기화일 때 자동으로 trigger 될 수 있도록 설정을 합니다 

            this.collection.on("add", this.renderRide, this);

        },


  - 실행하기 

$ static 

serving "." at http://127.0.0.1:8080

11:16:58 [200]: /

11:16:58 [200]: /css/screen.css

11:16:58 [200]: /js/ride.js

11:16:58 [200]: /js/underscore.js

11:16:58 [200]: /js/backbone.js

11:16:58 [200]: /img/my_cycle.png

11:16:58 [200]: /js/jquery.js

11:16:58 [404]: /favicon.ico


    + http://localhost:8080 호출 한 경우

   

    + 값을 입력하고 "Add" 클릭하였을 때 (평택 대회)

   



6) 모델 삭제하기 

  - index.html안에 삭제 버튼을 넣는다 

 <script id="rideTemplate" type="text/template">

        <img src="<%= coverImage %>"/>

        <ul>

            <li><%= title %></li>

            <li><%= rider %></li>

            <li><%= ridingDate %></li>

            <li><%= keywords %></li>

        </ul>

        <button class="delete">Delete</button>

    </script>


  - button에 대한 screen.css 를 조정한다 

.rideContainer ul {

    list-style-type: none;

    margin-bottom: 0;

}


.rideContainer button {

    float:right;

    margin: 10px;

}


  - 개별 모델에 delete button이 놓인다

   


  - 삭제 로직을 RideView 에 코딩한다 : 삭제 메소드 -> 이벤트 등록

     var RideView = Backbone.View.extend({

        tagName:"div",

        className:"rideContainer",

        template:$("#rideTemplate").html(),


        render:function () {

         //tmpl은 JSON객체를 받아서 html을 반환하는 함수이다.

            var tmpl = _.template(this.template); 

            //this.el은 tagName에 정의된 것이다. jQuery html() 함수를 사용하기 위해서는 $el을 쓴다.

            this.$el.html(tmpl(this.model.toJSON())); 

            return this;

        }, 


        events: {

            "click .delete": "deleteRide"

        },


        deleteRide:function () {

            //모델을 삭제한다.

            this.model.destroy();


            //뷰를 삭제한다.

            this.remove();

        }

    });



7) 컬렉션 모델 삭제하기

  - rides 배열에는 아직 그대로 데이터가 존재한다. 이를 삭제하기 위하여 먼저 remove 이벤트의 trigger를 RidesView에 등록한다

var RidesView = Backbone.View.extend({

        el: $("#rides"),


        initialize: function(){

            this.collection = new Rides(rides);

            this.render();

            // 컬렉션 add가 호출되면 renderRide를 trigger 한다 

            this.collection.on("add", this.renderRide, this);

            this.collection.on("remove", this.removeRide, this);

        },


        render: function(){

            var that = this;

            _.each(this.collection.models, function(item){

                that.renderRide(item);

            }, this);

        },


        renderRide: function(item){

            var rideView = new RideView({

                model: item

            });

            this.$el.append(rideView.render().el);

        },


        addRide: function(e){

            e.preventDefault();


            var formData = {};

            // jQuery의 each로 formData key=value 객체를 만듦

            $("#addRide").children("input").each(function (i, el) {

                if ($(el).val() !== "") {

                    formData[el.id] = $(el).val();

                }

            });


            // rides 배열에 저장

            rides.push(formData);

            // 컬렉션 객체에 저장 

            this.collection.add(new Ride(formData));

        }, 


        // 삭제된 모델을 인자로 자동 넣어준다 

        removeRide: function(removedRide){

            // attributes는 Model의 hash key=value object 이다 

            var removedRideData = removedRide.attributes;


            _.each(removedRideData, function(val, key){

                if(removedRideData[key] === removedRide.defaults[key]){

                    console.log(">> 1 : " + removedRideData[key]);

                    delete removedRideData[key];

                }

            });


            _.each(rides, function(ride){

                if(_.isEqual(ride, removedRideData)){

                    console.log(">> 2 : " + ride);

                    rides.splice(_.indexOf(rides, ride), 1);

                }

            });

        },

        

        // add버튼 누를때 이벤트 발생하여 addRide 메소드 호출

        events:{

            "click #add": "addRide"

        }

    });


  - 삭제버튼을 클릭하면 삭제가 잘 된다 



* 지금까지의 소스 

riding.zip



<참조>

  - 원문 : Backbone.js Developing 번역글

posted by 윤영식
2013. 3. 12. 13:55 Backbone.js

MVC 프레임워크를 사용하는 이유는 마틴 파울러 아저씨의 주장처럼 Clean Code를 생산하기 위해서이다. 클린 코드가 되면 클라이언트 입장에서 events와 callbacks의 유지보수 용이성이 높아지고, Testing이 쉬워진다. Backbone.js 는 그 중에서도 가장 간결하면서 여러 Third part 모듈을 붙일 수 있어서 확장성이 높다. 그러나 Backbone.js 만을 가지고 큰 규모의 확장성 있는 애플리케이션을 만들기에는 부족함이 존재한다. 이를 해결할 수 있는 방법을 알아보자 


1) MV* Framework : Backbone.js 

  - Selector : jQuery, YUI 등 선택

  - Template : Handlebar, Mustache 등 선택

  - MVC

    + Model : Data 유효성검사, 서버로부터 가져오고, 여러 화면에 공유하는 백본의 핵심 - Collection 개념으로 확장

    + View : 데이터의 표현, Cotroller 역할 하지 않고  EventListener를 통하여 Controller에게 위임한다 

    + Controller :  백본에선 진정한 Controller 는 없다. View & Router 가 controller와 유사하고, Router는 Model 이벤트의 요청에 대해 View 응답을 갖는다 

  - 백본은 SPA(Single Page Applications) 개발을 위한 기본적인 추상화 레이어(Abstraction Layer)일 뿐이다. 모든 것을 제공하진 않으니 필요한 요소를 잘 선택하여 모듈화해야 한다 



2) 좀 더 복잡한 SPA 개발하기 

  - 백본을 기반으로 Large Scale Application 개발을 위한 프레임워크들

  - http://chaplinjs.org/ 

  - http://thoraxjs.org/ : Backbone.js & Handlebar 

  - http://marionettejs.com/

  - Backbone.js LayoutManager

  - Aura : 위젯기반 프레임워크 



3) 채플린 배우기 

  - 백본은 저수준의 프레임워크이기 때문에 잘 구조화해서 사용하는 방법을 알아야 한다 

  - 백본 예제의 To do list 는 SPA 라고 할 수 없으며 좀 더 규모가 큰 구조화된 애플리케이션 개발의 가이드가 될 수 없다 

  - 예제 : 채플린 기반 Real world application 인 moviepilot

  - GitHub 저장소

  - 의존성 (SmartSolution에서 사용할 기준)

    + Backbone.js

    + Underscore.js

    + jQuery

    + Handlebar.js 

    + AMD (Requires.js)

    + CoffeeScript

  - 채플린 Boilerplate 소스



4) 채플린 Boilerplate 소스 돌려보기 

  - Git clone

$ git clone https://github.com/chaplinjs/chaplin-boilerplate.git

Cloning into 'chaplin-boilerplate'...

remote: Counting objects: 263, done.

remote: Compressing objects: 100% (204/204), done.

remote: Total 263 (delta 135), reused 178 (delta 50)

Receiving objects: 100% (263/263), 302.91 KiB | 148 KiB/s, done.

Resolving deltas: 100% (135/135), done.


  - static 수행 (블로깅)

static

serving "." at http://127.0.0.1:8080


// 브라우져 호출


// 브라우져 호출후 static console 출력 내용 

11:28:08 [200]: /

11:28:08 [200]: /js/vendor/require-2.1.1.js

11:28:08 [200]: /js/hello_world_application.js

11:28:08 [200]: /js/routes.js

11:28:08 [200]: /js/vendor/chaplin-0.6.0.js

11:28:08 [200]: /js/views/layout.js

11:28:08 [200]: /js/vendor/jquery-1.8.3.js

11:28:08 [200]: /js/vendor/underscore-1.4.3.js

11:28:09 [200]: /js/vendor/backbone-0.9.9.js

11:28:09 [200]: /js/controllers/hello_world_controller.js

11:28:09 [200]: /js/models/hello_world.js

11:28:09 [200]: /js/views/hello_world_view.js

11:28:09 [200]: /js/models/base/model.js

11:28:09 [200]: /js/views/base/view.js

11:28:09 [200]: /js/vendor/require-text-2.0.3.js

11:28:09 [200]: /js/lib/view_helper.js

11:28:09 [200]: /js/templates/hello_world.hbs

11:28:09 [200]: /js/vendor/handlebars-1.0.rc.1.js

11:28:09 [200]: /js/lib/utils.js

11:28:09 [404]: /favicon.ico


  - .coffee 소스 수정시 컴파일 방법

    + /coffee 디렉토리에 위치 /js 컴파일된 파일 

    + 컴파일 : coffee --bare --output js/ coffee/


  - 구조파악 

    + models, views, controllers, lib, vendor  디렉토리

    + vendor : backbone.js, jquery.js 등의 모듈 

    + views : Handlebar 템플릿 사용 -> js/templates/hello_world.hbs 

    + index.html : AMD 모듈 관리 (블로깅) -> coffee/hello_world_application.coffee 메인

   


5) 마리오네트

  - 다음 블로깅에서 마리오네트를 살펴보자 : 좀 더 활발하게 활동하고 있다는데 채플린 또는 마리오네트 아니면 코너스톤중 택일



<참조>

  - 채플린 (Chaplin)

  - JavaScript Framework 별 To Do List

  - 예제 : 와인셀러 (블러깅)

  - 채플린 Boilerplate 소스

  - 코너스톤 팀의 Backbone & CSS 를 위한 프레임워크 고도화


posted by 윤영식
2013. 3. 11. 17:13 Backbone.js

Backbone.js를 익히기 전에 Underscore에 대해서 알아보자.



1) Underscore

  - Functional Programming을 위한 자바스크립트 유틸리티 라이브러리 

  - 80 개의 펑션을 가지고 있다 

  - Groc 포멧의 소스코드 보기

  - GitHub Underscore.js

  - var _  변수 사용



2) 테스트 돌려보기 

  - GitHub에서 다운로드 : git clone https://github.com/documentcloud/underscore.git

  - package.json 내역

{

  "name" : "underscore",
  "description" : "JavaScript's functional programming helper library.",
  "homepage" : "http://underscorejs.org",
  "keywords" : ["util", "functional", "server", "client", "browser"],
  "author" : "Jeremy Ashkenas <jeremy@documentcloud.org>",
  "repository" : {"type": "git", "url": "git://github.com/documentcloud/underscore.git"},
  "main" : "underscore.js",
  "version" : "1.4.4",
  "devDependencies": {
    "phantomjs": "1.8.1-3"
  },
  "scripts": {
    "test": "phantomjs test/vendor/runner.js test/index.html?noglobals=true"
  }
}


  - scripts 의 test 에서 phantomjs 사용함 : 참조에서 phantomjs를 설치한다 (웹브라우져 없이 테스트시 많이 사용)

    + phantomjs 를 이용한 테스트 코드 작성 연구에 도움이 되겠다 

    + 테스트 코드는 underscore/test 디렉토리에 존재함 

$ npm test


> underscore@1.4.4 test /Users/nulpulum/git-repositories/underscore

> phantomjs test/vendor/runner.js test/index.html?noglobals=true


2013-03-11 13:58:26.100 phantomjs[32711:f07] *** WARNING: Method userSpaceScaleFactor in class NSView is deprecated on 10.7 and later. It should not be used in new applications. Use convertRectToBacking: instead.

Took 2309ms to run 588 tests. 588 passed, 0 failed.

  


3) Collection Functions

  - 테스트 코드

    + 모듈 설치하기 : npm install underscore 

  - 테스트 결과 

//////////////////////////////////////

// 샘플 코드 짜고 node 로 결과 확인하기 

$ node collection.js

-------each  : 개별 요소를 지정한 function에서 수행

1 0 [ 1, 2, 3 ]

2 1 [ 1, 2, 3 ]

3 2 [ 1, 2, 3 ]

-------map : 각 요소를 지정한 callback에서 수행

3

6

9

-------reduce : 리듀싱

6

-------find : callback에서 찾는 즉시 break

2

-------filter : callback에 매칭되는 것만 

[ 2, 4, 6 ]

-------reject : callback에 매칭되지 않는 것만 

[ 1, 3, 5 ]

-------every : 모두가 지정한 값일 경우 참

false

-------some : 하나라도 지정한 값일 경우 참

true

-------contains : 포함하면 참

true

-------invoke : 펑션 호출 

[ [ 1, 5, 7 ], [ 1, 2, 3 ] ]

-------pluck : 지정한 프러퍼티만 뽑아냄

[ 'dowon', 'haha', 'youngsik' ]

-------max : 지정한 프로퍼티중 최대인 것

{ name: 'youngsik', age: 99 }

-------min : 지정한 플터피중 최소인 것 

3

-------groupBy : 그룹핑 

{ '1': [ 1.3, 1.9 ], '2': [ 2.1, 2.4 ] }

-------countBy : callback 계산된 그룹핑 건수 

{ odd: 3, even: 2 }

-------toArray : 배열 리턴

[ 2, 3, 4 ]

-------size : 크기 

3

** 기타 몇가지 테스트 안한 API : where, findWhere, sortBy, shuffle 등



4) Array Functions

  - 테스트 코드 

  - 테스트 결과

////////////////////////////////////////////////////////////////

// 테스트 코드를 작성하면서 nodemon 을 통하여 결과값을 확인한다

$ nodemon array.js

------first : 배열 첫번째 요소

5

------initial : 마지막 요소 제외한 배열

[ 5, 4, 3, 2 ]

------last : 배열 마지막 요소 

1

------rest : 첫번째 요소 제외한 요소 

[ 4, 3, 2, 1 ]

------compact : '' 0 false null등을 제외한 요소만 

[ 4, 2 ]

------flatten : 1차원 배열로

[ 1, 2, 3, 4 ]

------without : 지정한 요소를 제거한 배열 

[ 1, 1, 3, 4 ]

------union : 중복 제거 

[ 1, 2, 3, 101, 11 ]

------intersection : 서로 중복되는 것만 

[ 1, 2 ]

------uniq : 유니크한 것만 

[ 1, 2, 3, 4, 5 ]

------zip : 각 배열의 요소끼리 2차원배열

[ [ 'a', 1, true ], [ 'b', 2, false ], [ 'c', 4, true ] ]

------object : 2개 배열의 요소를 javascript obejct형식의 {key:value}로 변환

{ a: 1, b: 2, c: 4 }

------indexOf : 앞에서 부터 지정한 위치

2

------lastIndexOf : 뒤에서 부터 지정한 위치

4

------sortedIndex : 지정한 값이 들어갈 위치 

2

------range : 지정한 길이 만큼 배열 만들기 

[ 0, 1, 2, 3 ]



5) Object Functions

  - 테스트 코드

  - 테스트 결과 

//////////////////////////////////////////////////////

// coffee-script 로 작성하고 compile과 watch 옵션을 준다 

$ coffee --compile --watch object.coffee


//////////////////////////////////////////////////////

// nodemon 으로 컴파일된 object.js 를 모니터링한다 

$ nodemon object.js 

11 Mar 16:40:06 - [nodemon] app crashed - waiting for file changes before starting...

11 Mar 16:40:14 - [nodemon] restarting due to changes...

11 Mar 16:40:14 - [nodemon] /Users/nulpulum/development/underscore/test/object.js


11 Mar 16:40:14 - [nodemon] starting `node object.js`

--------keys : 오브젝트의 키만

[ 'one', 'two', 'three' ]

--------values : 오브젝트의 값만

[ 1, 2, 3 ]

--------pairs : 각 오브젝트를 key:value 형태로 

[ [ 'one', 1 ], [ 'two', 2 ], [ 'three', 3 ] ]

--------invert : key:value를 거꾸로 

{ '1': 'one', '2': 'two', '3': 'three' }

--------functions : 객체의 모든 펑션 

[ '_',

  'after',

  'all',

  'any',

  'bind',

  'bindAll',

  'chain',

  'clone',

  'collect',

  'compact',

  'compose',

  'contains',

  'countBy',

  'debounce',

  'defaults',

  'defer',

  'delay',

  'detect',

  'difference',

  'drop',

  'each',

  'escape',

  'every',

  'extend',

  'filter',

  'find',

  'findWhere',

  'first',

  'flatten',

  'foldl',

  'foldr',

  'forEach',

  'functions',

  'groupBy',

  'has',

  'head',

  'identity',

  'include',

  'indexOf',

  'initial',

  'inject',

  'intersection',

  'invert',

  'invoke',

  'isArguments',

  'isArray',

  'isBoolean',

  'isDate',

  'isElement',

  'isEmpty',

  'isEqual',

  'isFinite',

  'isFunction',

  'isNaN',

  'isNull',

  'isNumber',

  'isObject',

  'isRegExp',

  'isString',

  'isUndefined',

  'keys',

  'last',

  'lastIndexOf',

  'map',

  'max',

  'memoize',

  'methods',

  'min',

  'mixin',

  'noConflict',

  'object',

  'omit',

  'once',

  'pairs',

  'partial',

  'pick',

  'pluck',

  'random',

  'range',

  'reduce',

  'reduceRight',

  'reject',

  'rest',

  'result',

  'select',

  'shuffle',

  'size',

  'some',

  'sortBy',

  'sortedIndex',

  'tail',

  'take',

  'tap',

  'template',

  'throttle',

  'times',

  'toArray',

  'unescape',

  'union',

  'uniq',

  'unique',

  'uniqueId',

  'values',

  'where',

  'without',

  'wrap',

  'zip' ]

--------extend : 두객체를 합침

{ name: 'dowon', age: 33 }

--------pick : 원하는 것만 끄집어 냄

{ age: 33 }

--------omit : 원하는 것을 삭제함 

{ name: 'dowon' }

--------defaults : 기본 값으로 대체 

{ flavor: 'chocolate', orange: 'lots' }

--------clone : 객체 복제하기 

{ name: 'dowon', age: 33 }

--------tap : 오브젝트 필터링시에 사용 

[ 2, 400 ]

--------has : 키가 존재하는지 체크 

true


** 그외  is** 관련 펑션이 존재함 (true/false 결과 리턴)



6) Utility Functions

  - 테스트 코드

  - 테스트 결과 

$ coffee --compile --watch utility.coffee

$ nodemon utility.js

1 Mar 17:07:19 - [nodemon] clean exit - waiting for changes before restart

11 Mar 17:08:06 - [nodemon] restarting due to changes...

11 Mar 17:08:06 - [nodemon] /Users/nulpulum/development/underscore/test/utility.js


11 Mar 17:08:06 - [nodemon] starting `node utility.js`

------noConflict : _ 언더바가 다른 모듈과 쫑나면 변경가능 

[Function]

------identity : 복사 

{ name: 'dowon' }

------times : 횟수만큼 수행

0

1

2

------random : min~max 사이값 랜덤 생성 

10

------mixin : 모듈에 펑션을 확장시킴

Youngsik

------uniqueId : 유니크 아이디를 만들어 줌

dowon_1

------escape 

dowon &amp; young &gt; hi &lt;

------unescape

dowon & young > hi <

------result : 지정된 키의 값 반환

hi

------template : 템플릿 엔진 <% ... %> 기본, {{ ... }} 등으로 변경가능

hello: dowon


------chain : 객첵를 여러 메소드에 걸쳐서 수행하고 싶을 경우 최초에 한번에 넣는다 

youngsik is 22


// 1) 과 2)는 동일한 표현이다 

1) _.map([1, 2, 3], function(n){ return n * 2; });

2) _([1, 2, 3]).map(function(n){ return n * 2; });



<참조>

  - Underscore.js 테스트 코드

  - PhantomJS 설치하기

  - package.json의 test 스크립트 구동하기 (Testing)


posted by 윤영식
2013. 2. 20. 17:01 Backbone.js

프론트앤드 자바스크립트 프레임워크의 시작은 백본의 이해가 기본이라고 생각합니다. 어떻게 배워야 할지 알아보죠


1) 이럴 때 사용하자 

  - modern single page web application 개발시 사용

  - 다른 최신의 (meteor.js, derby.js, ember.js)를 시작하기 이전에 기본으로 배우자 

  - 자바스크립트 MVC or MV* 프레임워크에 대해서 잘 이해하고 쓰자

  - Node.js와 궁합 잘 맞음 


2) 백본 기초 다지기

  - Developing Backbone.js Applications 번역서로 시작하자


3) 마스터 하기 

  - 자바스크립트를 먼저 익혀야 한다

  - 자바스크립트 생기초 개념

    + JavaScript Objects in Detail

    + JavaScript Variable Scope and Hoisting Explained

    + Understand JavaScript Closure with ease : 꼭 읽자. 설명 잘 했다

  - Model, View, Router, Collections에 대한 개념 정리

    + 모델이란

    + 뷰란

    + 라우터란

    + 컬렉션이란

  - 백본사용하여 개발된 예제 Wine Cellar 예제 시작하기 : Part 1

  - Developing Backbone.js Applications 복습 ^^;

  - 백본관련 블로깅 글 2개 읽기 

    + 백본의 MV* 각 역할에 대한 이해

    + 백본 시작하기 3단계

  - Wine Cellar CRUD 이해하기 : Part 2

  - Wine Cellar 어플리케이션 성능 개선 및 배울점 : Part 3 (꼭 읽자)

  - 마무리 블로깅 꼭 읽자 

    + 뷰와 서브뷰간의 통신 방법 이해하기1, 이해하기2

    + 페이지 변경 제어 이해하기1, 이해하기2


전체 하는데 30시간 정도 투자하시라 (하루 3시간씩 2주면 되겠네요)


4) 마무리

  - 이제 Node.js 를 배워보자 

  - 백본과 노드에 대해서 개념 잡았으면 현대적인 웹앱만들 준비 끝~~~

  - 실제 구현사례는 http://dailyjs.com/web-app.html 여기서 틈틈히 살펴보자 


5) 기타

  - 핸들바 템플릿엔진에 대한 이해

  - 백본 확장, 백본 Grunt, Unit Testing 방법등을 알아본다



<참조>

  - 원문 : http://javascriptissexy.com/learn-backbone-js-completely/

posted by 윤영식
2013. 1. 10. 23:33 Backbone.js

1) Backbone의 탄생

  - Clean Code를 위한 UI 단의 MVC Framework 

  - Single Page Application (SPA)를 구현하는 가벼운 Framework


2) Backbone과 함께 협력하는 모듈들

  - Backbone과 함께 쓸 수 있는 모듈은?

  - 프로젝트 구조는?

  - Underscore.js, jQuery 의존. MIT 라이센스


3) Backbone의 아키텍쳐 

  - DOM UI + View(Template) + Model(Collection) 동작방식 및 설명

  - Large Scale Application 개발시 사용하라

  - Backbone으로 만들어 보는 Chaplin : https://github.com/chaplinjs/chaplin (Real-World Single-page application)

  - Chaplin에 대한 설명 : 다양한 패턴을 사용한다


posted by 윤영식
2012. 12. 29. 11:36 Backbone.js

View단위 MVC Framework으로 알려진 몇가지에 대해서 알아보자. MOVE solution으로 M=Model, O=Operation, VE=View 이다. 특히 View는 Logical DOM을 핸들링하게(Stream을 타고 즉, 동적으로 Storage지까지 가는 것이다. Stream=Function 이다.) 되고 MVC의 Controller 역할을 하게 된다. Operation은 단순 서비스/이벤트 역할이다. 대표적인 프레임워크가 BackBone이다. 

HTML -> Dynamic -> WebApp 그 정점에 SPA(Single Page Application)이 존재하고, 결국 최종 종착점은 고객에게 서비스하기 위한 View 기술이다. 여기에 필요한 전체 구조는 BackBone + Node.js + MongoDB 라고 보면되고, 이들은 Stream(Dynamic)하게 그러면서 Functional로 연력되어 Schemaless하게 움직이게 된다.



1) 전체 레밸의 MVC Framework

  - 브라우저 : BackBone.js

  - 서버 : Node.js위에서 구동되는 Express.js

  - 스토리지 : MongoDB위의 Mongoos 

* Node.js로 구성할 수 있는 Web App의 구성도



> BackBone.js 


> Ember.js


> Angular.js


3가지의 장단점에 대해서 검토해 보자 (다음번에 구체적으로 정리) O;TL

posted by 윤영식
prev 1 next