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

Publication

Category

Recent Post

2013. 11. 28. 13:19 AngularJS/Concept

하기 영상을 보면 JSON Object일 때와 String(Primitive type) 일때의 처리가 틀리다 왜그럴까 고민을 해보자 



1. Angular.js scope 상속 방법

  - childe scope는 기본적으로 parent scope를 상속받는다. 

  - 단, directive에서 scope : {...} 설정에서 scope isolate을 정하게 되어있다 

  - 원칙

1) define objects in the parent for your model, then reference a property of that object in the child: parentObj.someProp

    부모 scope에 object를 정의하면 child scope로 상속 된다. 그러나 primitive type(string, number, boolean)은 상속되지 않는다 

2) use $parent.parentScopeProperty (not always possible, but easier than 1. where possible)

    $parent 의 프로퍼티를 사용한다. 항상 가능하진 않으나 1)번 보다는 쉽다

3) define a function on the parent scope, and call it from the child (not always possible)

    부모 scope에 function을 정의하면 child scope로 상속 된다



2. JavaScript Prototypal 상속에 대하여

  - scope chaing에 따른 상속 : childScope는 primitive, object 전부 호출 가능 




3. Angular.js Scope 상속에 대하여 

  - 상속유형 

1) The following create new scopes, and inherit prototypically: ng-repeat, ng-include, ng-switch, ng-view, ng-controller, directive with scope: true, directive with transclude: true.

    ng-repeat, ng-include, ng-switch는 새로운 scope를 만들고 prototypically를 상속을 한다. 

    즉, scope:true, transclude:true인 directive이다 (새로운 scope를 만들지 않는 것은 scope: false 이다)

2) The following creates a new scope which does not inherit prototypically: directive with scope: { ... }. This creates an "isolate" scope instead.

     prototypically사용을 하지 않고 새로운 scope를 만들려면 scope: {...} 를 정의하여 "isolate" scope를 만들어야 한다


  - ng-include의 예

// controller 에서 

$scope.myPrimitive = 50;

$scope.myObject    = {aNumber: 11}; 


// html 에서 

<script type="text/ng-template" id="/tpl1.html">

    <input ng-model="myPrimitive">

</script>

<div ng-include src="'/tpl1.html'"></div>


<script type="text/ng-template" id="/tpl2.html">

    <input ng-model="myObject.aNumber">

</script>

<div ng-include src="'/tpl2.html'"></div>

  - 최초 설정 상태 : ng-include는 새로운 ChildScope1, 2를 생성한다. ParentScope는 ng-include를 감싸는 상위 Controller scope 가정 


  - 파란색 input box에 값을 입력 할 때 : primitive는 child scope에 새로운 property가 생성됨



  - 분홍색 input box에 값을 입력 할 때 : object는 parentScope의 값이 변경됨 


  - 파란색을 분홍색처럼 영향을 미치고 싶다면 하기와 같이 사용하면 parent scope의 primitive 값을 child scope에서 제어가능

     * 펑션도 Object와 동일하게 Child에 상속된다 

<input ng-model="$parent.myPrimitive">

  - ng-switch 도 ng-include와 유사하게 동작한다 



4. ng-repeat 상속의 경우 

  - ng-include나 ng-switch와 약간 틀리다 

// controller 에서 

$scope.myArrayOfPrimitives = [ 11, 22 ];

$scope.myArrayOfObjects    = [{num: 101}, {num: 202}]


// html 에서 

<ul><li ng-repeat="num in myArrayOfPrimitives">

       <input ng-model="num">

    </li>

<ul>


<ul><li ng-repeat="obj in myArrayOfObjects">

       <input ng-model="obj.num">

    </li>

<ul> 

  - 최초 ng-repeat이 새로운 scope를 하나 만들고 iteration 하면서 아이템의 값을 새로운 scope를 또 만들어 새로운 property 에 할당함

    여기서, 새로운 proeperty란 loop 변수 - 위의 예에서 num 또는 obj - 이다. 즉, 하기와 같은 작업이 일어나는 것이다. 

childScope = scope.$new(); // child scope prototypically inherits from parent scope ...     

childScope[valueIdent] = value; // creates a new childScope property

  - 파란색 Primitive 배열 경우 : 새로운 scope가 생기고 loop 변수 num이 Childe scope에 생기고 primitive 값의 복사가 이루어진다. 

    즉, referencing 되지 않아 Parent Scope와 관련성이 없어진다


  - 분홍색 Object 배열 경우 : Child Scope가 생성되고 loop 변수 obj는 parent scope의 배열 요소를 referencing 한다. 

    즉, child에서 변경을 하면 parent scope 값도 변경되는 것이다 


  - ng-controller

    + 일반적인 prototypal 상속을 따른다 

    + controller 끼리 $scope를 통해 정보를 공유하는 것은 좋지 않다. 공유할려면 service를 이용한다 (참조)

  - ng-view

    + ng-include와 동일하다 



5. 사용자 정의 Directive의 Scope 경우

  - directives를 만들 때

    + scope: false 는 기본값이다. directive를 만들면 scope를 새로 생성하지 않는다 

    + scope: true 를 정의하면 일반적인 prototypal 상속을 따른다

    + scope: { ... } 경우는 isolate/isolated scope를 새롭게 생성한다.

  - scope: {...} 경우 상세 고찰 

1) prototypically 상속을 하지 않는다 

2) 재사용 가능한 컴포넌트를 만들 때 사용한다. 즉 컴포넌트가 parent scope의 값을 read/write 못하게 한다

3) 그러나 간혹 parent scope에 접근(access) 하고 싶을 경우 object hash를 사용한다 

    '=' : two way binding (isolate scope <-> parent scope) 

    '@' : one way binding (isolate scope <- parent scope)

    '&' : parent scope expressions 에 바인딩 된다 

4) objec hash는 자신의 directive의 attributes 가 바인딩시에 이용됨을 주의한다 

5) object hash 는 parent scope의 property를 지정 할 수 없다 

    즉, 'parentProp' 가 있을 때 <div my-directives>의 scope: {localProp: '@parentProp'} 지정로 하지 않고, 대신 접근하고 싶다면

    <div my-directives the-Parent-Prop=parentProp>  의 scope: {localProp: '@theParentProp'}로 parent property를 명시해야 한다 (참조 예제)

6) isolate scope 의 attribute 명칭과 parent scope의 property 명칭이 같다면 scope: { attributeName: '=' } 이런식으로 설정함

    틀리면 scope: { keyName1: '@isolateAttributeName', keyName2: '=isolateAttributeName'} 으로 정의함

  - isolate scope 개념도

    + isolate scope 가 생성되면 __proto__ 는 Scope를 레퍼런스 한다 (하기 그림의 오랜지색 박스 'Object')

    + isolate scope의 $parent는 parent scope를 레퍼런스 한다. 그렇다고 prototypically 상속을 parent scope로 부터 하진 않는다 

    + 하기와 같이 정의 하였을 경우 

// html 에서 : attribute 에서 parent scope의 property를 접근한다 

<my-directive interpolated="{{parentProp1}}" twowayBinding="parentProp2">


// directive 에서 : =, @, & 등을 사용하여 parent scope property의 접근 방법을 명시한다 

scope: { interpolatedProp: '@interpolated', twowayBindingProp: '=twowayBinding' } 


// directive의 linking function 에서 

scope.someIsolateProp = "I'm isolated" 

    + 위 코드이 개념도 


 - @ 을 사용하면 linking function안에 하기 코드를 사용한다. 

    즉, attrs.$observe('interpolated', function(value) { ... } 의 value에 11을 설정한다  

attrs.$observe('attr_name', function(value) { ... }

  - 따라서 scope.interpolatedProp는 linking function 안에 정의 되지 않으나, 

     scope.twowayBindingProp는 inking function 안의 정의 된다. 이에 대해 해당 링크를 참조하자 (예제)

 


6. Transclude 에 대하여

  - transclude :true 하면 새로운 "transcluded" child scope를 생성한다. 이는 일반적인 prototypically 상속을 한다 

  - transaclude content가 two way binding을 원한다면 $parent를 이용한다

  - isolate scope 객체와 transclude scope 객체는 형제지간으로 $parent는 같은 부모 object를 레퍼런스 한다 (sibling)

  - isolate scope 의 $$nextSibling은 transclude scope 객체를 레퍼런스 한다 



  - 링크 사이트를 참조한다


즉, Isolate Scope를 가지게 되면 일반적인 Prototypal 상속을 따르지 않는다. 그러나 Isolate Scope(== Child Scope)가 간혹 parent scope를 접근하고 싶을 때는 @, =, & 와 같은 object hash를 Directive의 Attribute 앞에 붙임으로써 접근 방법을 지정할 수 있다. 


* 주의) 버전 1.1.* 와 1.2.* 버전사이의 동작이 틀리다. 1.1.* 에서는 정상 작동하지만 1.2.* 에서 Isolate Scope방식이 정상 동작하지 않는다! (이유를 찾아봐야 함)

  예제에서 왼쪽 메뉴의 "External Resource"를 바꾸어 가며 테스트해 보라

  "http://code.angularjs.org/1.1.5/angular.js" <->  "http://code.angularjs.org/1.2.0/angular.js"



<참조>

  - 원문 : Angular.js Scope에 대한 이해 개념도

  - Angular.js Scope 상속 영역 

  - Angular.js Architecture 고려 사항 (필독)

  - Angular.js Isolated Scope에 대한 이해 (필독)

  - Isolated Scope @, =, & 예제

  - Transclude의 two-way binding 실현

  - 본문 다이어그램 소스보기 프로그램

posted by 윤영식
2013. 11. 28. 09:32 HTML5, CSS3/CSS

Bootstrap은 CSS Framework으로 프리젠테이션을 담당하고 jQuery UI 와 Angular.js는 자체 컨트롤도 가지고 있지만 DOM 핸들링을 한다. 만일 서비스를 만드는데 Bootstrap + jQuery UI 조합일 때 또는 Bootstrap + Angular.js 조합일 때 그리고 Bootstrap + jQuery UI + Angular.js 조합 일때 프리젠테이션을 통합하는 방법은 어떤 것들이 있을까? 각 조합에 대해 어떻게 사용할지 알아보자.



1. Twitter Bootstrap 만 사용할 경우 

  - 부트스트랩만 놓고 보면 CSS Framework으로써 기본적인 Fluid Layout 형태와 기본 HTML 컨트롤의 모양을 정의하고 있다

    또한 JavaScript 기반 다양한 component들도 포함한다 

  - 부트스트랩의 CSS 부분을 향상시키고 extra component를 추가한 부분 변경모델은 Bootflat, Bootplus, FlatStrap, LeapStrap

  - 또는 다양한 완성체 템플릿을 startBootstrap같은 곳에서 만들어 제공도 한다 

    



2. Twitter Bootstrap + jQuery UI  함께 사용할 경우 (이제 없어졌다)

  - 문제는 여기서 부터 시작한다 

  - Bootstrap의 CSS 스타일이 존재하고 jQuery UI 도 자체 theme을 CSS로 정의하고 있다

  - Boottrap의 CSS를 jQuery UI에 접목하여 사용할 경우는 jQuery UI Widgets 에 대해서 Bootstrap theme을 입힌 것을 사용한다

    http://addyosmani.github.io/jquery-ui-bootstrap/

    



3. Twitter Bootstrap + Angular.js 함께 사용할 경우

  - Angular에 JavaScript component를 포팅하기 위하여 Directive로 감싸야하는 문제가 있다

  - jQuery UI와 Bootstrap theme을 결합하였다면 Angular.js 와 Bootstrap theme 을 결합하고 있다

  - 하지만 이것은 Bootstrap v2.3.* 버전의 component를 Angular.js Directive로 구현하는 것이다

    http://angular-ui.github.io/bootstrap/

    



4. Twitter Bootstrap + jQuery UI + Angular.js 함께 사용할 경우 

  - 해당 형태는 3번의 연장선으로 보면 된다 

  - Twitter Bootstrap의 javascript component를 Angular.js Directive화 하였듯, jQuery UI component들도 Directive화 한것이다 

    http://angular-ui.github.io/

     

   - 소스 : https://github.com/angular-ui/


기존의 JavaScript Component를 처리하려면 Directive화 과정을 거쳐야 하는데 이런 번거로운 과정을 해소하고자 Bootstrap의 JavaScript Component를 Directive로 만들어 놓은 Angular UI Bootstrap이 나왔다. jQuery UI를 지원하기 위하여 Angular UI 에 상당부분 즐겨 사용하는 JavaScript Component를 포함시켰으나 이 또한 jQuery UI의 모든 것을 커버하고 있지는 못하다. 


이래저래 문제되는 것은 Angular.js를 기반으로 개발할 때 UI를 담당하는 JavaScript Component를 Angular.js의 Directive화를 잘 해야 한다는 것이다. 



<참조>

  - Bootstrap ToolBox : 다양한 UI 컴포넌트들 

posted by 윤영식
2013. 11. 28. 07:08 My Projects/BI Dashboard

BI Dashboard 인 만큼 엑셀의 그리드 형태의 표현은 필수적이다. 오픈소스중에서 가장 많이 사용하고 있는 jqGrid를 Angular.js에 포팅하고 RESTful API를 호출하여 표현하는 방법을 알아보자.  




1. jqGrid 설치하기 

  - 최신버전 v4.5.4

  - jqGrid만을 MIT 라이센스하에서 사용을 할 수 있다. 

// bower 통하여 설치하기

$ bower install jqgrid --save


// index.html 에 설정하기

<!-- build:css(.tmp) styles/main.css -->

    <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css">

    <link rel="stylesheet" href="bower_components/jqgrid/css/ui.jqgrid.css">

    <link rel="stylesheet" href="styles/main.css">

<!-- endbuild -->

... 중략 ...

<!-- build:js scripts/plugins.js -->

    <script src="bower_components/bootstrap/dist/js/bootstrap.js"></script>

    <script src="bower_components/respond/respond.src.js"></script>

    <script src="bower_components/jqgrid/js/i18n/grid.locale-en.js"></script>

    <script src="bower_components/jqgrid/js/jquery.jqGrid.js"></script>

<!-- endbuild -->

  - jqgrid의 theme을 위하여 jquery-ui 의 theme을 입힌다 

// bowe 통하여 설치하기 

$ bower install jquery-ui --save


지금까지의 bower components 목록 버전 의존성

$ bower list

BIDashboard

├── angular#1.2.1 (1.2.2 available)

.. 중략 ..

│ └── angular#1.2.1

├─┬ bootstrap#3.0.2

│ └── jquery#1.9.1 (2.0.3 available)

├── console-shim#05b957a4b1

├── es5-shim#2.0.12 (latest is 2.1.0)

├── jqgrid#4.5.4

├── jquery#1.9.1 (latest is 2.0.3)

├── jquery-migrate#1.2.1

├─┬ jquery-ui#1.10.3

│ └── jquery#1.9.1 (2.0.3 available)

├── json3#3.2.5 (3.2.6 available)

├── modernizr#2.7.0

└── respond#1.3.0


// index.html 에 설정하기 

<!-- build:css(.tmp) styles/main.css -->

    <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css">

    <link rel="stylesheet" href="bower_components/jquery-ui/themes/smoothness/jquery-ui.css">

    <link rel="stylesheet" href="bower_components/jqgrid/css/ui.jqgrid.css">

    <link rel="stylesheet" href="styles/main.css">

<!-- endbuild -->

... 중략 ...

<!-- build:js scripts/plugins.js -->

    <script src="bower_components/bootstrap/dist/js/bootstrap.js"></script>

    <script src="bower_components/respond/respond.src.js"></script>

    <script src="bower_components/jquery-ui/ui/jquery-ui.js"></script>

    <script src="bower_components/jqgrid/js/i18n/grid.locale-en.js"></script>

    <script src="bower_components/jqgrid/js/jquery.jqGrid.js"></script>

<!-- endbuild -->



2. jqGrid 테스트 메뉴 추가하기 

  - 기존 프로그램에서 jqGrid 테스트 메뉴를 추가한다 

    + step-1 : views 폴더 밑으로 jqGridTest.html 파일 생성

    + step-2 : scripts 폴더 밑으로 biz 폴더 만들고 (여기선 sales) jqGridBiz.js 파일 생성 (화면 : 비즈니스모듈 = 1:1)

                   jqGridBiz.js 의 모듈 명칭, 컨트롤러 명칭을 정의한다 

    + step-3 : Main Application (DashboardApp.js) 안에 신규 Biz 모듈 설정 및 Routing 환경 설정

    + step-4 : index.html 안에 jqGridBiz.js 스크립트 태그 추가

    + step-5 : index.html 안의 메뉴설정에 jqGridTest.html이 보일 수 있도록 routing url 설정

// step-1

// views/jqGrid.html 파일 내역

jqGrid Test Page


// step-2

'use strict';

var RestTestBiz = angular.module('DasbhoardApp.JqGridBiz', ['DasbhoardApp.RestfulSvc']);

RestTestBiz.controller('JqGridBiz.salesCtrl', ['$scope', 'RestfulSvcApi', function ($scope, RestfulSvcApi) {


}]);


// step-3

// DashboardApp.js 변경 내역 

'use strict';

var DashboardApp = angular.module('DasbhoardApp', [

  'ngRoute',                                                  

  'ngCookies',

  'ngResource',

  'ngSanitize',

  'DasbhoardApp.CommonCtrl',

  'DasbhoardApp.RestTestBiz',

  'DasbhoardApp.JqGridBiz',

  'DasbhoardApp.RestfulSvc'

]);


DashboardApp.config(['$routeProvider', function ($routeProvider) {

    $routeProvider

      .when('/', {

        templateUrl: 'views/main.html'

      })

      .when('/resttest', {

        templateUrl: 'views/restTest.html',

        controller: 'RestTestBiz.personCtrl'

      })

      .when('/jqgridtest', {

        templateUrl: 'views/jqGridTest.html',

        controller: 'JqGridBiz.salesCtrl'

      })

      .otherwise({

        redirectTo: '/'

      });

  }]);


// step-4

// index.html 에 추가 

<!-- build:js scripts/dashboard-spa.js -->

    <script src="scripts/DashboardApp.js"></script>

    <script src="scripts/common/controllers/CommonCtrl.js"></script>

    <script src="scripts/common/services/RestfulSvc.js"></script>

    <script src="scripts/person/RestTestBiz.js"></script>

    <script src="scripts/sales/JqGridBiz.js"></script>

<!-- endbuild -->


// step-5

// index.html 의 메뉴 부분에 추가 

<ul class="nav navbar-nav">

<li data-ng-class="activeWhen(path()=='/')">

 <a href="" data-ng-click="setRoute('/')">Home</a>

</li>

<li data-ng-class="activeWhen(path()=='/resttest')">

 <a href="" data-ng-click="setRoute('/resttest')">RESTTest</a>

</li>

<li data-ng-class="activeWhen(path()=='/jqgridtest')">

 <a href="" data-ng-click="setRoute('/jqgridtest')">jqGridTest</a>

</li>

</ul>


// final

// 디렉토리 구조

// http://localhost:8080/#/jqgridtest  호출 결과 



3. jqGrid 의 Angular.js 적용전략 - Directive 만들기 

  - jqGridTest.html partial view html 안에 일반적인 jqGrid 표현 내역을 입력하면 화면에 아무것도 출력되지 않는다

    즉, jqGrid 사용하기 와 같은 <table> 태그와 <script> 태그를 jqGridTest.html 넣으면 화면출력이 되지 않는다는 것이다. 

    이는 Angular.js router를 통하여 partial html이 보여질 때 DOMP (DOM Parser)는 html만을 파싱하여 add 할 뿐이고, 

    <script> 태그는 수행되지 않기 때문이다 (참조)

  - jqGrid에 대한  Common Directive 만들기 : JqGridDrtv.js 

// Directive 모듈 JqGridDrtv.js 개발

'use strict';

var jqGridDrtv = angular.module('DashboardApp.JqGridDrtv', [ 'DasbhoardApp.RestfulSvc' ]);


/**

 * mc is Mobile Convergence of MobiConSoft

 * @use : <mc-jq-grid config="config" data="data"></mc-jq-grid>

 * config and data is used in Controller of Angular.js

 */

jqGridDrtv.directive('mcJqGrid', [ 'RestfulSvcApi', function(RestfulSvcApi) {

return {

restrict : 'E',

scope : {

config : '=',

data : '=',

},

link : function(scope, element, attrs) {

var table;

                         // config attribute 값의 변경을 체크하여 반영한다 

scope.$watch('config', function(newValue) {

element.children().empty();

table = angular.element('<table></table>');

element.append(table);

$(table).jqGrid(newValue);

});

                        // data attribute 값의 변경을 체크하여 반영한다 

scope.$watch('data', function(newValue, oldValue) {

var i;

for (i = oldValue.length - 1; i >= 0; i--) {

$(table).jqGrid('delRowData', i);

}

for (i = 0; i < newValue.length; i++) {

$(table).jqGrid('addRowData', i, newValue[i]);

}

});

}

};

} ]);

  - 메뉴에서 동작하도록 적용하기 

    + step-1 : Controller에 JqGridDrtv.js의 config, data attribute에 대한 샘플 데이터 설정을 한다 

    + step-2 : DashboardApp 안에 JqGridDrtv 모듈 의존관계를 설정한다

    + step-3 : index.html 안에 JqGridDrtv.js <script> 태그에 포함시킨다 

    + step-4 : jqGridTest.html 에 <mc-jq-grid> 태그를 포함시킨다 

// step-1 : JqGridBiz.js 에서 config, data 값 설정

'use strict';

var JqGridBiz = angular.module('DasbhoardApp.JqGridBiz', ['DasbhoardApp.RestfulSvc']);


JqGridBiz.controller('JqGridBiz.salesCtrl', ['$scope', 'RestfulSvcApi', function ($scope, RestfulSvcApi) {

$scope.config = {

  datatype: "local",

  height: 150,

    colNames:['Inv No','Date', 'Client', 'Amount','Tax','Total','Notes'],

    colModel:[

    {name:'id',index:'id', width:60, sorttype:"int"},

    {name:'invdate',index:'invdate', width:90, sorttype:"date"},

    {name:'name',index:'name', width:100},

    {name:'amount',index:'amount', width:80, align:"right",sorttype:"float"},

    {name:'tax',index:'tax', width:80, align:"right",sorttype:"float"},

    {name:'total',index:'total', width:80,align:"right",sorttype:"float"},

    {name:'note',index:'note', width:150, sortable:false}

    ],

    multiselect: true,

    caption: "Manipulating Array Data"

   };

   

$scope.data = [

{id:"1",invdate:"2007-10-01",name:"test",note:"note",amount:"200.00",tax:"10.00",total:"210.00"},

{id:"2",invdate:"2007-10-02",name:"test2",note:"note2",amount:"300.00",tax:"20.00",total:"320.00"},

{id:"3",invdate:"2007-09-01",name:"test3",note:"note3",amount:"400.00",tax:"30.00",total:"430.00"},

{id:"4",invdate:"2007-10-04",name:"test",note:"note",amount:"200.00",tax:"10.00",total:"210.00"},

];

}]);


// step-2 : DashboardApp.js에서 의존성 설정

var DashboardApp = angular.module('DasbhoardApp', [

  'ngRoute',                                                  

  'ngCookies',

  'ngResource',

  'ngSanitize',

  'DasbhoardApp.CommonCtrl',

  'DashboardApp.JqGridDrtv',

  'DasbhoardApp.RestTestBiz',

  'DasbhoardApp.JqGridBiz',

  'DasbhoardApp.RestfulSvc'

]);


// step-3 : index.html 에서 <script> 태그 추가 

<!-- build:js scripts/dashboard-4.js -->

    <script src="scripts/DashboardApp.js"></script>

    <script src="scripts/common/controllers/CommonCtrl.js"></script>

    <script src="scripts/common/directives/JqGridDrtv.js"></script>

    <script src="scripts/common/services/RestfulSvc.js"></script>

    <script src="scripts/person/RestTestBiz.js"></script>

    <script src="scripts/sales/JqGridBiz.js"></script>

<!-- endbuild -->


// step-4 : jqGridTest.html 안에 <mc-jq-grid> 태그를 넣는다 

<div id="gridtest" class="container">

<div class="row">

<div class="col-md-8 alert alert-info">

<i class="icon-hand-right"></i> This is jqGrid Directive for AngularJS

</div>

</div>

<mc-jq-grid config="config" data="data"></mc-jq-grid>

</div>


// 최종결과 

// 디렉토리 구조



4. jqGrid에 Bootstrp CSS 적용하기 

  - jqGrid는 기본적으로 jquery-ui theme을 사용한다. Bootstrap으로 기본적인 Layout과 controller를 사용하면 Grid 또한 같은 사용자 경험을 주어야  한다 

  - jqGrid에 Bootstrap과 유사한 CSS 값이 적용된 화면 참조 (소스)

  - 적용 순서

    + styles/main.css 안에 jgGrid.bootstrap.css 내역을 복사하여 넣는다

    + main.css 가 맨밑에 있으므로 jqgrid의 css를 다시 쓰는 역할이다 

<!-- build:css(.tmp) styles/main.css -->

<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css">

<link rel="stylesheet" href="bower_components/jquery-ui/themes/smoothness/jquery-ui.css">

<link rel="stylesheet" href="bower_components/jqgrid/css/ui.jqgrid.css">

<link rel="stylesheet" href="styles/main.css">

<!-- endbuild -->

  - 결과 

    



* jQuery UI를 Angular.js에 적용하는 방법

  - Bootstrap 스타일의 CSS를 적용했다면 그다음은 Directive로 만든 jqGrid의 column sorting을 시도해 보자 

  - column우측을 클릭하면 contents 영역에서 "loading..." 메세지만 나오고 sorting이 되지않을 것이다. 해결해 보자 

   


  - jQuery Plugins을 Directive로 만드는 이유 (참조)

// 하기와 같은 코드가 있을 경우 controller scope에서 동작을 하지 않으며, model의 변경도 반영되지 않는다

$('.datepicker').datepicker();


// 안될 경우 Angular.js 의 Directive 생성하여 동작토록 만들어야 한다

// datepicker를 wrapping한 directive 소스 

var directives = angular.module('directives', []);

directives.directive('datepicker', function() {

   return function(scope, element, attrs) {

       element.datepicker({

           inline: true,

           dateFormat: 'dd.mm.yy',

           onSelect: function(dateText) {

               // input field의 jQuery Object

               var modelPath = $(this).attr('ng-model');

               // angular.js $parse와 동일한 역할

               // modelPath = a.b.c , scope = {}, dateText = 2013.11.27 이면 리턴값은 {a : { b : { c: '2013.11.27 }}} 됨

               putObject(modelPath, scope, dateText);  

               scope.$apply();

           }

       });

   }

});


// html 태그에서 datepicker attribute 로 사용한다 

<input type="text" datepicker ng-model="myobj.myvalue" />

  - Directive를 만들어주는 function의 파라미터 값의 의미

    + scope : controller가 관장하는 scope

    + element : jQuery object

    + attrs : 태그의 attribute 속성 key/value

  - datepicker의 callback function을 정의

    + onSelect : 사용자가 date을 선택했을 때 수행할 datepicker의 callback 이다 

    + $apply : 해당 controller scope에 변경값을 적용하기 위하여 scope.$apply를 호출해야 한다 (참조)

  - 데이터가 Object.properties 형태로 관리될 때 좀 더 우아하게 처리하는 방법은 $parse를 이용한다 (참조)

    + step-1 : 선택한 값은 controller의 scope안에 반영시키기 위하여 attrs.ngModel로 가져와서 $parse 파싱한다 

    + step-2 : 날짜의 값을 선택하여 해당 scope의 ngModel에 assign 반영한다 

myApp.directive('datepicker', function ($parse) {

    return function (scope, element, attrs, controller) {

        // step-1

        var ngModel = $parse(attrs.ngModel);

        $(function(){

            element.datepicker({

               ...

               onSelect:function (dateText, inst) {

                    scope.$apply(function(scope){

                        // step-2 Change binded variable

                        ngModel.assign(scope, dateText);

                    });

               }

            });

        });

    }

});

 

function MyCtrl($scope) {

    $scope.userInfo = {

        person: {

            mDate: '1967-10-07'

        }

    };  

 

 * 다른 jQuery UI 들의 Directive 소스

 * 소스 : https://github.com/ysyun/SPA_Angular_SpringFramework_Eclipse_ENV/tree/feature_jqgrid_angular

 


새로운 Directive IE8 에 적용하기

  - IE8에서는 새로운 directive를 만들어 element 태그로 사용할 경우 

  - IE8 이하일 경우 DOM을 만들어준다 (참조, 호환성가이드)

 <head>

    <!--[if lte IE 8]>

      <script>

        document.createElement('mc-jq-grid');

      </script>

    <![endif]-->

</head>



<참조>

  - Partial View html에서 javascript코드가 적용안되는 이유

  - jqGrid Directives 만들기

  - jqGrid에 Bootstrap CSS 적용하기

  - jQuery UI를 datepicker를 Angular.js Directive로 적용하기

  - jQuery UI를 Angular.js Directive로 만든 소스(GitHub) 

  - $parse를 이용하여 a.b.c 으로 시작하는 것을 json object 형태로 바꾸어주는 방법

posted by 윤영식
2013. 11. 27. 04:20 My Projects/BI Dashboard

Angular.js에서 Controller는 View DOM객체의 표현을 담당한다면 Service 모듈은 View단에 표현되는 NonVisible한 비즈니스 업무 데이터를 담당한다. 이전 블로그까지 모든 데이터 핸들링은 Controller에 담겨 있었는데 이를 Service 모듈로 분리 래팩토링 해보자. 또한 모듈분리를 하면서 $http를 추상화한 $resource를 사용해 보자 




1. factory vs service 무엇을 쓸까? (참조)

  - <module>.factory는 객체를 new 해서 return 해 주어야 한다. 

    따라서 별도의 function을 정의하고 해당 펑션을 new해서 return하는 구조일 때 사용

  - <module>.service는 function의 내용을 정의하고 new한 객체를 return할 필요가 없다 

  - <module>.factory 는 공통된 것을 function으로 이미 만들어 놓은 것을 사용할 때. 즉, 공통 모듈 <Module>Svc 에 생성

  - <module>.service 는 업무적으로 $resource/$http 이용하여 RESTful API 호출할  때. 즉 업무 모듈 <Module>Biz 에 생성



2. $resource 사용하기 

  - $resource 객체는 RESTful 클라이언트 애플리케이션 개발을 위한 $http 상위 레벨의 서비스이다

  - http에 대한 보다 정교한 header configuration이 가능하다

  - resource interceptors 가 있다

  - 보다 향상된 promises api를 제공한다 

  - API

// 사용법

$resource(url[, paramDefaults][, actions]);


// 의미 

- url

  string 이고, /user/:username 처럼 :<param> 형태로 입력가능하다. 전체 url을 넣어도 됨 


- paramDefaults 

  url parameters object 이고, /path/:verb 일때 파라미터값이 {verb:'greet', name:'dowon'} 이면

  url 결과는 /path/greet?name=dowon 으로 간다. @가 앞에 붙으면 전달하는 객체(json)에서 값을 추출하여 대입한다

 예) 

  var User = $resource('/user/:userId', {userId:'@id'});

  var user = User.get({userId:123}, function() {

     user.abc = true;

     user.$save();

  });


- actions 

  object.<object> 이고, $http.config의 action 을 상속받으며 사용자 정의하여 action을 추가할 수 있다.

  형식)

  {action1: {method:?, params:?, isArray:?, headers:?, ...},

   action2: {method:?, params:?, isArray:?, headers:?, ...},

   ...}


