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

Publication

Statistics Graph

Category

Recent Comment

2015.01.21 16:15 My Projects/Jandi

2주차 진행을 하며 기존 웹 저장소를 별도로 구성해 완전히 별도의 애플리케이션을 구축할 예정이었으나 프러덕션 저장소의 업무로직의 복잡도와 지속적인 개발로 인한 신규 저장소 소스의 불일치 문제가 발생하였다. 따라서 완전히 새로운 저장소로 별도 애플리케이션을 개발하기 보다 Lagacy 코드를 리팩토링 하고 테스트 코드를 신규로 넣고 동시에 연속적인 빌드 환경을 구축하기로 한다. 



1. 모듈 리팩토링


  - AngularJS에서 하나로 묶여 있는 모듈을 여러개로 쪼개는 작업을 진행한다. 

    + 다행히 기존 애플리케이션이 generator-angular-fullstack 으로 되어 있기 때문에 별도로 작업했던 내용을 적용한다. 

    + jandiApp 모듈만 존재한다면 애플리케이션을 위한 추상화 모듈과 공통 모듈로 두가지를 생성한다. 

  - web_client 폴더는 jandi의 웹 애플리케이션 저장소 이름이다. 

    + web_client/client 폴더 밑으로 components 폴더를 만들고 그 밑으로 app와 base 폴더를 만든다. 

    + components/app는 애플리케이션과 연관이 있는 대표 모듈 폴더이다. 

    + components/base는 jandi외에도 어디서나 사용 가능한 그리고 웹에서 bower로 다운로드한 모듈의 폴더이다. 

$ cd web_client/client/

$ mkdir components && cd components

$ mkdir app

$ mkdir base


  - app와 base에 대한 대표 모듈을 만든다.

    + components/app/app.framework.js 파일 생성

    + components/base/base.farmework.js 파일 생성

    + app.framework 모듈은 base.framework 모듈을 의존한다. 

// app.framework.js 내역 

(function() {

  'use strict';


  angular

    .module('app.framework', ['base.framework']);

    

})();


// base.framework.js 내역 

(function() {

  'use strict';


  angular

    .module('base.framework', []);

    

})();


  - 자바스크립트 로딩 우선순위를 지정한다. 

    + index.html 파일의 app.js 보다 먼저 로딩 될 수 있도록 app.framework.js와 base.framework.js를 지정한다. 

    + grunt serve 명령 수행시 app.framework.js와 base.framework.js 파일이 자동 inject 되지 않도록 ingore 설정을 한다. 

    + watch와 inject에 경로를 설정하고 설정앞에 ! 주면 exclude하겠다는 의미이다. 

// index.html 일부 내역 

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

<script src="components/base/base.framework.js"></script>

<script src="components/app/app.framework.js"></script>

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

<!-- injector:js -->

<script src="assets/javascripts/angulartics-ga-custom.js"></script>



