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

Publication

Category

Recent Post

2016. 2. 18. 18:02 Angular/Concept

프론트앤드 자바스크립트 개발이 점점 복잡해 짐에 따라 모듈 패턴으로 코드를 작성하고 단일 책임 원칙(Single Responsibility Principle)을 지키는 것이 좋다. 모듈 코드를 작성한 후 모듈을 로딩하고 배포(번들링)하는 다양한 방법들이 존재한다. 먼저 Module Loader에 대해 살펴보고 다음 글에서 Module Bundler에 대해 정리해 본다. 







모듈 패턴


모듈패턴을 사용하는 이유

  - 유지보수성(Maintainability): 단일 책임 원칙에 따라 필요한 기능을 담고 있으면서 별도 폴더와 파일로 유지하면 변경이나 확장 발생시 찾고 수정하기 쉽다. 전제 조건은 외부에 노출하는 API를 일관되게 유지하는 것이 중요하다. 

  - 이름공간(Namespacing): 자바스크립트에서 전역변수를 통한 개발을 하지 않는다. 이를 위해 즉시실행함수표현(IIFE)를 사용하여 전역변수의 오염을 방지하는데, 모듈 패턴 또한 전역변수 오명을 방지한다. 

  - 재사용성(Resuability): 모듈의 성격을 잘 나누어 놓으면 다음 프로젝트에서 그대로 사용해 쓸 수 있다. 보통 SDK나 Base Framework을 만들어 놓으면 초기 구축 비용을 최소로 할 수 있다. 


JohnPapa Angular 스타일 가이드를 보면 특별히 모듈을 지원하는 라이브러리의 도움없이 자바스크립트 모듈 패턴 방식으로 앵귤러 v1 코드를 작성토록 가이드하고 있고, 앵귤러 팀에서도 공식적으로 추천하고 있다. 

(function () {

    'use strict';


    angular

        .module('a3.common.action')

        .factory('currentAction', currentAction);


    /* @ngInject */

    function currentAction(ActionType, stateManager) {

        return {

            setDashboard: setDashboard,

            setWorkspace: setWorkspace

        };


        function setDashboard(dashboardId) {

            var action = {

                ...

            };

            stateManager.dispatch(action);

        }


        function setWorkspace(workspaceId, taskerId) {

            var action = {

                ...

            };

            stateManager.dispatch(action);

        }

    }

})();


순수 자바스크립트로 모듈 단위로 만든 후 상호 운영은 어떻게 해야할까? 일단 index.html에 설정을 하고 사용하는 순서에 index.html에 script 태그를 통해 로딩을 하는 간단한 방식을 생각해 볼 수 있다. 하지만 필요한 시점에 자바스크립트에서 로딩을 해서 사용하는 방식을 명시적으로 하려면 별도 로더의 도움이 필요하다. 




CommonJS & AMD & UMD


CommonJS는 지정한 코드를 동기적으로 로딩하는 방식으로 서버 사이드의 Node.js에서 사용한다. 

  - Object 만을 대상으로 한다.

  - module.exports 구문으로 Object를 export 한다

  - require 구문으로 Object를 import 한다. 

// 파일명: module.js 

function module() {

  this.hi = function () { return 'hi'; }

}

module.exports = module;



// 사용하는 파일: test.js

var module = require('module');

var m = new module();

m.hi();


위에서 require를 차례로 호출하면 동기적으로 하나씩 로딩을 한다. 즉, 비동기적이지 않기 때문에 로딩이 전부 되어야 수행이 된다. 브라우져에서 동기적으로 모듈 파일을 로딩하게 되면 모든 파일이 로딩된 후 화면이 실행되므로 성능 이슈를 야기할 수 있다. 따라서 CommonJS는 Node.js에서 주로 사용하고 브라우져에서는 사용하지 않는다. 


AMD(Asynchronous Module Definition)은 비동적으로 모듈을 로딩한다. 

  - Object, function, constructor, string, JSON 등 다른 타입들도 로딩이 가능한다. 

  - define 구문을 사용한다.

 define(['jquery', 'angular'], function($, angular) { 

   ...

 });