- 리턴타입

  기본적으로 resource에서 제공하는 action과 사용자 정의 action을 담아서 리턴한다 

  기본 action)

{ 'get':    {method:'GET'},

  'save':   {method:'POST'},

  'query':  {method:'GET', isArray:true},

  'remove': {method:'DELETE'},

  'delete': {method:'DELETE'} };



3. $resource 공통 모듈 만들기

  - $resource의 기본 action을 보면 'update': {method:'PUT'} 이 존재하지 않는다. 이를 custom action으로 포함시키고 공통 모듈로 만들어 보자. 

  - 공통 모듈을 만들기 전에 ngResource를 추가해야 한다. 애플리케이션에 없다는 다음과 과정을 거친다 

// bower로 angular resource 설치 및 bower.json에 추가 

$ bower install angular-resource --save


// index.html 에 추가 

<!-- build:js scripts/core.js -->

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

    <script src="bower_components/jquery-migrate/jquery-migrate.min.js"></script>

    <script src="bower_components/angular/angular.js"></script>

    <script src="bower_components/angular-route/angular-route.js"></script>

    <script src="bower_components/angular-resource/angular-resource.js"></script>

    <script src="bower_components/angular-cookies/angular-cookies.js"></script>

    <script src="bower_components/angular-sanitize/angular-sanitize.js"></script>

<!-- endbuild --> 


// ngResource가 필요한 모듈에 추가하기  

var DashboardApp = angular.module('DasbhoardApp', [

  'ngRoute',                                                  

  'ngCookies',

  'ngResource',

  'ngSanitize',

  'DasbhoardApp.CommonCtrl',

  'DasbhoardApp.RestTestBiz',

]);

  - $resource를 이용하여 공통 모듈을 만들어 보자 

// common/services/RestfulSvc.js 파일을 생성한다 

'use strict';

// 모듈 명칭 정의

var RestfulSvc = angular.module('DasbhoardApp.RestfulSvc', []);


RestfulSvc.factory('RestfulSvcApi', ['$resource', function ($resource) {

/**

* Restful API version. it must be attached the called url.

*/

var prefixUrl = '/api/v1';

/**

* @domain : biz object ex) person

* @key : biz object id ex) persion id is 123

* @action : server action name. if it exist, you can define or not.

* ex) http://www.bennadel.com/blog/2433-Using-RESTful-Controllers-In-An-AngularJS-Resource.htm

* add a update action for method of PUT

*/

return $resource( 

                           // 호출하는 url 형식

    prefixUrl + '/:domain/:key/:action',

                           // 호출 url 형식의 :domain :key :action 에 동적으로 받게 되는 파라미터

   {

    domain: "@domain",

    key: "@key",

    action: "@action"

   },

                            // 추가 action들 one, all, update 

   {

     one: { method: 'GET', isArray: false },

     all: { method: 'GET', isArray: true },

      update: {method:'PUT'}

    }

   );

}]);


// 새롭게 서비스 공통 모듈을 생성하였으니 index.html 과 DashboardApp 모듈에 추가한다 
// index.html 에서 
    <script src="scripts/common/controllers/CommonCtrl.js"></script>
    <script src="scripts/common/services/RestfulSvc.js"></script>

// DashboardApp.js 에서 
var DashboardApp = angular.module('DasbhoardApp', [
  'ngRoute',                                                  
  'ngCookies',
  'ngResource',
  'ngSanitize',
  'DasbhoardApp.CommonCtrl',
  'DasbhoardApp.RestTestBiz',
  'DasbhoardApp.RestfulSvc'
]);

 


4. Controller에 Service 모듈의 서비스 호출하기 

  - $http 방식을 $resource에 대한 추상 모듈 서비스로 대체한다

  - 기존의 RestTestBiz.js의 "RestTestBiz.personCtrl" 컨트롤러 로직을 리팩토링한다 

'use strict';

// 의존하는 모듈 주입

var RestTestBiz = angular.module('DasbhoardApp.RestTestBiz', ['DasbhoardApp.RestfulSvc']);


// 사용할 모듈의 서비스 주입 

RestTestBiz.controller('RestTestBiz.personCtrl', ['$scope', 'RestfulSvcApi', function ($scope, RestfulSvcApi) {


  var bizDomain = 'person';

  var load = function () {

    console.log('before calling...');

       // 사용자 정의 액션 all 호출 

    RestfulSvcApi.all(

    // param key domain의 값인 bizDomain는 @domain에 맵핑된다 

    { domain: bizDomain },

    // success

    function (response) {

    $scope.persons = response;

    console.log('persons is ', $scope.persons);

    }, 

    // error

    function (response) {

    });

     };


  load();


  $scope.save = function () {

  console.log('Save Person : ', $scope.person);

  RestfulSvcApi.save(

                        // 파라미터를 열거하면 하나의 json 객체로 합쳐준다 

  { domain: bizDomain }, $scope.person,

  function(response) { load(); },

  function(response) {}

  );

  };


  $scope.update = function () {

  console.log('Update Person : ', $scope.person);

  RestfulSvcApi.update(

  { domain: bizDomain }, $scope.person ,

  function(response) { load(); },

  function(response) {}

  );

  };


  $scope.deletePerson = function(personId) {

  console.log('Delete Person : ', personId);

  RestfulSvcApi['delete'](

  { domain: bizDomain, key: personId }, 

  function(response) { load(); },

  function(response) {}

  );

  };


}]);

  - RestfulSvcApi 와 같은 모듈 서비스를 해주는 추상화 공개 모듈이 존재한다. (참조)

    + Restangular

    + ModelCore

    + Angular-ServiceStack

    + Angular-ActiveRecord

    + Breezejs


  * 소스 : https://github.com/ysyun/SPA_Angular_SpringFramework_Eclipse_ENV/tree/feature_angular_service



<참조>

  - Angular.js v1.2 에서 향상된 내역들

  - ngResource를 통하여 트위터 연동하기 예제

  - $resource를 통한 RESTful API 구현하기 (필독)

  - rest 스타일의 ORM 모듈들

posted by 윤영식
2013. 11. 25. 09:36 AngularJS/Start MEAN Stack

MEAN Stack을 가지고 서비스를 만드는데 사용을 하려면 어떤 순서로 익혀야 할까? 각각의 기술을 따로 배우는 것은 어차피 전문서적들이 있기 때문에 여기서 다시 상세히 설명하는 것은 의미가 없을 것 같고, 서비스를 기획, 개발, 런칭까지의 과정을 알아보자. 모든 과정의 내용은 웹 서비스쪽에 공개되어 공유될 것이다 




Sprint-1) Full Stack 개발자 되기

  - MEAN 개념과 개요

    + AngularJS

    + NodeJS + ExpressJS

    + MongoDB + Redis(Advanced)

  - MEAN 이외에 알아야 할 것들

    + JavaScript 

    + Bootstrap (or Zurb Foundation

    + NodeJS Modules

    + Mongoose



Sprint-1) 개발 준비 단계

  - 로컬 개발 환경 만들기 

    + Vagrant + Linux 환경 구성하기

    + 개발환경에 Node.js, MongoDB 설치하기 

    + Trello 에서 Scrum 개발하기 

  - MEAN Stack 개발도구 개발환경에 설치하고 익히기 

    + Yeoman : 클라이언트 라이브러리관리 및 배포 Grunt, Bower, Yo 설치하기 

    + NPM : 서버 라이브러리 관리

  - Git & GitHub 저장소 만들기 

    + Git Branch 전략 

    + GitHub 사용방법

  - 클라우드 환경에 배포하여 테스트 하기

    + Cloud Development(Nitrous.io) 를 통한 협업개발 및 테스트하기

    + Heroku같은 PaaS에 배포하여 테스트하기 

 


Sprint-1) MEANK Stack 개발 환경 구성하기

  - SRS 작성하기 

    + Software Request Requirement란 무엇인가?

    + Trello 에 소프트웨어 요구사항 정의서 작성하기 

    + Balsamiq 도구 사용하여 Wireframe & Mockup 그리기 화면 그리기

  - AngularJS Scaffolding 코드 사용하기 

    + angular-generator 알아보기 

    + angular 기본골격 만들기

    + NodeJS로 테스트 하기 

  - MEAN Stack 테스트 전략 

    + AngularJS에서의 테스트 방법 : Karma Framework

    + NodeJS에서의 테스트 방법 : Mocha 그외 



Sprint-2) 서비스 프로토타입 개발하기

  - 가장 중요한 화면 설계하기

    + 클라이언트 화면 Mockup 구체화 하기 

    + NodeJs에서 처리 할 RESTful API 설계하기 

    + MongoDB 에 저장 할 Document Schema 설계하기

  - MongoDB Store 개발 및 테스트

    + Dynamic Schema, Embedded vs Reference 에 대한 이해 

    + Mongoose 기반으로 Schema 만들기

    + Test Framework 이용한 Model 단위 테스트하기 

  - Node.js 기반 RESTful API 개발 및 테스트

    + RESTful API 코드 뼈대 만들기 in NodeJS

    + Test Framework 이용한 서비스 단위 테스트하기

    + RESTful Client 테스트 도구 또는 curl 로 호출 테스트하기

  - Angular.js 기반 화면 개발 및 테스트

    + AngularJS와 Bootstrap 이용하여 화면 구성하기

    + AngularJS 애플리케이션 구조 개발 및 RESTful API 서비스 개발 

    + Test Framework 이용한 화면 단위 테스트하기 

  - Heroku 같은 PaaS에 올려서 통합 테스트 하기 



Sprint-3) 서비스 개발 반복하기

  - 반복하기 

    + sprint-01에서 반복하였던 과정을 지속하기 

  - 메인 및 로그인 화면 만들기 

    + Bootstrap의 다양한 도구 활용

    + OAuth 기능 로그인 하기  

  - 채팅기능 만들기 

    + 채팅 Mockup 만들기

    + NodeJS Socket.io API 구현

    + MongoDB 채팅 Schema 설계

    + AngularJS Socket.io 멀티 케스팅 구현 

    + Bootstrap 통한 화면 구현 및 AngularJS 연동

 - Heroku 같은 PaaS에 올려서 프로토타입 테스트 하기 



Sprint-3) 서비스 런칭하기

  - 서비스 일일 빌드시스템 구성하기 

    + Travis (또는 Jenkins)기반 자동 배포 기능 만들기 

    + Travis 에서 빌드된 것을 클라우드시스템(PaaS) 자동 배포하기 

  - 런칭 페이지 만들기 

    + GitHub Pages를 이용한 런칭 만드는 방법

    + Bootstrap을 이용하여 런칭 페이지만들어 GitHub에 반영하기 

  - 모니터링 하기

    + 클라이언트 AngularJS Log 모니터링 하기
    + NodeJS Log 모니터링 하기 

    + MEAN Stack 운영 및 장애 모니터링 전략 



Next Project) MEAN Stack Advanced

  - 채팅기능 고도화하기 

    + SNS 서비스 아키텍쳐들 알아보기  

    + 대용량 데이터를 처리를 위한 아키텍쳐 설계하기

  - NodeJS + Redis + MongoDB 연동하기 

    + Redis 알아보기 

    + NodeJS-Redis 연동하기 

  - 정제된 데이터 RDBMS와 연동하기

    + NodeJS - MySQL 연동하기 

    + MongoDB의 MapReduce를 이용하여 데이터 마이닝하기 

    + NodeJS에서 MongoDB 마이닝 데이터 MySQL에 넣기 

  - SNS 서비스 운영 고도화

    + NodeJS와 Nginx 연동하기 

    + NodeJS를 Proxy 서버로 만들어 Clustering 하기

    + MongoDB Sharding 구조 만들기 

    + Redis Master/Slave구조 만들기 

  - 유지보수 전략

    + 테스트 환경과 프로덕션 환경의 재구성

    + NodeJS Scale-out 확장 전략

    + MongoDB Sharding 및 Data Backup 전략 

    + Redis Scale-out 전략 

  - 빅데이터의 활용

    + 개인화를 위한 데이터 활용 방안

    + MongoDB Integration Framework을 이용한 MapReducing


전체 서비스를 만들고 런칭하기까지 참 많은 것들을 익히고 개발해야 하지만, 누군가에게 의미있는 가치를 줄 수 있는 SNS(Social Network Service)를 할 수 있다는 것에 만족을 느낀다. 


posted by 윤영식
2013. 11. 22. 03:53 My Projects/BI Dashboard

Angular.js를 하면서 모듈단위로 개발을 할 수 있고, 여러명이 동시에 관련 모듈을 개발하여 합칠 수도 있다. 즉, 엔터프라이즈급 애플리케이션 개발 공수가 든다면 모듈을 나누고 어떻게 합쳐서 하나의 SPA로 만드는지 알아보자 




1. 모듈 단위 나누기

  - 기존의 하나의 애플리케이션 모듈로 진행되는 것을 여러 모듈로 나어 큰 규모의 애플리케이션을 만들자 (참조)

    + 하나의 애플리케이션 .js 파일을 하나의 모듈로 한다

    + 모듈은 앞글자가 대문자로 시작한다 (App, Biz / Ctrl, Svc, Flt, Drtv 등 접미어를 붙여서 구분한다)

    + 모듈파일이 많이 커질때는 Lazy Loading을 고려한다 (참조1, 참조2)

    + 일반 기능들의 명칭은 소문자로 시작한다 (Ctrl, Svc, Flt, Drtv 등 접미어를 붙인다)

    + 공통으로 사용되는 것은 $rootScope 또는 Factory를 이용한다 

    + 파일과 모듈이 분리되어 테스트 코드를 쉽게 만들어 테스트 가능하다

  - 업무 모듈 유형 

    + Application Main 모듈 : <ProjectName>App.js (주로 routing 관련 설정)

    + Biz 모듈 : <UI명칭>Biz.js (화면과 맵핑되는 모듈 = n controller + n service/factory 존재)

  - 공통 모듈 유형 

    + Controller 모듈 : CommonCtrl.js (메뉴, 로그인/로그아웃 UI 핸들링)

    + Service 모듈 : CommonSvc.js (메뉴, 로그인/로그아웃 서버통신 처리)

    + Directives 모듈 : CommonDrtv.js (일반적인 지시자들), UtilDrtv.js (유틸리티), UXDrtv.js (UI), <User Defined>Dtrv

    + Filters 모듈 : CommonFlt (공통), UtilFlt (유틸) 필터링 관련 모듈 

  - /src/main/scripts의 기능별 폴더를 생성하여 모듈을 분리한다. 또는 업무적으로 폴더를 분리할 수도 있다

// /src/main/scripts 에서 

// 기존 폴더

/controllers/main.js

app.js 


// 추가 폴더 및 명칭변경 

/controllers

/directives

/filters

/services

DashboardApp.js

  - 주로 많이 개발하는 것이 화면단위의 <BizName>Ctrl/Svc.js 가 될 것이다 

  - vendor가 제공하는 컴포넌트는 Bower를 통하여 설치 관리되며, 배포시에 Grunt를 통하여 minification 된다 



2. 공통 모듈 분리하기 

  - 폴더를 만들었으면 이제 .js 안에 모듈명을 지정하고, 의존관계를 설정한다 

  - 모듈 명칭은 namespace 구분을 위하여 자바의 패키지처럼 <ApplicatonName>.<구분>.<FileName> 이라고 대소문자 작명한다

  - 모듈 분리 순서

    + app.js 파일명칭을 DashboarApp.js 로 변경하고, 모듈명칭을 DashboardApp로 시작한다

    + controllers/main.js 파일명칭을 CommonCtrl.js 로 변경하고, 모듈명칭을 <ApplicationName>.CommonCtrl로 변경한다

// DashboardApp.js 에서 

// 두번째 파라미터 배열에서 의존관계에 있는 모듈을 정의한다

var DashboardApp = angular.module('DasbhoardApp', [

  'ngCookies',

  'ngResource',

  'ngSanitize',

  'DasbhoardApp.CommonCtrl'

]);


// CommonCtrl.js 에서 

var CommonCtrl = angular.module('DasbhoardApp.CommonCtrl', []);

    + 분리된 파일은 index.html 에 추가한다 : 향후 파일이 늘어나면 Lazy loading을 고려한다 

// index.html 에서 

<!-- build:js scripts/dashboard-spa.js -->

<script src="scripts/DashboardApp.js"></script>