// Gruntfile.js 일부 내역

 watch: {

            injectJS: {

                files: [

                    '<%= yeoman.client %>/{app,components}/**/*.js',

                    '!<%= yeoman.client %>/{app,components}/**/*.spec.js',

                    '!<%= yeoman.client %>/{app,components}/**/*.mock.js',

                    '!<%= yeoman.client %>/components/base/base.framework.js',

                    '!<%= yeoman.client %>/components/app/app.framework.js',

                    '!<%= yeoman.client %>/app/app.js'],

                tasks: ['injector:scripts']

            }, 

... 중략 ....

injector: {

            options: {},

            // Inject application script files into index.html (doesn't include bower)

            scripts: {

                options: {

                    transform: function(filePath) {

                        filePath = filePath.replace('/client/', '');

                        filePath = filePath.replace('/.tmp/', '');

                        return '<script src="' + filePath + '"></script>';

                    },

                    starttag: '<!-- injector:js -->',

                    endtag: '<!-- endinjector -->'

                },

                files: {

                    '<%= yeoman.client %>/index.html': [

                        [

                            '{.tmp,<%= yeoman.client %>}/assets/javascripts/*.js',

                            '{.tmp,<%= yeoman.client %>}/{app,components}/**/*.js',

                            '!{.tmp,<%= yeoman.client %>}/components/base/base.framework.js',

                            '!{.tmp,<%= yeoman.client %>}/components/app/app.framework.js',

                            '!{.tmp,<%= yeoman.client %>}/app/app.js',

                            '!{.tmp,<%= yeoman.client %>}/{app,components}/**/*.spec.js',

                            '!{.tmp,<%= yeoman.client %>}/{app,components}/**/*.mock.js']

                    ]

                }

            },


  모듈 리팩토링 준비가 되었다. 리팩토링할 대상을 선정한다. generator-angular-fullstack으로 애플리케이션을 생성하면 client/app/app.js 파일이 메인 파일이 된다. 먼저 app.js안에 애플리케이션 모듈 set과 config, run에 다양한 설정이 되어 있다면 이들을 base.framework 또는 app.framework으로 분리할 수 있는지 점검한다. 일반적으로 다음과 같은 상태로 app.js를 설정하고 한다. 





  위와 같이 app.js 한 곳에 다양한 설정들이 있는 것을 리팩토링을 통해 모듈단위로 나눌것이다. 이렇게 할 수 있는 것은 메인 모듈이 의존하는 모듈을 설정하면서 의존모듈을 로딩하면서 의존모듈의 config, run을 수행해 주고 그리고 자신의 모듈을 로딩하기 때문이다. app.js안에 app.framework과 base.framework 모듈의 의존관계를 설정하고 로그를 찍어보자. 먼저 의존관계의 모듈 config와 최종 애플리케이션 모듈인 jandiApp config를 로딩한 후 run을 다시 의존 모듈부터 수행시킨다. 즉, 애플리케이션을 최초 호출할 때 config, run 은 최초 한번 수행됨을 이용해서 각 기능의 초기화를 모듈단위로 나누는 것이다. 

// app.js 의존관계 설정 

angular.module('jandiApp', [

    'base.framework',

    'app.framework',

    ... 중략 ...]);


// config, run의 수행 순서 로그 

=========> Base framework Module config

=========> App framework Module config

=========> jandiApp Module config

=========> Base framework Module run

=========> App framework Module run

=========> jandiApp Module run




2. Config 모듈 생성 


 최초 환경 설정 부분을 모듈로 만들어서 app.framework 모듈에 의존성을 설정해 보자. app.js의 run 메소드에는 api 버전과 서버주소에 대한 기본 환경 설정이 있다. 이것을 config.js라는 별도의 모듈로 빼서 환경설정에 대한 부분을 독립시킨다. 

  - config.js 모듈 파일 생성 : 최초 환경 셋업이 필요한 부분은 config 모듈의 run 메소드에 설정한다. 즉, app.js 있던것을 옮겨옴 

  - config.service.js 서비스 생성 : 해당 서비스를 통해 어디서나 주입을 받아 사용토록 한다. 

$ cd web_client/components/app

$ mkdir config && cd config


// app.js 일부내역 

app.run(function() {

$rootScope.server_address   = configuration.api_address + "inner-api/";

      $rootScope.server_uploaded  = configuration.api_address;

$rootScope.api_version      = configuration.api_version;

$rootScope.configuration    = configuration;  

});


// components/app/config/config.js 내역 

(function() {

  'use strict';


  angular

    .module('app.config', [])

    .constant('configuration', {

      name                : 'local',

      api_address         : 'http://www.jandi.com/',

      api_version         : '2',

      ga_token            : 'UA-222222-1',

      ga_token_global     : 'UA-1111111-1',

      mp_token            : 'abcdefghijklmn',

      base_url            : '.jandi.io',

      base_protocol       : 'http://',

      main_address        : 'http://www.jandi.com/main/#/',

      app_store_address   : 'xxxx',

      play_store_address  : 'yyyy'

    })

    .run(run);


  /* @ngInject */

  function run($rootScope, config) {

    // server address, server api version 

    config.init($rootScope);

  }


})();


// components/app/config/config.service.js 내역 

(function() {

  'use strict';


  angular

    .module('app.config')

    .service('config', config);

    

  /* @ngInject */

  function config(configuration) {

    var self = this;

    this.init = init; 


    function init($rootScope) {

      // TODO : maybe remove next factoring

      // compatibility for current version

      $rootScope.server_address   = configuration.api_address + "api/";

      $rootScope.server_uploaded  = configuration.api_address;

      $rootScope.api_version      = configuration.api_version;

      

      // configuration constant service       

      configuration.server_address  = configuration.api_address + "api/";

      configuration.server_uploaded = configuration.api_address;

      configuration.api_version     = configuration.api_version;


      // config service

      self.server_address  = configuration.api_address + "inner-api/";

      self.server_uploaded = configuration.api_address;

      self.api_version     = configuration.api_version; 

    }

  }

})();


  config.js 에서 app.config 모듈을 run 하면서 config.service.js 를 주입받아 최초에 init()을 하고 있다. 파라미터로 $rootScope를 전달하는데 이는 초기 app.js에서 환경값을 $rootScope에 설정해서 사용하기 때문이다. 향후에는 $rootScope를 통해 접근하는 것을 없애고 config 서비스 통해 필요한 정보를 접근토록 할 예정으로 1차로 기존 버전의 애플리케이션 수정없이 환경설정으로 뺄 수 있는 상태로 리팩토링한다. AngularJS에서 초기 설정 정보를 접근하기 위한 원칙을 다음과 같이 세우면 좋다. 


  - AngularJS의 서비스는 singleton object 이므로 초기 값을 변수로 가지고 있고 접근자 API를 노출한다. 

  - AngularJS 서비스에 $scope, $rootScope를 절대로 주입하지 않는다. 오직 controller와 module.run 에서만 주입을 받아 사용한다. 

  - 따라서 서비스는 서비스를 주입받아 공통정보에 접근해야 한다. 


원칙에 따라 하나씩 모듈로 쪼개고 app.js에는 업무적인 로직에만 집중할 수 있도록 만든다. 향후 components/app/* 모듈은 사용자가 사용하는 웹화면과 관리자가 사용하는 웹화면에 동시에 적용되는 애플리케이션과 관련된 공통 모듈이 들어갈 것이다. 현재까지 모듈의 의존관계 모습은 다음과 같이 app.js의 jandiApp 의존관계는 app.framework 밖에 없다. 하지만 서로 의존관계가 설정되어 있기 때문에 jandiApp 모듈의 AngularJS 모든 컴포넌트는 base.framework과 app.framework의 의존관계 설정 모듈의 컴포넌트를 주입받아 사용할 수 있게 된다. 

// app.js 의존관계 

angular

  .module('jandiApp', [

    'app.framework'

  ]);


// app.framework.js 의존관계

 angular

    .module('app.framework', [

        'base.framework',

        'app.config'

      ])

    .run(run)

    .config(config);


// base.framework.js 의존관계

  angular

    .module('base.framework', [

      'ui.router',

      'ui.bootstrap',

      'gettext'

      'ngCookies',

      'ngResource',

      'ngSanitize',

      'ngAnimate'

    ])


  좀 더 구조적인 관계의 모듈 설정을 통해 구조적인 애플리케이션을 개발하는 첫단추를 꽤었다. 


저작자 표시 비영리 변경 금지
신고

'My Projects > Jandi' 카테고리의 다른 글

[Jandi] 애플리케이션 모듈 리팩토링  (0) 2015.01.21
[Jandi] 팀 커뮤니케이션 서비스  (0) 2015.01.07
posted by peter yun 윤영식
2015.01.07 11:29 My Projects/Jandi

Tosslab의 잔디는 팀 그룹간에 원활한 커뮤니케이션을 위한 도구이다. 채팅 및 파일공유를 할 수 있어서 업무 커뮤케이션 툴로 사용하기에 적당하고 모바일, 웹에서 접근할 수 있다. 



잔디의 기술


  잔디는 MEAN Stack 기술을 사용하고 모바일은 네이티브로 개발한다. 업무적인 커뮤니케이션 도구는 사무실에서 웹을 통해 사용할 경우가 많아서 특히 웹에 대한 사용성이 좋아야 한다. 블로그에서는 프론트앤드 영역만을 다루도록 한다. 기존에는 AngularJS v1.2.* 기반으로 되어 있고 개발을 진행함에 따라 웹 애플리케이션의 복잡도가 증가하여서 애플리케이션을 재구성할 필요가 발생하였다. 




모듈단위 애플리케이션 구성


 애플리케이션 복잡도를 단순화 시키고 애플리케이션을 구조화하는 방안이 필요하다. 애플리케이션을 구조화 한다는 것은 거시적인 부분과 미시적인 부분의 조합으로 이루어진다. 먼저 거시적인 부분은 아키텍쳐레벨로 AngularJS의 모듈을 구성하는 것이다. 


 

  AngularJS 모듈은 컴포넌트의 그룹이다. 모듈안에는 다양한 컴포넌트를 가지고 있고 컴포넌트 성격별로 구분해 놓을 필요가 있다. 위의 예에서 가장 기본으로 있어야할 AngularJS에서 제공하는 모듈 묶음과 어느 프로젝트에서나 쓰일만한 모듈을 묶어놓은 공통 모듈이 있을 것이다. 그리고 업무적으로 추상화할 필요가 있는 업무 공통 모듈이 존재하고 업무를 처리하는 업무 모듈식으로 나눌 수 있다. 하위 모듈은 상위 모듈을 참조할 수 없고 상위 모듈만이 하위모듈안의 컴포넌트를 DI(Dependency Injection) 방식으로 사용을 할 수 있다. 




제너레이터를 통한 애플리케이션 구성


 기존의 애플리케이션을 보기하면서 다시 재구성하기 위해 무엇부터 시작을 해야할까? 


  - Yeoman Generator를 선정한다. generator-angular-fullstack을 사용할 예정이다. 

  - 자바스크립트 코딩 스타일은 구글의 자바스크립트 스타일 가이드를 따른다. 

  - AngularJS의 코딩 스타일은 존파파의 AngularJS 스타일 가이드를 따른다.


  generator-angular-fullstack의 템플릿을 존파파의 가이드에 따라 변경한 프로젝트를 만들어 본다. 불필요한 예제는 제거하고 몇가지 영역의 템플릿만을 취할 것이다. 먼저 generator-angular-fullstackgenerator-ng-component를 각각 git clone 하고 명칭을 generator-jdi, generator-jdi-component라고 바꾸고 내부의 .git폴더를 제거한 후 각 템플릿의 내용을 파파죤스 가이드에 따라서 수정을 한다. 

$ git clone https://github.com/DaftMonk/generator-angular-fullstack.git

$ git clone https://github.com/DaftMonk/generator-ng-component.git


$ mv generator-angular-fullstack generator-jdi

$ mv generator-ng-component generator-jdi-component


$ cd generator-jdi && rm -rf .git

$ cd generator-jdi-component && rm -rf .git


 파파죤스 가이드에 따라 generator-jdi-component/templates 폴더 밑의 모든 앵규러 컴포넌트를 다음과 같은 형식으로 수정한다. 

   + IIFE : Immediately Invoke Function Expression

   + 앵귤러 모듈 변수를 만들지 않음 

   + 컴포넌트의 펑션을 밖으로 뺌

   + DI 하는 부분은 ng-annotation을 이용함으로 주입받는 펑션위에 /* @ngInject */ 를 둚

(function() { 

  'use strict';


  angular

    .module('<%= scriptAppName %>')

    .controller('<%= classedName %>Ctrl', <%= classedName %>Ctrl);


  /* @ngInject */

  function <%= classedName %>Ctrl($scope) {

         

  }

})();


 수정한 내역에 대해 테스트는 다음과 같이 진행을 하면 된다.  먼저 테스트용 test 폴더를 하나 만든다. 테스트 폴더 밑에 node_modules 폴더를 만들고 밑으로 generator-jdi와 generator-jdi-component 에 대해 심볼릭 링크를 건다. 각 generator-* 폴더로 들어가 npm install을 해준다. 이제는 test밑에서 "yo jdi Jandi" 명령을 치면 애플리케이션 생성 프롬프트를 묻게 된다. 애플리케이션 템플릿은 generator-jdi/app/templates 밑에 존재하고 여기에 있는 파일도 파파죤스 스타일 가이드에 따라 수정을 해준다. 

$ mkdir test && cd test

$ mkdir node_modules && cd node_modules 


$ ln -s <fullPath>/generator-jdi  generator-jdi

$ ln -s <fullPath>/generator-jdi-component generator-jdi-component


// 둘은 같은 디렉토리에 있어야 함 

$ cd <fullPath>/generator-jdi-component && npm install

$ cd <fullPath>/generator-jdi && npm install 


$ cd test 

$ yo jdi Jandi


  각 수정한 generator-jdi, generator-jdi-component는 링크를 참조한다. 이제는 누구나 사용할 수 있도록 npm repository에 등록을 한다. npm publish 로 package.json 파일에 등록된 내역을 기반으로 배포가 된다. 따라서 clone한 generator의 버전을 따르지 않고 최초 0.0.1 버전으로 수정해 배포한다. 

$ cd generator-jdi-component 

$ npm publish

+ generator-jdi-component@0.0.1


$ cd generator-jdi

$ npm publish

+ generator-jdi@0.0.1


 이제 글로벌로 generator-jdi만을 설치해서 어디서나 "yo jdi <App명칭>" 명령을 수행해 최초 애플리케이션 골격을 만들 수 있다. 

$ npm install -g generator-jdi

$ mkdir web_tiger && cd web_tiger

$ yo jdi Jandi



저작자 표시 비영리 변경 금지
신고

'My Projects > Jandi' 카테고리의 다른 글

[Jandi] 애플리케이션 모듈 리팩토링  (0) 2015.01.21
[Jandi] 팀 커뮤니케이션 서비스  (0) 2015.01.07
posted by peter yun 윤영식
2015.01.05 12:32 My Projects/Bagle

베이글은 그룹 컨텐츠 공유 서비스이다. 그룹 관리자는 멀티미디어 컨텐츠 및 설문 조사를 위한 다양한 내용을 개제할 수 있고 멤버는 이에 대한 답변을 하거나 댓글은 단다. 페이스북과 유사한 UI이지만 퍼블릭 SNS가 아닌 버티컬 SNS를 지향한다. 





베이글의 탄생


  이전 StudyGPS의 서비스 v1.5를 v2.0으로 개편하면서 서비스이름을 새로 공모했고 최종적으로 베이글을 선택했다. 미국에서 프로젝트를 하면서 금요일 아침마다 먹었던 베이글이 생각나서 개발팀 이름도 MondayBread로 한 마당에 베이글이 어떠냐는 의견을 내놓았는데 잘 매칭되어 채택이 되었다. 베이글은 교육을 타케팅하는 서비스이다. 주로 선생님이 그룹을 만들고 학생이 답변을 다는 형태이다. 선생님은 학생들에게 학업과 관련한 컨텐츠를 제공하면서 이에대한 설문 조사를 수행할 수 있다. 컨텐츠는 파일 첨부, 이미지, 동영상등을 게재할 수 있고 설문형식은 주관식, 객관식등의 조사와 댓글을 통해 의견을 들을 수 있다. 


  보수적인 교육시스템에서는 자유로운 퍼블릭 SNS로 쌍방향 소통 보다는 단방향의 문제를 내는 사람과 문제를 푸는 사람을 구분하여 관심분야의 그룹을 운영할 수 있도록 하는데 목적이 있다. 




베이글의 기술


  스타트업에서 인기를 누리고 있는 MEAN (MongoDB ExpressJS AngularJS Node.js) 스택을 사용한다. 모바일은 하이브리드 앱방식으로 개발을 진행하고 이를 위한 프레임워크로 Ionic을 사용한다. 그외 Sass트위터 부트스트랩도 함께 사용한다. 하이브리드 앱 개발에서 중요한 체크 포인트는 다음과 같다. 


  1) 네이티브 UX에 근접한 경험을 줄 수 있는가?

  2) 네이티브 UI 컴포넌트와 같이 잘 정의된 레이아웃이나 컴포넌트가 있는가?

  3) HTML과 Javascript의 분리해서 네이티브처럼 개발을 진행할 수 있는가?


  1)번을 위한 약간 2주간 AppGyver를 사용해 보았으나 제약 사항이 많고 특히 MPA(Multi-Page Application)으로 WebView를 정말 Multi개만큼 생성해서 운영해야 제대로된 네이티브 기능을 사용할 수 있다. 이것의 단점은 모든 뷰가 하나의 AngularJS SPA가 되버린다는 것이다.  즉, 화면별로 WebView가 개별적으로 운영되면서 나타나는 현상이다. 하지만 steroids와 scanner 앱을 통해 편한 개발환경을 제공하고 있다. 따라서 아주 간단한 App을 만든다면 AppGyver도 쓸만한 프레임워크이자 플랫폼라 생각한다. AppGyver의 supersonic 하이브리드 프레임워크도 결국 Ionic을 내부적으로 사용하고 있고 SPA 방식으로 개발하면서 네이티브 수준의 UI와 UX를 선사하는 Ionic 프레임워크를 최종 선택하게 되었다. 올해(2015)초에는 정식 v1.0이 릴리즈 될 예정이고 ionic 내부에 angular, angular-sanitize, angular-animation, ui-router를 의존관계로 가지고 있고 AngluarJS v1.3.6 최신버전을 사용하고 있다. 


 2)번은 Sass로 일정부분 디자인을 하고 트위터 부트스트랩과 같은 UI 프레임워크를 사용하면 되고 3)번은 reactive programming을 할 수 있는 AngularJS의 양방향 데이터 바인딩을 사용하면 HTML과 JS파일을 분리해 개발을 할 수 있다.  


