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

Publication

Category

Recent Post

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. 16. 12:34 NodeJS/Modules

Node.js에서 모듈을 사용하는 방법과 브라우져에서 SPA(Singe page application) 기반으로 개발을 진행할 때 Javascript 모듈의 의존성 관계 관리를 위한 모듈 사용방법은 차이가 있다. 각각에 대해 간단히 알아보자 


1) Node.js 모듈 

  - CommonJS 패턴을 사용한다 : Synchronous 로딩이다 

  - require('dowon') 방식으로 module을 로딩한다 

  - module.exports 를 통하여 functionality를 노출시킨다. 모듈이라는 컴포넌트를 사용하기 위한 외부 Interface를 지정하는 것이다.


  - calc.js 모듈 파일 

var Calc = function(start) {
	var that = this;
	
	this.add = function(x) {
		start = start +x;
		return that;
	};

	this.multiply = function(x) {
		start = start * x;
		return that;
	};

	this.equals = function(callback) {
		callback(start);
		return that;
	};
}
// module.exports를 사용한다 
module.exports = {
	add: function(x, y) {
		return new Calc(x).add(y || 0);
	},
	multiply: function(x, y) {
		return new Calc(x).multiply(y || 1);	
	}
}


- app.js 모듈 파일 

// require를 호출하여 모듈 즉, 컴포넌트를 로딩한다
var Calc = require('./calc.js');

// 모듈에서 외부로 노출시킨 API를 호출한다
Calc.add(1, 2)
	.multiply(3)
	.equals(function (result) {
		console.log(result);
	});



2) 브라우져 기반 모듈

  - Require.js 를 -AMD(Asynchronous Module Definition)- 사용한다 : Asynchronous 로딩이다

  - CommonJS와 약간의 차이가 있다

  - require 를 통해 dependencies와 callback을 얻는다 : 주로 호출하는 Main 입장의 application에서 사용할 때 

  - define 을 통해 dependencies를 얻고 API를 노출한다 : 개별 모듈을 정의할 때  

  - RequireJS사용하기 블로그

  - 제품 리스트를 보여주는 메인 소스 

//require 사용
require(['jquery', './big-cart'], function($, bigCart) {  <=== big-cart.js 의 펑션을 호출한다. 즉, big-car.js에 의존관계
	$(document).ready(function() {
		bigCart.init();
	});
})


  - 쇼핑 카트 

// define 사용하여 정의
define(['./pubsub', 'jquery'], function(pubsub, $) {  <=== pubsub.js 의 펑션을 호출한다. 즉, pubsub.js에 의존관계
	var cart, count = 0;

	pubsub.sub('add-to-cart', function(itme) {
		count++;

		cart.find('h1').html(count);

		var li = $('< li >')
			.html(item.name)
			.data('key', item.id);

		cart.find('ul').append(li);

	});

	pubsub.sub('remove-from-cart', function(item) {
		count--;

		cart.find('h1').html(count);
		cart.find('li').filter(function() {
			return $(this).data('key') == item.id;
		}).remove();

	});

	return {
		init: function() {
			cart = $('.big-cart');
		}
	}
});


  - 카트 내역 관리 모델 

define(function() {  <=== 의존하는 것이 없다 
	var cache = {};
	return {
		pub: function(id) {
			var args = [].slice.call(arguments, 1);
			if(!cache[id]) {
				cache[id] = [];
			}

			for(var i=0, il=cache[i].length; i -1) {
					cache[id] = cache[id].slice(0, index).concat(cache[id].slice(index+1));
				}
			}
		}
	}
});



3) CommonJS와 Require.js 개념 정리


4) AMD 개념


5) AMD 동영상
  - 하나의 자바스크립트 파일의 내역을 재사용할 수 있도록 - DRY (Don't Repeatly Yourself) - 모듈로 분리함
    + 첫째는 단순히 <script> 태그를 사용하여 개발자가 순서대로 넣는 호랑이 담배피던 시절의 방식
    + 둘째는 define을 사용하여 모듈라이제이션하고 사용시에 require 한다 
  - part01
 


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 윤영식