<script src="scripts/controllers/CommonCtrl.js"></script>

.. 이곳에 추가를 한다 ..

<!-- endbuild -->



3. 업무 모듈 분리하기 

  - Person 테이블을 보여주는 restTest.html 파일에 대한 별도 모듈을 만든다. (resttest.html을 restTest.html로 명칭 변경했음)

  - 1 view : 1 module 로 구성하고, 1 module :  n controller + n service 를 넣는다. 즉 1 module : 1 biz 로 한다

// step-1 : 기존 scripts 에 나열 되었던 controllers, directives, filters, services 폴더를 common 폴더를 만들어 옮긴다 

// step-2 : 업무적인 폴더는 scripts 폴더에 정의한다. 여기서는 person 폴더를 생성함 

// step-3 : scripts/person 폴더 밑에 RestTestBiz.js 파일을 만든다 

// step-4 : index.html 내역을 다음과 같이 수정한다


<!-- build:js scripts/dashboard-spa.js -->

<script src="scripts/DashboardApp.js"></script>

<script src="scripts/common/controllers/CommonCtrl.js"></script>

<script src="scripts/person/RestTestBiz.js"></script>

<!-- endbuild -->


// 최종 모습 

  - CommonCtrl.js 에 들어있는 restTestCtrl 코드를 RestTestBiz.js 파일로 옮기고, 명명규칙에 따라 수정한다 

var RestTestBiz = angular.module('DasbhoardApp.RestTestBiz', []);


RestTestBiz.controller('RestTestBiz.personCtrl', ['$scope', '$http', function ($scope, $http) {

    .. 중략 ..

    $scope.deletePerson = function(id) {

    console.log('Delete Person : ', id);

        $http['delete'](actionUrl + id).success(function () {

            load();

        });

    };

  }]);


// RestTestBiz.service or RestTestBiz.factory 를 정의한다 

  - DashboardApp.RestTestBiz 모듈을 DashboardApp.js에서 의존성을 등록하고, Routing시의 controller 명칭도 수정 등록한다 

var DashboardApp = angular.module('DasbhoardApp', [

  'ngRoute',                                                  

  'ngCookies',

  'ngResource',

  'ngSanitize',

  'DasbhoardApp.CommonCtrl',

  'DasbhoardApp.RestTestBiz',

]);


DashboardApp.config(['$routeProvider', function ($routeProvider) {

    $routeProvider

      .when('/', {

        templateUrl: 'views/main.html'

      })

      .when('/resttest', {

        templateUrl: 'views/restTest.html',

        controller: 'RestTestBiz.personCtrl'

      })

      .otherwise({

        redirectTo: '/'

      });

  }]);



* Angular.js v1.2.1 업그레이드 

  - 가장 큰점은 Angular의 route 가 별도로 분리되었다는 것이다 (참조1, 참조2)

  - v1.2.1 버전을 설치해 보자 

// bower를 통하여 기존 모듈 제거 및 재설치 작업 수행

$ bower uninstall angular angular-resource angular-cookies angular-mocks angular-sanitize angular-scenario

bower uninstall     angular

bower uninstall     angular-resource

bower uninstall     angular-cookies

bower uninstall     angular-mocks

bower uninstall     angular-sanitize

bower uninstall     angular-scenario


// bower.json에서 버전 정보를 삭제한다 

// --save 설치 

$ bower install angular angular-route angular-resource angular-cookies angular-sanitize --save


// --save-dev 설치 

$ bower install angular-mocks angular-scenario --save-dev


// 설치 버전 의존성 정보

$ bower list

bower check-new     Checking for new versions of the project dependencies..

SolarDasbhoard#0.0.0 /Users/nulpulum/development/eclipse_workspace/lg_solar/SPA_ASE

├── angular#1.2.1

├─┬ angular-cookies#1.2.1

│ └── angular#1.2.1

├─┬ angular-mocks#1.2.1

│ └── angular#1.2.1

├─┬ angular-resource#1.2.1

│ └── angular#1.2.1

├─┬ angular-route#1.2.1

│ └── angular#1.2.1

├─┬ angular-sanitize#1.2.1

│ └── angular#1.2.1

├─┬ angular-scenario#1.2.1

│ └── angular#1.2.1

├─┬ bootstrap#3.0.2

│ └── jquery#1.9.1 (2.0.3 available)

├── es5-shim#2.0.12 (latest is 2.1.0)

├── jquery#1.9.1 (latest is 2.0.3)

├── jquery-migrate#1.2.1

├── json3#3.2.5

└── respond#1.3.0

  - routing 관련 설정 변경

// index.html 에서 angular.js 밑으로 angular-route.js 추가 

<script src="bower_components/angular/angular.js"></script>

<script src="bower_components/angular-route/angular-route.js"></script>


// DashboardApp.js 파일에서 ngRoute 모듈 추가 

var DashboardApp = angular.module('DasbhoardApp', [

  'ngRoute',                                                  

  'ngCookies',

  'ngResource',

  'ngSanitize',

  'DasbhoardApp.CommonCtrlMod'

]);



* HTML5 지원하는 모듈 넣기 

  - html5shiv를 포함한 modernizr를 설치하여 오랜된 브라우져에서는 HTML5가 지원토록 한다 (참조)

  - IE 9 이하에서 선택적으로 넣자 

// bower install

$ bower install modernizr --save


// index.html 에서 

    <!--[if lt IE 9]>

      <script src="bower_components/modernizr/modernizr.js"></script>

      <script src="bower_components/es5-shim/es5-shim.js"></script>

      <script src="bower_components/json3/lib/json3.min.js"></script>

    <![endif]-->



* 메뉴 동작방식 변경하기 

  - data-ng-click 방식으로도 변경해 본다

// index.html 에서 

// 기존 코드 

<div class="collapse navbar-collapse">

  <ul class="nav navbar-nav">

    <li data-ng-class="activeWhen(path()=='/')"><a href="#/">Home</a></li>

    <li data-ng-class="activeWhen(path()=='/resttest')"><a href="#/resttest">RESTTest</a></li>

  </ul>

</div>


// 변경 코드

<div class="collapse navbar-collapse">

  <ul class="nav navbar-nav">

    <li data-ng-class="activeWhen(path()=='/')">

      <a href="" data-ng-click="setRoute('/')">Home</a>

    </li>

    <li data-ng-class="activeWhen(path()=='/resttest')">

      <a href="" data-ng-click="setRoute('/resttest')">RESTTest</a>

    </li>

  </ul>

</div>

  - 모듈안에서 사용되는 controller, service, filter, directive의 명칭은 <Module명>.<기능명칭> 처음에는 소문자로 시작한다 

// index.html 에서 

<div class="navbar navbar-inverse navbar-fixed-top"  data-ng-controller="CommonCtrlMod.menuCtrl">

..중략..

</div> 

  - menuCtrl안에 setRoute 메소드를 정의한다 

CommonCtrlMod.controller('CommonCtrlMod.menuCtrl', ['$scope', '$location', function ($scope, $location) {

    $scope.activeWhen = function (value) {

    return value ? 'active' : '';

    };

    $scope.path = function () {

        return $location.url();

    };

    $scope.setRoute = function (url) {

        $location.path(url);

    }    

  }]);


* 소스 : https://github.com/ysyun/SPA_Angular_SpringFramework_Eclipse_ENV/tree/feature_angular_modules



<참조>

  - Angular.js 기반 큰규모 애플리케이션 구조 만들기

  - Angular.js v1.2 에서 변경 추가된 사항들-Outsider

  - Angular.js v1.2 에서 변경된 것들

  - HTML5 지원을 위한 Modernizr vs Html5shiv 에 대하여 

posted by 윤영식
2013. 11. 21. 06:08 My Projects/BI Dashboard

고객이 사용하는 브라우져중 IE는 v8이상만 지원토록 한다면, 이전의 블로그 소스는 IE8이상에서 제대로 보이지 않을 것이다. IE8 이상 및 Chrome, Safari, Firefox에 지원토록 애플리케이션 환경을 개선해 보자 (사실 IE8에서 돌아가면 다른 곳은 문제되지 않음)




1. Windows OS 에서 소스 셋업하기 

  - GitHub에서 환경파일을 클론하고 Windows 환경 설정이 없는 branch로 이동

  - npm install 수행

$ git clone git@github.com:ysyun/SPA_Angular_SpringFramework_Eclipse_ENV.git

$ git checkout feature_mybatis_mapper

$ npm install .

  - npm install 시에 imgmin 관련 오류가 발생한다. package.json과 Gruntfile.js 에서 이미지 압축설정을 제거한다 

// package.json 에서 두가지 설정 제거 (분홍색)

"grunt-contrib-imagemin": "~0.2.0",

"grunt-svgmin": "~0.2.0",


// Gruntfile.js 에서 imagemin, svgmin 관련 설정 제거 (분홍색)

imagemin: {

.. 중략 ..

},

svgmin: {

.. 중략 ..

},

.. 중략 ..

dist: [

'coffee',

'copy:styles',

'imagemin',

'svgmin',

'htmlmin'

]

  - Mysql 설치하고 사용자 생성

    + WEB-INF/spring/dbpool.properties에서 Database를 생성하고 사용자를 생성한다 

    + 테스트 person table을 만든다 

mysql> GRANT ALL 

          on <DBName>.* 

          to <UserName>@localhost 

          identified by '<Password>' with grant option;

mysql> create database <DBName>;

mysql> flush privileges;

mysql> update mysql.user set password = password('<password>') 

           where host='localhost' and user='<UserName>';

mysql> flush privileges;

mysql> \q

$ mysql -u <UserName> -p <DBName>

Enter password : *****

mysql> create table person( id int, name varchar(30));

  - eclipse에서 Maven Project로 import를 해서 Tomcat 7 버전에서 수행하여 보면 아직은 제대로 나오지 않을 것이다. 



2. jQuery 1.9.* 이상 사용할 때 ie 객체 null 에러 해결 

  - Angular.js와 함께 사용시 jQuery 버전이 1.2 이상을 경우 IE8 에서 window.ie 객체를 찾으면서 오류가 발생한다 

  - 해당 오류를 해결하기 위하여 jquery-migrate을 추가한다 (참조)

// bower를 통하여 bower_components 밑으로 설치한다

$ bower install jquery-migrate --save


// index.html 에 추가한다 

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

<script src="bower_components/jquery-migrate/jquery-migrate.min.js"></script>

<script src="bower_components/angular/angular.js"></script>



3. CDN 문제 해결

  - jquery 또는 angular와 같이 유명한 Javascript 모듈은 Grunt 빌드할 때 구글의 CDN 쪽으로 url을 자동 변경한다. 

  - Gruntfile.js에서 CDN 관련 내역을 삭제한다 

// 분홍색 제거 

cdnify: {

  dist: {

    html: ['<%= yeoman.dist %>/*.html']

  }

},


// 분홍색 제거 