앞으로 개발하면서 느꼈던 점과 기술적인 이슈등을 회고식으로 적어볼 예정이다. 


  

저작자 표시 비영리 변경 금지
신고

'My Projects > Bagle' 카테고리의 다른 글

[Bagle] 베이글이란?  (0) 2015.01.05
posted by peter yun 윤영식
2013.12.04 01:20 My Projects/BI Dashboard

상단의 대메뉴를 선택한 후 본문의 왼쪽에 있는 소메뉴를 클릭하면 오른쪽에 본문의 내용이 나온다. 본문을 보기까지의 라우팅(Routing)을 보면 대메뉴 클릭 -> 소메뉴 클릭 -> 본문 으로 이어지는 DOM의 변경이 일어난다. jqGrid, HigChart등의 json 환경 내역을 관리하는 화면을 만들기 위하여 Routing 방식을 고도화 해보자




1. Nest View

  - 큰메뉴 -> 작은 메뉴 -> 더 작은 메뉴 -> 본문 식의 View가 내제되어 라우팅 될 경우

  - UI-Router 를 이용한 데모, Plunker 데모

  - Route-Segment 를 이용한 데모



2. Admin 화면 구성하기

  - index.html 메뉴 추가하기

  - component에서 jqgrid, highchart와 같은 컴포넌트의 config 값 (json type)을 관리한다