jquery, angular 파일에 대해 비동기적으로 로딩한다. AMD대표적 구현체로는 RequireJS가 있다. 


UMD(Universal Module Definition)은 AMD와 CommonJS의 기능을 둘다 지원하는 것이다. 

  - AMD, CommonJS를 고려한다 

  - 구현체로 SystemJS를 들 수 있다. SystemJS는 Universal dynamic module loader로 AMD, CommonJS뿐만 아니라 브라우져의 global scripts와 NodeJS 패키지 로딩을 하고 Traceur 또는 Babel 과 같이 작동할 수도 있다. 특히, Angular 2에서 사용한다.

  - 아래와 같이 CommonJS와 AMD를 체크하여 사용할 수도 있다. 구현 방식에 대한 다양한 예를 참조한다. 

(function (d3, jQuery) {

    'use strict';

   var Sankey2 = { ... };

   ....


    // Support AMD

    if (typeof define === 'function' && define.amd) {

        define('Sankey2', ['d3'], Sankey2);

    } 

   // Support CommonJS

   else if ('undefined' !== typeof exports && 'undefined' !== typeof module) {

        module.exports = Sankey2;

    } 

   // Support window

   else {

        window.Sankey2 = Sankey2;

    }


})(window.d3, window.$);


브라우져에서 ES6 module loader가 아닌 SystemJS (Universal module loader)를 사용할 경우 System.import 호출로 AMD, CommonJS, ES6 모듈 형식을 로딩할 수 있게 API를 제공하고  패키지 메니져로 JSPM을 사용할 수도 있다. JSPM은 무저항 브라우져용 모듈 패키지 메니져 (frictionless browser package management)로써 ES6 module loader가 작동하지 않는 곳에서 사용하는 Polyfill 이면서 AMD, CommonJS, Globals 자바스크립트 모듈 형식을 로딩할 수 있다. 



Native JS


자바스크립트 ES2015 (ES6)에서 모듈 로더를 공식지원한다. ES2015는 모듈의 importing과 exporting을 제공한다. (참조) 간결관 syntax와 비동기 로딩과 cyclic dependencies에 대해 보다 잘 지원을 한다. 

  - import, export 를 사용한다. 

// app.js 

export let count = 1;


export function hi() {

  return 'hi-' + count++;

}


// test.js

import * as app from './app';


console.log(app.hi());

console.log(app.count);



모듈로더에 대해 정리를 해보자. 자바스크립트 개발시 모듈 패턴에 입각하여 개발할 때 다양한 방식을 사용할 수 있으나

  - NodeJS 기반 서버 사이드 개발은 CommonJS 이고

  - Browser 기반 클라이언트 사이드 개발은 AMD구현체 중 하나인 RequireJS 를 사용한다. 


ppt에서 r.js는 RequireJS에서 제공하는 모듈 번들러이다. 모듈 로더에 맞는 모듈을 개발한 후에 모듈 파일을 운영 배포하기 위해 번들링 즉, 묶는 과정을 거친다. 번들링 방법은 대해 다음 글에서 살펴보자. 




<참조> 

  - 모듈 로딩 다이어그램

  - 모듈 로딩: CommonJS & AMD & UMD

  - 모듈 번들링: Browserify & Webpack 

  - CommonJS와 AMD - D2 

  - UMD 구현 예 

  - SystemJS: Universal dynamic module loader

  - Rollup.js: 차세대 Javascript module bundler

  - ES6 Module Loader Polyfill: Top Level SystemJS

  - ES6 vs CommonJS 비교

  - JavaScript Module Pattern

posted by 윤영식
2015. 4. 16. 17:50 My Services/Smart Analytics

AngularJS SPA 형태 개발을 진행할 때 AMD(Asychronous Module Definition)에 대한 고려를 하지 않고 있다. 계속해서 확장 되어야 하는 애플리케이션을 개발한다면 AMD를 염두에 두자. 하지만 AMD가 아니라 앵귤러 스러운 모듈단위의 동적 로딩을 하고 싶을 경우 어떻게 하는지 Smart Analytics 관점에서 컨셉을 테스트해 보자. 