grunt.registerTask('build', [

'clean:dist',

'useminPrepare',

'concurrent:dist',

'autoprefixer',

'concat',

'copy:dist',

'cdnify',



4. Bootstrap RWD 적용 문제 해결

  - Bootstrap v3을 사용할 경우 IE8 에서 MediaQuery를 지원하지 않는 관계로 반응형 웹 디자인(RWD)이 적용되지 않는다 (참조)

  - IE8 이상에서 MediaQuery 지원을 위하여 respond.js 를 설치한다 

// bower 설치

$ bower install respond --save


// index.html 설정 : respond.js 설정은 bootstrap 밑으로 넣는다

<!-- build:js scripts/plugins.js -->

<script src="bower_components/bootstrap/dist/js/bootstrap.js"></script>

<script src="bower_components/respond/respond.src.js"></script>

<!-- endbuild -->



5. Minification 으로 인한 Angular.js 애플리케이션 파일 오류 문제 해결

  - Grunt 빌드를 통하여 main.js (controller 파일)이 minification 되면 $scope, $http등도 이름이 축약되어 IE에서 인식을 못 한다 

// index.html 원본 소스

// 하기 <!-- --> 주석처리 된것을 Grunt가 인식하여 scripts 폴더 밑으로 sciprts.js 파일명으로 minification 된다

<!-- build:js({.tmp,app}) scripts/scripts.js -->

<script src="scripts/app.js"></script>

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

<!-- endbuild -->


// dist/index.html 배포된 소스 

<script src="scripts/d41d8cd9.scripts.js"></script>

  - app.js 와 main.js 파일에서 $scope, $http 등과 같은 명칭이 축약되어 어떤것인지 알수 있게 배열 String(문자)로 명시해 준다 (참조)

// app.js 에서 

.config(['$routeProvider', function ($routeProvider) {

$routeProvider

  .when('/', {

    templateUrl: 'views/main.html',

    controller: 'MainCtrl'

  })

  .when('/resttest', {

    templateUrl: 'views/resttest.html',

    controller: 'RestTestCtrl'

  })

  .otherwise({

    redirectTo: '/'

  });

}]);


// main.js 에서 

angular.module('SolarDasbhoardApp')

  .controller('MenuCtrl', ['$scope', '$location', function ($scope, $location) {

    $scope.activeWhen = function (value) {

    return value ? 'active' : '';

    };


    $scope.path = function () {

        return $location.url();

    };

  }])

.. 하위도 동일 



6. HTML 검증 호환을 위해 html 태그 수정 

  - ng-* 로 되어 있는 속성을 전부 data-ng-*로 변경하여 HTML Validation 호환성을 확보한다 (참조

  - *.html 에서 data-를 전부 붙여준다 

// index.html 에서 

<body data-ng-app="SolarDasbhoardApp" data-ng-controller="MenuCtrl">


<ul class="nav navbar-nav">

<li data-ng-class="activeWhen(path()=='/')"><a href="#/">Home</a></li>

<li data-ng-class="activeWhen(path()=='/resttest')"><a href="#/resttest">RESTTest</a></li>

</ul>


<div class="container" data-ng-view=""></div>


// views/restest.html 에서 

<form accept-charset="UTF-8" data-role="form">

                    <fieldset>

      <div class="form-group">

        <input class="form-control" placeholder="ID" data-ng-model="person.id" type="text" value="99">

    </div>

    <div class="form-group">

    <input class="form-control" placeholder="Name" data-ng-model="person.name" type="text" value="dowon">

    </div>

    <input class="btn btn-lg btn-success btn-block" type="submit" value="Save" data-ng-click="save()">

    <input class="btn btn-lg btn-success btn-block" type="submit" value="Update" data-ng-click="update()">

    </fieldset>

</form>

.. 중략 ..

<tr data-ng-repeat="person in persons">

    <td>{{person.id}}</td>

    <td><a data-ng-click="delete(person.id)">{{person.name}}</a></td>

</tr>



7. IE에서 delete 예약어 문제 해결

  - IE에서 수행을 할려면 여전히 main.js 에서 오류가 있다고 나올 것이다

  - $http.delete에서 delete가 ie에서 예약어 이기 때문이다. 따라서 두군에 delete를 수정해 주어야 한다. 

// main.js 에서 

// 기존 코드 

 $scope.delete = function(id) {

    console.log('Delete Person : ', id);

        $http.delete(actionUrl + id).success(function () {

            load();

        });

    }


// 변경 코드 

 $scope.deletePerson = function(id) {

    console.log('Delete Person : ', id);

        $http['delete'](actionUrl + id).success(function () {

            load();

        });

    }


// views/resttest.html 에서 delete호출을 deletePerson 으로 변경

<td><a data-ng-click="deletePerson(person.id)">{{person.name}}</a></td>

 


8. ng-app 명명 문제 해결

  - IE8이상에서 ng-app의 위치와 선언방법을 변경한다 

// 형식

<html xmlns:ng="http://angularjs.org" id="ng-app" ng-app="optionalModuleName">


// 변경

<html class="ng-app:DasbhoardApp" id="ng-app" data-ng-app="DasbhoardApp" xmlns:ng="http://angularjs.org">


이제 Angular.js와 Bootstrap v3 을 IE8 버전 이상에서 사용해 보자 

* 소스 : https://github.com/ysyun/SPA_Angular_SpringFramework_Eclipse_ENV/tree/feature_windows_ie8



* 하루종일 삽질해서 알아낸 IE8 내용

  - 위의 소스를 IE8에서 수행하면 RestTest가 제대로 안될 것이다 (하기 내역은 feature_windows_ie8 에 첨부되지 않았음)

  - 문제 : IE 8 초기버전에서 console 객체가 undefined 라는 것 (참조)

// 이런 내용이 들어가야 한다 

/**

 * Protect window.console method calls, e.g. console is not defined on IE

 * unless dev tools are open, and IE doesn't define console.debug

 */

(function() {

  if (!window.console) {

    window.console = {};

  }

  // union of Chrome, FF, IE, and Safari console methods

  var m = [

    "log", "info", "warn", "error", "debug", "trace", "dir", "group",

    "groupCollapsed", "groupEnd", "time", "timeEnd", "profile", "profileEnd",

    "dirxml", "assert", "count", "markTimeline", "timeStamp", "clear"

  ];

  // define undefined methods as noops to prevent errors

  for (var i = 0; i < m.length; i++) {

    if (!window.console[m[i]]) {

      window.console[m[i]] = function() {};

    }    

  } 

})();


// bower 통하여 console-shim을 설치한다 

$ bower install console-shim --save


// index.html에 선택적으로 설정한다 

    <!--[if lt IE 9]>

      <script src="bower_components/modernizr/modernizr.js"></script>

      <script src="bower_components/es5-shim/es5-shim.js"></script>

      <script src="bower_components/json3/lib/json3.min.js"></script>

      <script src="bower_components/console-shim/console-shim.js"></script>

    <![endif]-->



<참조>

  - Angular.js 공식 IE 호환성 설정

  - IE 버전에 대한 조건부 주석처리하기

  - HTML5와 CSS3 지원을 위한 Modernizr 사용 이유


posted by 윤영식
2013. 11. 20. 10:58 AngularJS/Start MEAN Stack

갑작이 떠오른 아이디어를 모바일 서비스로 빠르게 만들고 싶다. 클라이언트/서버/스토어 이런 것을 해줄 사람은 없다. 나 홀로 만들어 보고 프로토타입핑해서 스타팅해보고 싶을 때 MEAN Stack을 사용하자 




1. 스프링같은 "WebApp Framework" 에 대한 고민

  클라이언트단의 Android 또는 iOS 네이티브 코드는 익히는데 시간은 없고, 다양한 스마트 기기에 대응을 했으면 한다. 그리고 JavaScript를 어느 정도 할 수 있다. 이때 생각할 수 있는 대안이 "웹앱 사이트" 또는 "모바일 웹앱"을 만드는 것이다. 그러나 예전 jQuery 코드를 생각해 보자. 조금만 복잡해 지면 쉽게 스파게티가 되고, 테스트 코드는 엄두도 못내며 결국 유지보수의 골칫덩어리로 남게 된다. 

그렇다면 자바에서 Spring Framework과 같이 DI가 되면서 MVC처럼 모듈화 개발을 가능하게 해주는 클라이언트단 프레임워크는 없을까? 처음엔 Backbone.js를 살펴 보았고, 예로 Trello와 같은 서비스에서 사용을 하고 있다. 하지만 코드의 길이가 길어지고 불필요한 중복코드의 남발이 야기되어 좀 더 간결하면서 예전 Flex Framework과 같은 것은 없을지 고민해 보았다. HTML을 확장해서 사용해도 자동 해석을 하여 브라우져가 이해할 수 있는 HTML로 바꾸어 주면 되는 그런 프레임워크 말이다. 역시 구글 형님들도 똑같은 고민을 하고 프로젝트에 적용하고 놀라운 효과를 본 새로운 프레임워크를 공식 런칭하게 되니 이름하여 AngularJS





2. 새로운 "I/O Machine" 에 대한 고민

  서비스에서 요하는 기능중 가장 중요한 부분은 Push 기능이 아닐까 싶다. 실시간으로 변화하는 정보를 수많은 유저에게 바로 Push 서비스해줄 수 있는 서버가 필요하다. 하지만 Push를 위하여 별도의 Port를 사용하긴 싫고 웹앱이므로 요청하였던 HTTP Port를 사용하고 싶다면 어떤 것을 선택해야 할까?

서버단이 Java라면 vert.x도 고려해 볼 만하다. 하지만 아직 레퍼런스와 문서가 부족하다. 하지만 여기에 우리에게 구세주와 같은 세로운 I/O 머신이 나왔으니 V8엔진을 기반으로 돌아가는 Node.js이다. Node.js를 사용하는 가장 큰 목적중 하나가 Socket.io와 같은 이벤트 기반의 Push 모듈들이 아닐까 생각이든다. Node.js는 모듈기반으로 필요한 것들을 추가하여 사용할 수 있다. 이를 위하여 NPM(Node Package Manager)이 존재하고 방대한 모듈과 레퍼런스들이 존재한다. 또한 Chrome에 적용된 V8 자바스크립트 해석기를 달고 있으므로 모든 코딩은 JavaScript 만으로 이루어진다





3. 스키마에 자유로운 "Store"에 대한 고민 

  서비스를 빠르게 만들고 싶은데 개발을 진행하다 보면 자주 테이블이 변경되고 이에 맞추어 서버코드들도 수정을 해주거나 새롭게 테이블을 생성해 주는 번거로운 작업들을 거친다. 따라서 스키마가 자유로운 데이터베이스를 원한다. 그리고 서비스이므로 사용자의 데이터를 많이 쌓아 두고 싶다. 이를 통해 향후 마케팅 용으로 사용하려 한다. 이왕이면 클라이언트/서버가 모두 자바스크립트이므로 데이터베이스 핸들링도 자바스크립트 이면 좋겠다. 이를 위해 새롭게 나온 NoSQL 이 있으니 이름하여 MongoDB. JSON형태로 데이터를 저장하고 정규화 없이 도큐먼트 형태로 저장을 할 수 있어서 스키마의 제약이 없다. 또한 테라급의 데이터 정도는 MongoDB를 Sharding구조로 만들어 MapReduce를 이용하여 처리할 수 있다. 그리고 Node.js 처럼 V8 해석기를 탑재하여 데이터 핸들링을 JavaScript로 할 수 있다. 마치 Oracle의 SqlPlus에서 Sql을 수행하듯 MongoDB가 정의한 형식의 쿼리를 수행할 수 있다. 





4. Node.js를 기업용 WAS(Web Application Server)로 만들기  

  초창기 Java를 떠올려 보자. 처음엔 Applet에 열광하여 업무를 애플릿으로 만든적이 있다. 그때 서버단은 Apache + PHP정도 였을 것이다. 그리고 SUN에서 J2EE 스팩을 발표하면서 Tomcat, JBoss, WebSphere, WebLogic 같은 WAS가 나왔으며, 이 또한 C계열의 TP-Monitor처럼 Java기반 I/O Machine 이 나오게 된 것이다. Java진영의 WAS는 나름의 공통된 스팩을 기반으로 웹을 이끌어 갈 수 있는 새로운 I/O 머신으로 떠올랐고, 기업에서 요구하는 Transaction 처리에 대한 스팩도 제안하여 많이 사용하게 되었다. 최근에는 Tomcat같은 Servlet 엔진만 갖춘 WAS에 Spring Framework를 사용하면 충분히 기업용 Transaction을 처리하는 업무를 개발할 수 있는 환경까지 오게 되었다. 그러나 Java의 WAS가 web1.0의 중심 머신이었다면 Node.js기반의 WAS가 web2.0의 중심 머신으로 되기 위한 조건은 무엇일까?

  결국 Node.js위에 올라가는 Java의 J2EE 스팩과 같은 견고한 스팩이 필요하다. 현재까지는 아직 그러한 스팩이 나오진 않으나 Java의 Servlet 스팩에 비견할 만한 프레임워크가 나왔으니 그것이 Express.js 이다. 이제 web2.0의 I/O 머신은 Node.js가 될 것이고, 여기에 웹에 대한 표준 스팩구현체는-사실 Node.js를 위한 공통된 스팩은 없다- 아니지만 가장 많이 사용하는 Express.js를 통하여 Node.js가 Tomcat과 같은 기능을 보유하게 되었다.




Angular.js -> Node.js + Express.js -> MongoDB 를 사용하게 되면 

  - JavaScript로 모두 개발을 한다. 클라이언트/서버 분리하여 개발할 필요가 없다. 그냥 FullStack JavaScript개발을 하면 된다

  - 클라이언트 개발이 MV* 개발이 되어 서버 개발에 익숙한 개발자가 쉽게 접근할 수 있다 

  - 서버에서 Push 기능을 쉽게 적용할 수 있으므로 다양한 서비스의 응용이 가능하다 

  - 자바의 서블릿 엔진 구현체인 Tomcat과 같이 충실한 RESTful I/O 머신을 쉽게 구현할 수 있다

  - 서버의 멀티 쓰레드 문제에서 해방될 수 있다. Asynch의 세상에서 놀자 

  - 데이터 스키마를 미리 정의하지 않고 에자일하게 개발을 진행할 수 있다

  - 큰 규모의 데이터를 저장하고 처리 할 수 있다  



그러나 우리에게 남은 과제는 이것들을 어떻게 배우고 사용하는지 잘 모른다는 것이다. 각자를 심도 있게 배우는 것은 별도의 가이드북을 따로 보길 바란다. 대신 여기서는 실전 서비스를 어떻게 하면 빠르게 만들 수 있을지 노하우를 적어보려한다. 낮에는 회사에서 업무를 보면서 향후 스타트업을 준비하는 개발자 또는 사람들에게 좀 더 이로운 가치를 전달해 주고 싶은 개발자가 3개월안에 개발할 수 있는 서비스를 이 책을 통해 런칭할 수 있는 것이 가능해 질 수 있길 바란다. 그래서 책을 쓰면서 동시에 작은 서비스를 만들어 보려 한다. 책은 가이드이고 그물일 뿐이다. 실제 돌아가는 서비스가 물고기이다. 즉, 실제 서비스를 낚지 못하면 이 책의 가치도 없다고 생각한다. 실제로 이것을 증명해 보이고 싶다. 



Getting Start MEAN

          


posted by 윤영식
2013. 11. 16. 05:46 My Projects/BI Dashboard

Spring MVC의 RESTful 방식을 사용하면서 AngularJS를 Eclipse에서 개발하기 위한 제반 설정 환경을 만들어 보도록 한다. 핵심은 Java를 위한 라이브러리 의존성 관리와 빌드를 위하여 Maven을 사용하고 JavaScript는 의존성 관리는 bower를 사용하고 빌드도구는 grunt를 사용하게 된다. 따로 따로 사용하지 않고 하나의 프로젝트로 합치는 과정을 생각해 보자 



1. Maven을 이용한 Spring MVC 환경 만들기 

  - 이미 Eclipse Juno 기반에서 Maven + Spring MVC 환경 설정 블로깅을 하였다

  - pom.xml 에 logback 관련 내용 추가 (참조)

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<groupId>com.ysyun</groupId>

<artifactId>DashboardTest</artifactId>

<version>0.0.1-SNAPSHOT</version>

<packaging>war</packaging>

<url>http://maven.apache.org</url>


<properties>

<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<spring.version>3.2.0.RELEASE</spring.version>

<junit.version>4.11</junit.version>

<jdk.version>1.6</jdk.version>

</properties>


<dependencies>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-core</artifactId>

<version>${spring.version}</version>

<exclusions>

<exclusion>

<artifactId>commons-logging</artifactId>

<groupId>commons-logging</groupId>

</exclusion>

</exclusions>

</dependency>


<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-web</artifactId>

<version>${spring.version}</version>

</dependency>


<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-webmvc</artifactId>

<version>${spring.version}</version>

<exclusions>

<exclusion>

<groupId>commons-logging</groupId>

<artifactId>commons-logging</artifactId>

</exclusion>

</exclusions>

</dependency>


<!-- use logback logger -->

<dependency>

<groupId>ch.qos.logback</groupId>

<artifactId>logback-access</artifactId>

<version>1.0.13</version>

</dependency>

<dependency>

<groupId>ch.qos.logback</groupId>

<artifactId>logback-core</artifactId>

<version>1.0.13</version>

</dependency>

<dependency>

<groupId>ch.qos.logback</groupId>

<artifactId>logback-classic</artifactId>

<version>1.0.13</version>

</dependency>

<dependency>

<groupId>org.slf4j</groupId>

<artifactId>slf4j-api</artifactId>

<version>1.7.5</version>

</dependency>

<dependency>

<groupId>org.slf4j</groupId>

<artifactId>jcl-over-slf4j</artifactId>

<version>1.7.5</version>

</dependency>

<dependency>

<groupId>org.slf4j</groupId>

<artifactId>log4j-over-slf4j</artifactId>

<version>1.7.5</version>

</dependency>

<dependency>

<groupId>org.slf4j</groupId>

<artifactId>slf4j-jcl</artifactId>

<version>1.7.5</version>

</dependency>


<dependency>

<groupId>junit</groupId>

<artifactId>junit</artifactId>

<version>${junit.version}</version>

<scope>test</scope>

</dependency>

</dependencies>


<build>

<finalName>DashboardTest</finalName>

<plugins>

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-compiler-plugin</artifactId>

<version>3.0</version>

<configuration>

<source>${jdk.version}</source>

<target>${jdk.version}</target>

</configuration>

</plugin>

</plugins>

</build>

</project>

  - 다음에 logback.xml 환경파일을 resource에 놓아 classpath에 잡아준다. 개발/배포 따로 구성한다 

<?xml version="1.0" encoding="UTF-8"?>

<configuration scan="true">

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">

        <encoder>

            <charset>utf-8</charset>

            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>

        </encoder>

    </appender>

    <logger name="ch.qos.logback" level="WARN"/>

    <logger name="org.apache" level="WARN"/>

    <logger name="org.springframework" level="WARN"/>

    <logger name="org.springframework.web" level="WARN"/>

    <logger name="org.springframework.security" level="WARN"/>

    <logger name="org.springframework.cache" level="WARN"/>


    <root level="DEBUG">

        <appender-ref ref="CONSOLE"/>

    </root>

</configuration>




2. SPA 환경 잡아주기 

  - Yeoman을 통하여 angular 프로젝트 만들기 (참조)

$ yo angular SolarDasbhoard


  - 위의 파일을 각각 다음과 같이 이동한다 

    + app/ 폴더에 있는 것을 src/main/webapp로 복사한다 

    + node_modules 및 test 폴더 그리고 나머지 모든 파일을 eclipse 프로젝트 root로 복사한다 

      여기서 node_modules안의 모듈은 grunt와 karma 테스트 모듈들로 운영에 배포될 필요가 없다. 

      운영 배포는 webapp/ 밑에 있는 파일들이고, 사용하는 모듈은 src/main/webapp/bower_components/ 밑에 위치한다 

      


  - app폴더 밑의 내용이 src/main/webapp로 이동하였으므로 관련 환경설정 내역을 수정한다 

// bower 설정파일인 .bowerrc  변경 

{

    "directory": "src/main/webapp/bower_components"

}


// karm.conf.js 파일 내역 수정

    files: [

      'src/main/webapp/bower_components/angular/angular.js',

      'src/main/webapp/bower_components/angular-mocks/angular-mocks.js',

      'src/main/webapp/bower_components/angular-resource/angular-resource.js',

      'src/main/webapp/bower_components/angular-cookies/angular-cookies.js',

      'src/main/webapp/bower_components/angular-sanitize/angular-sanitize.js',

      'src/main/webapp/scripts/*.js',

      'src/main/webapp/scripts/**/*.js',

      'test/mock/**/*.js',

      'test/spec/**/*.js'

    ],


// grunt build를 위한 Gruntfile.js 내역 수정 

// 소스의 위치와 grunt build시에 dist 폴더에 .html과 WEB-INF/* 파일까지도 copy 하도록 수정 

 yeoman: {

      // configurable paths

      app: require('./bower.json').appPath || 'src/main/webapp',

      dist: 'dist'

    },

... 중략 ...

 copy: {

      dist: {

        files: [{

          expand: true,

          dot: true,

          cwd: '<%= yeoman.app %>',

          dest: '<%= yeoman.dist %>',

          src: [

            '*.{ico,png,txt,html}',

            '.htaccess',

            'bower_components/**/*',

            'images/{,*/}*.{gif,webp}',

            'styles/fonts/*',

            'WEB-INF/*'

          ]

        }, {

          expand: true,

          cwd: '.tmp/images',

          dest: '<%= yeoman.dist %>/images',

          src: [

            'generated/*'

          ]

        }]

      },

      styles: {

        expand: true,

        cwd: '<%= yeoman.app %>/styles',

        dest: '.tmp/styles/',

        src: '{,*/}*.css'

      }

    },



3. .war 배포파일 만들기 

  - Maven(java 서버)와 Yo(javascript 클라이언트) 환경설정을 완료하였다면 빌드했을 때 최종파일을 .war파일로 묶어보자 

  - grunt build를 했을 경우 프로젝트의 root/dist 디렉토리가 신규 생성되어 src/main/webapp/* 파일이 minification 되어 들어있다

  - 해당 파일을 .war안에 묶기 위하여 pom.xml 에 플러그인 설정 추가

<build>

<finalName>DashboardTest</finalName>

<plugins>

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-compiler-plugin</artifactId>

<version>3.0</version>

<configuration>

<source>${jdk.version}</source>

<target>${jdk.version}</target>

</configuration>

</plugin>

<!-- grunt build 수행하여서 생성되는 폴더인 dist 를 지정한다 -->

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-war-plugin</artifactId>

<version>2.4</version>

<configuration>

<warSourceDirectory>dist</warSourceDirectory>

</configuration>

</plugin>

</plugins>

</build>


  - 배포파일 war 만들기 순서

$ grunt build (if error ocurred, option --force)

$ mvn clean install

[INFO] Scanning for projects...

[INFO] ------------------------------------------------------------------------

[INFO] Building DashboardTest 0.0.1-SNAPSHOT

[INFO] ------------------------------------------------------------------------

.. 중략 ..

[INFO] ------------------------------------------------------------------------

[INFO] BUILD SUCCESS

[INFO] ------------------------------------------------------------------------

[INFO] Total time: 5.109s

[INFO] Finished at: Fri Nov 15 15:43:09 EST 2013

[INFO] Final Memory: 12M/81M

[INFO] ------------------------------------------------------------------------

  * eclipse 의 "Run As..." 의 "5 Maven Build"를 해도 같은 결과를 받음 


이제 eclipse 상에서 Spring Framework + AngularJS Framework 으로 개발을 해보자 

GitHub URL : https://github.com/ysyun/SPA_Angular_SpringFramework_Eclipse_ENV



<참조>

  - SLF4J를 통하여 logback 사용하기

  - Yeoman을 통한 AngularJS + Express 환경구성하기

  - Maven 기본 디렉토리 layout, pom.xml 에서 변경하는 방법

posted by 윤영식
2013. 11. 12. 13:08 HTML5, CSS3/jQuery

jQuery UI 스타일로 작성된 내역을 AngularJS 스타일의 개발방식으로 변형하는 방법을 알아보자



1. AngularJS 길을 가다 

  - jQuery에서 UI 컴포넌트를 모아 놓은 것이 jQuery UI라면 AngularJS기반으로 UI 컴포넌트를 모아 놓은 것이 AngularJS UI 이다

    사실, Angular UI는 jQuery UI를 AngularJS를 가지고 확장한 것이다

  - jQuery UI를 통하여 DOM을 조작하여 화면을 보여주는 것과 AngularJS를 통하여 UI를 조작하는 방법은 틀리다

  - AngularJS Way를 먼저 살펴본다  

  - AngularJS를 사용하여 SoC(Separate Of Concern) 관심분리를 수행하여 테스트 및 모듈화가 가능해 진다 

    + Controller : $scope를 통한 view와 two way binding 

    + Service : view와 분리된 비즈니스 로직 구현

    + Directive : 재사용 가능 컴포넌트이며 model 값을 DOM 프러퍼티와 연결해주고, DOM 이벤트 반응하여 model 값을 업데이트한다  



2. jQuery UI Plugin 방식을 보자 

  - datepicker에 대한 사용을 본다 

<div>

    Date Of Birth:

    <input type="text" id="dateOfBirth">

    <br>

    Current user's date of birth:

    <span id="dateOfBirthDisplay"></span>

</div>

  - 자바스크립트 코드 : 비즈니스 로직과 DOM 조작이 합쳐져 있다. 날짜를 선택하면 해당 날짜의 값을 span에 표현함 


$(function () {

   var user = {

      dateOfBirth: new Date(1970, 0, 1)

   };


   var displayValue = function () {

      $("#dateOfBirthDisplay").text(new Date(user.dateOfBirth).toDateString());

   };


   var processChange = function() {

      user.dateOfBirth = $("#dateOfBirth").datepicker("getDate");

      displayValue();

   };


   $("#dateOfBirth").datepicker({

         inline: true,

         onClose: processChange,

         onSelect: processChange

      }

   );


   displayValue();


   // initial display of value in input-box

   $("#dateOfBirth").datepicker("setDate", user.dateOfBirth);


});



3. datepicker를 AngularJS 방식으로 SoC 분리해 보자

  - 위의 jQuery 방식을 angularjs 방식의 model, controller, directive로 분리해 본다 

  - AngularJS 접근법

   Step 1) Model을 먼저 생각하고 Service를 만든다

   Step 2) Model이 표현될 View에 대해 생각해 보고 template을 만들고, 필요하면 자동 모델 바인딩되는 Directive도 만든다.

   Step 3) 각 View 를 Controller에 연결한다 (ng-view and routing, ng-controller)

- Model : user.dateOfBirth

- Directive : input 태그를 my-datepicker 태그로 만든다

- Controller : $scope를 통해 model을 제어한다 

  - AngularJs 의 Step1), Step 2), Step 3) 수행

// Step 1, 3) model

function DemoController($scope) {

   $scope.user = {

      dateOfBirth: new Date(1970, 0, 1)

   }

}


// Step 2) directive in html 

<div ng-app="demo" ng-controller="DemoController">

    Date Of Birth:

    <my-datepicker type="text" ng-model="user.dateOfBirth" />

    <br>

    Current user's date of birth:

    <span id="dateOfBirthDisplay">{{user.dateOfBirth}}</span>

</div>

  


4. my-datepicker Directive 만들기 

  - AngularJS Directive 만들기에 따른 myDatepicker

    + compile 첫번째 phase : DOM 변경

    + compile 두번째 phase : DOM value 조작 

// demo 모듈에 directive 만들기 

angular.module("demo", []).directive('myDatepicker', function ($parse) {

   return {

      restrict: "E",

      replace: true,

      transclude: false,

      // compile은 최초 한번 호출되고, return 되는 펑션이 link 펑션이 된다

      compile: function (element, attrs) {

         // Phase 1 - Compile function

         // 엘러먼트의 어트리뷰트 express을 function으로 변환한다 

         // user.dateOfBirth에 대한 set, get이 가능토록 해준다 

         // attrs.ngModel === ng-model 과 같다 

         var modelAccessor = $parse(attrs.ngModel);


   // 엘러먼트 태그를 바꾸어 준다 

         var html = "<input type='text' id='" + attrs.id + "' ></input>";

         var newElem = $(html);

         element.replaceWith(newElem);


         // Phase 2 - Link function

         // - onClose, onSelect를 정의한다 

         // - 이벤트 발생시 콜백을 정의한다 

         // - 값이 변경되었을 때 Angular Context로 들어가서 watch가 불리고 최종적으로 값을 변경한다 

         return function (scope, element, attrs, controller) {


// jQuery 이벤트 콜백 펑션이 호출되면 $apply를 호출하여 Angular Context가 적용되어 $watch가 자동 호출토록 한다 

// compile, link, watch 이해하기  

            var processChange = function () {

               var date = new Date(element.datepicker("getDate"));


               // HTML element 값이 변경되었음을 $watch가 호출되도록 trigger 해주는 콜백펑션을 등록한다 

               scope.$apply(function (scope) {

                  // Change bound variable

                  modelAccessor.assign(scope, date);

               });

            };


// jQuery의 이벤트 콜백을 등록한다 

            element.datepicker({

               inline: true,

               onClose: processChange,

               onSelect: processChange

            });


// scope.$apply안엣 modelAccessor의 값이 변경되면 datepicker 의 setDate를 호출한다

            scope.$watch(modelAccessor, function (val) {

               var date = new Date(val);

               element.datepicker("setDate", date);

            });

         };

      }

   };

});



<참조>

  - 원문 : 존재하는 컴포넌트를 AngularJS의 Directives로 만들기

  - AngularJS Way

  - AngularJS Directive 만들기 

  - datepicker에서 $parse를 사용하게된 이유

posted by 윤영식
2013. 11. 9. 07:08 My Services/PlayHub

Angular 기반 SPA를 위한 Push 환경은 Node 기반의 Socket.io를 사용한다. Angular.js의 장점중 하나가 DI(Dependency Injection)이고 이를 Node에서 구현한 프레임워크인 express-train (축약, train)을 사용한다. train은 특정 폴더에 .js 파일을 놓으면 자동으로 DI되어 사용을 할 수 있게 해준다. 일정한 규약만을 지키면 개발 코드를 모듈화 하여 관리할 수 있고 테스트 할 수 있다. train은 express에 DI 기능을 접목해 놓은 것으로 보면 된다





1. Express-Train 설치하기 

  - 설치 : npm install -g express-train 

  - 새로운 Node 환경 프로젝트 생성

// train이 사용하는 기본 템플릿을 기반으로 자동으로 palette.fronend.node 디렉토리에 기본 폴더와 파일을 생성해 준다 

$ train new palette.frontend.node 

// 수행 http://localhost:4000 호출 

$ train run 

  - 디렉토리 구조 

    + app : auto-injected 로 표현된 것 .js 파일을 해당 디렉토리에 놓으면 다른 곳에서 파일 명칭으로 불러와서 (function parameter DI 방식) 사용을 할 수 있다 

    + bin : 이것은 node_modules/express-train 밑에 존재한다 

    + test : Mocha를 이용한다

    + public : Angular.js 기반으로 작성한 모든 static 파일들이 이곳에 놓일 것이다. 엄밀히 따지면 Distributed된 Frontend파일이 놓인다

app

  /controllers      -- application controllers (**autoinjected**)

  /lib                 -- application specific modules (**autoinjected**)

  /middleware    -- application middleware (**autoinjected**)

  /models          -- application models (**autoinjected**)

  /public            -- static content (html, js, css, etc) : SPA모든 파일을 해당 폴더 밑에 복사할 것이다  

  /views            -- view templates (loaded into express' view engine) : 서버단의 파일 jade, ejs, hbs 같은 서버 template 파일

  index.js           -- index file exports the express train application : train run 시에 수행되는 파일 based on Node.js


bin                   -- executable scripts : 

doc                  -- documentation

config              -- environmental configuration files : train 환경 설정 파일 

test                 -- tests : 서버 테스트 파일

package.json    -- npm package.json (needs to have express-train as a dependency)

  - train의 환경파일 : development와 production 환경파일을 구분함 

{

  "cookie_secret": "boggle at the situation",

  "session":{

    "key": "boggle at the situation",

    "secret": "boggle at the situation"

  },

  "client_port": 3000,

  "request_timeout": 1000,

  // RESTful 호출 접두사 Restanuglar 참조 

  "restful_api_version": "/api/v1",

  "server_name": "first",

  // REDIS 연결 Client Library 환경 설정

  "data_broker": {

    "port" : 6379,

    "host" : "localhost",

    "auth" : ""

  },

  // MongoDB 환경 설정

  "mongodb":{

    "uri": "mongodb://localhost/playhub_palette"

  },

  "playhub_channel_file": "channels.json",

  // Winston 에러 로그가 쌓일 MongoDB 정보 

  "winston_mongo": {

    "level": "error",

    "db": "playhub_palette",

    "host": "localhost",

    "port": 27017,

    "collection": "sd_logs"

  },

  // Winston 로그 파일의 명칭 설정

  "winston_file": {

    "filename": "sd.log",

    "exception": "sd_exception.log",

    "maxSize": 20480,

    "maxFiles": 12,

    "colorize": true

  },

  "character_encoding":"utf-8"

}



2. Express-Train 파일 사용하기 

  - auto-injected 되는 폴더에 모듈로 운용하고 싶은 .js 파일을 놓는다. 주로 MVC 파일과 Utility 또는 Service 파일들이다 

  - auto-inject 환경내역은 node_modules/express-train/lib/app.js 파일안에 이미 설정되어 있다.

// app.js 내역중 일부 

// autoinject라는 옵션을 주면 해당 지정된 폴더의 .js파일을 메모리에 로딩하고 DI를 한다 

// 이때 주의할 것은 상호 참조가 되지 않도록 하기 위한 폴더 전략이 중요하다. 위에서 부터 아래순서로 로딩되어 DI 된다 

var LOCATIONS = createApplication.locations = {

    pkg:{

        path: '../package.json'

    },

    config:{

        path: '../config'

    },

    logs:{

        path: '../logs'

    },

    models: {

        path: 'models',

        autoinject: true,

        aggregateOn: 'models'

    },

    views: {

        path: 'views'

    },

    lib: {

        path: 'lib',

        autoinject: true

    },

    controllers: {

        path: 'controllers',

        autoinject: true

    },

    pub: {

        path: 'public'

    },

    middleware: {

        path: 'middleware',

        autoinject: true

    }, 

   // 만약 app밑에 services폴더를 만든다면! 

    services: {

       path: 'services',

       autoinject: true

    },

}; 


// app.js 에서 express를 생성하고 views와 public 폴더를 강제 설정하고 있다

// _.each에서 autoinject 폴더의 모든 파일을 읽고 DI를 수행해 준다. 이때 DI되는 .js 파일은 Singleton 패턴이다 

// tree.constant로 저장된 객체를 DI 받을 수 있다

function createApplication(dir, locs) {

    catchErrors();


    var app = express(),

        locations = _.defaults((locs || {}), LOCATIONS);

        tree = new nject.Tree();


    app.set('views', path.join(dir, locations.views.path));

    app.set('public', path.join(dir, locations.pub.path));


    var config = loadConfig(path.join(dir, locations.config.path));

    tree.constant('config', config);


    _.each(_.where(locations, {autoinject: true}), function(location) {

        traverseAndRegister(path.join(dir, location.path), tree, location.aggregateOn)

    });


    //allow override of app

    if(!tree.isRegistered('app')) {

        tree.constant('app', app);  

    }


    //log path 하나 추가함 

    var logPath = path.join(dir, locations.logs.path);

    tree.constant('logPath', logPath);


    return tree.resolve();

}


  - 폴더 전략 : app.js에 설정된 순서에-위에서 아래로- 따라 로딩됨, 참조가 많은 것은 주로 service가 되지 않을까 함

    + lib/1level : 가장 기본적인 파일 참조하는 것이 없는 것들 - util, logger, db connection, constant

    + lib/2level : express-train의 파일들  - route, views, middleware 설정

    + lib/3level : 1, 2level의 .js를 사용하는 파일들 

////////

// 형식

module.exports = function(app, config, logger, ...) {

  // app, config, logger 등 파일이름을 대소문자 구분하여 넣으면 자동 DI를 해준다. 객체는 train에서 Singleton으로 관리한다 

}


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

// 예) lib/1level/logger.js

// MongoDB에 winston logger를 이용하여 에러발생 내역을 저장하거나 파일로 떨굼 


var winston = require('winston'),

  path = require('path');


module.exports = function(app, config, logPath) {


  winston.info('[1level: log config] init');

  require('winston-mongodb').MongoDB;


  var dbOpts = {

    level: config.winston_mongo.level,

    db: config.winston_mongo.db,

    collection: config.winston_mongo.collection,

    host: config.winston_mongo.host,

    port: config.winston_mongo.port

  };

  winston.info('[1level: log config] DB option is ' + JSON.stringify(dbOpts) );


  var fileName = path.join(logPath, config.winston_file.filename);

  winston.info('[1level: log config] general log : ' + fileName);

  var exceptionFileName = path.join(logPath, config.winston_file.exception);

  winston.info('[1level: log config] exception log : ' + exceptionFileName);


  var fileOpts = {

    filename: fileName,

    maxsize: config.winston_file.maxSize,

    maxFiles: config.winston_file.maxFiles,

    colorize: config.winston_file.colorize

  }


  var exceptionOpts = {

    filename: exceptionFileName,

    maxsize: config.winston_file.maxSize,

    maxFiles: config.winston_file.maxFiles,

    colorize: config.winston_file.colorize

  }


  var logger = new winston.Logger({

    transports: [

      new winston.transports.Console(),

      new winston.transports.File(fileOpts),

      new winston.transports.MongoDB(dbOpts)

    ],

    exceptionHandlers: [

      new winston.transports.Console(),

      new winston.transports.File(exceptionOpts),

      new winston.transports.MongoDB(dbOpts)

    ]

  });


//  testing

// logger.log(config.winston_mongo.level, '[log config] Test logging module completely');

//  logger.info('==============>> hi info');

//  logger.warn('==============>> hi warn');

//  logger.error('==============>> hi error');  <- MongoDB에도 쌓인다 


  return logger;

}


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

// 예) lib/2level/ 또는 lib/2level에서 logger사용하기 

// lib/2level/views.js 파일에서 

// logger.js 파일에서 파일명칭을 - 대소문자 구분 - Function Parameter DI 해준다 

module.exports = function (applogger) {

  logger.info('[2level: view engine - jade] init');

  app.set('view engine', 'jade');

};




방법-1. Angular 프로젝트 배포후 Express-Train 프로젝트로 통합 

  - 엄밀히 따지면 합치는 시점은 개발이 다 된후 수행한다. 즉, SPA와 Node 파트의 Git 저장소를 틀리게 가져감을 전제로 한다 

    + SPA 저장소 : palette.frontend

    + Node저장소 : palette.frontend.node 

  - 개발이 된후 SPA와 Node 통합은 다음 순서로 하자 

    + Yeoman을 통하여 생성된 Angular 스케폴딩 파일은 별도로 개발한다 -> grunt build 수행 -> dist 폴더에 배포파일이 생성된다 

    + palette.frontend.node/app/public/ 와 palette.frontend.node/app/views 폴더 밑의 모든 파일을 삭제한다

    + palette.frontend.node/app/public/ 밑으로 dist/* 모든 파일을 복사한다 

// 폴더구조 

playhub

  |_ palette.frontend         (별도 git repository)

  |_ palette.frontend.node (별도 git repository)


// Angular 기반 frontend 파일 

playhub$ cd palette.frontend

playhub$ grunt build 


// Express-train에서 사용하지 않을 파일 삭제  

playhub$ cd ../palette.frontend.node/app/public

playhub$ rm -rf *

playhub$ cd ../views

playhub$ rm -rf *


// Angular 기반 SPA frontend파일을 Express-train의 app/public/ 폴더 밑으로 복사 

playhub$ cp -rf ../../../palette.frontend/dist/* ../public/

  - 테스트 하기 

$ cd palette.frontend.node

train run 

// http://localhost:4000/ 으로 호출한다 



방법-2. Express-Train 프로젝트를 Angular 프로젝트로 통합

  - Angular 프로젝트(palette.frontend)의 grunt, bower, yo, karma 기능을 그대로 사용한다 

  - Express-Train 프로젝트(palette.frontend.node)는 express-train기반으로 이미 스케폴딩된 서버단 애플리케이션이다. 

    즉 palette.frontend.node의 디렉토리 구조를 변경시켜보자  

// express-train 프로젝트로 이동하기 

$ cd palette.frontend.node


// express-train의 bin 디렉토리를 palette.frontend.node로 이동하기  

$ cd node_modules/

$ cd express-train/

$ ls -arlt

total 80

drwxr-xr-x   3 nulpulum  staff    102  9 17 17:20 test

-rw-r--r--   1 nulpulum  staff     90  9 17 17:20 index.js

-rw-r--r--   1 nulpulum  staff     82  9 17 17:20 .npmignore

-rw-r--r--   1 nulpulum  staff  13393  9 26 19:13 ReadMe.md

drwxr-xr-x   4 nulpulum  staff    136 11  8 14:14 bin

drwxr-xr-x   5 nulpulum  staff    170 11  8 14:14 boilerplates

-rw-r--r--   1 nulpulum  staff  14976 11  8 14:14 package.json

drwxr-xr-x   4 nulpulum  staff    136 11  8 14:14 lib

drwxr-xr-x  12 nulpulum  staff    408 11  8 14:14 node_modules

drwxr-xr-x  11 nulpulum  staff    374 11  8 14:14 .

drwxr-xr-x  13 nulpulum  staff    442 11  8 14:14 ..

// bin을 palette.frontend.node 루트로 이동시킨다 

$ mv bin ../../


// bin으로 들어가서 train run 명령을 수행 - 내부적으로 commander 모듈을 사용한다 

$ train run

module.js:340

    throw err;

          ^

Error: Cannot find module 'commander'

    at Function.Module._resolveFilename (module.js:338:15)

    at Function.Module._load (module.js:280:25)

    at Module.require (module.js:364:17)

    at require (module.js:380:17)

    at Object.<anonymous> (/Users/nulpulum/development/playground/playhub/palette.frontend.node/bin/train:3:15)

    at Module._compile (module.js:456:26)

    at Object.Module._extensions..js (module.js:474:10)

    at Module.load (module.js:356:32)

    at Function.Module._load (module.js:312:12)

    at Function.Module.runMain (module.js:497:10)


// 에러가 발생하므로 commander, underscore, handlebars, boilerplate 모듈을 설치한다 

$ cd .. && npm install commander --save 

$ npm install underscore --save

$ npm install handlebars --save

$ npm install boilerplate --save-dev

$ cd bin && train run 

module.js:340

    throw err;

          ^

Error: Cannot find module '../../lib/app'

    at Function.Module._resolveFilename (module.js:338:15)

    at Function.Module._load (module.js:280:25)

    at Module.require (module.js:364:17)

    at require (module.js:380:17)

    at Object.<anonymous>(/Users/nulpulum/development/playground/playhub/palette.frontend.node/bin/commands/console.js:4:13)

    at Module._compile (module.js:456:26)

    at Object.Module._extensions..js (module.js:474:10)

    at Module.load (module.js:356:32)

    at Function.Module._load (module.js:312:12)

    at Module.require (module.js:364:17)


// 에러 다시 수정 하기 

$ cd ../node_modules/express-train/

$ ls -alrt

total 80

drwxr-xr-x   3 nulpulum  staff    102  9 17 17:20 test

-rw-r--r--   1 nulpulum  staff     90  9 17 17:20 index.js

-rw-r--r--   1 nulpulum  staff     82  9 17 17:20 .npmignore

-rw-r--r--   1 nulpulum  staff  13393  9 26 19:13 ReadMe.md

drwxr-xr-x   5 nulpulum  staff    170 11  8 14:14 boilerplates

-rw-r--r--   1 nulpulum  staff  14976 11  8 14:14 package.json

drwxr-xr-x   4 nulpulum  staff    136 11  8 14:14 lib

drwxr-xr-x  12 nulpulum  staff    408 11  8 14:14 node_modules

drwxr-xr-x  10 nulpulum  staff    340 11  9 15:19 .

drwxr-xr-x  16 nulpulum  staff    544 11  9 15:29 ..


// express-train의 lib밑의 모든파일을 bin 밑으로 이동시킨다 

$ mv lib/* ../../bin/

// index.js 파일을 이동한 bin 디렉토리에 같이 놓는다 

$ mv index.js ../../bin/


// express-train 은 삭제한다 

$ cd .. && rm -rf express-train

$ cd ../../bin/

// 최종 lib이 bin 밑으로 들어갔다 

$ ls -alrt

total 32

-rw-r--r--   1 nulpulum  staff    64  9 17 17:20 runner.js

-rw-r--r--   1 nulpulum  staff  3594  9 27 14:06 app.js

drwxr-xr-x  11 nulpulum  staff   374 11  9 15:19 ..

-rw-r--r--   1 nulpulum  staff    89 11  9 15:56 index.js

-rwxr-xr-x   1 nulpulum  staff   785 11  9 16:02 train

drwxr-xr-x   7 nulpulum  staff   238 11  9 16:02 .

drwxr-xr-x   3 nulpulum  staff   102 11  9 16:08 commands


// commands 폴더에서 runt.js를 제외한 사용하지 않는 나머지 파일은 삭제하거나 나중을 위해서 다른 위치로 백업한다 

$ cd commands && rm console.js cycle.js new.js boilerplate.js


// commands 폴더의 run.js 안에 내역을 수정 한다 

// 기존 내역 : var appPath = path.join(process.cwd(), '../app/index.js');

// 수정 내역 

var appPath = path.join(process.cwd(), './index.js');


// lib밑에 있는 index.js가 가르키는 파일 수정 

// 기존 내역 : app: require('./lib/app')

// 수정 내역

// module.exports = {

//   app: require('./app.js')

// };


var paletteApp = require('./app.js');

module.exports = paletteApp(__dirname);


// palette.frontend.node 루트의 app 디렉토리명칭을 api 라고 변경한다 

$ cd ../../ && mv app api 

  

// 다음으로 직접 app.js안의 인식하는 디렉토리 경로를 상대경로로 변경한다 

$ cd bin && vi app.js

//위치 변경하기 

var LOCATIONS = createApplication.locations = {

    pkg:{

        path: '../package.json'

    },

    config:{

        path: '../config'

    },

    logs:{

        path: '../logs'

    },

    models: {

        path: '../api/models',

        autoinject: true,

        aggregateOn: 'models'

    },

    views: {

        path: '../api/views'

    },

    lib: {

        path: '../api/lib',

        autoinject: true

    },

    controllers: {

        path: '../api/controllers',

        autoinject: true

    },

    // 향후 palette.frontend의 grunt build시에 dist 폴더 밑으로 되는 것을 public 폴더로 바꿀 것이다 

    pub: {

        path: '../public'

    },

    middleware: {

        path: '../api/middleware',

        autoinject: true

    }

};


// api 디렉토리 밑에 있는 public 디렉토리를 루트로 옮겨서 테스트 해본다 

$ mv app/public .


// bin 디렉토리 밑에 있는 train 쉘 실행하기 

$ train run 

[express train application listening on 4000]


  - Express-Train기반의 서버구조를 만들었고, 이제 이것을 palette.frontend 쪽으로 옮겨보자 

// palette.frontend.node 프로젝트의 package.json 명칭을 변경 - 나중에 palette.frontend쪽의 package.json과 합치기 위함

$ mv package.json packate.json.node


// public 폴더는 삭제한다 

$ rm -rf public 


// 옮기기 

$ mv * ../palette.frontend/

mv: rename node_modules to ../palette.frontend/node_modules: Directory not empty

mv: rename test to ../palette.frontend/test: Directory not empty

$ cd node_modules

$ mv * ../../palette.frontend/node_modules/


// palette.frontend 에서 dist를 public으로 명칭 변경하여 테스트 

$ cd ../../palette.frontend

$ mv dist public 

$ cd bin

$ train run

[express train application listening on 4000]


// 이제 grunt build를 수행할 때 dist 디렉토리를 만드는 것이 아니라 public 으로 만들어 보자 

$ cd .. && rm -rf public 


// grunt 환경파일의 내역 수정 

$ vi Gruntfile.js

// 변경내역 

 grunt.initConfig({

    yeoman: {

      // configurable paths

      app: require('./bower.json').appPath || 'app',

      dist: 'public'  // 'dist'를 'public'으로 변경한다 

    },


// 다시 grunt로 빌드를 하면 public 폴더가 생성된다 

$ grunt build 

$ cd bin && train run 

[express train application listening on 4000]


// 최종 palette.frontend의 합친 구조

  - api : Node 서버 

  - app : SPA Angular

  - public : app 배포 디렉토리이면서 bin/train run 수행시 인식되는 서비스 폴더


// palette.frontend git 저장소에 push하기전에 package.json.node의 내용을 package.json에 통합한다 

{

  "name": "palette",

  "version": "0.0.1",

  "dependencies": {

    "express": "3.2.x",

    "express-train": "1.0.x",

    "connect-timeout": "latest",

    "connect-mongodb": "latest",

    "mongoose": "latest",

    "async": "latest",

    "express-hbs": "latest",

    "passport": "latest",

    "winston": "latest",

    "commander": "~2.0.0",

    "underscore": "~1.5.2",

    "handlebars": "~1.1.2",

    "nject": "~0.1.6"

  },

  "devDependencies": {

    "grunt": "~0.4.1",

    "grunt-contrib-copy": "~0.4.1",

    "grunt-contrib-concat": "~0.3.0",

    "grunt-contrib-coffee": "~0.7.0",

    "grunt-contrib-uglify": "~0.2.0",

    "grunt-contrib-compass": "~0.5.0",

    "grunt-contrib-jshint": "~0.6.0",

    "grunt-contrib-cssmin": "~0.6.0",

    "grunt-contrib-connect": "~0.5.0",

    "grunt-contrib-clean": "~0.5.0",

    "grunt-contrib-htmlmin": "~0.1.3",

    "grunt-contrib-imagemin": "~0.2.0",

    "grunt-contrib-watch": "~0.5.2",

    "grunt-autoprefixer": "~0.2.0",

    "grunt-usemin": "~0.1.11",

    "grunt-svgmin": "~0.2.0",

    "grunt-rev": "~0.1.0",

    "grunt-concurrent": "~0.3.0",

    "load-grunt-tasks": "~0.1.0",

    "grunt-google-cdn": "~0.2.0",

    "grunt-ngmin": "~0.0.2",

    "time-grunt": "~0.1.0",

    "karma-ng-scenario": "~0.1.0",

    "grunt-karma": "~0.6.2",

    "karma-script-launcher": "~0.1.0",

    "karma-chrome-launcher": "~0.1.0",

    "karma-firefox-launcher": "~0.1.0",

    "karma-html2js-preprocessor": "~0.1.0",

    "karma-jasmine": "~0.1.3",

    "karma-requirejs": "~0.1.0",

    "karma-coffee-preprocessor": "~0.1.0",

    "karma-phantomjs-launcher": "~0.1.0",

    "karma": "~0.10.4",

    "karma-ng-html2js-preprocessor": "~0.1.0",

    "mocha": "latest"

  },

  "engines": {

    "node": ">=0.8.0"

  },

  "main": "./app/index.js",

  "scripts": {

    "test": "grunt test",

    "start": "cd bin && train run",

    "server-test": "mocha test"

  }

}


// npm 명령으로 scripts 설정 내역 호출하기 

$ npm start

[express train application listening on 4000]


  - 최종 완성본 저장소 : MyPlayHub GitHub Repository

// git clone 하여 train run 수행시 다음과 같이 오류가 발생할 경우 

$ train run

env: node\r: No such file or directory


// dos2unix 패키지를 설치하여 수정하여 준다 

$ brew install dos2unix

==> Installing dos2unix dependency: gettext

==> Downloading http://ftpmirror.gnu.org/gettext/gettext-0.18.3.tar.gz

.. 중략 ..


$ dos2unix train

dos2unix: converting file train to Unix format ...

$ train run 

[express train application listening on 4000]



방법-3. Yeoman Generator FullStack을 통한 통합

  - Express + Angular의 통합

  - Angular는 클라이언트의 파일 전송과 암호화를 위하여 Lint와 압축을 위해 Grunt를 사용함 

  - Express는 서버단이므로 압축이 필요없음 

// FullStack generator 설치 

$ sudo npm install -g generator-angular-fullstack

// FullStackTest라는 애플리케이션 명칭으로 스케폴딩 생성

$ yo angular-fullstack FullStackTest

// 빌드하여 클라이언트 파일 Lint 및 압축 

// public 폴더밑으로 배포본이 만들어짐 

$ grunt build 

... 중략 ...

Elapsed time

useminPrepare:html                   25ms

concurrent:dist                          5s

autoprefixer:dist                        113ms

concat:public/scripts/modules.js  23ms

copy:dist                                  640ms

cdnify:dist                                 21ms

ngmin:dist                                263ms

cssmin:public/styles/main.css     33ms

uglify:dist                                  28ms

uglify:public/scripts/plugins.js      373ms

uglify:public/scripts/modules.js   134ms

usemin:html                             482ms

usemin:css                              152ms

Total                                        8s



<참조> 

  - Angular-Express-Train-Seed

  - Yeoman : generator-fullstack with angular and express

  - dos2unix 설치하여 사용하기

posted by 윤영식
2013. 9. 28. 16:18 AngularJS/Concept


posted by 윤영식
2013. 9. 25. 09:16 My Services/Smart Visualization

SPA 방식의 개발을 위해서는 Client에 MV* Framework을 선택해야 한다. 초창기는 Backbone.js(이하, 백본)를 많이 사용하였고, 구현체로 Trello가 있으니 해당 서비스의 아키텍쳐를 참조해 보자. 그리고 SKT에서 코너스톤이라는 이름의 Framework도 백본을 사용하여 "웹앱"을 만들 수 있도록 안드로이드/iOS용 런타임 환경도 제공한다. 백본은 가벼우면서 광범위하게 많이 사용을 하고 있지만 개발 복잡성 비교에서 중간정도에 위치한다. 


<Trello 아키텍쳐>



브라우져에서 동작하는 애플리케이션의 템플릿이 .html 이면서 <chart ../> 와 같이 사용자 정의 html tag를 사용하여 View단을 좀 더 단순화 및 컴포넌트화 할 수 있는 AngularJS를 사용하기로 한다. AngularJS에는 여러 장점이 있지만 "웹 애플리케이션을 견고하게 만드는 방법" 블로깅을 통해 백본대신 왜 AngularJS를 선택했는지 갈음한다. AngularJs 프레임워크 기반의 SPA 개발을 위하여 쉽게 접근하는 방법은 도구를 통하여 하는 것인데, 오스마니님이 개발한 Yeoman을 통하여 AngularJS기반 프로젝트를 쉽게 구성할 수 있다. 



1. Yeoman 사용하기

  - 사전에 NodeJS 최신버전이 설치되어 있어야 한다. 

    설치후 npm (Node Package Manager)를 통하여 Yeoman(이하, yo)을 설치함

  - Yo 설치하기  : 가이드를 따라서 설치하면 된다 

    + yo : angular, express 등 관련 framework에 대한 scaffolding 프로젝트를 자동으로 만들어 주는 기능을 한다 

              generator-angular, generator-express와 같이 generator-*가 앞에 붙는다. npm 통하여 설치함 

              generator 만드는 방법은 "generator-angular와 express 연동하기" 블로그 참조한다 

    + bower : client단의 필요한 컴포넌트를 자동설치할 수 있다. 의존성관계 bower.json 파일에 설정한다. npm 과 유사함

    + grunt : 개발된 소스를 테스트, 빌드하는 javascript의 ant과 같은 역할을 한다. Gruntfile.js 에 설정한다 

    


  - yo 설치후 "yo -h" 수행하면 설치된 generator 목록을 볼 수 있다. 또는 yo라고 수행해도 된다 (보여지는 형식만 틀리다)

// generator와 관련된 MUST HAVE generator

$ sudo npm install -g generator-generator


// angularjs 관련 generator 설치 

$ sudo npm install -g generator-angular


// AngularJS 관련 scaffolding 명령 목록 

$ yo -h 

Angular

  angular:app  (default : angular) 

  angular:common

  angular:constant

  angular:controller

  angular:decorator

  angular:directive

  angular:factory

  angular:filter

  angular:main

  angular:provider

  angular:route

  angular:service

  angular:value

  angular:view


// express generator도 설치하자 

sudo npm install -g generator-express


* 여기서 잠깐!

만일 사용하는 OS가 Windows라면 지금 당장 Linux를 설치해서 사용한다. 우리가 배포하고 서비스하는 서버는 Cloud환경일 가능성이 다분히 높기 때문이다. Windows Server같은 곳에서 Node.js를 운영하는 것은 상상조차 하고 싶지 않다. 

  - Vagrant를 Windows에 설치해서 쉽게 Linux환경에 접근토록 하자



2. AngularJS 스케폴딩 만들기

  - yo를 통하여 AngularJS 기반의 SPA 프로젝트를 만든다. 여기에는 Node.js로 동작하는 서버 코드는 생성되지 않는다 

  - GitHub에 SmartVisualization 라고 생성을 한다. 각자의 계정에 만든다 

// GitHub의 저장소 clone 하고 angular 스케폴딩 명령 수행

$ git clone https://github.com/<자신의 아이디>/SmartVisualization.git  SmartVisualization

$ cd SmartVisualization

$ yo angular:app SmartVisualization

[?] Would you like to include Twitter Bootstrap? Yes

[?] Would you like to use the SCSS version of Twitter Bootstrap with the Compass CSS Authoring Framework? No

[?] Which modules would you like to include? (Press <space> to select)

❯⬢ angular-resource.js

 ⬢ angular-cookies.js

 ⬢ angular-sanitize.js


... 모듈들 자동 설치 중략 ...


// npm : package.json

// bower : .bowerrc, bower.json

// grunt : Gruntfile.js

// test 도구인 karma: karma*

// git 관련 : .git*

$ ls -alrt

-rw-r--r--   1 nulpulum  staff    11  2 21  2013 .gitattributes

-rw-r--r--   1 nulpulum  staff   415  4 16 00:02 .editorconfig

-rw-r--r--   1 nulpulum  staff   394  6 10 21:31 .jshintrc

-rw-r--r--   1 nulpulum  staff    56  6 10 21:31 .gitignore

-rw-r--r--   1 nulpulum  staff    44  6 10 21:31 .bowerrc

-rw-r--r--   1 nulpulum  staff   120  7 15 06:32 .travis.yml

-rw-r--r--   1 nulpulum  staff  1304  8 12 05:33 karma.conf.js

-rw-r--r--   1 nulpulum  staff  1348  8 12 05:33 karma-e2e.conf.js

drwxr-xr-x   5 nulpulum  staff   170  9 25 08:51 ..

drwxr-xr-x   5 nulpulum  staff   170  9 25 09:04 test

-rw-r--r--   1 nulpulum  staff   404  9 25 09:04 bower.json

-rw-r--r--   1 nulpulum  staff  8217  9 25 09:04 Gruntfile.js

drwxr-xr-x  12 nulpulum  staff   408  9 25 09:04 app

drwxr-xr-x  16 nulpulum  staff   544  9 25 09:04 .

drwxr-xr-x  39 nulpulum  staff  1326  9 25 09:04 node_modules

-rw-r--r--   1 nulpulum  staff  1466  9 25 09:05 package.json


  - Yeoman을 통하여 AngularJS 프로젝트 스케폴딩을 만들었다면 Grunt명령을 통하여 테스팅을 위한 서버를 기동할 수 있다 

// 명령을 수행하면 Node.js를 기반으로 서버가 기동하고 9000 port로 Chrome브라우져가 자동 실행되어 호출된다

// Grunt는 Java의 Ant와 같은 도구이다 

$ grunt server

Running "server" task


Running "clean:server" (clean) task

Cleaning .tmp...OK


Running "concurrent:server" (concurrent) task


    Running "copy:styles" (copy) task

    Copied 2 files

    

    Done, without errors.


    Elapsed time

    copy:styles  15ms

    Total        16ms


    Running "coffee:dist" (coffee) task


    Done, without errors.


    Elapsed time

    coffee:dist  10ms

    Total        10ms


Running "autoprefixer:dist" (autoprefixer) task

File ".tmp/styles/bootstrap.css" created.

File ".tmp/styles/main.css" created.


Running "connect:livereload" (connect) task

Started connect web server on localhost:9000.


Running "open:server" (open) task


Running "watch" task

Waiting...


// 브라우져 자동 실행 및 9000 포트 자동 호출된 결과물 


다음은 AngularJS 에 대하여 알아보자



<참조>

  - Client MV* Framework에대한 ToDoMVC 개발 복잡성 비교

  - Yeoman의 generator-angular와 express 연동하기

  - Vagrant를 통하여 개발환경 꾸미기

posted by 윤영식
2013. 9. 23. 11:40 NodeJS/Concept

Node.js 위에 기본적으로 Express.js를 많이 사용하지만 좀 더 추상화 되고 MVC 다운 프레임워크를 사용해 보고자. Express.js 및 Socket.io 와 다양한 데이터베이스를 추상화해서 사용할 수 있는 기능을 갖춘 Sails.js Framework을 사용해 보도록 한다. 



1. 사용 배경

  - Enterprise 급 MVC Framework : Express.js + Socket.io 기본 설정되어 사용

  - Waterline 이라는 adapter를 통하여 다양한 Database의 호환성 보장 

    + 흐름 : Model 객체 <-> Waterline <-> Databases 

    + 지원 데이터베이스 : MySql, PostgreSql, MongoDB, etc

    + 데이터베이스 연결을 하지 않으면 파일 또는 디스크에 백만개 밑으로 key:value 저장하는 node-dirty의 어뎁터를 사용한다

  - 손쉬운 설치 및 RESTful Web Services 개발

  - socket.io 및 redis 통합 

  - SPA (Single Page Application) 개발을 위한 client framework (backbone, ember, angular)의 adapter 제공 

    + Yeoman generator 제공 : Generator Sails AngularJS



2. 설치 및 사용

  - Yeoman을 기본 설치한다 : Yeoman 이해와 설치하기

  - "generator-sails-angular" 설치 후 "yo" 명령수행 : 빨간색의 Sails-angular 선택 (주의, sails 0.9.4 버전과 호환안됨)

// generator 설치

$ sudo npm install -g generator-sails-angular generator-angular


// yeoman 명령 수행

$ yo

[?] What would you like to do? (Use arrow keys)

 ❯ Run the Angular generator (0.3.1)

   Run the Backbone generator (0.1.7)

   Run the Bootstrap generator (0.1.3)

   Run the Express generator (0.1.0)

   Run the Generator generator (0.2.0)

   Run the Mocha generator (0.1.1)

   Run the Sails-angular generator (0.0.1)

   Update your generators

   Install a generator

   Find some help

   Get me out of here!


// 선택을 하면 Server에 Sails 환경과 Client에 AngularJS 환경이 동시에 설정된다 

// Client는 기본 Twitter bootstrap을 사용하고 Client 추가 Component는 "bower install <Component명칭>" 을 사용하여 설치한다 

// Server는 "npm install <module명칭>"을 사용하여 설치한다


  - "sails lift" 수행 (2013.09.23현재 0.9.4 버전으로 sails upgrade : sudo npm update sails -g )

$ sails lift

info:

info:

info:    Sails.js           <|

info:    v0.9.4              |\

info:                       /|.\

info:                      / || \

info:                    ,'  |'  \

info:                 .-'.-==|/_--'

info:                 `--'-------'

info:    __---___--___---___--___---___--___

info:  ____---___--___---___--___---___--___-__

info:

info: Server lifted in `/Users/prototyping/sails/test`

info: To see your app, visit http://localhost:1337

info: To shut down Sails, press <CTRL> + C at any time.


debug: --------------------------------------------------------

debug: :: Mon Sep 23 2013 10:05:04 GMT+0900 (KST)

debug:

debug: Environment : development

debug: Port : 1337

debug: --------------------------------------------------------



3. Sails MVC 이해하기 

  - Model 

    + Waterline 이라는 Sails Adapter를 통하여 다양한 데이터베이스의 객체로 사용되어 진다 

    + 단, Waterline의 형식(JSON 포멧)에 맞추어서 Model을 정의한다 : 모든 데이터베이스에 추상화된 정의이다 

    + /config/adapters.js 파일에 Database 환경 설정을 한다 또한 관련 데이터 베이스 adapter를 설치해야 함 

       예) MongoDB 설치 

$ npm install sails-mongo --save

npm http 200 https://registry.npmjs.org/sails-mongo

npm http GET https://registry.npmjs.org/sails-mongo/-/sails-mongo-0.9.5.tgz

.. 중략 ..

    + /api/models/ 디렉토리 밑에 <Model>.js 파일이 위치한다

// model 생성  

$ sails generate model Person


// 생성 내역 : Model의 맨앞자는 자동으로 대문자로 변경됨 

$ /api/models> cat Person.js

/*---------------------

:: Person

-> model

---------------------*/

module.exports = {

attributes : {

// Simple attribute:

// name: 'STRING',


// Or for more flexibility:

// phoneNumber: {

// type: 'STRING',

// defaultValue: '555-555-5555'

// }

}

};/


// CRUD : 기본 Deferred Promised 지원 

생성 : Person.create

조회 : Person.findOne/find

갱신 : Person.update

제거 : Person.destroy



  - Controller

    + view와 model 사이의 제어 역할을 한다

    + /api/controllers/ 디렉토리에 위치함 

// comment controller 만들기 : 최종 명령 tag뒤에 스페이스가 있으면 안된다. 

// controller 뒤 첫번째 인자 : comment가 controller의 명칭

// controller 두번째 인자이후 : comment의 메소드 명칭들 열거  

$ sails generate controller comment create like tag 


// 결과 

$ /api/controllers> cat CommentController.js

/*---------------------

:: Comment

-> controller

---------------------*/

var CommentController = {

// To trigger this action locally, visit: `http://localhost:port/comment/create`

create: function (req,res) {

// This will render the view: /views/comment/create.ejs

res.view();

},


// To trigger this action locally, visit: `http://localhost:port/comment/like`

like: function (req,res) {

// This will render the view: /views/comment/like.ejs

res.view();

},


// To trigger this action locally, visit: `http://localhost:port/comment/tag`

tag: function (req,res) {

// This will render the view:/views/comment/tag.ejs

res.view();

}


};

module.exports = CommentController; 


// 브라우져 호출 

http://localhost:1337/comment/create (like, tag) 


  - Route

    + RESTful 호출을 uri를 제어하고 싶다면 Route를 지정한다 

    + /config/routes.js 파일안에 정의한다 

    + controller와 action을 정의한다 

    + home의 경우 

       1) /config/routes.js 에 하기와 같이 home 컨트롤러 정의 

       2) /api/controllers/HomeController.js 존재 : index액션(펑션) 존재

       3) /views/home/index.ejs 파일 존재 : 서버에서 rendering되어 클라이언트 응답 