// index.html 메뉴 추가하기 

<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 class="dropdown">

        <a href="" class="dropdown-toggle" data-toggle="dropdown">Admin <b class="caret"></b></a>

        <ul class="dropdown-menu">

          <li class="dropdown-header">Project</li>

          <li><a href="">User</a></li>

          <li><a href="">Customer</a></li>

          <li><a href="">Code</a></li>

          <li><a href="">Role</a></li>

          <li class="divider"></li>

          <li class="dropdown-header">MobiconSoft</li>

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

      <a href="" data-ng-click="setRoute('/mc-component')">Component</a>

    </li>

          <li><a href="">Dashboard</a></li>

        </ul>

    </li>

  </ul>

</div>  



// Component partial 화면과 모듈 생성 및 등록

// 1) MCAdminCtrl.js 모듈을 만들기 

// 2) views/mc/component.html 화면 만들기

// 3) DashboardApp.js 에 MCAdminCtrl.js 모듈 명칭 등록하기 : DashboardApp.MCAdminCtrl

// 4) index.html 에 MCAdminCtrl.js 모듈 파일 <script> 태그 추가하기

  - 서버 코드로 ComponentController / Component / ComponentService / ComponentMapper(java & xml) 을 만든다 

    + compType : grid, chart, option 등 다양하게 존재할 수 있다 

// ComponentMapper.xml 에서 : 기존 소스는 typeAlias 사용하였음

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

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- 

id : primary key

mysql -u user1 -p DBName

mysql> CREATE TABLE mc_component(

      id MEDIUMINT NOT NULL AUTO_INCREMENT,

      comp_type VARCHAR(5) NOT NULL,

      comp_cfg TEXT NOT NULL,

      PRIMARY KEY (id) 

    );

 -->

<mapper namespace="com.mobiconsoft.dashboard.mapper.ComponentMapper">

 

    <resultMap id="component" type="com.mobiconsoft.dashboard.domain.Component" >

        <result property="id" column="id"/>

        <result property="compType" column="comp_type"/>

        <result property="compCfg" column="comp_cfg"/>

    </resultMap>

 

    <select id="getComponents" resultType="com.mobiconsoft.dashboard.domain.Component">

        SELECT

        *

        FROM

        component

    </select>

 

    <select id="getComponent" parameterType="Integer" resultType="com.mobiconsoft.dashboard.domain.Component">

        SELECT

        *

        FROM

        component

        WHERE

        id=#{id}

    </select>

 

    <insert id="saveComponent" parameterType="com.mobiconsoft.dashboard.domain.Component"<!-- useGeneratedKeys="true" keyProperty="id"> -->

        INSERT INTO

        component(comp_type, comp_cfg)

        VALUES

        (#{compType}, #{compCfg})

    </insert>

    

    <update id="updateComponent" parameterType="com.mobiconsoft.dashboard.domain.Component"

        UPDATE 

        component

        SET

        comp_type=#{compType}, 

        comp_cfg=#{compCfg}

        WHERE

        id=#{id} 

    </update>

 

    <delete id="deleteComponent" parameterType="Integer">

        DELETE FROM

        component

        WHERE

        id=#{id}

    </delete>

 

</mapper>


// 서버 전체 파일 



3. UI-Router 사용하기

  - 관리 메뉴에서 Component를 선택하면 CRUD 할 수있는 메뉴 구조를 본문에서 가져야 한다

  - UI-Router를 이용하여 기존의 Routing 환경을 고도화한다 

// angular-route 라 반드시 설치되어 있어야 하고, 1.2.* 도 지원함 

// bower 설치 

bower install angular-ui-router --save

bower angular-ui-router#~0.2.0 install angular-ui-router#0.2.0

  - index.html에 route-segment 추가하기 

<!-- build:js scripts/angular-3.js -->

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

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

    <script src="bower_components/angular-ui-router/release/angular-ui-router.js"></script>

    .. 중략 ..

<!-- endbuild -->

  - DashboardApp.js 메인 소스에 ui-router 모듈을 추가한다 (예제 참조)

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

  'ngRoute', 

  'ui.router',

  .. 중략 ..

  'DasbhoardApp.RestfulSvc'

]);

  - DashboardApp.js 의 config 에서 $routeProvider를 고도화한다 (소스 참조)

DashboardApp.config(['$stateProvider', '$urlRouterProvider', function ($stateProvider, $urlRouterProvider) {


   $urlRouterProvider.otherwise("/main");

   $stateProvider

      .state('main', {

      url: '/main',

        templateUrl: 'views/main.html'

      })

      .state('resttest', {

      url: '/resttest',

        templateUrl: 'views/restTest.html',

        controller: 'RestTestBiz.personCtrl'

      })

      .state('jqgridtest', {

      url: '/jqgridtest',

        templateUrl: 'views/jqGridTest.html',

        controller: 'JqGridBiz.salesCtrl'

      })

      .state('mc-component', {

      url: '/mc-component',

        templateUrl: 'views/mc/component.html',

        controller: 'MCAdminCtrl.componentCtrl'

      });

  }]);


DashboardApp.run(['$rootScope', '$state', '$stateParams',

    function ($rootScope,   $state,   $stateParams) {


      // It's very handy to add references to $state and $stateParams to the $rootScope

      // so that you can access them from any scope within your applications.For example,

      // <li ng-class="{ active: $state.includes('contacts.list') }"> will set the <li>

      // to active whenever 'contacts.list' or one of its decendents is active.

      $rootScope.$state = $state;

      $rootScope.$stateParams = $stateParams;

  }]);

  - index.html 의 링크 내역 수정 (소스 참조)

   + ui-sref 애프리뷰트 directive 사용으로 routing 되는 정보를 현재 html 페이지의 ui-view 로 연결한다 

   + ui-view 애트리뷰트 directive 사용으로 화면이 나타나는 태그 영역

   + 메뉴 active는 $state.includes('<stateName>') 을 이용하여 현재의 state 이면 active로 표현 

  -즉 한페이지에서 ui-sref와 ui-view를 통해 확장해 가는 방식

<div class="collapse navbar-collapse">

  <ul class="nav navbar-nav">

    <li data-ng-class="{ active: $state.includes('main') }"><a ui-sref="main">Home</a></li>

    <li data-ng-class="{ active: $state.includes('resttest') }"><a ui-sref="resttest">RESTTest</a></li>

    <li data-ng-class="{ active: $state.includes('jqgridtest') }"><a ui-sref="jqgridtest">jqGridTest</a></li>

    

    <!-- <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> -->

    <li class="dropdown">

        <a href="" class="dropdown-toggle" data-toggle="dropdown">Admin <b class="caret"></b></a>

        <ul class="dropdown-menu">

          <li class="dropdown-header">Project</li>

          <li><a href="">User</a></li>

          <li><a href="">Customer</a></li>

          <li><a href="">Code</a></li>

          <li><a href="">Role</a></li>

          <li class="divider"></li>

          <li class="dropdown-header">MobiconSoft</li>

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

      <a href="" data-ng-click="setRoute('/mc-component')">Component</a>

     </li> -->

     <li data-ng-class="{ active: $state.includes('mc-component') }"><a ui-sref="mc-component">Component</a></li>

          <li><a href="">Dashboard</a></li>

        </ul>

    </li>

  </ul>