개념 


  - CommonJS : exports, require, module을 통한 서버사이드 자바스크립트 모듈화 스팩이고 Node.js에서 많이 사용함

  - AMD : 브라우져에서 모듈화에 대한 스팩. 가장 많이 쓰는 구현체 require.js  를 통한 자세한 사용예(NHN)

    + 특징 : 동적 로딩, 의존성 관리, 모듈화 

    + define을 통한 모듈 패턴을 적용하여 모듈을 만든다

    + require를 통해 의존관계 모듈들을 주입받아 사용한다. 

  - AngularJS는 모듈화와 DI 즉, 의존성 주입을 가능하게 하고, RequireJS는 JS 파일의 의존성을 관리하고 동적 로딩을 통한 성능 최적화를 얻는다. (참조)





Smart Analytics에서 바로 보는 동적 로딩 시나리오

  

  ElasticSearch의 Kibana는 엘라스틱서치의 데이터를 자유롭게 표현할 수 있는 대시보드 소프트웨어이다. AngularJS + Require.js를 사용하고 있다. 하지만 좀 더 AngularJS 스러운 방식으로 앵귤러 모듈을 필요한 시점에 동적으로 할 수는 없을까? 화면이 라우팅될 때 또는 필요한 시점에 필요한 모듈만 로딩할 수 있도록 지원하는 앵귤러 컴포넌트로 ocLazyLoad 모듈이 있고 이를 이용하여 대시보드 화면에 국한하여 시나리오를 써보자


  - 사용자가 로그인을 하면 사용자마다 할당 된 대시보드 목록을 가져오고 첫번째 대시보드를 브라우저 영역 전체화면으로 보여준다.

  - 대시보드에는 사용자가 직접 제작한 차트가 보이고 해당 차트는 별도의 파일로 존재하고 앵귤러의 독립 모듈로 존재한다. 따라서 대시보드안에 들어가는 차트들은 플로그인 방식으로 동적으로 로딩되어 대시보드 일부분에 포함되어 들어가야 한다. 

  - 그러나 사용자가 직접 제작하지 않고  저작도구를 통해 제작한 차트는 별도의 파일로 존재하지 않고 환경값을 서버로 부터 받아 차트 컨테이너에 자동으로 해석되어 표현된다. 


  여기서 동적으로 로딩되어 표현할 것은 직접 별도로 제작된 차트를 대상으로 하고, 별도의 파일로 존재한다. 해당 파일은 로컬 또는 네트워크를 통해 로딩 될 수 있어서 다양한 차트 화면을 대시보드로 수용할 수 있다. 마치 플러그인 방식으로 대시보드 운영 서버의 재기동 없이도 플러그인을 설치하기만 하면 자동으로 대시보드의 특정위치에 차트가 표현되는 것이다. 

  

   - chart-config.json + chart-container를 통해 차트를 자동으로 표현하거나

   - 로컬에서 사용자가 직접 제작한 bizChart01.js 파일을 동적으로 로딩해서 표현하거나, bizGrid01.js를 원격에서 동적 로딩하는 개념이다. 







ocLazyLoad 기반 앵귤러 모듈의 동적 로딩


  ocLazyLoad 모듈을 이용해 파일 로딩하는 예를 보자. 설치하고 모듈 의존성 설정

// survey-gorilla 설문조사 오픈소스를 위해 만들어 놓은 제너레이터로서 generator-angular-fullstack을 기반으로 수정한 것이다. 

$ npm install generator-sg --save

$ mkdir prototyping & cd prototyping


// 애플리케이션 생성 : mongodb는 선택안하고, ui-bootstrap과 sass를 선택함

$ yo sg SAProto 

// oclazyload를 설치함 

$ bower install oclazyload --save

bower install       oclazyload#0.6.3


  ocLazyLoad 모듈을 위한 별도의 서비스를 /app/components/base/pluginer 폴더에 만들어 보자. pluginer 앵귤러 서비스를 base 모듈에 포함시키기 위해 먼저 base.module.js를 만든다. 

// base.module.js 를 만들고, oc.lazyLoad 모듈에 대한 의존관계를 설정한다. 

$ cd client/components

$ mkdir base & cd base

$ vi base.module.js 