// Routes

// *********************

// 

// This table routes urls to controllers/actions.

//

// If the URL is not specified here, the default route for a URL is:  /:controller/:action/:id

// where :controller, :action, and the :id request parameter are derived from the url

//

// If :action is not specified, Sails will redirect to the appropriate action 

// based on the HTTP verb: (using REST/Backbone conventions)

//

// action을 정의하지 않으면 다음의 기본 형식을 따른다 

// GET:     /:controller/read/:id

// POST:   /:controller/create

// PUT:      /:controller/update/:id

// DELETE:/:controller/destroy/:id

//

// If the requested controller/action doesn't exist:

//   - if a view exists ( /views/:controller/:action.ejs ), Sails will render that view

//   - if no view exists, but a model exists, Sails will automatically generate a 

//       JSON API for the model which matches :controller.

//   - if no view OR model exists, Sails will respond with a 404.

//

module.exports.routes = {

// To route the home page to the "index" action of the "home" controller:

'/' : {

controller : 'home'

}


// If you want to set up a route only for a particular HTTP method/verb 

// (GET, POST, PUT, DELETE) you can specify the verb before the path:

// 'post /signup': {

// controller : 'user',

// action : 'signup'

// }


// Keep in mind default routes exist for each of your controllers

// So if you have a UserController with an action called "juggle" 

// a route will be automatically exist mapping it to /user/juggle.

//

// Additionally, unless you override them, new controllers will have 

// create(), find(), findAll(), update(), and destroy() actions, 

// and routes will exist for them as follows:

/*


// Standard RESTful routing

// (if index is not defined, findAll will be used)

'get /user': {

controller : 'user',

action : 'index'

},

'get /user/:id': {

controller : 'user',

action : 'find'

},

'post /user': {

controller : 'user',

action : 'create'

},

'put /user/:id': {

controller : 'user',

action : 'update'

},

'delete /user/:id': {

controller : 'user',

action : 'destroy'

}

*/

};


  - View

    + Sails는 기본 ejs를 사용한다 

    + /views/ 밑에 위치한다 

    + Partial 파일을 include 할 수 있다 

    + 메인 파일 : layout.ejs 

   


  - Adapter

    + 데이터베이스를 바꾸고 싶다면 /config/adapters.js 에서 내용을 바꾼다 : MongoDB를 사용한다면 mongo adapter를 설치해야 한다