</div>


<!-- Add your site or application content here -->

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

<div ui-view class="container" style="margin-top:30px"></div>



4. Component 관리를 위한 UI-Router 만들기

  - component 관리 화면 만들기

    + step-1 : component.html 으로 컴포넌트의 목록을 보여주고 컴포넌트를 선택하면 component.detail.html 이 우측에 표현된다

    + step-2 : component.read.html 로 컴포넌트 상세 정보를 읽거나 업데이트 또는 삭제한다  

    + step-3 : component.create.html 로 새로운 컴포넌트 정보를 저장한다 

    + step-4 : ComponentMod.js 개발하기 - CRUD Controller와 Service 그리고 index.html에 파일 추가하기 

    + step-5 : DashboardApp.js routing 정보 업데이트 및 ComponentMod.js 모듈 추가하기 

    

// index.html 에서 ui-view에 component.html전체가 표현됨

 <div ui-view class="container" style="margin-top:30px"></div>


// step-1,2,3) *.html 파일 만들기 

// component.html 에서 목록을 받아와서 ng-repeat 하기 

<li ng-repeat="component in components"

    ng-class="{ active: $state.includes('mc-component.detail') && $stateParams.componentId == component.id }">

  <a ui-sref=".detail({componentId:component.id})" style="padding: 2px 2px">{{component.name}}</a>

</li>

.. 중략 ..

// 여기에 component.detail.html 과 component.create.html 내용이 표현됨 

<div ui-view></div>


// component.detail.html : value에 값을 표현하기, ng-model을 통하여 controller에서 변경값 처리하기 (create html도 유사함) 

<form class="form-horizontal" role="form">

<div class="form-group">

<label class="col-sm-3 control-label"> Name</label>

<div class="col-sm-9">

<input type="text" class="form-control" placeholder="Name" ng-model="component.name" value="{{component.name}}" required>

</div>

</div>

<div class="form-group">

<label class="col-sm-3 control-label"> Type</label>

<div class="col-sm-9">

<input type="text" class="form-control" placeholder="Type" ng-model="component.type" value="{{component.type}}" required>

</div>

</div>

<div class="form-group">

<label class="col-sm-3 control-label"> Config</label>

<div class="col-sm-9">

<textarea class="form-control" id="message" name="message" ng-model="component.cfg" value="{{component.cfg}}"

placeholder="Config" rows="5" required></textarea>

</div>

</div>

<div class="form-group last">

<div class="col-sm-offset-3 col-sm-9">

<button type="submit" class="btn btn-success btn-primary" data-ng-click="update()">Update</button>

<button type="submit" class="btn btn-error btn-primary" data-ng-click="deleteComp()">Delete</button>

</div>

</div>

</form>

  - step-4 : CompoonentMod.js 개발 

'use strict';

// 모듈 정의 

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

// ui-router의 서비스 추가 

ComponentMod.controller('ComponentMod.componentCtrl', ['$rootScope', '$scope', 'RestfulSvcApi', '$state', '$stateParams', 'componentSvc',

                                                              function ($rootScope, $scope, RestfulSvcApi, $state, $stateParams, componentSvc) {

        // 서브 화면에서 Create, Delete, Update가 일어나면 목록의 components 를 two-way binding으로 업데이트하기 위함

       $scope.$on("updateComponentList", function(event, data){

          console.log('--broadcasting data, ', data);

          $scope.components = data;

       });

var getAll = function() {

console.log('-----all: ', $scope.components);

componentSvc.getAll();

};

var getOne = function() {

console.log('-----one: ', $stateParams.componentId);

RestfulSvcApi.one({ domain: 'component', key: $stateParams.componentId },

function(response) {

console.log('componentCtrl : one data=',response);

   $scope.component = response;

},

function(response) {});

};

if($state.current.name === 'mc-component' || (!$stateParams.componentId && !$scope.components)) {

getAll();

};

if($state.current.name === 'mc-component.detail' && $stateParams.componentId) {

getOne();

};

$scope.update = function() {

console.log('componentCtrl : component=', $scope.component);

RestfulSvcApi.update({ domain: 'component'}, $scope.component,

function(response) {

console.log('---update, ', response);

getAll();

},

function(response) {});

};

$scope.deleteComp = function() {

RestfulSvcApi['delete']({ domain: 'component', key: $scope.component.id },

function(response) {

console.log('---delete, ', response);

$scope.component = {};

getAll();

},

function(response) {});

};

  

$scope.save = function() {

console.log('----> component is ', $scope.component);

RestfulSvcApi.save({ domain: 'component' }, $scope.component,

function(response) {

console.log('---save, ', response);

getAll();

},

function(response) {});

};

 }]);


// 전체 목록에 대해서만 Service로 구현함 : Broadcasting에 대한 별도 추가 테스트 필요함

ComponentMod.service('componentSvc', ['$rootScope', 'RestfulSvcApi', function ($rootScope, RestfulSvcApi) {

this.getAll = function() {

RestfulSvcApi.all({ domain: 'component' },

function(response) {

console.log('componentSvc : all data=', response);

$rootScope.$broadcast('updateComponentList', response);

},

function(response) {});

};

}]);

  - step-5 : DashboardApp.js routing status 업그레이드

'use strict';


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

  'ngRoute', 

  'ui.router',

  'ngAnimate',

  'ngCookies',

  'ngResource',

  'ngSanitize',

  'DasbhoardApp.CommonCtrl',

  'MobiConSoft.ComponentMod',

  'DashboardApp.JqGridDrtv',

  'DasbhoardApp.RestTestBiz',

  'DasbhoardApp.JqGridBiz',

  'DasbhoardApp.RestfulSvc'

]);


DashboardApp.config(['$stateProvider', '$urlRouterProvider', function ($stateProvider, $urlRouterProvider) {


    $urlRouterProvider.otherwise("/main");

    $stateProvider

      .state('main', {

      url: '/main',

        templateUrl: 'views/main.html'

      })

      .state('resttest', {

      url: '/resttest',

        templateUrl: 'views/restTest.html',

        controller: 'RestTestBiz.personCtrl'

      })

      .state('jqgridtest', {

      url: '/jqgridtest',

        templateUrl: 'views/jqGridTest.html',

        controller: 'JqGridBiz.salesCtrl'

      })

      .state('mc-component', {

      url: '/mc-component',

        templateUrl: 'views/mc/component.html',

        controller: 'ComponentMod.componentCtrl'

      })

     .state('mc-component.detail', {

      url: '/detail/{componentId:[0-9]{1,4}}',

       templateUrl: 'views/mc/component.detail.html',

       controller: 'ComponentMod.componentCtrl'

     })

     .state('mc-component.create', {

      url: '/create',

       templateUrl: 'views/mc/component.create.html',

       controller: 'ComponentMod.componentCtrl'

     });

  }]);


DashboardApp.run(['$rootScope', '$state', '$stateParams',

    function ($rootScope,   $state,   $stateParams) {

      $rootScope.$state = $state;

      $rootScope.$stateParams = $stateParams;

  }]);

  - 결과화면 

    


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



<참조>

  - AngularJS Views and Directives

  - Angular Route Segment

  - UI-Route : 예제 소스

저작자 표시 비영리 변경 금지
신고
posted by peter yun 윤영식
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 peter yun 윤영식
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 peter yun 윤영식
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 peter yun 윤영식
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 peter yun 윤영식
2013.11.20 09:11 My Projects/BI Dashboard