(function() {

  'use strict';


angular 

    .module('sa.base', ['oc.lazyLoad']);


})();



// pluginer 서비스를 만들어 보자 

$ mkdir pluginer & cd pluginer

$ vi pluginer.service.js 


(function() {

  'use strict';


  angular

    .module('sa.base')

    .service('pluginer', pluginer);


  /* @ngInject */

  function pluginer($q, $ocLazyLoad) {

    this.load = load;


    function load(moduleName, filePath) {

      var deferred = $q.defer();


      $ocLazyLoad

        .load({

          name: moduleName,

          files: [ filePath ]

        })

        .then(function() {

          console.log('loaded plugin: ' + moduleName + ' successfully.');

          deferred.resolve('ok');

        }, function() {

          console.log('failed plugin: ' + moduleName + ' successfully.');

          deferred.reject('fail');

        });


      return deferred.promise;

    }

  }


})();


// 최종 폴더 구조 


  pluginer.service.js는 $ocLazyLoad를 통해 지정한 파일의 모듈을 로딩해서 사용할 수 있고 promise 패턴을 통해 비동기적인 상황에 대응한다. 다음으로 플러그인을 하나 만들어 보자. 우선 client 밑에 plugins 폴더를 만들고 index.html에 포함되지 않는 *.js / *.css / *.jpg(png) 등을 놓는다. plugin.bizchart01이라는 별도의 모듈을 만들고 bizchart01 서비스를 만든다. 

// client 밑에 plugins 폴더를 생성한다.

$ mkdir plugins & cd plugins

$ mkdir bizchart01 & cd bizchart01

$ vi bizchart01.js 


(function() {

  'use strict';


  angular

    .module('plugin.bizchart01', [])

    .service('bizchart01', bizchart01);


  function bizchart01() {

    this.draw = draw;


    function draw() {

      return 'drawing....';

    }

  }


})();


// plugins 폴더 

 

  이제 pluginer 서비스를 통해 plugin.bizchart01 모듈을 필요한 시점에 로딩을 하고 bizchart01 서비스의 draw() 를 호출해서 출력해 보자. sa.base 모듈을 app.js에 의존관계를 설정하고 app/main/main.html 를 바꿔보자. 

// /client/app/app.js 의 sa.base 모듈 의존 설정 

  angular

    .module('saprotoApp', [

 'ngCookies',

 'ngResource',

 'ngSanitize',

 'ui.router',

 'ui.bootstrap',

                  'sa.base'

])

    .config(config)

    .factory('authInterceptor', authInterceptor)

    .run(run);


// main.html 과 main.controller.js를 수정하여 테스트 코드 작성

// main.html 

 <li><a href="#" ng-click="lazyloading()">Load Module</a>: {{lazyloadingMsg}}</li>


// main.controller.js 

(function () {

  'use strict';


  angular

    .module('saprotoApp')

    .controller('MainCtrl', MainCtrl);


  /* @ngInject */

  function MainCtrl($scope, $injector, pluginer) {

    $scope.lazyloading = lazyloading;


    function lazyloading() {

      pluginer

        .load('plugin.bizchart01', 'plugins/bizchart01/bizchart01.js')

        .then(function() {

          var bizchart01 = $injector.get('bizchart01');

          $scope.lazyloadingMsg = 'lazyloading message: ' + bizchart01.draw();

          console.log($scope.lazyloadingMsg);

        });

    }    

  }


})();


  pluginer를 통해 "plugins/bizchart01/bizchart01.js" 에 있는 "plugin.bizchart01" 모듈을 로딩하고, $injector를 통해 "bizchart01" 서비스를 얻어와 draw()를 호출했다. 이를 확인할 수 있는 것은 크롬의 개발자 도구에서 브라우저에서 로딩 링크를 클릭했을 때 실제로 bizchart01.js 파일을 호출하는지 보면 된다. 


1) "Load Module"를 클릭한다. 



2) 크롬 개발자 도구의 Nework에서 "Load Module" 링크를 클릭시 bizchart01.js을 요청하는지 확인한다. 



이제 별도의 차트를 앵귤러 모듈로 만들어서 필요한 시점에 로딩할 수가 있다. 이외에도 ui-router를 통해 partial view가 변경될 때도 가능하고 다양한 예제는 ocLazyLoad를 참조한다. 