// Configure installed adapters

// If you define an attribute in your model definition, 

// it will override anything from this global config.

module.exports.adapters = {


// If you leave the adapter config unspecified 

// in a model definition, 'default' will be used.

'default': 'memory',

// In-memory adapter for DEVELOPMENT ONLY

// (data is NOT preserved when the server shuts down)

        // Sails-dirty는 Node-Dirty의 Adapter를 memory or disk에 JSON 을 저장하는 store이다. 백만개 밑으로 저장시 사용

memory: {

module: 'sails-dirty',

inMemory: true

},


// Persistent adapter for DEVELOPMENT ONLY

// (data IS preserved when the server shuts down)

// PLEASE NOTE: disk adapter not compatible with node v0.10.0 currently 

// because of limitations in node-dirty

// See https://github.com/felixge/node-dirty/issues/34

disk: {

module: 'sails-dirty',

filePath: './.tmp/dirty.db',

inMemory: false

},


// MySQL is the world's most popular relational database.

// Learn more: http://en.wikipedia.org/wiki/MySQL

mysql: {

module : 'sails-mysql',

host : 'YOUR_MYSQL_SERVER_HOSTNAME_OR_IP_ADDRESS',

user : 'YOUR_MYSQL_USER',

password : 'YOUR_MYSQL_PASSWORD',

database : 'YOUR_MYSQL_DB'

}

};

   + generator-sails-angular 를 yeoman통하여 설치하였다면 SPA를 개발하는 것이므로 실제로 ejs 쓸 일이 거의 없고, Client 단에서 AngularJS 정의에 따라 Routing과 View Control이 발생한다. 따라서 Controller를 통하여 수행할 action에서 response.view() 하는 것이 아니라, response.json(<JSON Object>) 만을 응답으로 주면 될 것으로 보인다. 또한 업무적인 처리는 action에서 구현하면 된다   

   + response.send(형식)

res.send(); // 204

res.send(new Buffer('wahoo'));

res.send({ some: 'json' });

res.send('<p>some html</p>');

res.send('Sorry, cant find that', 404);

res.send('text', { 'Content-Type': 'text/plain' }, 201);

res.send(404);

   + response.json(형식)

res.json(null);

res.json({ user: 'tj' });

res.json('oh noes!', 500);

res.json('I dont have that', 404);


* Redis나 데이터베이스의 외부 인터페이스 라이브러리는 Custom Adapter 개발을 수행한다. (만드는 방법



3. MongoDB 연결하여 RESTful 호출 테스트 

  - mongodb 환경설정 : sails-mongo 어뎁터가 설치되어 있어야 함 

$ cd config && vi adapters.js  이동하여 mongodb 설정 입력


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

// adapters.js 수정 내역 

module.exports.adapters = {

  // If you leave the adapter config unspecified 

  // in a model definition, 'default' will be used.

  'default': 'mongo',


  // In-memory adapter for DEVELOPMENT ONLY

  memory: {

    module: 'sails-memory'

  },


  // Persistent adapter for DEVELOPMENT ONLY

  // (data IS preserved when the server shuts down)

  disk: {

    module: 'sails-disk'

  },


  // sails v.0.9.0

  mongo: {

    module   : 'sails-mongo',

    host     : 'localhost',

    port     : 27017,

    user     : '',

    password : '',

    database : 'sailsdb'

  }

};

  

  - Person model 수정

// ProjectName/models/Person.js  수정 내역 

module.exports = {

  attributes: {

  name: 'string',

  phone: 'string',

  address: 'string',

  sex: 'string',

  etc: 'string' 

  }

};


  - PersonController의 create 수정 : Promise 구문 형식 사용가능  

// ProjectName/controllers/PersonController.js  수정 내역

module.exports = {

  create: function (req,res) {

    Person.create({ 

      name: req.param('name'),

      phone: req.param('phone'),

      address: req.param('address'),

      sex: req.param('sex'),

      etc: req.param('etc')

    }).done(function(err, person){

      if(err) throw err;

      res.json(person);

    });

  },

  .. 중략 ..

}


  - Postman을 통하여 호출 테스트 : Postman chrome extension을 설치한다. 


  - mongo shell 에서 person collection 조회 : model이 mongodb에서 collection과 맵핑된다  

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

// mongo shell 확인 

mongo

MongoDB shell version: 2.4.5

connecting to: test

show dbs

local 0.078125GB

sailsdb 0.203125GB

use sailsdb

switched to db sailsdb

show collections

person

system.indexes

> db.person.find();

{ "name" : "도원", "phone" : "1004", "address" : "seoul", "sex" : "man", "etc" : "developer", "createdAt" : ISODate("2013-09-23T02:28:51.281Z"), "updatedAt" : ISODate("2013-09-23T02:28:51.281Z"), "_id" : ObjectId("523fa76377e9f89562000001") }



4. MongoDB의 _id를 sequence number로 바꾸기 

  - "_id" : ObjectId("523fa76377e9f89562000001") 라고 나오는 것을 "_id" : 1 씩 증가하는 정수로 바꾸기 

  - 최대한 sails-mongo adapter의 api를 사용하여 구현한다 

  - sequence document 만들기 

> db.seq.insert(

 {

   _id: 'personid',

   seq: 0

 });

> db.seq.find();

{ "_id" : "personid", "seq" : 0 }


  - Sails Model을 정의한다 

$ sails generate model seq

$ cd models && vi Seq.js


// Seq.js 내역 

// _id를 통하여 여러 model의 sequence 값을 구분하기 위해 사용한다 

module.exports = {

  attributes: {

  _id: 'string',

  seq: 'integer'

  }

};


  - Person의 모델에서 명시적으로 _id 를 integer로 정의한다 

module.exports = {

  attributes: {

  _id: 'integer',

  name: 'string',

  phone: 'string',

  address: 'string',

  sex: 'string',

  etc: 'string'

  }

};


  - PersonController.js 내역 수정 : Gist 소스 파일

  /**

   * /person/create

   */ 

  create: function (req,res) {

      // seq 번호를 얻어온다 

      Seq.find({_id: 'personid'}).done(function(err, seqObj) {

        if(err) throw err;

        // 배열임을 주의  

        var seqNo = seqObj[0].seq;

        seqNo++;

        console.log('>>> seqNo is', seqNo);

        

        // insert시에 _id 값을 넣어준다 

        Person.create({ 

          _id: seqNo, 

          name: req.param('name'),

          phone: req.param('phone'),

          address: req.param('address'),

          sex: req.param('sex'),

          etc: req.param('etc')

        }).done(function(err, person){

          if(err) throw err;

          res.json(person);

        });


       // sails-mongo의 api에 한번에 select & update해주는 findAndModify 메소드가 없는 관계로 seqNo를 update해준다 

        Seq.update({_id: 'personid'}, {seq: seqNo}).done(function(err, seqObj){

          if(err) throw err;

        })

      });

  },


  - Postman으로 테스트 데이터를 입력한다 


  - mongo shell로 mongodb에 저장된 내역을 확인해 보자 

// postman통하여 데이터를 2개 넣었을 경우 

> db.seq.find();

{ "_id" : "personid", "seq" : 2, "updatedAt" : ISODate("2013-09-23T09:47:29.548Z") }


> db.person.find();

{ "_id" : 1, "name" : "윤도원-8", "phone" : "1004", "address" : "목동", "sex" : "사람", "etc" : "모비콘 블로깅", "createdAt" : ISODate("2013-09-23T09:45:36.391Z"), "updatedAt" : ISODate("2013-09-23T09:45:36.391Z") }

{ "_id" : 2, "name" : "윤도원-9", "phone" : "1004", "address" : "목동", "sex" : "사람", "etc" : "모비콘 블로깅", "createdAt" : ISODate("2013-09-23T09:47:29.547Z"), "updatedAt" : ISODate("2013-09-23T09:47:29.547Z") }

>

 

 

5. Sails를 통항 WebApp 만들기

  - SailsCast Site 참조

  - SailsCast GitHub 소스

  - 유튜브 동영상 25 강좌 보기


 

<참조>

  - Sails.js + AngularJS + MongoDB 데모

  - MongoDB auto increment Sequence Field 방법

  - Mongoose Sequence Table 통한 auto increment plugin  : express 와 mongoose 사용시 해당 plugin을 사용

posted by 윤영식
2013. 9. 4. 14:05 AngularJS/Start MEAN Stack

Mobile 서비스 개발을 위하여 JavaScript 기반의 기술 스택을 선택하여 사용하다 보니 1년사이에 MongoDB, Express.js, Angular.js, Node.js를 공부하고 솔루션에 적용하게 되었다. 그간 해당 기술들을 추상화한 프레임워크들을 사용하여 개발을 진행해 왔다. 그러나 가끔 풀리지 않는 문제에 대한 근원적인 해결책을 찾는데는 다시 처음부터 추상화 내역에 대한 이해가 바탕이 되지 않으면 풀리지 않았다. 


즉, 기본 스택을 추상화한 스택사용시에는 추상화 영역에 대한 사용경험과 전반적인 이해가 없이는 오히려 시간공수만 더 늘어나는 격이 된다. 예로 Express와 MongoDB를 좀 더 수월하게 사용키위하여 Trains, Sails Framework들을 사용해 보았고 나름의 장점은 있으나 왠지 남의 옷을 입은듯한 느낌이랄까? 문제 발생시 빠른 대응이 어려웠다 (개인적으론 Trains Framework의 Node.js단의 IoC 방식을 좋아한다)


따라서 소규모 팀으로 개발하면서 하나의 가치를 빠른 시간안에 전달하고자 한다면 기본 Stack기반으로 진행하면서 필요한 시점에 스스로 추상화 모듈을 만들어 사용하는 방법이 좋지 않을까 생각한다. (Don't Invent Wheel 이니 초기엔 쓸만한 것을 GitHub에서 찾아 응용하고 없으면 개발하자) 그런 의미에서 mean.io 에서 기본 Stack에 충실하면서 현재 사용되는 최신 개발 기술들 - Jade, Bootstrap, Mongoose, Bower, Grunt 같은 - 도 함께 접목되어 적당히 어려운 상태이므로 개발시 집중(Flow)을 가능케 하는 구조를 가지고 있다. (너무 어려우면 흥미를 읽고, 너무 쉬우면 긴장감이 떨어진다. 적당히 어려우면 흥미와 긴장감을 주어 개발시 집중을 가능케 한다) 



1. 설치

  - 홈페이지에서 zip 파일 다운로드 

  - 프로젝트로 이동해서 

$ cd mean


// 사전에 node.js 설치 

// Node, npm 설치 shell

$ npm install .

 

// 사전에 mongodb 설치하고 start

$ grunt



2. 접속 

  - http://localhost:3000/

    + Sign Up 할 수 있다

    + 테스트 글을 CRUD 할 수 있다 

    


  - MongoDB 

> show collections

articles

sessions

system.indexes

users


해당 스택을 통해 SPA(Single Page Application) 개발에 대한 Lean(신속한) 스타트업을 시도하자.

 


<참조>

  - mean.io

posted by 윤영식
2013. 8. 22. 15:36 AngularJS/Concept

AngularJS의 서비스 종류는 여러가지가 있습니다. Factory만 알았다면 Costant, Value, Provider 그리고 서비스의 확장 방법등에 대해 알아보자. "AngularJS Service is an Application Brain"



1. 개념

  - 서비스는 여러 종류가 있다

  - 항시 Singleton 이다. 즉, 하나의 Instance만 존재한다 



2. Constant

  - 공통으로 사용하는 환경값을 지정할 때 상수를 지정한다 

  - 사용자 정의 Directive에서 환경값을 가져다 쓰고 싶을 때 유용하다

// script 

app = angular.module("app", []);


app.controller('MainCtrl', function($scope, fooConfig) {

  $scope.fooConfig = fooConfig;

});


app.constant('fooConfig', {

  config1: true,

  config2: "Default config2"

});


// html

<!DOCTYPE html>

<html ng-app="app">

<head>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>

<meta charset=utf-8 />

<title>JS Bin</title>

</head>

  <body ng-controller="MainCtrl">

    config1: {{fooConfig.config1}}

    <br />

    config2: {{fooConfig.config2}}

  </body>

</html>



3. Value

  - Value Service는 Constant Service와 유사하지만 최초 한번은 변경을 할 수 있다. Factory 서비스의 작은 동생뻘이다 

  - Directive에서 사용하기 유용하다 

  - Value를 가지고 계산할 수는 없다 

// script 

app = angular.module("app", []);


app.controller('MainCtrl', function($scope, fooConfig) {

  $scope.fooConfig = fooConfig;

  // 최초 한번의 변경이 가능

  angular.extend(fooConfig, {config3: "I have been extended"}); 

});


app.value('fooConfig', {

  config1: true,

  config2: "Default config2 but it can changes"

});


// html 

<!DOCTYPE html>

<html ng-app="app">

<head>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>

<meta charset=utf-8 />

<title>JS Bin</title>

</head>

  <body ng-controller="MainCtrl">

    config1: {{fooConfig.config1}}

    <br />

    config2: {{fooConfig.config2}}

    <br />

    config3: {{fooConfig.config3}}

  </body>

</html>



4. Factory

  - 가장 일반적으로 사용하는 서비스 종류이다 

  - 팩토리는 어떤 형태의 데이터타입이라도 리턴할 수 있다. 리턴된 오브젝트를 가지고 작업을 한다 

  - 단, 리턴된 오브젝트의 값을 변경하면 해당 팩토리의 인스턴스를 사용하는 모든 곳에 변경값이 반영된다 

  - Revealing Module Pattern 식으로 작성을 한다 

// script 

app = angular.module("app", []);

app.controller('MainCtrl', function($scope, foo) {

  $scope.foo = foo;

});


// revealing module pattern 방식의 팩토리 서비스

app.factory('foo', function() {

  var thisIsPrivate = "Private";

  function getPrivate() {

    return thisIsPrivate;

  }

  return {

    variable: "This is public",

    getPrivate: getPrivate

  };

});


// html

<!DOCTYPE html>

<html ng-app="app">

<head>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>

<meta charset=utf-8 />

<title>JS Bin</title>

</head>

  <body ng-controller="MainCtrl">

    public variable: {{foo.variable}}

    <br />

    private variable (through getter): {{foo.getPrivate()}}

  </body>

</html>



5. Service 

  - "service" service는 Factory service와 유사사지만 생성자명칭(== 서비스명칭)을 넘기면 자동으로 new를 통하여 생성된다 

    즉, Factory 처럼 오브젝트의 리턴이 필요없다 

  - 하기 예는 완전히 동일하다. 주의 할 것은 factory안에서 또는 service사용시 new을 여러번 하여도 반드시 한번만 인스턴스화 한다

    즉, Singleton 패턴으로 객체 하나만이 생성된다 

// script 

app = angular.module("app", []);


// Service를 통해서 생성 

app.controller('MainCtrl', function($scope, foo) {

  $scope.foo = foo;

});


app.service('foo', function() {

  var thisIsPrivate = "Private";

  this.variable = "This is public";

  this.getPrivate = function() {

    return thisIsPrivate;

  };

});


// Factory를 통해서 생성 : MainCtrl 에서 foo 파라미터 값과 하위 로직을 foo2로 바꿔도 동일 결과 

app.controller('MainCtrl', function($scope, foo2) {

  $scope.foo = foo2;

});


app.factory('foo2', function() {

  return new Foobar();

});


function Foobar() {

  var thisIsPrivate = "Private";

  this.variable = "This is public";

  this.getPrivate = function() {

    return thisIsPrivate;

  };

}


// 또는 펑션을 넘길 수도 있다 : : MainCtrl 에서 foo 파라미터 값과 하위 로직을 foo3로 바꿔도 동일 결과 

app.controller('MainCtrl', function($scope, foo3) {

  $scope.foo = foo3;

});


app.service('foo3', Foobar);


// html 

<!DOCTYPE html>

<html ng-app="app">

<head>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>

<meta charset=utf-8 />

<title>JS Bin</title>

</head>

  <body ng-controller="MainCtrl">

    public variable: {{foo.variable}}

    <br />

    private variable (through getter): {{foo.getPrivate()}}

  </body>

</html>

 


6. Provider

  - Provider는 factory의 큰형님 뻘이다 

  - $get fuction을 가지고 있어야 한다. 만일 Provider 명칭이 foo이고 controller에 inject될 때 실제는 $get function의 결과값이 inject되는 것이다. 왜 factory로 사용하면 간편한데 굳이 이렇게 할까?

  - 이유인즉슨, config function을 통해서 provide에 대한 환경설정을 할 수 있기 때문이다 

  - 예를 보자 

    1) thisIsPrivate 변수가 $get 펑션 밖에 위치한다 

    2) 따라서 setPrivate 메소드를 통하여 thisIsPrivate 변수값을 변경할 수 있다 

    3) 이런게 하는 이유는 우리가 필요로 하는 다양한 환경값을 바꾸어서 환경설정을 하고 싶기 때문이다

    4) Provider명칭이 'foo' 이므로 app.config 에서 'fooProvider' 명을 준다 