이전 블로그에서 myBatis를 사용하면서 DAO Interface를 만들고, DAO를 구현한 클래스를 사용하는 방법을 테스트해 보았다. DAO를 구현하지 않고 Interface의 메소드만 선언하면 myBatis에서 자동 구현되어 사용할 수 있게 하는 방법을 알아보자 




1. Mapper 인식하는 방식

  - Spring context xml에 설정하기 : UserMapper는 단순 Interface

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">

  <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />

  <property name="sqlSessionFactory" ref="sqlSessionFactory" />

</bean>

  - Mapper scanner를 등록하는 방식 : component-scan과 유사하게 mapper를 검색해 준다 (안됨)

<beans xmlns="http://www.springframework.org/schema/beans"

  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"

  xsi:schemaLocation="

  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

  http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd">

 

  <mybatis:scan base-package="org.mybatis.spring.sample.mapper" />


</beans>

  - @MapperScan 애노테이션 사용 : java로 환경설정

@Configuration

@MapperScan("org.mybatis.spring.sample.mapper")

public class AppConfig {


  @Bean

  public DataSource dataSource() {

    return new EmbeddedDatabaseBuilder().addScript("schema.sql").build()

  }


  @Bean

  public SqlSessionFactory sqlSessionFactory() throws Exception {

    SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();

    sessionFactory.setDataSource(dataSource());

    return sessionFactory.getObject();

  }

}

  - MapperScannerConfigurer 사용 : 본 셋팅에는 해당 설정을 사용한다 

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">

  <property name="basePackage" value="com.mobiconsoft.dashboard.mapper" />

</bean>



2. pom.xml의 myBatis 버전 업그레이드 

  - mybatis-spring.jar 에서 1.2 이상의 최신 버전을 사용토록 한다 

<mybatis.spring.version>1.2.1</mybatis.spring.version>

<mysql.connector.version>5.1.27</mysql.connector.version>


<dependency>

<groupId>org.mybatis</groupId>

<artifactId>mybatis</artifactId>

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

</dependency>

<dependency>

<groupId>org.mybatis</groupId>

<artifactId>mybatis-spring</artifactId>

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

</dependency>



3. myBatis Mapper 환경 설정

  - 위치를 webapp 폴더 밑으로 옮긴다 

    

  - dbpool-context.xml의 환경설정 변경 : id 명칭은 내부적으로 동일 이름을 사용하니 변경하지 말자 

<!-- mybatis sql session template -->

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

        <property name="dataSource" ref="dataSource" />

<property name="mapperLocations" value="classpath:mappers/*Mapper.xml" />

<property name="configLocation" value="/WEB-INF/mybatis-config.xml" />

</bean>


<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">

<constructor-arg ref="sqlSessionFactory" />

</bean>


<!-- mybatis mapper auto scanning -->

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">

<property name="basePackage" value="com.mobiconsoft.dashboard.mapper" />

</bean>

  - mybatis-config.xml 내역 수정

<configuration>

    <settings>

        <setting name="cacheEnabled" value="false" />

        <setting name="useGeneratedKeys" value="true" />

        <setting name="defaultExecutorType" value="REUSE" />

    </settings>

</configuration>

  - Person.xml 맵퍼 xml 파일명칭을 PersonMapper.xml 로 변경하고 WEB-INF/classes/mappers/ 폴더 밑으로 이동한다 

    + namespace는 반드시 PersonMapper interface 파일이 있는 위치를 명시한다 

    + resultType, parameterType 시에 full package + class 명칭을 명시한다 


<mapper namespace="com.mobiconsoft.dashboard.mapper.PersonMapper">

 

    <resultMap id="person" type="com.mobiconsoft.dashboard.domain.Person" >

        <result property="id" column="id"/>

        <result property="name" column="name"/>

    </resultMap>

 

    <select id="getPersons" resultType="com.mobiconsoft.dashboard.domain.Person">

        SELECT

        *

        FROM

        person

    </select>

 

    <select id="getPerson" parameterType="Integer" resultType="com.mobiconsoft.dashboard.domain.Person">

        SELECT

        *

        FROM

        person

        WHERE

        id=#{id}

    </select>

 

    <insert id="savePerson" parameterType="com.mobiconsoft.dashboard.domain.Person"

        INSERT INTO

        person(id, name)

        VALUES

        (#{id}, #{name})

    </insert>

    

    <update id="updatePerson" parameterType="com.mobiconsoft.dashboard.domain.Person"> 

        UPDATE 

        person 

        SET

        name=#{name}

        WHERE

        id=#{id} 

    </update>

 

    <delete id="deletePerson" parameterType="Integer">

        DELETE FROM

        person

        WHERE

        id=#{id}

    </delete>

 

</mapper>

  - 이제 PersonDAO.java interface를 PersonMapper.java로 바꾸어 보자 

    + PersonMapper.xml 에서 정의한 parameterType은 메소드의 파라미터 타입와 일치해야 한다

    + PersonMapper.xml 에서 정의한 resultType은 메소드의 리턴 타입과 일치해야 한다 

@Repository(value="personMapper")

public interface PersonMapper {

public List<Person> getPersons();

public Person getPerson(int id);

public void savePerson(Person person);

public void updatePerson(Person person);

public void deletePerson(int id);

}

  - grunt build 할 때 WEB-INF/ 밑의 모든 폴더/파일 dist 폴더에 copy하는 옵션 변경

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/**/*'

          ]


  - PersonService Interface 제거하고 구현체를 PersonService로 변경하였음. 최종 소스 디렉토리 

    + controller : RESTful 요청 처리 

    + service : 실제 업무 트랜잭션 @Transactional 처리

    + mapper : myBatis mapper interface 

    + domain : 비즈니스 객체. 주로 테이블과 1:1 맵핑되는 Model

    

  - 실행

    

* 저장소 : https://github.com/ysyun/SPA_Angular_SpringFramework_Eclipse_ENV/tree/feature_mybatis_mapper



<참조>

  - myBatis Mapper 설정방법

저작자 표시 비영리 변경 금지
신고
posted by peter yun 윤영식
2013.11.20 01:33 My Projects/BI Dashboard

데이터 저장소를 MySQL을 사용하며 CRUD를 위하여 MyBatis를 사용한다. Spring 프레임워크에서 사용하기 위한 설정과 사용법을 알아보자 



1. JDBC Pool 설정

  - tomcat 7을 사용하므로 tomcat의 jdbc-pool을 사용한다 (참조)

  - maven pom.xml : 이미 $TOMCAT_HOME/lib 밑에 라이브러리가 존재할 경우 provided 설정

<!-- spring jdbc dependency spring-tx -->

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-jdbc</artifactId>

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

</dependency>


<!-- tomcat jdbc -->

<dependency>

<groupId>org.apache.tomcat</groupId>

<artifactId>tomcat-jdbc</artifactId>

<version>${tomcat.jdbc.version}</version>

<!--scope>provided</scope-->

</dependency>

  - spring context 와 properties 설정하기 