테스트 소스 : https://github.com/Smart-Analytics/prototyping/tree/feature/lazyloading




<참조>


  - CommonJS와 AMD 개념 비교 (NHN)

  - AMD : require.js 사용예

  - Node.js와 브라우져 사용예 (Mobicon)

  - ocLazyLoad GitHub

posted by 윤영식

오브젝트간의 이벤트 전달이 필요할 경우 loose coupling 을 유지하면서 커뮤니케이션이 가능토록 해주는 중재자 패턴에 대해 알아보자 



1) 중재자 패턴

  - 하나의 오브젝트 상태 변경시 다른 오브젝트가 해당 변경 정보를 알아야 할 경우

  - 별도의 중재자를 두어서 해당 중재자가 한 오브젝트의 변화에 대해 관심을 가지고 있는 오브젝트들한테 변경 정보를 전달한다

  

    + Mediator : Colleague 오브젝트와 통신할 수 있는 인터페이스를 정의한다 

    + ConcreteMediator : ConcreteColleagues 오브젝트들의 레퍼런스를 가지고 있다. 정보를 전달해 주는 역할을 수행

    + Colleague classes : Mediator 오브젝트의 레퍼런스를 가지고 있다가 다른 Colleague와 통신하길 원하면  Mediator를 통해서 통신을 한다 


  - 사용예

    + GUI Libraries : GUI 컴포넌트에서 하나가 선택되면 다른 것들이 enable/disable 되는 경우 

    + Chatting Application

       Chatroom : Mediator 로써 참석자들끼리 대화를 중재 인터페이스 정의

       ChatroomImpl : ConcreteMediator 한 참가자가 메세지 보내면 다른 참가들에게 메세지 전송하는 역할을 구현

       Participant : Colleague 참가자 인터페이스 정의

       HumanParticipant, Bot : ConcreteColleague 는 사람이 될 수도 bot이 될 수도 있다. Mediator 레퍼런스를 참조한다다



2) 구현상의 고려사항

  - Mediator 인터페이스정의는 Colleague 들이 여러개의 Mediator가 필요할 경우이다. 한개의 Mediator만 필요하다면 굳이 인터페이스 정의는 필요없다

  - Colleague의 상태가 변하면 정보를 Mediator에 전달하고 해당 정보에 관심이 있는 Colleague에 정보를 전달해 주는 Observer 패턴과 유사하다

  - 정보가 많을 경우 Asynch를 고려한다면 Message Queue 가 필요할 수 있다 

  - Colleague가 많아지면 Mediator 구현체가 복잡해 질 수 있다. 



3) Mediator.js 분석

  - 사이트 : https://github.com/ajacksified/Mediator.js

  - 중재자 패턴을 이용하여 WebSocket, AJAX call, DOM 이벤트를 쉽게 다루고 테스트 할 수 있도록 한다 

  - 채널(Channel)이 중재자가 되어서 Colleague들에게 관심 정보를 전파한다 

  - 설치하기 

$ npm install mediator-js

npm http GET https://registry.npmjs.org/mediator-js

npm http 200 https://registry.npmjs.org/mediator-js/-/mediator-js-0.9.2.tgz

mediator-js@0.9.2 node_modules/mediator-js


  - Node.js에서 사용하기 

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

// md_node.js 파일

var Mediator = require("mediator-js").Mediator,

    mediator = new Mediator();


mediator.subscribe("wat", function(){ console.log(arguments); });

mediator.publish("wat", 7, "hi dowon", { one: 1 }); 


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

// 결과 (wat이 채널) 

$ node md_node.js

{ '0': 7,

  '1': 'hi dowon',

  '2': { one: 1 },

  '3':

   { namespace: 'wat',

     _subscribers: [ [Object] ],

     _channels: [],

     _parent:

      { namespace: '',

        _subscribers: [],

        _channels: [Object],

        _parent: undefined,

        stopped: false },

     stopped: false } }


  - 브라우져에서 AMD로 사용하기 

     + require.js 와 mediator.js 파일은 md_browser.html 파일 위치에서 js/ 폴더 밑에 존재함

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