// script 

app = angular.module("app", []);

app.controller('MainCtrl', function($scope, foo) {

  $scope.foo = foo;

});


app.provider('foo', function() {

  var thisIsPrivate = "Private";

  return {

    setPrivate: function(newVal) {

      thisIsPrivate = newVal; 

    },

    

    $get: function() {

      function getPrivate() {

        return thisIsPrivate;

      }


      return {

        variable: "This is public",

        getPrivate: getPrivate

      };

    } 

  };

});


app.config(function(fooProvider) {

  fooProvider.setPrivate('New value from config');

});


// html 

<!DOCTYPE html>

<html ng-app="app">

<head>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>

<meta charset=utf-8 />

<title>JS Bin</title>

</head>

  <body ng-controller="MainCtrl">

    public variable: {{foo.variable}}

    <br />

    private variable (through getter): {{foo.getPrivate()}}

  </body>

</html>



7. Decorator (보너스1)

  - 기존 서비스에 새로운 펑션을 넣고 싶을 경우 사용한다

  - 예를 보자 

    1) $provide는 모든 서비스들을 내부적으로 생성할 때 사용한다 

    2) $provide.decorator 를 통해서 서비스를 확장할 수 있다. 

        첫번째 인자는 서비스 명칭, 두번째는 콜백으로 서비스 인스턴스인 $delegate을 받는다 

    3) $delegate에 greet 라는 메소드를 확장 정의 한다 

    4) 만일 3rd party 라이브러리 사용시 확장하고 싶을 경우 decorator를 사용한다 

// script 

app = angular.module("app", []);


app.controller('MainCtrl', function($scope, foo) {

  $scope.foo = foo;

});


app.factory('foo', function() {

  var thisIsPrivate = "Private";

  function getPrivate() {

    return thisIsPrivate;

  }

  return {

    variable: "This is public",

    getPrivate: getPrivate

  };

});


app.config(function($provide) {

  $provide.decorator('foo', function($delegate) {

    $delegate.greet = function() {

      return "Hello, I am a new function of 'foo'";

    };

    

    return $delegate;

  });

});


// html 

<!DOCTYPE html>

<html ng-app="app">

<head>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>

<meta charset=utf-8 />

<title>JS Bin</title>

</head>

  <body ng-controller="MainCtrl">

    public variable: {{foo.variable}}

    <br />

    private variable (through getter): {{foo.getPrivate()}}

    <br />

    greet: {{foo.greet()}}

  </body>

</html>



8. 새로운 인스턴스 생성하기 (보너스2)

  - 팩토리 메소드를 두어서 호출시 마다 무언가 새로 생성하고 싶을 경우 사용한다 

  - 예를 보자 

    1) Function 1급 class인 Person을 만든다 

    2) MainCtrl, SecondCtrl 각각 getById(1) 을 호출하지만 서로 다른 인스턴스가 전달되었다

    3) "Update It" 버튼을 클릭하면 두번째 열만 "Dave - Canada" 로 바뀜을 확인 할 수 있다 

// script 

app = angular.module('app', []);

app.controller('MainCtrl', function($scope, personService) {

  $scope.aPerson = Person.getById(1);

});

app.controller('SecondCtrl', function($scope, personService) {

  $scope.aPerson = Person.getById(1);

  $scope.updateIt = function() {

    $scope.aPerson.update();

  };

});


// Our class

function Person( json ) {

  angular.extend(this, json);

}


Person.prototype = {

  update: function() {

    // Update it (With real code :P)

    this.name = "Dave";

    this.country = "Canada";

  }

};


Person.getById = function( id ) {

  // Do something to fetch a Person by the id

  return new Person({

    name: "Jesus",

    country: "Spain"

  });

};


// Our factory

app.factory('personService', function() {

  return {

    getById: Person.getById

  };

});


// html

<!DOCTYPE html>

<html ng-app="app">

<head>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>

<meta charset=utf-8 />

<title>JS Bin</title>

</head>

  <body>

    <p ng-controller="MainCtrl">{{aPerson.name}} - {{aPerson.country}}</p>

    <div ng-controller="SecondCtrl">

      {{aPerson.name}} - {{aPerson.country}}

      <button ng-click="updateIt()">Update it</button>

    </div>

  </body>

</html>

  - 위의 script 코드를 CoffeeScript로 하면 class 표현을 쉽게 할 수 있다 

app.controller 'MainCtrl', ($scope, personService) -> $scope.aPerson = personService.getById(1) app.controller 'SecondCtrl', ($scope, personService) -> $scope.aPerson = personService.getById(2) $scope.updateIt = () -> $scope.aPerson.update() class Person constructor: (json) -> angular.extend @, json update: () -> @name = "Dave" @country = "Canada" @getById: (id) -> new Person name: "Jesus" country: "Spain" app.factory 'personService', () -> { getById: Person.getById 

}



<참조>

  - 원문 : Understanding Service Types

  - 가장 잘 설명된 글 : http://stackoverflow.com/questions/15666048/angular-js-service-vs-provider-vs-factory (강추)

  - 관련글 : GDG Korea WebTech 고재도님 링크

  - AngularJS Creating Service 매뉴얼

  - 이미지 : AngularJS Large Scale Architecture

  - JavaScript에서 객체를 만드는 3가지 방법

  - $provider를 통한 생성

posted by 윤영식
2013. 8. 21. 11:41 AngularJS/Concept

AngularJS에 대해 어떻게 생각하시나요? 그냥 webpage를 만드는 Front-end 단의 JavaScript Framework 정도로 생각하시나요? AngularJS는 클라이언트의 브라우져에서 SPA(Single Page Application)를 구현하기 위한 프레임워크입니다. Webpage를 만드는 것이 아니라 SPA의 A처럼 애플리케이션을 만드는 프레임워크라는 것이지죠. Adobe의 Flex 프레임워크와 대비하여 생각해도 되지 않을까 합니다. AngularJS로 개발한다면 새로운 관점에서 바라보아야 합니다. Angular Way의 길에 들어오세요



1. 페이지를 디자인 하지 말고 DOM(Document Object Model) 조작을 통해 페이지를 변경하라 

  - "나는 몇개의 DOM 조각을 가지고 있다 이것을 가지고 뭔가 동작하는 것을 만들고 싶다"라는 생각에서 AngularJS는 출발한다 

  - 완성하고 싶은 것이 있으면 애플리케이션 아키텍쳐 디자인을 한 다음 마지막에 화면 디자인을 한다 



2. AngularJS 와 함께 jQuery 에 대해서 말하지 마라 

  - AngularJS를 사용하면 jQuery 를 바탕으로 개발하는 것은 배제하길 바란다. Angular Way를 가라~~

  - jQuery 플러그인을 AngularJS에서 사용하기 위해 $apply를 남발하여 코드를 꼬아 놓지 말자. jQuery 플러그인 대부분은 약간의 코드조각으로 AngularJS에서 재작성되어 질 수 있다 (Directive로 만들어서 사용하기)



3. 아키텍쳐라는 단어를 항상 생각하라

  - SPA (Sinlge Page Application)는 application 이다. webpage가 아니다. 서버사이드 개발과 똑같이 생각하라

  - 따라서 모듈화 하고 확장성 있고 테스트 가능하게 개발해야 한다 


3-1. 화면은 공식적으로 표현되는 것 

  - jQuery 경우를 보자 

<ul class="main-menu">
    <li class="active">
        <a href="#/home">Home</a>
    </li>
    <li>
        <a href="#/menu1">Menu 1</a>
        <ul>
            <li><a href="#/sm1">Submenu 1</a></li>
            <li><a href="#/sm2">Submenu 2</a></li>
            <li><a href="#/sm3">Submenu 3</a></li>
        </ul>
    </li>
    <li>
        <a href="#/home">Menu 2</a>
    </li> 

</ul>


// JavaScript 

$('.main-menu').dropdownMenu();

  - 위의 경우를 단순한 AngularJS의 Directive로 표현한다. 이렇게 하면 jQuery처럼 HTML과 Javascript를 분리할 필요가 없고 해당 <ul> HTML 태그가 dropdown-menu임을 바로 알 수 있다.  

<ul class="main-menu" dropdown-menu>
    ... 

</ul>

  - 애플리케이션 개발시 반은 jQuery 반은 AngularJS 스타일로 - half jQuery, half AngularJS - 절대로 절대로 개발하지 말라

  - 디자인을 하지 말고, Markup을 해라. 즉, 아키텍쳐링을 하고 그 다음에 디자인을 하라. 


3-2. Data Binding

  - DOM을 조작(첨부,삭제)하는데 있어서 Two Way Binding을 적극적으로 사용하라 

  - jQuery 의 경우를 보자 : append하고 delete 하는 조작들을 넣어야 한다. 

$.ajax({
  url: '/myEndpoint.json',
  success: function ( data, status ) {
    $('ul#log').append('<li>Data Received!</li>');
  

});

<ul class="messages" id="log"</ul>

  - AngularJS 를 보자 : $scope를 통하여 controller와 view 간의 데이터가 삭제 되거나 첨부되는 것을 보다 쉽고 깔끔하게 처리해 준다

// controller 코드
$http( '/myEndpoint.json' ).then( function ( response ) {
    $scope.log.push( { msg: 'Data Received!' } ); 

});


// html 코드 

<div class="messages">
    <div class="alert" ng-repeat="entry in log">
        {{ entry.msg }}
    </div> 

</div>


3-3. 모델객체와 화면과 분리되어 있다

  - $scope를 통하여 view와 controller간 모델역할을 수행한다 

  - 이 방법은 view와 완전 독립적이어서 separation of concern을 유지하고 테스트 가능하게 만들준다


3-4. 관심의 분리 (SoC, separation of concern)

  - 아키텍쳐링을 하여 개발하면 관심 영역별로 분리하여 유지할 수 있다

  - 화면(view)은 공식적인 표현되는 것이고, 모델(model)은 데이터를 대표하며, 업무를 수행하는 서비스를 갖게된다 

  - 이것은 3-5에서 이야기하는 DI와도 연관이 있다 


3-5. 의존성 주입 (DI, Dependency Injection)

  - 코드안의 하드코딩된 부분을 제거하고 의존관계에 있는 모듈을 다양한 방법으로 전달해 준다

  - AngularJS에서는 펑션의 파라미터로 넘겨준다. 이를 통해 테스트시에 서버를 직접 호출할 필요없이 필요한 목서비스(mock service)를 전달하여 테스트를 할 수도 있다 



4. 항시 TDD 하라 

  - Test Driven Development 를 수행하라

  - jQuery로 테스트 할 때는 항시 테스트 화면을 만들어 해야 제대로 된 테스트 수행이 가능해진다. 그러나 AngularJS는 그렇지 않다

  - 내부에 사용되는 기능을 모듈로 분리해 개발하고 DOM조작에 대해서 테스트하고 애플리케이션과 통합할 수가 있기 때문이다 

  - 그것은 관심의 분리(SoC) 방식으로 개발하기 때문에 TDD가 AngularJS에서 가능한 것이다 

  - 예를 보자 

// HTML : when-active라는 AngularJS Directive(지시자)

<a href="/hello" when-active>Hello</a>

// Test Script : Karma를 보자. DOM을 조작하여 화면을 이동시킨 후 true, false인지 체크한다 

it( 'should add "active" when the route changes', inject(function() {
    var elm = $compile( '<a href="/hello" when-active>Hello</a>' )( $scope );

    $location.path('/not-matching');
    expect( elm.hasClass('active') ).toBeFalsey();

    $location.path( '/hello' );
    expect( elm.hasClass('active') ).toBeTruthy();
}));


// Script : 정상적으로 라우팅이 되면 active class 첨부, 아니면 삭제 

.directive( 'whenActive', function ( $location ) {
    return {
        scope: true,
        link: function ( scope, element, attrs ) {
            scope.$on( '$routeChangeSuccess', function () {
                if ( $location.path() == element.attr( 'href' ) ) {
                    element.addClass( 'active' );
                }
                else {
                    element.removeClass( 'active' );
                }
            });
        }
    };
});



5. Directive(지시자)는 패키징된 jQuery가 아니다 

  - Directive안에서 DOM 조작을 하라. 하지만 jQuery 스타일은 배제하라 

  - Directive는 template을 가지고 있는 widget과 유사하다. 이를 통해 SoC를 보여주고 있고, 테스트를 가능하게 한다 

  - jQuery 스타일의 버튼토글 코딩을 보자 : AngularJS Directive안에 아래와 같이 jQuery 스타일의 DOM 조작을 수행하지 말라

.directive( 'myDirective', function () {
    return {
        template: '<a class="btn">Toggle me!</a>',
        link: function ( scope, element, attrs ) {
            var on = false;

            $(element).click( function () {
                if ( on ) {
                    $(element).removeClass( 'active' );
                }
                else {
                    $(element).addClass( 'active' );
                }

                on = !on;
            });
        }
    }; 

});

  - Angular Way 방식의 버튼코글 : ng-show, ng-class, ng-binding 과 같은 Directive를 통해 DOM 조작을 수행하라  

.directive( 'myDirective', function () {
    return {
        scope: true,
        template: '<a class="btn" ng-class="{active: on}" ng-click="toggle()">Toggle me!</a>',
        link: function ( scope, element, attrs ) {
            scope.on = false;
            scope.toggle = function () {
                scope.on = !$scope.on;
            };
        }
    }; });

  - Directive는 jQuery같은 펑션의 집합이 아니다. Directive는 HTML의 확장이다. 이는 HTML안에 우리가 수행하고 싶은 무언가를 넣고 싶은데 못 할 경우에 Directive를 만들어야 하는 것이다. 그래서 HTML의 일부로서 사용하는 것이다. 



Angular Way에서 jQuery 방식을 사용하지 말고 포함조차 시키지 말아라. 


- View  === AngularJS의 Directive가 포함된 HTML이다 

- Model === $scope를 통하여 Controller에서 Two way binding을 자동 수행한다 

- View와 Controller를 통하여 표현과 행위를 분리한다 (SoC)

- AngularJS 애플리케이션 접근법

   Step 1) Model을 먼저 생각하고 Service를 만든다

   Step 2) Model이 표현될 View에 대해 생각해 보고 template을 만들고, 필요하면 자동 모델 바인딩되는 Directive도 만든다.

   Step 3) 각 View 를 Controller에 연결한다 (ng-view and routing, ng-controller)



<참조>

  - 원문 : Think in AngularJS

  - AngularJS의 상속에 대한 이해 (그림포함)

posted by 윤영식
2013. 8. 20. 17:32 AngularJS/Prototyping

iCheck 플러그인은 HTML input의 type이 Checkbox나 Radio 의 모양을 UX 입장에서 직관적으로 만들어 준다. AngularJS하에서 이런 JQuery Plugin을 사용하려면 Directive(지시자) 화하여 사용해야 하는데 어떻게 만드는지 알아보자 




1. Directive 만드는 형식 

  - 사용하려는 HTML 안에서 

<div directiveName ></div>

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


  - AngularJS Directive(지시자) 만들기

    + pluginActivationFunction은 플러그인의 호출하는 펑션이다. 

    + scope.$eval 은 플러그인의 환경값을 넣어주면 된다 

App.directive('directiveName', function() {

    return {

        restrict: 'A',

        link: function(scope, element, attrs) {

            $(element).'pluginActivationFunction'(scope.$eval(attrs.directiveName));

        }

    };

}); 



2. iCheck Directive 만들기 

  - ng-app 모듈에 Directive를 만든다 

    + Radio 박스 선택에 따른 부가화면을 show하거나 hidden 하는 처리를 한다  

    + Attribute로 ngIcheck 지시자를 만들었다. (HTML에서는 ng-icheck 로 사용된다)

    + ifChecked의 iCheck 이벤트를 받았다. iCheck의 Radio를 사용하면서 AngularJS의 Radio 이벤트를 받을 수 없게 되었기 때문이다 

    + Radio의 attribute로 value="file" or "direct" 라는 두개의 Radio 선택 옵션을 주었다. 

    + AngularJS Context안에서 $scope의 값 변경을 인식하기 위하여 $scope.$apply()를 iCheck.on listener의 핸들러 펑션에 호출함.

    + $(element).iCheck(<환경값>); 의 plugin 초기화 펑션을 호출해 주었다 

.directive('ngIcheck', function() {

   return {

       restrict: 'A',

       controller: function($scope, $element) { 

        $element.on('ifChecked', function(event){

 if(event.target.value === 'file') {

  $scope.isFile = true;

  $scope.isDirect = false;

  $scope.$apply();

 } else if(event.target.value === 'direct') {

  $scope.isDirect = true;

  $scope.isFile = false;

  $scope.$apply();

 } else {

  $scope.isFile = false;

  $scope.isDirect = false;

  $scope.$apply();

 }

});

       },

       link: function(scope, element, attrs) {

           $(element).iCheck({

        checkboxClass: 'icheckbox_square-blue', 

        radioClass: 'iradio_square-blue', 

        increaseArea: '20%' 

       });

       }

   };

});


// 중요)

// iCheck를 AngularJS의 Directive로 하지 않고 사용하려면 HTML하단에 해당 script를 넣어준다

// 이렇게 하면 Radio 버튼의 이벤트가 AngularJS Context안에서 작동하지 않기 때문에 AngularJS의 기능을 사용할 수 없다 

// 결국 이렇게 사용하지 않기 위해선 JQuery Plugin을 AngularJS Directive로 만들어 AngularJS Context에서 돌아가게 해주는 것이다

<script>

$(document).ready(function(){

  $('input').iCheck({

    checkboxClass: 'icheckbox_square-blue',

    radioClass: 'iradio_square-blue',

    increaseArea: '20%'

  });

});

</script>


  - HTML에서 사용하기 

    + <input type="radio" name="iCheck" .../> : 여기까지는 iCheck에서 사용함 

    + <input ... value="file" ng-icheck> : AngularJS에서 사용. ng-icheck Attribute를 만다면 AngularJS가 컴파일하고 link하고 이벤트 반응에 대하여 controller에서 처리한다 

    + Radio의 선택에 따라서 ng-show="isFile", ng-show="isDirect"에 따라서 해당 div가 show, hidden 되어 진다 

  <table id="maForm" class="table table-striped table-bordered">

      <thead>

        <tr>

          <th width="25%"><i class="icon-check-sign icon-small"></i> Options</th>

          <th width="75%" class="not-for-mobile"><i class="icon-edit icon-small"></i> Insert Data</th>

        </tr>

      </thead>

      <tbody>

        <tr>

          <td>

            <label>Select Input Method</label> <br>

            <table class="table table-condensed">

              <tr class="active">

                  <td><input type="radio" name="iCheck" value="file" ng-icheck /></td>

                  <td>Upload File</td>

              </tr>

              <tr class="active">

                  <td><input type="radio" name="iCheck" value="direct" ng-icheck /></td>

                  <td>Input Direct</td>

              </tr>

            </table>

          </td>

          <td>

          <!-- input direct data -->

          <div ng-show="isDirect">

  <textarea ng-model="ma.direct" class="form-control" rows="12"></textarea>

  </div>

  <!-- upload file such as .csv -->

  <div ng-show="isFile">

                  <input id="fileupload" ng-model="ma.file" type="file" name="files[]" multiple>

  </div>

          </td>

        </tr>

        <tr>

      <td colspan="2">

  <i class="icon-bar-chart icon-small"></i> Moving Average Chart<br>

  <img src="http://www.placehold.it/880x300/EFEFEF/AAAAAA&amp;text=no+image">

      </td>

        </tr>

      </tbody>

    </table>


  - 결과 : 선택하는 옵션 박스가 iCheck 플러그인을 통하여 좀 더 직관적으로 나오게 되었다

    + 단순한 Radio 옵션

    

    + 변경된 iCheck Radio 옵션

    



<참조> 

  - Correct way to integrate JQuery Plugins in AngularJS 

  - How to convert a jQuery plugin to AngularJS Directive : 유튜브 동영상

  - Directive의 $watch 와 $apply 완벽 이해 : $apply, $digest, $watch 에 대한 상세 설명

  - Thinking in AngularJS : Angular Way 

  - SlideJS Plugins을 Directive로 만드는 유사한 예제

'AngularJS > Prototyping' 카테고리의 다른 글

[AngularJS] Twitter Search 만들기  (0) 2013.04.05
[AngularJS] Todo 목록 만들기  (0) 2013.04.05
[AngularJS] Hello World 만들기  (0) 2013.04.04
posted by 윤영식
2013. 4. 27. 16:36 AngularJS/Concept

AngularJS에 구성 요소에 대한 Snippet을 살펴본다. 또한 jQuery와 통합하여 사용하는 방법을 알아보자 



1. 들어가기

  - Angular를 통해서 Client-Side에서 데이터와 애플리케이션 로직을 분리한다

  - 최초에 넣어야 할 라이브러리들 

<script type="text/javascript" src="/path/to/angular/angular.js"></script>

<script type="text/javascript" src="/path/to/angular/angular-resource.js"></script>



2. Module

  - 정의

    + Modules are used to fully encapsulate all the angular code of your application into one entry point

    + 컴포넌트와 같으면 Self living 할 수 있다

    + 큰 프로젝트를 할 때의 모듈 나누는 방법

  - 메인 레이아웃 파일에 다음과 코드를 넣는다

    + <html 태그에 넣거나 <body 에 넣거나 한다 

    + SPA를 한다면 아마도 <body> 태그 밑의 특정 부분이 Dynamic DOM 이 될 것이다 

<html data-ng-app="YOUR_APP_NAME">

또는

<html ng-app="YOUR_APP_NAME">


  - JavaScript에서 사용하기 

    + angular는 글로벌 변수이고, directive/controller/filter/service등을 만들기 위한 App 변수를 만들었다

    + ['ngResource']와 같이 의존하는 라이브러리를 설정한다  

var App = angular.module('YOUR_APP_NAME', ['ngResource']);



3. Binding, Expression

  - 양방향 data binding, 조건문, 반복문

  - {{ expression }} 데이터 표현

// html

<div class="records" data-ng-repeat="record in records | orderBy:orderProp">

  <h5> {{ record.title }} </h5>

</div>


// js 

$scope.records = [{ title : 'one' }, { title : 'two' }, { title : 'three' }]; 