// web.xml 에서 dbpool-context.xml을 인식하기 위하여 설정변경

    <!-- root-context.xml, mybatis-context.xml -->

    <context-param>

        <param-name>contextConfigLocation</param-name>

        <param-value>

            /WEB-INF/*-context.xml

        </param-value>

    </context-param>


// dbpool-context.xml 에서 properties 에서 환경값을 읽어서 설정한다 

// common-jdbc, c3po보다 tomcat의 jdbc-pool 성능이 더 좋다 

<bean

class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

<property name="locations">

<value>/WEB-INF/dbpool.properties</value>

</property>

</bean>


<bean id="dataSource" class="org.apache.tomcat.jdbc.pool.DataSource"

destroy-method="close">

<property name="driverClassName" value="${jdbc.driverClass}" />

<property name="url" value="${jdbc.url}" />

<property name="username" value="${jdbc.username}" />

<property name="password" value="${jdbc.password}" />

<property name="initialSize" value="${jdbc.min.size}" />

<property name="maxActive" value="${jdbc.max.size}" />

<property name="maxIdle" value="5" />

<property name="minIdle" value="2" />

</bean>


// dbpool.properties 내역 

jdbc.driverClass=com.mysql.jdbc.Driver

jdbc.url=jdbc:mysql://localhost:3306/SolarDB?autoReconnect=true

jdbc.username=admin

jdbc.password=admin

jdbc.min.size=2

jdbc.max.size=10



2. Transaction 설정

  - @Transactional 애노테이션 기반으로 트랜잭션을 관리하고자 할 경우 설정

  - DAO 보다는 가급적 DAO를 호출하는 Service Interface의 Insert, Update, Delete 메소드 선언할 때 @Transactional 설정 (참조)

// dbpool-context.xml 에서 dataSource를 TransactionManager에 할당한다 

<!-- transaction manager -->

<context:annotation-config/>

<tx:annotation-driven transaction-manager="transactionManager" />

<bean id="transactionManager"

class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

<property name="dataSource" ref="dataSource"></property>

</bean>

  - @Transactional 사용시 Propagation 동작 방식에 대해서 숙지하자 (참조)

  - Spring과 그외 관련 설정 파일을 WEB-INF/spring 폴더로 위치 변경함

    



3. myBatis 설정

  - DataSource와 Transaction에 대한 설정이 끝났다면 myBatis를 설정한다 

  - MySQL을 사용할 것이다

  - myBatis의 Resource인 config와 mapper xml 파일은 src/main/resource로 이동하자 (Person.xml, mybatis-config.xml)

    


// dbpool-context.xml 에서 dataSource를 설정한다

// SqlSessionTemplate 을 DAO에서 사용할 것이다 

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

<property name="dataSource" ref="dataSource" />

<property name="configLocation" value="classpath:mybatis-config.xml" />

</bean>

<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">

<constructor-arg ref="sqlSessionFactory" />

</bean>



// mybatis-config.xml 에서 mapper.xml의 위치를 설정 

// Person.xml 과 같은 mapper xml을 여러개 열거 할 수 있다 

<configuration>

    <settings>

        <setting name="cacheEnabled" value="false" />

<setting name="useGeneratedKeys" value="true" />

<setting name="defaultExecutorType" value="REUSE" />

    </settings>

    

    <typeAliases>

        <typeAlias alias="Person" type="com.mobiconsoft.dashboard.domain.Person"></typeAlias>

    </typeAliases>

    

    <mappers>

        <mapper resource="com/mobiconsoft/dashboard/mybatis/mapper/Person.xml"></mapper>

    </mappers>

</configuration>

  - Mysql을 설치 및 Person Table을 하나 만들고 Mapper xml을 설정한다. 여기서는 예제로 Person.xml 파일 하나만 설정함 

// mysql 에서 테이블 생성하기 (참조)

mysql> create table person(

    -> id INT,

    -> name VARCHAR(30));


// Person.xml 맵핑 xml 내역 

// 호출 방법 ID : <namespace>.<id> ex) com.mobiconsoft.dashboard.person

<mapper namespace="com.mobiconsoft.dashboard.Person">

 

    <resultMap type="Person" id="PersonResult">

        <id property="id" column="id"/>

        <result property="name" column="name"/>

    </resultMap>

 

    <select id="getPersons" resultMap="PersonResult">

        SELECT

        *

        FROM

        person

    </select>

 

    <select id="getPerson" parameterType="Integer" resultMap="PersonResult">

        SELECT

        *

        FROM

        person

        WHERE

        id=#{id}

    </select>

 

    <insert id="savePerson" parameterType="Person"> <!-- useGeneratedKeys="true" keyProperty="id"> -->

        INSERT INTO

        person(id, name)

        VALUES

        (#{id}, #{name})

    </insert>

    

    <update id="updatePerson" parameterType="Person"

        UPDATE 

        person 

        SET

        name=#{name}

        WHERE

        id=#{id} 

    </update>

 

    <delete id="deletePerson" parameterType="Integer">

        DELETE FROM

        person

        WHERE

        id=#{id}

    </delete>

</mapper>

  - DAO와 DAO를 구현한다. 

// DAO Interface 

public interface PersonDAO {

  public List<Person> getPersons();

  public Person getPerson(int id);

  public Person savePerson(Person person);

  public Person updatePerson(Person person);

  public void deletePerson(int id);

}


// DAO Implementation

@Repository

public class PersonDAOImpl implements PersonDAO {

@Autowired

private SqlSession sqlSession;

private static final Logger logger = LoggerFactory.getLogger(PersonDAOImpl.class);

// end must be point . 

private static final String NS = "com.mobiconsoft.dashboard.Person.";


@Override

public List<Person> getPersons() {

logger.info("dao select all");

return sqlSession.selectList(NS+"getPersons");

}


@Override

public Person getPerson(int id) {

logger.info("dao select one id is " + id);

return sqlSession.selectOne(NS+"getPerson");

}


@Override

public Person savePerson(Person person) {

logger.info("dao save person is " + person);

sqlSession.insert(NS+"savePerson", person);

return person;

}

@Override

public Person updatePerson(Person person) {

logger.info("dao update person is " + person);

sqlSession.update(NS+"updatePerson", person);

return person;

}


@Override

public void deletePerson(int id) {

logger.info("dao delete id is " + id);

sqlSession.delete(NS+"deletePerson", id);

}

}

  - 기존 PersonServiceImpl의 내역을 변경한다 : @Transactional을 통하여 트랜잭션을 Service 레벨에서 관리한다 

@Service

public class PersonServiceImpl implements PersonService {


@Autowired

PersonDAO personDAO;

@Override

public List<Person> getPersons() {

return personDAO.getPersons();

}


@Override

public Person getById(Integer id) {

return personDAO.getPerson(id);

}


@Override

@Transactional

public Person save(Person person) {

personDAO.savePerson(person);

return person;

}

@Override

@Transactional

public Person update(Person person) {

personDAO.updatePerson(person);

return person;

}

@Override

@Transactional

public void delete(Integer id) {

personDAO.deletePerson(id);

}

}


* 저장소 : https://github.com/ysyun/SPA_Angular_SpringFramework_Eclipse_ENV/tree/feature_mybatis



<참조>

  - Spring, myBatis 설정 방법

  - Tomcat 7일 경우 Spring에서 Tomcat JDBC-Pool 사용하기

  - Common-JDBC와 C3P0와 Tomcat JDBC-Pool 성능차이

  - Transaction Manager 선언적으로 사용하기

  - Transaction Propagation 동작 방식

  - Mac에서 기존 MySQL 삭제하기 

저작자 표시 비영리 변경 금지
신고
posted by peter yun 윤영식
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 peter yun 윤영식
2013.11.15 04:42 My Projects/BI Dashboard

자바기반 서버 환경 구축을 위한 기반 기술 설정에 대하여 알아보자. 우선 빌드환경인 Maven과 Spring Framework을 설치하고 간단히 RESTful 호출 테스트를 해보자 



1. Eclipse 플러그인 설치 

  - Eclipse Juno를 다운로드하여 설치 및 JDK1.6 사용

  - Sublime text 사용에 익숙하다면 http://eclipsecolorthemes.org/ 플러그인을 설치후 에서 Sublime Theme으로 바꾸어 사용한다 

  - Eclipse Marketplace에서 

    + STS (Spring Tool Suite - juno 4.2)를 설치

    + Maven (Maven Integration for Eclipse - juno and newer)를 설치

    + Git 사용한다면 EGit - Git Team Provider를 설치

    + SVN 사용한다면 Subversive - SVN Team Provider 설치 (참조1, 참조2)

       > "Help/Install New Software..."에서 

       > SVN Connector - http://community.polarion.com/projects/subversive/download/eclipse/3.0/juno-site/ 통해 설치

          Mac의 HL 부분은 unselect 

       > SVN Kit 1.7.8 선택한다. 

      


  

2. Spring MVC 프로젝트 구성

  - Maven으로 Simple project를 만든다. jar 배포 프로젝트로 만듦

  - 프로퍼티에서 Project facets에서 "Dynamics Web Module"를 선택하고 "Further configuration available..."에서 webapp 설정

  - webapp를 설정하였으면 src/main 밑으로 강제 이동시킴 

    

  - 프로퍼티에서 Deployment Assembly에서 add... 하여 Maven Denpendencies... 라이브러리가 WEB-INF/lib에 포함되도록 설정

    (참조)

    


  - pom.xml 에서 <packaging>jar</packaging>에서  <packaging>war</packaging>로 바꾼다 

  - 하기 의존성 라이브러리를 추가한다 

    + Spring Framework : v3.2.0

    + JDK : v1.6

    + JUnit : v4.11

<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>


<properties>

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

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

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

</properties>


<dependencies>


<!-- Spring 3 dependencies -->

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-core</artifactId>

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

</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>

</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>



3. RESTful 테스트 하기 

  - Controller 추가하기 : RESTful uri 설정

package com.lge.dashboard;


import org.springframework.stereotype.Controller;

import org.springframework.ui.ModelMap;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;


@Controller

@RequestMapping("/")

public class HelloController {

 

@RequestMapping(value="/welcome", method = RequestMethod.GET)

public String welcome(ModelMap model) {

 

model.addAttribute("message", "Maven Web Project + Spring 3 MVC - welcome()");

 

//Spring uses InternalResourceViewResolver and return back index.jsp

return "index";

}

 

@RequestMapping(value="/welcome/{name}", method = RequestMethod.GET)

public String welcomeName(@PathVariable String name, ModelMap model) {

 

model.addAttribute("message", "Maven Web Project + Spring 3 MVC - " + name);

return "index";

 

}

 

}

  - web.xml 설정 : spring dispatcher를 설정한다 

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

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"

xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"

id="WebApp_ID" version="3.0">

<display-name>Counter Web Application</display-name>


<servlet>

<servlet-name>mvc-dispatcher</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<load-on-startup>1</load-on-startup>

</servlet>


<servlet-mapping>

<servlet-name>mvc-dispatcher</servlet-name>

<url-pattern>/</url-pattern>

</servlet-mapping>


<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>/WEB-INF/mvc-dispatcher-servlet.xml</param-value>

</context-param>


<listener>

<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

</listener>

</web-app>

  - mvc-dispathcer-servlet.xml 스프링 환경설정 

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:context="http://www.springframework.org/schema/context"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="

        http://www.springframework.org/schema/beans     

        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

        http://www.springframework.org/schema/context 

        http://www.springframework.org/schema/context/spring-context-3.0.xsd">

 

<context:component-scan base-package="com.lge.*" />

 

<bean

class="org.springframework.web.servlet.view.InternalResourceViewResolver">

<property name="prefix">

<value>/WEB-INF/pages/</value>

</property>

<property name="suffix">

<value>.jsp</value>

</property>

</bean>

 

</beans>

  - WEB-INF/pages폴더를 만들고 index.jsp 파일 생성 

<html>

<body>

<h2>Hello World!</h2>

 

<h4>Message : ${message}</h1>

</body>

</html>

  - 전체 설정 정보 

    

  - Tomcat v7.0 설정이 되었다면 Run As 에서 "Run on Server"로 DashboardTest 컨텍스트 추가하여 Tomcat 기동

    /<contextName>/welcome/<value> 넣어서 호출하면 하기와 같이 나온다 

    

  - maven Goal에 대한 명령을 수행할 경우 

Goal은 ant의 target과 같은 개념으로, mvn의 경우 pre-defined goal을 가지고 있다.

mvn compile : 컴파일 수행

mvn package : jar 파일로 패키징

mvn test : JUnit 테스트 수행

mvn install : local repository (PC내의 디렉토리)에 해당 jar 파일을 저장 

mvn deploy  : remote repository (Nexus)에 jar 파일 저장

mvn clean : 컴파일 내용 모두 삭제

mvn build : 위의 예제는 일반적은 spring-java 코드로 build goal을 제공하지 않는다. 

(Eclipse에서 run as하면 나와서 헷갈리게 만드는데) build goal을 지원하는 프로젝트를 만들거나 또는 pom.xml안에 인위적으로 build goal을 지정해줘야 한다. 보통 clean install 등 여러 명령을 스페이스로 구분하여 파라미터를 줄 수 있다 

  - 만들어진 pom.xml 파일 

<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>

</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>

</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>



4. Spring 에 대한 Enterprise 환경 설정 원칙 

  - Enterprise Spring Best Practices

  - Bean Post Processors를 등록하는 어노테이션

RequiredAnnotationBeanPostProcessor – (@Required)

CommonAnnotationBeanPostProcessor – (@PostConstruct, @PreDestroy, @Resource, etc.)

AutowiredAnnotationBeanPostProcessor – (@Autowired, @Value, @Inject, etc.)

PersistenceAnnotationBeanPostProcessor – (@PersistenceUnit, @PersistenceContext, etc.)

  - annotation-config 지시자 설정

<context:annotation-config/>

  - 애플리케이션 컴포넌트 scanning 지시자 설정

<context:component-scan base-package="com.gordondickens.enterprisespring" use-default-filters="false">

<context:exclude-filter 

   expression="org.springframework.stereotype.Controller"

   type="annotation"/>

</context:component-scan>

  - 스캐닝 원칙 

+ Component scanning should ONLY be configured in the bootstrap config file, not in every XML config file

+ Do NOT also include the <context:annotation-config/> directive, it is automatically included by component scan

+ Do NOT start scanning from “com” and/or “org”, as this will scan ALL sub packages in all of the project and jars for candidates!

+ Be as specific as possible with the packages

+ Do NOT cross application boundaries with component-scan

  + Create an applicationContext-services.xml for scanning services

  + Create an applicationContext-persistence.xml for persistence and entity beans

  + Create an applicationContext-webmvc.xml for persistence and entity beans

  + Create an applicationContext-webservice.xml for web service beans

  + Import these references into the applicationContext-bootstrap.xml to these elements


Why separate the discovery files into layer specific configuration?

  + Unit testing is easier as discovery of beans is more specific

  + Allows the project to be separated into multiple Jar/Wars

  + Lowers risk of widely scoped discovery issues, overscanning and beans being replaced by multiple scanners



<참조>

  - Eclipse Theme 사용방법

  - STS + myBatis + RESTful 설정 및 테스트

  - Eclipse Juno기반 Spring MVC with maven으로 만드는 과정

  - Maven Console 기반 webapp RESTful 서비스 만드는 과정

  - AngularJS + SpringMVC 샘플 프로젝트

  - Eclipse에서 Maven 개발환경 설정

  - Spring에서 Enterprise 환경 설정 원칙

저작자 표시 비영리 변경 금지
신고
posted by peter yun 윤영식
prev 1 next

티스토리 툴바