// md_browser.html

<!doctype html>

<html lang="en">

<head>

<meta charset="UTF-8">

<title>Test</title>

</head>

<body>


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

<script>

        // base url을 반드시 주어야 한다 

requirejs.config({

   baseUrl: './js/'

});


        // 주의) mediator.js 에서 define을 Mediator 로 M을 대문자로 정의하였다 

require(['Mediator'], function(mediator) {

 mediator.subscribe("wat", function(){ console.log(arguments); });

 mediator.publish("wat", 7, "hi", { one: 1 });

});

</script>


</body>

</html>


   + subscribe/publish API

mediator.subscribe(channel, callback, <options>, <context>);

mediator.publish(channel, <data, data, ... >)

mediator.remove(channel, <identifier>)


   + on/bind 는 subscribe 의 alias 이고, trigger/emit 은 publish 의 alias, off 는 remove의 alias

   + Channel의 Namespace 로 : 를 사용한다 예) application:chat



<참조>

  - http://www.oodesign.com/mediator-pattern.html

  - 3실 청년 설명

  - 예제를 가지고 시퀀스 다이어그램을 통해서 설명

  - 다이어그램 보기 : UML 기초

  - 자바스크립트의 Mediator 패턴 설명

  - 자바스크립트 디자인패턴 - 설명

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:56 NodeJS/Modules

AMD를  사용하지 않으면 엔터프라즈급의 웹앱을 만들 수가 없다. 요즘 Java 에서 Spring Framework이 엔터프라이즈 구현에 핵심이듯이 서로의 의존관계를 관리하고 주입하여 준다. 전통적인 방식은 HTML에서 <script> 태그를 사용하여 모듈들을 순차적으로 코딩한다. 그런 순차적 코딩이 아닌 모듈개념으로 어떻게 전환할 수 있는지 알아보자 



1) 예전 방식의 자바스크립트 로딩방식

  - html 코드

    + add.js : 더하기 연산

    + multi.js : 곱하기 연산

    + app.js : add, multi 연산을 사용하고 결과 출력

<html>

<body>

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

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

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

</body>

</html>


  - 나머지 코드들

    + 전역변수 cal를 사용하기 위하여 모든 .js에서 undefined을 확인해야 한다

// add.js 

var cal = cal || {};


cal.add = function(x, y) {

return x + y;

}


// multi.js

var cal = cal || {};


cal.multiply = function(x, y) {

return x*y;

}


// app.js 

// 전역변수 cal를 이용하여 호출한다 

console.log('add result is ' + cal.add(4, 5));

console.log('multi result is ' + cal.multiply(4, 5));


  - html 호출 결과 

  



2) AMD 방식으로 로딩하기

  - add.js, multi.js  모듈화를 위하여 define() 펑션을 이용한다

  - app.js 에서 모듈을 사용하기 위하여 require() 펑션을 이용한다

// add.js 

// var cal 과 같은 변수 선언이 없어졌다. 

// 1) Functional Programming의 최대 장점이 변수 설정으로 인한 메모리 할당이 필요없어지게 되었다

// 2) Anonymous Function == Closure Function으로 만들었다  

define(function() {

return function(x, y) {

return x + y;

}

}) 


// multi.js

define(function() {

return function(x, y) {

return x * y;

}

})


// app.js

// 의존관계에 있는 것을 require의 첫번째 인자에 배열로 설정한다 

require(['add', 'multi'], function(add2, multi2) {

console.log('AMD : add result is ' + add2(4, 5));

console.log('AMD : multi result is ' + multi2(4, 5));

});


  - AMD 구현체 require.js를 사용하여 index.html 을 변경한다 

    + data-main 값으로 app.js 를 지정한다

    + src 값으로 require.js 를 지정한다 

<html>

<BODY>

<script data-main="app" src="http://requirejs.org/docs/release/2.1.5/minified/require.js"></script>

</BODY>

</html>


  - 결과 확인

  


** 테스트 소스

AMD.zip



<참조>

  - Require.js 홈페이지

  - Require.js 이용하여 AMD 모듈 작성하기

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. 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 윤영식
prev 1 next