4. Dependency Injection

  - 코드를 조직화 하고 테스트 가능하게 만들어준다

  - controllers, module configuraitons, directives, filters, resources, routes 들을 DI 할 수 있다 

// 메소드 파라미터로 DI를 해준다

var Ctrl = function($scope, $http, $location) {

  //now you can use any of the injected variables


  //to change the URL after something has happened then you can use $location

  $location.path('/path/to/new/page');

}

//and now the injection of the variables

Ctrl.$inject = ['$scope','$http','$location'];


  - DI에 대한 개념을 제대로 잡고 싶다면 보지타님의 동영상 보라



5. Routes

  - 요청 uri path와 controller를 맵핑하는 것이다 

// 모듈의 config 에서 정의한다 

// $routeProvider 서비스를 통하여 정의한다 

// uri path에 대하여 templateUrl 과 controller를 맵핑한다 

App.config(['$routeProvider', function($routes) {


  $route.when('/',{

    templateUrl : '/templates/home.html',

    controller : HomeCtrl

  });


  $route.when('/register',{

    templateUrl : '/templates/register.html',

    controller : RegisterCtrl

  });


  $routes.otherwise({

    redirectTo : '/'

  });


}]);



6. Controller & Scope

  - 컨트롤러는 $scope와 함께하여 데이터를 연동한다 

  - controller 만들기 (ng-app의 명칭 즉, 모듈명이 없을 경우)

    + $scope.title를 변경하면 angular는 데이터 변경을 모른다 

    + 하기와 같은 예제에서는 $scope.$apply() 가 필요하다 

    + $apply() 는 일반 JavaScript Context에서 수행되는 것을 AngularJS Context로 수행 되게 해준다 (필히 참조)

    + $apply, $digest, $$phase 란 무엇인가?

// angularjs 모듈에서 파생되지 않은 컨트롤러 펑션

var SomeCtrl = function($scope, $http, $location) {

  $scope.value = 'some value';

};

// controller에게 서비스를 주입하여 준다 (DI)

SomeCtrl.$inject = ['$scope','$http','$location'];


// html에서 {{ }} 를 설정

<div class="header">{{ title }}</div>


// 하기와 같이 설정을 하면 {{ title }} 이 자동으로 바뀌어야 한다  

$scope.title = 'this is awesome';


//if a scope digestion is already going on then it will get picked up and you won't

//have to call the $scope.$apply() method

// $apply를 호출하면 $digest cycle로 들어가고 model이 변화를 감지하여 $watch 에 등록된 expression의 변경을 수행한다

// 즉, view가 re-rendering 되는 것이다 

if(!$scope.$$phase) { //this is used to prevent an overlap of scope digestion

  $scope.$apply(); //this will kickstart angular to recognize the change

}


  - $scope는 $rootScope 상속한 것이다

    + 만일 전체 애플리케이션에서 $scope를 통하여 어떤 펑션을 사용하고 싶다면 하기와 같이 한다

    + 모듈이 로딩 될 때 config 와 run 이 자동 호출된다. run은 main메소드와 유사하다 (참조

App.run(['$rootScope', function($rootScope) {

  $rootScope.sharedFunction = function() { ... };

}]);


  - controller 사용하기 정식 방법 두가지 

    + html 태그안에서 ng-controller 통하여 설정

    + $routes의 controller 설정

// html

<div data-ng-controller="SomeCtrl">...</div>


// js

$routes.when('/some/path',{

  controller : Ctrl,

  templateUrl : '/templates/controller.html'

});



7. Services

  - 전체 애플리케이션에서 공유하는 것들

  - DI를 통하여 객체를 전달함 

//define the service

// App 모듈에 myService가 정의한 서비스의 명칭

App.factory('myService', ['myOtherService', '$location', function(myOtherService, $location) {

  return function(input) {

    //do something with the input using the myOtherService or the $location objects.

    return input;

  };

}]);


//use the service within the controller

// Controller에서 $scope와 myService DI하여 공유한다 

var HomeCtrl = function($scope, myService) {

  var input = '123';

  input = myService(input);

};

HomeCtrl.$inject = ['$scope','myService'];


//use the service a directive

// App 모듈에 directive를 정의한다. myService 서비스를 DI하여 공유한다 

App.directive('myDirective', ['myService',function(myService) {

  return {

    link: function($scope, element, attrs) {

      var input = '123';

      input = myService(input);

    }

  }

}]);



8. Models

  - RESTful Web Service를 호출을 위하여 7번의 서비스 개념으로 만들어 놓고 사용할 수 있다 

// ModelName이라고 서비스를 만든다

// $resource를 통하여 서버사이드로 호출한다 

App.factory('ModelName', ['$resource', function($resource) {

  $resource.url('/path/to/model/controller/:id',{

    id : '@id', //this binds the ID of the model to the URL param

  },{

    query : { method : 'GET', isArray : true }, //this can also be called index or all

    save : { method : 'PUT' }, //this is the update method

    create : { method : 'POST' },

    destroy : { method : 'DELETE' }

  }

}]);


// Controller 에서 ModelName 서비스를 DI 받아서 호출한다

var SomeCtrl = function($scope, $http, $location, ModelName) {

  //now you can use ModelName to do your business

};

SomeCtrl.$inject = ['$scope','$http','$location','ModelName'];


  - ModelName을 통하여 다음과 같이 사용할 수 있다 (연구필요)

    + 서비스를 CRUD query 형태로 만들어서 사용한다

    + Breeze.js 로 확장할 수도 있겠다 

//list all the records on the page

var results = ModelName.query({ search : 'all' }, onSuccessFn, onFailureFn);


//get a specific record

var record = ModelName.get({ id : 123 }, onSuccessFn, onFailureFn); //onSuccessFn and onFailureFn are optional callback functions where you can further customize the response


//create a new ModelName record

var record = new ModelName();


//update that record

record.someAttr = 'someValue';

record.$save();


//or if you prefer to submit your data in a different way

ModelName.save({

    id : record.id

  },{

  somePostObject : {

    attr1 : 'value',

    attr2 : 'value2'

  }

});


//destroy the record (and include a token)

record.destroy({ token : record.token });



9. Directives

  - HTML 코드를 통하여 만들어 놓은 플러그인과 링크를 설정해 주는 것이다. 애플리케이션내에서 블럭을 격리시켜준다. (그냥 컴포넌트 개념을 생각하며 되겠다. 화면 구성을 위하여 UI 컴포넌트 추가하는 것 처럼)

  - 웹앱을 구조화하는데 도움을 준다 

  - Plugin, Validation(유효성검사), Dynamic text properties 또는 i18n, l10n 에도 활용한다 

// link 는 Controller와 Directive가 $scope 를 통하여 데이터를 공유하는 중요 통로이다 

angular.directive('myDirective',function($compile) {

  return {

    templateUrl : '/path/to/some/template.html', //(optional) the contents of this template can be downloaded and constructed into the element

    replace : true, //whether or not to replace the inner data within the element

    link : function($scope, $element, attributes) { //this is where your magic happens

      $scope.title = '...';

    }

  };

});



10. Filters

  - 필터는 재사용 가능한 오퍼레이션이다 

  - binding operation에 내장되어 함께 수행된다 예) 정렬, 문자변형, Pagination 등

// App 모듈에서 myUppercase 명칭의 filter 정의  

App.filter('myUppercase', function(data) {

  for(var i=0; i < data.length; i++) {

    data[i].title = data[i].title.toUpperCase();

  }

  return data;

});


// html에서 사용하기 

<div data-ng-repeat="for record in records | filter:myUppercase">...</div>


// 또는 javascript 코드에서 호출가능

//be sure to inject the $filter object

var values = ['one','two','three'];

values = $filter('myUppercase')(values);



11. HTML5 Mode

  - angular app이 angular의 routing system 안에서 HTML5 history방식으로 관리되게 해준다 

  - 해당 코드만 넣으면 된다 

App.config(['$locationProvider', function($location) {

  $location.html5Mode(true); //now there won't be a hashbang within URLs for browers that support HTML5 history

}]);



12. jQuery 사용

  - 가장 간단하게 angular.js 스크립트 태그 전에 jQuery 태그를 먼저 위치시키면 된다

  - Angular.js는 jqLite를 기본 사용하나 jQuery가 먼저 설정되면 jQuery로 대체되어 사용한다 



<참조>

  - 원문 : use angularjs to power your web application

  - AngularJS 소개 ppt


posted by 윤영식
2013. 4. 22. 23:42 Dev Environment/Build System

Yeoman은 generator-* 를 통하여 스케폴딩 코드 껍데기를 만들어준다. 이중 angular를 위해서 generator-angular와 테스트를 위한 generator-karma등 다양한 generator가 있고, 사용자가 원하는 generator를 만들 수가 있다. generator-angular안에는 클라이언트단만을 고려한 스케폴딩만 존하여 generator-angular를 GitHub에서 forking하여 ExpressJS를 붙여 프로젝트에 실제 필요한 스케폴딩을 만들어 본다



1) generator-angular forking

  - https://github.com/yeoman/generator-angular 에서 fork를 했다


  - 로컬로 복제한다 

    + git clone https://github.com/ysyun/generator-angular 

  - yeoman 1.0 beta 부터는 명령이 yo 로 바뀌었다. 포스팅 보고서 설치를 한다

  - yo 로 수행할 수 있는 generator 목록을 보자 

    + 만일 generator-angular 를 설치했다면 Angular 라고 나온다

    + Naming convention 규칙에 따라서 generator-[Name] 으로 generator가 만들어지고 [Name]만을 취한다

    + generator들은 npm install -g 옵션으로 설치하여 맥(Mac) 기준으로 /usr/local/lib/node_modules/* 밑에 설치된다 

$ yo --help

 Usage: yo GENERATOR [args] [options]


General options:

  -h, --help     # Print generator's options and usage

  -f, --force    # Overwrite files that already exist


Please choose a generator below.


Angular  <<=== generator-angular 

  angular:app

  angular:common

  angular:controller

  angular:directive

  angular:filter

  angular:main

  angular:route

  angular:service

  angular:view


Bootstrap

  bootstrap:app


Express

  express:app

  express:basic

  express:common


Generator

  generator

  generator:app

  generator:subgenerator


Karma

  karma:app


Mocha

  mocha:app

  mocha:generator


Webapp

  webapp:app


$ cd /usr/local/lib/node_modules && ls -alrt

drwxr-xr-x  13 nobody  staff   442  4 12 13:45 generator-karma

drwxr-xr-x  18 nobody  staff   612  4 12 14:30 npm

drwxr-xr-x  15 nobody  staff   510  4 12 14:31 bower

drwxr-xr-x  13 nobody  staff   442  4 12 15:03 grunt-cli

drwxr-xr-x   7 nobody  staff   238  4 18 09:57 yo

drwxr-xr-x  26 nobody  staff   884  4 18 10:42 generator-angular

drwxr-xr-x  16 nobody  staff   544  4 18 16:59 grunt-init

drwxr-xr-x  10 nobody  staff   340  4 19 23:46 grunt-express

drwxr-xr-x  16 nobody  staff   544  4 19 23:46 grunt

drwxr-xr-x  12 nobody  staff   408  4 22 10:20 coffee-script

drwxr-xr-x  16 nobody  staff   544  4 22 11:04 generator-express

drwxr-xr-x  14 nobody  staff   476  4 22 11:31 generator-generator

drwxr-xr-x  13 nobody  staff   442  4 22 11:44 generator-bootstrap

drwxr-xr-x  29 root    wheel   986  4 22 15:53 generator-angular_dowon

lrwxr-xr-x   1 root    wheel    48  4 22 17:08 generator-angular-sd -> /Users/nulpulum/development/generator-angular-sd


  - fork한 generator-angular를 로컬머신에서 generator-angular-sd 라는 별도 디렉토리에 받고, /usr/local/lib/node_modules/밑에 symbolic link 를 걸어주었다. 

  - 다시 yo --help를 해보면 하기와 같이 angular-sd가 나온다.

    + 앞으로 명령을 yo angular-sd:app 또는 yo angular-sd:express 명령을 치면 스케폴딩 소스가 생기는 것이다. 

Angular-sd

  angular-sd:app

  angular-sd:common

  angular-sd:controller

  angular-sd:directive

  angular-sd:express  <<=== 만들 대상 

  angular-sd:filter

  angular-sd:main

  angular-sd:route

  angular-sd:service

  angular-sd:view



2) Angular Express Generator 만들기 

  - https://github.com/hmalphettes/yeoman-angular-express-example  샘플을 참조해서 작성을 시도해 보았다 

  - 일단 Yeoman 스케폴딩 방식에 대해서 알아보자 (Yeoman Wiki 설명문)

    + Yeoman은 디렉토리별로 index.js 파일을 기본으로 참조한다 

    + 디폴트 디렉토리는 app 이다

    + 그렇다면 다음과 같은 구조가 될 것이다. 즉, yo --help 했을 때 최상위 generator-angular-sd 밑의 디렉토리명이 옵션이 된다

    + 초기 generator를 만드는 도구는 npm install -g generator-generator로 설치한다 (직접 만들고 싶다면 설치,우선 참조만)

angular-sd/

    app/index.js

    common/index.js

    controller/index.js

    ... 중략 ...

    view/index.js


  - yo angular-sd:express 를 수행하려면  generator-angular-sd/express/  디렉토리를 만들고, index.js를 만든다

    + 코드는 yeoman-generator 를 사용한다 

    + 파란색 부분이 중요하다. 모든 스케폴딩 코드 템플릿을 generator-angular-sd/templates/ 밑에 관리하는 것이 일반적이다

    + templates 디렉토리 밑에 express 디렉토리를 만든다  

// index.js

'use strict';

var path = require('path');

var util = require('util');

var yeoman = require('yeoman-generator');


module.exports = ExpressGenerator;


function ExpressGenerator() {

  yeoman.generators.Base.apply(this, arguments);

}


util.inherits(ExpressGenerator, yeoman.generators.Base);


ExpressGenerator.prototype.setupEnv = function setupEnv() {

  this.sourceRoot(path.join(__dirname, '../templates/express'));

  this.directory('.', '.', true);

};


  - templates/express/ 디렉토리 밑에 express 관련 views, routes, public 등을 놓아도 되지만 여기서는 app.js 만들 놓는다. 

    + yo angular-sd를 먼저 수행하면 app 디렉토리가 express의 public 정적 디렉토리 파일 역할을 해준다. 

    + Angular를 사용한다는 것은 SPA이기 때문에 index.html 하나만 두고 전부 partial html로 사용할 것이다.

    + 따라서 Express의 jade는 사용하지 않을 것이다. app.js 파일 하나만 만듦

    + 정적 파일 디렉토리를 app로 지정하고, '/' 요청은 '/app/index.html'로 보냄. index.html 은 angular 파일이다 

var express = require('express')

  , routes = require('./server/routes')

  , http = require('http')

  , path = require('path')

  , fs = require('fs');


var app = express();


app.configure(function(){

  app.set('port', process.env.PORT || 3000);

  // angular /app/views

  // app.set('views', __dirname + '/server/views');

  // app.set('view engine', 'jade');

  app.use(express.favicon());

  app.use(express.logger('dev'));

  app.use(express.bodyParser());

  app.use(express.methodOverride());

  app.use(app.router);

  // angular app == public dir

  app.use(express.static(path.join(__dirname, 'app')));

});


app.configure('development', function(){

  app.use(express.errorHandler());

});


app.get('/', function(req, res) {

  res.sendfile(__dirname + '/app/index.html');

});

// app.get('/', routes.index);

// app.get('/users', user.list);


http.createServer(app).listen(app.get('port'), function(){

  console.log("Express server listening on port " + app.get('port'));


  - package.json 내역에 express 포함시킴 

    + 스케폴딩 위치는 generator-angular-sd/templates/common 밑에 package.json 파일이 존재한다 (수정할 파일)

    + package.json의 내역을 참조하여 node_modules에 필요 모듈을 다운로드 한다 

    + 맨 마지막에 fork 하였던 https://github.com/ysyun/generator-angular 을 travis ci에 연동하기 위해 scripts{ test:...} 넣음

    + grunt express-server를 위하여 grunt-express를 넣음 

{

  "name": "anulgar-express",

  "description": "Grunt task for running an Express Server for Smart Dashboard",

  "homepage": "http://mobincon.tistory.com",

  "author": {

    "name": "Yun Young Sik",

    "email": "ysyun@yuwin.co.kr"

  },

  "repository": {

    "type": "git",

    "url": "https://github.com/ysyun/generator-angular"

  },

  "version": "0.0.1",

  "scripts": {

    "test": "grunt travis --verbose"

  },

  "dependencies": {

    "express": "~3.2.0",

    "jade": "~0.29.0"

  },

  "devDependencies": {

    "grunt": "~0.4.1",

    "grunt-contrib-copy": "~0.4.0",

    "grunt-contrib-concat": "~0.1.3",

    "grunt-contrib-coffee": "~0.6.4",

    "grunt-contrib-uglify": "~0.2.0",

    "grunt-contrib-compass": "~0.1.3",

    "grunt-contrib-jshint": "~0.3.0",

    "grunt-contrib-cssmin": "~0.5.0",

    "grunt-contrib-connect": "~0.2.0",

    "grunt-contrib-clean": "~0.4.0",

    "grunt-contrib-htmlmin": "~0.1.1",

    "grunt-contrib-imagemin": "~0.1.2",

    "grunt-contrib-livereload": "~0.1.2",

    "grunt-bower-requirejs": "~0.4.1",

    "grunt-usemin": "~0.1.10",

    "grunt-regarde": "~0.1.1",

    "grunt-rev": "~0.1.0",

    "grunt-karma": "~0.3.0",

    "grunt-open": "~0.2.0",

    "matchdep": "~0.1.1",

    "grunt-google-cdn": "~0.1.1",

    "grunt-ngmin": "~0.0.2",

    "grunt-express": "git://github.com/hmalphettes/grunt-express.git",

    "socket.io": "*"

  },

  "engines": {

    "node": ">=0.8.0"

  }

}


  - 명령을 수행해 본다

    + yo angular-sd  : angular-sd:app 와 동일하고 generator-angular-sd/app/index.js 설정한 내역을 수행한다 

    + yo angular-sd:express : express 관련 파일을 스케폴딩한다. 초기에 jade도 넣었다가 그냥 app.js 만 놔두었다 

$ mkdir angular-express

$ cd angular-express

$ yo angular-sd

  ... 쭉 엔터치고 한참 의존관계있는 파일을 받는다 ...

$ ls -alrt

drwxr-xr-x   4 nulpulum  staff   136  4 22 23:15 ..

drwxr-xr-x   4 nulpulum  staff   136  4 22 23:16 test

-rw-r--r--   1 nulpulum  staff  1491  4 22 23:16 package.json

-rw-r--r--   1 nulpulum  staff  1273  4 22 23:16 karma.conf.js

-rw-r--r--   1 nulpulum  staff  1134  4 22 23:16 karma-e2e.conf.js

-rw-r--r--   1 nulpulum  staff   360  4 22 23:16 component.json

-rw-r--r--   1 nulpulum  staff  8189  4 22 23:16 Gruntfile.js

-rw-r--r--   1 nulpulum  staff   409  4 22 23:16 .jshintrc

-rw-r--r--   1 nulpulum  staff    50  4 22 23:16 .gitignore

-rw-r--r--   1 nulpulum  staff    11  4 22 23:16 .gitattributes

-rw-r--r--   1 nulpulum  staff   415  4 22 23:16 .editorconfig

-rw-r--r--   1 nulpulum  staff    38  4 22 23:16 .bowerrc

drwxr-xr-x  12 nulpulum  staff   408  4 22 23:16 app

drwxr-xr-x  15 nulpulum  staff   510  4 22 23:16 .

drwxr-xr-x  30 nulpulum  staff  1020  4 22 23:17 node_modules


$ yo angular-sd:express

   create app.js



3) Grunt file 만들기

  - Express에 대해서 grunt를 통하여 test, build, preview를 수행하기 위하여 Gruntfile.js를 수정한다

     + generator-angular-sd/templates/common/Gruntfile.js 가 수정파일이다 

     + package.json 파일 에서  "grunt-express": "git://github.com/hmalphettes/grunt-express.git" 꼭 들어가야 함

  - Gruntfile.js : 기존 generator-angular의 Gruntfile.js에서 express 관련 내역을 첨부한다 (파일참조)

'use strict';

var lrSnippet = require('grunt-contrib-livereload/lib/utils').livereloadSnippet;

var mountFolder = function (connect, dir) {

  return connect.static(require('path').resolve(dir));

};

var path = require('path');


module.exports = function (grunt) {

  // load all grunt tasks

  require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);


  // configurable paths

  var yeomanConfig = {

    app: 'app',

    dist: 'dist',

    server: 'server'

  };


  try {

    yeomanConfig.app = require('./component.json').appPath || yeomanConfig.app;

  } catch (e) {}


  grunt.initConfig({

    yeoman: yeomanConfig,

    watch: {

      coffee: {

        files: ['<%%= yeoman.app %>/scripts/{,*/}*.coffee'],

        tasks: ['coffee:dist']

      },

      coffeeTest: {

        files: ['test/spec/{,*/}*.coffee'],

        tasks: ['coffee:test']

      },

      compass: {

        files: ['<%%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],

        tasks: ['compass']

      },

      livereload: {

        files: [

          '<%%= yeoman.app %>/{,*/}*.html',

          '{.tmp,<%%= yeoman.app %>}/styles/{,*/}*.css',

          '{.tmp,<%%= yeoman.app %>}/scripts/{,*/}*.js',

          '<%%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',

          '<%%= yeoman.server %>'

        ],

        tasks: ['livereload']

"Gruntfile.js" [dos] 349L, 8192C

        ],

        tasks: ['livereload']

      }

    },

    connect: {

      options: {

        port: 9000,

        // Change this to '0.0.0.0' to access the server from outside.

        hostname: 'localhost'

      },

      livereload: {

        options: {

          middleware: function (connect) {

            return [

              lrSnippet,

              mountFolder(connect, '.tmp'),

              mountFolder(connect, yeomanConfig.app)

            ];

          }

        }

      },

      test: {

        options: {

          middleware: function (connect) {

            return [

              mountFolder(connect, '.tmp'),

              mountFolder(connect, 'test')

            ];

          }

        }

      }

    },

    express: {

      livereload: {

        options: {

          port: 9000,

          bases: [ path.resolve('.tmp'), path.resolve(yeomanConfig.app) ],

          monitor: {},

          debug: true //,

          // server: path.resolve('/app')

        }

      },

      test: {

        options: {

          port: 9000,

          bases: [ path.resolve('.tmp'), path.resolve(yeomanConfig.app) ],

          port: 9000,

          bases: [ path.resolve('.tmp'), path.resolve(yeomanConfig.app) ],

          monitor: {},

          debug: true //,

          // server: path.resolve('/app')

        }

      }

    },


  ... 중략 ...

  grunt.registerTask('default', ['build']);



  // for express

  grunt.registerTask('express-server', [

    'clean:server',

    'coffee:dist',

    'compass:server',

    'livereload-start',

    'express:livereload',

    'open',

    'watch'

  ]);


  grunt.registerTask('express-test', [

    'clean:server',

    'coffee',

    'compass',

    'connect:test',

    'karma'

  ]);


  grunt.registerTask('travis', ['jshint', 'test']);


  - 명령 수행하기 

    + grunt express-test : 테스트 

    + grunt : 빌드

    + grunt express-server : 미리보기 수행


    + 명령을 수행하면 자동으로 브라우져가 뜨면 성공



모든 것이 성공했다면 Remote Repository로 push한다. 그리고 Travis-ci에 forking한 프로젝트를 연결한다. 해당 부분은 다음에 보도록 하자.


결과물 : https://github.com/ysyun/generator-angular



<참조>

  - Travis CI 연동하기

  - 2013.11 Monthly Digest for Yeoman

  - Yeoman : generator-fullstack with angular and express

  - Yeoman : generator-mean with angular and express, mongodb, node

posted by 윤영식
prev 1 2 3 next