Flutter

1.   Flutter란?

Flutter는 Google에서 만든 Cross-Platform Framework입니다. 하나의 코드로 Android / IOS, Desktop App까지 개발할 수 있습니다. 언어는 구글이 개발한 Dart를 사용합니다.

 

2.   Widget

Flutter에서는 모든 것이 Widget으로 시작합니다. Image, Icon, Text, Row, Column, Padding도 모두 Widget입니다. Flutter는 크게 Material Widget과 Cupertino Widget을 자체적으로 제공하기 때문에 look&feel, 빠른 속도, 커스터마이즈, 확장 가능성 등을 지원받습니다.

 

Flutter은 Widget과 Renderer들을 플랫폼 영역에서 앱의 영역으로 옮겨서 위젯들의 커스터마이징이 가능해졌고 확장 가능해졌습니다. 플랫폼의 캔버스를 통해 디바이스 스크린에 위젯을 그리고 이벤트들(터치, 타이머 등)과 서비스(위치, 카메라) 등에 접근하게 됩니다.

Dart 프로그램(초록색) 영역과 네이티브 플랫폼 코드(파란색) 사이에는 인코딩과 디코딩을 담당하는 인터페이스가 여전히 존재합니다. 그러나 JavaScript 브릿지와 비교했을 때 비교할 수 없을 정도로 빠른 성능을 가지고 있습니다.

 

 

Flutter을 이용해서 간단한 위젯 트리를 만들었습니다.

이 코드에서 레이아웃 포함 모든 것은 위젯입니다. Center 위젯은 위젯의 부모(위의 예제에서는 스크린)의 중앙에 위젯의 자식 위젯들을 위치시킵니다. Column 위젯은 이 위젯의 자식 위젯들을 수직으로 나열시킵니다. 이 Column은 Text 위젯과 Icon 위젯(색 속성을 가지는)을 가지고 있습니다. Flutter는 Column뿐 아니라 Row, Grid, List 등 꽤나 많은 위젯을 포함하고 있습니다. 게다가, Flutter는 Silver Layout Model이라고 칭하는 스크롤링을 위해 사용되는 고유한 레이아웃 모델을 가지고 있습니다. 스크롤링은 반드시 즉각적이면서도 부드럽게 반응해야만 사용자들이 물리적인 화면을 드래그했을 때, 손끝에 이미지가 붙어있는 듯한 느낌을 줍니다. Flutter의 레이아웃은 굉장히 빨라서 바로 이것을 해낼 수 있습니다.

 

4.   Dart 프로그래밍 언어

 Flutter는 새로운 프레임마다 새로운 뷰 트리를 구축합니다. 하나의 프레임을 위해 많은 수의 객체들을 생성하게 됩니다. 

Dart는 이러한 시스템에서 굉장히 효율적인 “generational garbage collection”을 사용하고 있습니다. 이 정책에서는 객체들 (특히 짧은 시간 동안 살아있는 객체들)의 비용이 매우 쌉니다. 또한 Dart는 앱에서 필요한 코드만을 포함시키는 “tree shaking”라는 컴파일러를 가지고 있어서 아주 큰 위젯 라이브러리에서 한 개 혹은 두 개만을 사용하더라도 부담 없이 사용하시면 됩니다.

 

5.   StatelessWidget과 statefulWidget

Flutter의 Widget 은 StatelessWidget과 StatefulWidget를 상속받아 만들 수 있습니다. Widget 은 Build 메서드를 포함하며, 이 Build 메서드를 이용해서 Layer Tree를 만듭니다. StatelessWidget 은 단 한번만 Build 과정이 일어나기 때문에 한번 그려진 화면은 계속 유지되며, 성능이 좋습니다. StatefulWidget 은 state를 포함하며, setState 가 발생할 때마다 다시 Build 과정이 일어나기 때문에 동적 화면을 쉽게 구현이 가능합니다.

 

6.   적합성

Flutter로 만든 앱은 오래된 os뿐 아니라 최신 os에서도 동일하게 동작합니다. 더불어, 미래의 os버전에서도 동일하게 동작할 것입니다. 위젯은 앱의 일부이기 때문에 플랫폼의 업데이트로 인해 앱이 갑자기 이상하게 보이거나 하는 문제가 발생하지 않습니다.

 

 

New Multi-Channel Dynamic CMS

D3.js를 이용하여 웹차트 만들기2 - 파이그래프

 

웹차트 만들기 1편에 이어 응용 편으로 파이그래프를 그려보도록 하겠습니다~!

 

파이그래프 그리기

먼저, 파이그래프의 전체적 윤곽을 잡는 코드와 설명입니다.

[line6]

D3 라이브러리를 사용하기 위해서 해당 스크립트 링크 주소를 넣어줍니다.

[line11 ~ 15]

파이차트를 그려내기 위해서 고정적으로 사용할 변수들을 선언합니다.

- 파이차트를 그려내기 위한 바깥쪽반지름(outerRadius), 안쪽반지름(innerRadius)

- d3.scale.category20() 으로 d3 표준색상을 지정합니다.

 

[line17 ~ 22]

파이차트에서 사용할 데이터를 dataset변수에 할당합니다.

 

[line24 ~ 30]

해당라인의 코드는 아래와 같은 구조로 후에 그리게 될 파이차트를 감싸는 태그를 만듭니다.

select(“#pie”)로 id값이 pie인 요소를 선택하고 [line24]

 

그 자식으로 <svg width=450, height=450></svg>요소를 추가합니다. [line25~27]
(.append()함수로 가장 끝에 자식 요소를 추가 하고 .attr()함수로 속성값을 지정할 수 있습니다.)

 

 그 다음 svg 자식으로 <g transform=”translate(225,225)”></g>요소를 추가합니다. [line28~29]
(.attr("transform", "translate(" + x축이동거리 + "," + y축이동거리 + ")")를 이용해 해당 요소를 이동시킬 수 있습니다.)

 

마지막으로 사용할 데이터를 data()함수를 이용해 바인딩 합니다. [line30]

[line32]

후에 그려 낼 파이차트의 속성값을 정의 합니다.

.outerRadius(바깥쪽 지름), .innerRadius(안쪽지름) 속성을 정의해야 파이차트를 그릴 수 있습니다!
(예를 들면, .innerRadius(0).outerRadius(50)의 경우 100x100의 원 그래프가 생성됩니다.)

 

 [line34]

파이차트의 특별한 형식에 맞는 값으로 추출 할 수 있는 구조를 만들어줍니다. 즉, 추후에 d.value라는 데이터 값을 가져와 파이차트의 조각들을 채울 것입니다.

다음은 실질적으로 파이차트를 그려내는 코드와 설명입니다.

 

[line36 ~ 40]

해당 라인의 코드는 아래와 같은 구조를 만들어냅니다.

변수vis 저장된 태그 내(<g transform=”translate(225,225)”></g>) 에서 모든 <g class=”slice”></g> 요소를 선택하고 [line36]

 

변수pie 데이터 구조에 맞게 데이터를 바인딩 합니다. [line37]
(변수vis 에서 dataset이라는 데이터를 바인딩 한 상태임)

 

가져온 데이터 개수만큼 enter()로 실행해서 도형을 생성할 수 있게 되며 [line38]

 

append함수를 이용해 <g class=”slice”></g>요소들을 만들어 냅니다. [line39~40]

 

[line42 ~ 44]

각 <g class=”slice”></g>요소 자식으로 <path></path>를 추가 한 다음 “d”속성을 이용해 파이차트의 path를 자동으로 그려주고 “fill”속성을 이용해 파이차트의 색상을 칠해줍니다.

 

[line46 ~ 55]

그려진 파이차트 안에 데이터 값을 할당하고 위치 값을 설정해 주는 라인입니다.

function(d)를 사용해서 파이그래프에 나타날 데이터를 가져 올 수 있으며 [line50]

나머지 라인은 <text></text>를 추가하고[line46] 위치를 잡아주는 속성값들 입니다.

 

[line57 ~ 61]

파이그래프 정중앙에 “주식”이라는 텍스트를 추가하는 코드입니다.

파이그래프 그리기는 여기서 마치고 기회가 된다면 다음 편에 이어서 또 다른 차트를 소개하도록 하겠습니다~~

 


D3.js 시리즈 1편에 대한 내용을 살펴보고 싶다면, 아래 링크를 클릭해주세요.

▶ D3.js를 이용하여 웹차트 만들기1 – 개념 및 예제


 

New Multi-Channel Dynamic CMS

D3.js를 이용하여 웹차트 만들기1 – 개념 및 예제

D3.js란?

안녕하세요. 자바스크립트 라이브러리 중 하나인 D3.js에 대해서 소개해드리려고 합니다.

D3.js란 자바스크립트 라이브러리를 처음 들어보신 분도 또는 자주 사용해보셨던 분들도 계시겠지만, 이번에는 처음 접해보시는 분들을 위해 조금 자세하고 쉽게 설명해드리도록 하겠습니다.

(이미 자주 사용하시고 저보다 더 많은 지식을 알고 계시는 분들에게 미리 양해 말씀드립니다^^)

우선 D3는(D3.js를 줄여서 D3라고 함) 완전히 오픈소스이며, Data-Driven-Documents의 약자로 D가 연속으로 3번 들어가서 일명 D3라는 명칭으로 부르며, 웹브라우저상에서 동적이고 인터렉티브한 정보시각화를 구현하기 위한 자바스크립트 라이브러리입니다. D3의 장점은 새로운 언어 형태가 아니라 자바스크립트 문법을 취하고 있으며, 웹 표준인 HTML, SVG, CSS에서 파생되었기 때문에 쉽게 접근이 가능하며, 각종 차트에 필요한 기능들을 함수 단위로 제공해준다는 점입니다. 무엇보다 좋은 점은 D3는 브라우저 내장 요소 검사기를 활용해서 쉽게 디버깅을 할 수 있다는 것입니다.

또한 D3는 2012년 8월에 2 버전이 출시되었고 2016년에 4버전, 현재는 5버전이 출시된 상태이지만 현재 공식 사이트( https://d3js.org )를 보면 대부분의 차트들이 3버전으로 되어있다는 걸 확인하실 수 있습니다.

더 자세한 내용이 궁금하시다면 D3 공식 사이트( https://d3js.org )를 참고 바랍니다.

이제 D3가 어떤 라이브러리 인지 대충 감이 오시죠??

어떤 라이브러리인지 감이 오셨다면 이제 한번 사용해보도록 하겠습니다.

 

D3 환경 세팅

D3 환경 세팅을 하는 방법에는 두 가지가 있습니다.

첫 번째는, 공식사이트( https://d3js.org/ )에서 D3를 다운받아 폴더에 직접 넣는 방법이 있습니다.

두 번째는, 아래와 같이 CDN방식으로 D3를 사용하는 방법이 있습니다.

두 가지 방법 중 편한 방법을 선택해서 사용하게 되면 D3를 사용할 수 있는 기본적인 환경 세팅이 완료되며 두 번째 방법인 CDN 방식은 네트워크 연결이 끊기게 되면 D3를 사용할 수 없기 때문에 첫 번째 방법을 조심스럽게 추천해 드립니다.

앗… 한가지 중요한 걸 깜빡했네요…

우선 보여드리는 예제는 3버전으로 진행되며, D3는 보통 데이터 형식으로 JSON 형식을 많이 사용하게 되는데 이때 서버의 동작 없이 로컬환경에서 실행하게 된다면 자바스크립트의 보안정책으로 인해 크로스 브라우징 이슈 문제가 생겨 실행 되지 않습니다….

이점 참고하여 JSON 파일을 사용하게 된다면 node나 tomcat 등을 이용하여 실행하시길 권장 드립니다.

 

D3 기본 문법

D3를 사용하려면 기본 문법은 알고 있어야겟죠???

● d3.select – 특정 태그 하나를 선택한다.

● d3.selectAll – 특정 태그 전체를 선택한다.

● selection.attr – 선택 태그의 속성값 지정한다.

● selection.data – 집어넣을 즉 차트에 사용할 데이터를 가져온다.

● selection.enter – 데이터 개수만큼 태그가 부족할 시에 추가한다.(ex 데이터가 5개, p태그가 3개일시 데이터 개수에 맞게 p태그 2개를 더 추가한다.)

● selection.append – 새로운 태그를 추가한다.

위의 6가지 문법은 D3를 사용하는데 있어 가장 기본적인 문법이며 더 많은 D3문법은 다음 API사이트( https://github.com/zziuni/d3/wiki/API-Reference )를 참고바랍니다.

 

D3 기본 차트 그리기1

한눈에 이해가 가시나요?? 이해가 한 번에 되지 않을 수도 있기 때문에 라인별로 자세하게 설명해드리도록 하겠습니다.

10라인을 보시면 우선 높이와 넓이가 500인 SVG 태그를 하나 생성합니다.

11라인은 D3를 사용하기 위해 CDN 방식을 사용하였습니다.

13라인은 차트에 사용할 데이터를 배열 형태로 미리 만들어 놓습니다.

(간단한 예제이기 때문에 다음과 같이 배열 형태로 만들어 두었지만, 나중에 더 복잡한 차트를 만들기 위해서는 보통 데이터를 JSON 형식으로 만들어 사용하며 이 JSON 데이터를 parsing 해서 사용하게 됩니다.)

15라인은 d3의 문법 중 하나이며 SVG 라는 태그를 선택하라는 의미입니다.

17~22라인을 보시면 data 배열의 수 만큼 반복문을 실행하며, d3의 기본 문법을 이용하여 배열 안의 데이터 개수만큼 사각형 모양의 차트를 생성하게 됩니다. 자세히 보시면 jQuery처럼 d3가 기본적으로 체이닝 기법을 사용한다는걸 알 수 있겠죠?,

자 이제 코드를 작성했으니 결과물을 확인해 봐야겠죠??

D3의 기본 문법을 이용하여 기본적인 막대차트를 그려 보았습니다. 간단한 문법만으로 차트 생성이 되었죠??

결과물은 화면에 잘 출력이 되었지만, 혹시 여기서 의문점이 생기신 분들 계신가요?? 의문점이 생긴다면 D3의 기본 개념에 대해서 잘 이해하고 계신다는 겁니다. 위의 코드는 자바스크립트의 문법인 forEach 구문을 이용하여 도형을 생성했는데 이것은 D3의 장점이 아닙니다. D3는 데이터 기반으로, 쉽게 데이터를 처리하는 것이 목적이기 때문이죠.

그럼 D3의 장점을 살려서 코드를 다시 작성해볼까요??

 

D3 기본 차트 그리기2

위의 코드가 D3의 장점을 살려서 새롭게 작성한 코드입니다.

기존의 자바스크립트 코드인 반복문을 이용하지 않고 .data() 안에 사용할 데이터를 넣어 지정하고 각 데이터별로 enter()를 실행해서 도형을 생성하게 됩니다. 또한, 데이터를 가가져오는 방식을 배열로 지정할 수도 있고 위의 코드와 같이 함수의 파라미터로 넘어오는 값을 사용할 수도 있습니다.

 

D3 기본 차트 그리기3(디자인)

그럼 다음은 좀 더 발전된 차트를 그려 보기 전에 차트에 디자인을 좀 해줘야겠죠??

어느 부분이 달라졌는지 파악하셨나요??

14라인에 color라는 새로운 배열이 생겼고, 22라인에 fill이라는 새로운 것이 생겼습니다.

무슨 기능인지 알아볼까요??

우선 D3의 fill은 CSS의 background-color와 같은 기능을 합니다. 또한 선, 즉 테두리의 스타일을 입히고 싶은 경우에는 stroke를 사용하게 됩니다.

이외에도 D3에서 사용하는 고유 CSS 문법이 있지만 자세한 건 D3 공식문서나 github에 방문하셔서 확인해 보시는걸 권장해 드립니다.

다시 코드로 돌아가서 22번째 라인에서 rect 차트에 color 배열 안에 있는 색상을 적용합니다. 디자인은 CSS 파일을 따로 작성하여 연결하는 게 가장 좋은 방법이지만 D3에서는 다음과 같이 인라인 스타일로 직접 넣어주는 방식으로도 사용이 가능하다는 걸 보여드리기 위해 다음과 같은 방법을 사용하였습니다.

자 이제 결과물을 볼까요?????

이전 차트보다 좀 더 화려해졌죠???

그럼 다음 글에서는 D3로 또 다른 형태의 차트를 구현해 보는 시간을 갖도록 하겠습니다.

 


D3.js에 대한 더 자세한 내용을 살펴보고 싶다면, 아래 링크를 클릭해주세요.

▶ D3.js를 이용하여 웹차트 만들기2 - 파이그래프


 

 

사이버이메지네이션 HTML5 CHART webPonent CHART

New Multi-Channel Dynamic CMS

Java Web Application 에서의 Angular 개발환경 구성 및 배포

약 1년 전쯤 사내 인트라넷의 모바일 웹을 만드는 프로젝트를 진행하였습니다.

당시 기존의 소스를 그대로 활용하여 모바일 화면에 맞춘 웹 퍼블리싱만 작업하면 쉽게 포팅이 가능하였지만, 개인적으로 Angular 를 학습해보고 싶었던 때였습니다. 그래서 Angular를 사용하여 만들기를 건의하여 컨펌받고 작업에 착수하였습니다.

지금까지 Java, JSP를 다뤄오고 JavaScript라고 해봤자 ES5 기반의 문법과 jQuery 라이브러리 정도만 사용하다가 처음으로 Node.js, Angular, ES6, TypeScript 등의 JavaScript 신문물들을 접하게 되니 대체 어떤 것부터 진행해야 할지 막막했었지만 필요한 부분들을 서적과 가이드 문서를 참고하여 차근차근 학습해가다 보니 결국 완성하긴 했습니다.

Angular 프로젝트를 시작하면서 이것을 어떻게 서버에 연결하고, 백엔드 작업을 하고, 배포를 해야 할지가 초기의 가장 큰 난관이었습니다. 처음 Angular 프로젝트를 생성할 때 Node.js 를 통해 설치가 이뤄지는 것을 보고 “그럼 Node.js로 서버를 구성할 수 있다고 했으니, Node.js로 DB에도 연결하고 백엔드 처리도 해야 하는 것인가?” 라고 생각을 하였지만, 그렇게 되면 사내 서버에 Node.js를 설치해야 하고 모바일 전용 서버도 새로 구성, 기존 컨트롤러들도 다시 구현해야 하는 상황이 발생하여 기존에 사용 중인 서버와 그에 연결된 인트라넷 프로젝트를 그대로 활용하여 작업해볼 생각을 하게 되었습니다.

다행히 Angular 에서의 proxy 설정을 통해 angular-cli 커맨드 셋의 ng serve 로 로컬 환경에서 인트라넷과 연결이 가능했고, 배포 시에도 ng build 의 base-href 속성으로 문제없이 배포가 가능했습니다.

 

1. 로컬 서버 구성

사내 인트라넷은 현재 Java 1.6, 사내 프레임워크 coreFrame, Apache Tomcat 과 MySQL 을 사용하고 있습니다.

사실 Angular와 기존 웹 프로젝트의 연결은 Angular 프로젝트 build 설정과 기존 웹 프로젝트에서의 빌드 된 Angular 프로젝트의 위치만 맞으면 동작합니다.

중요한 것은 기존의 웹 프로젝트를 Angular 는 HttpClient를 사용하여 마치 API 서버처럼 활용할 것이기 때문에 기존 프로젝트에서 특정 URL로 호출 시 반환되는 데이터를 JSON으로 맞춰주는 것입니다.

 

2. Angular 프로젝트 생성

프로젝트를 진행 할 때의 Angular 버전은 4 버전이었고 글을 작성하는 현재 Angular 7 이 발표된 상황입니다. 6개월 단위로 메이저 릴리즈가 실시될 예정이니 곧 Angular 8 이 등장하겠군요.

뭔가 버전업이 너무 빨라서 따라가기 힘들 듯 하지만 다행히 Angular 2 부터는 Angular Update Guide (https://update.angular.io/) 를 통해 손쉽게 업데이트가 가능하니 버전 업데이트에 대한 우려는 잠시 내려놓아도 될 듯합니다. (저도 별 문제 없이 최근에 사내 인트라넷 모바일 버전 업데이트를 진행했습니다. Angular 4 -> Angular 7)

Angular 프로젝트의 생성은 매우 간단합니다.

 (1) \o\ac(○,1)1 Node.js 설치 (https://nodejs.org) - 10.15.3 LTS

 (2) \o\ac(○,2)2 Angular CLI 전역 설치

(3) 원하는 위치에 Angular 프로젝트 생성

보통 라우팅 기능을 사용하기 때문에 y 선택 (Angular 7 에서 추가된 선택지라 Y/N 선택에 따른 차이를 확인해 보니 기본적으로 라우팅 설정이 되어있느냐 마냐의 차이일 뿐이었습니다. 마치 웹 프로젝트 새로 생성할 때 web.xml 생성 여부와 비슷한…)

지금까지 Angular 에서 주로 SCSS 를 사용했기에 SCSS로 선택

짜잔! Angular 프로젝트 생성이 완료되었습니다.

생성된 프로젝트 폴더로 이동하여 ng serve 를 입력하면 간편하게 Angular CLI 개발 서버가 생성되고 기본적으로 지정되어있는 http://localhost:4200 으로 접속이 가능합니다.

 

3. Angular 프로젝트와 로컬 서버 연결

Java Web 프로젝트와 Angular 프로젝트가 구성되었으니 이제 Angular 에서 Java Web 프로젝트를 연결해볼 차례입니다. Java Web 프로젝트의 로컬 주소는 http://localhost:8080 이고, Angular 프로젝트의 로컬 주소는 http://localhost:4200 입니다.

여기서 연결이란 것이 뭐 대단한 것은 아닙니다. Angular 프로젝트에서 Java Web 프로젝트의 특정 URL 과 통신할 때 Java Web 프로젝트로 연결된다고 생각하시면 됩니다.

예를 들어, ‘/mobile/organization/notice/noticeController/getNotices.cmd’ 라는 URL을 호출할 때 ‘http://localhost:8080/mobile/organization/notice/noticeController/getNotices.cmd’ 이렇게 Java Web 프로젝트를 호출하게 만드는 것입니다.

그 역할에 대한 내용을 이제부터 작성할 proxy config 에 기술할 것입니다.

위와 같이 Angular 프로젝트 내부에 proxy.conf.json 파일을 생성하고 JSON 형태로 내용을 기술합니다.

그리고 Angular 프로젝트의 개발 서버를 다시 올려봅시다.

이번엔 ng serve 뒤에 proxy config 설정과 localhost 를 0.0.0.0 으로 지정하여 개발 서버를 생성하도록 했습니다. 이 설정은 URL 시작 부분이 ‘/mobile’, ‘/main/appr’ 일 경우 앞으로 아래의 그림과 같이 동작할 것입니다.

이해가 되셨나요?

앞으로 Angular 프로젝트의 개발 서버를 올릴 때 위 커맨드를 입력하기만 하면 됩니다. 음… 매번 저렇게 긴 커맨드를 입력하기 귀찮다고요? 그렇다면 방법이 있습니다.

이렇게 package.json 파일에서 scripts 에 속해있는 start 속성의 값으로 커맨드를 지정해 놓으면 됩니다. 그리고 서버를 생성할 때 위 커맨드 대신

이렇게 사용하면 간단하게 해결됩니다.

참고로 이 내용은 로컬 작업 환경에서만 적용되는 것입니다. 배포 시엔 적용이 되지 않거든요. 배포 했을 때도 Angular 프로젝트를 개발하며 사용할 ‘/mobile’, ‘/main/appr’ 으로 시작된 URL 들이 Java Web 프로젝트를 향하게 하는 것은 곧 설명하겠습니다.

 

4. HttpClientModule 을 사용하여 호출 해보기

이제 proxy 설정 한대로 잘 호출 되는지 확인해볼 차례입니다. Angular 에선 HttpClientModule 을 사용하여 HTTP 요청을 주고받습니다. 테스트 예로 공지사항의 리스트를 받아와 보겠습니다. 우선 Angular 프로젝트에 notice 라는 컴포넌트를 추가해보겠습니다.

콘솔에서 notice 컴포넌트를 생성할 폴더로 이동한 다음에 ng generate component notice 를 입력하면 Angular-CLI 가 자동으로 컴포넌트에 필요한 요소들을 생성해줍니다.

이렇게 말이죠. 

컴포넌트가 생성되었으면 가장 상위의 app 모듈에서 notice 컴포넌트를 열 수 있도록 라우팅 설정을 해줍니다. 이미 Angular 프로젝트를 생성할 때 라우팅 초기 설정을 자동으로 되도록 했고, Angular-CLI 를 통해 컴포넌트를 생성했기 때문에 app 모듈에는 라우팅 관련 설정이 되어있어서 추가할건 한 줄 밖에 없습니다.

app-routing.module.ts 에 notice 컴포넌트와 관련된 path 와 component 를 추가해줍니다. 라우팅 설정도 끝이 났으니 이제 notice 컴포넌트를 작성할 차례입니다.

Angular 에서 원하는 개발 방식은 객체의 모델, 서비스를 분리하여 관리하는 것이지만 이건 테스트 예제라서 notice 컴포넌트에 모두 넣어두었습니다. HttpClientModule 에 속해있는 HttpClient 클래스를 사용하니 app 모듈에 HttpClientModule 을 import 해주는 작업도 해주어야 합니다.

마지막으로 공지사항 리스트 데이터를 삽입할 table과 css를 추가하면 테스트 할 준비가 완료됩니다.

모든 준비가 완료되었으니 라우터에서 설정한 주소대로 http://localhost:4200/notice 에 접속을 합니다.

공지사항에 사용될 리스트를 잘 가져왔습니다. 그러면 요청한 URL을 볼까요?

우측의 Request URL 을 확인해보면 host 가 Angular 프로젝트의 서버 주소로 되어있지만 proxy 설정을 통하여 실제론 Java 웹 서버에 요청하여 데이터를 가져왔습니다.

정말 간단하죠?

 

5. 기존 프로젝트에 Angular 프로젝트 배포

기존 프로젝트에 Angular 프로젝트를 배포해보겠습니다. Angular는 TypeScript 기반으로 개발이 되기 때문에 배포를 하려면 번들링을 해줘야 합니다. Angular-CLI 에 내장된 모듈 번들러인 webpack은 자동적으로 Angular 프로젝트가 의존하는 모듈들을 로드해주고 TypeScript에서 JavaScript 로의 트랜스파일링, 문법체크 등을 자동적으로 진행하여 번들링 해줍니다.

설정은 간단합니다. package.json 파일에서 build 스크립트에 base-href 속성을 내가 배포할 위치로 설정해주면 됩니다. 그럼 빌드 해볼까요?

콘솔에서 위 커맨드를 입력하면 package.json 에서 설정한 build 스크립트가 실행됩니다. 빌드가 완료되면 Angular 프로젝트 내에 dist 폴더가 생성되고 그 안에 번들링 된 프로젝트 내용물들이 생성됩니다.

생성된 파일들을 build 스크립트에서 지정한 기존 프로젝트 내의 경로에 배포해주면 끝입니다.

http://localhost:8080/mobile/app/index.html 로 접속하여 작업한 결과물이 제대로 뜨는지 확인해주세요.

지금까지 기존 프로젝트 내에서 동작할 Angular 프로젝트를 간단한 예제로 프로젝트 생성, 로컬 개발환경 설정, 통신 테스트, 번들링 그리고 배포까지 알아보았습니다. Angular 에 대하여 더 관심이 있다면 이미 많은 서적과 https://angular.io/ 에서의 가이드 문서를 통해 계속 학습을 진행해보길 바랄게요.

 

New Multi-Channel Dynamic CMS

일렉트론 개발환경 구축하기

앞서 Electron 개념을 정리해보았는데요. 오늘은 Electron 개발환경 구축에 대해 정리해보았습니다. Electron 개념에 대한 내용은 아래 링크에서 확인해 볼 수 있습니다.

[Electron 개념정리 클릭] 

Electron 애플리케이션은 근본적으로 Node.js 애플리케이션이라고 할 수 있습니다. 그렇기에 사용자의 데스크톱에 Node.js 와 git이 기본적으로 설치되어 있다는 가정 하에 일렉트론 개발하기를 진행하겠습니다.

일렉트론 설치하기

간단한 프로젝트 생성을 위해 먼저 electron_test라는 폴더를 생성 후 깃헙을 통해 일렉트론 프로젝트를 생성합니다.

프로젝트가 생성이 된다면 생성된 프로젝트 디렉터리로 이동하여 의존성 설치와 애플리케이션 실행을 실행시켜 줍니다.

설치와 실행이 다 되었다면 폴더 구조를 한번 보겠습니다.

Node.js 모듈과 마찬가지로 시작점은 package.json 입니다. 기본적으로 Electron 애플리케이션 대부분은 다음과 같은 폴더 구조를 가지고 있습니다: 여기서 주요하게 봐야 될 파일들은 package.json, main.js, index.html 파일들 입니다.

먼저 package.json파일을 보겠습니다. npm은 기본적인 package.json 파일을 생성하여 정보를 제공하는데 main필드에 명시된 스크립트는 애플리케이션을 시작하는 스크립트이며, 메인 프로세스에서 실행됩니다. start 스크립트를 electron 런타임으로 변경함으로써 npm을 시작할 때 일렉트론으로 실행되게 됩니다.

일렉트론 애플리케이션은 자바스크립트로 개발하며, Node.js를 개발할 때 사용하던 개념과 메소드를 그대로 이용해서 애플리케이션을 개발할 수 있습니다. Electron의 모든 API와 기능은 electron 모듈을 통해 접근할 수 있습니다. 

main.js파일입니다. main.js파일은 브라우저 창을 생성하고 애플리케이션에서 발생한 모든 시스템 이벤트를 처리합니다.

app객체는 메인 프로세스에서 사용되는 애플리케이션의 이벤트 생명 주기를 제어하는 객체입니다. 일렉트론을 실행하면 최초로 구형되는 클래스이며 일렉트론이 초기화를 끝냈을 때 발생하는 ‘ready’, 윈도우를 닫을 때 발생하는 ‘window-all-closed’, 애플리케이션이 활성화될 때 발생하는 ‘activate’등 여러 가지 윈도우 이벤트를 가지고 있습니다. 모든 창을 닫는 app.quit(), 현재 애플리케이션의 디렉터리 값을 반환하는 app.getAppPath(), 현재 애플리케이션의 이름값을 반환하는 app.getName등 현재 애플리케이션의 상태를 알아보는 메소드를 제공합니다.

BrowserWindow객체는 메인 프로세스에서 사용되는 클래스로서 브라우저 윈도우를 생성하고 제어하는 객체 입니다.

width와 height 값으로 새로운 윈도우를 생성했을 때의 사이즈 값을 설정 할 수 있고 loadFile() 으로 새로운 윈도우를 생성했을 때 보여질 html파일을 설정할 수 있습니다.

 

 

마지막으로 index.html 파일은 보여주고 싶은 웹 페이지에 해당합니다.

일반적으로 우리가 이미 알고 있는 html파일이며 <script>로 js파일을 require함으로써 불러온 js파일로부터 렌더러 프로세스를 이용할 수 있습니다.

개발하기

지금까지 설치한 일렉트론을 이용하여 간단한 예제를 만들어 보겠습니다. 데스크톱 어플리케이션인 만큼 윈도우 운영체제라는 기반 하에 윈도우 알림을 띄우는 예제를 만들어 보겠습니다.

제가 윈도우 알림을 띄우기 위해 사용 할 모듈은 node-notifier 모듈 입니다. 운영체제에 알림을 띄우기 위한 모듈들은 여러 가지 있지만 각자 사용할 OS에 맞춰 선택하시면 됩니다. 저는 Mac, Windows, Linux 모두 호환이 되는 node-notifier을 이용하여 진행하겠습니다.

먼저 node-notifier사용을 위해선 npm을 통한 node-notifier모듈 설치가 필요합니다.

설치가 되었다면 모듈을 불러옴으로써 사용할 수 있습니다.

 

node-notifier을 사용할 때 title에는 알림의 제목을, message에는 설명, icon 에는 이미지파일을 지정함으로써 윈도우 알림을 사용자의 데스크톱에 설정할 수 있습니다.

 

 

 

 

 

배포하기

일렉트론을 배포하기 위해 npm을 통한 electron-builder모듈을 설치해 줌으로써 애플리케이션을 일일이 수동으로 패키지로 만드는 대신 작업을 자동화 시킬 수 있습니다.

electron-builder모듈을 설치하고 나선 package.json파일의 script 하위항목에 다음과 같은 옵션을 추가해야 합니다 이 옵션들은 커맨드 창에서 입력할 수 있는 명령어를 script 하위항목에 추가함으로써 추후에 build 실행을 간소화 해줍니다.

Package.json의 최상위 항목에는 다음과 같은 내용을 추가합니다.

Build 후 결과물의 저장경로를 지정해주는 명령어와 소스코드를 asar 아카이브로 압축하는 패키징 옵션들입니다. asar 아카이브를 통해 애플리케이션의 소스코드가 사용자에게 노출되는 것을 방지할 수 있습니다.

 

 

 

 

 

 

 

 

 

설정을 끝마친 뒤 npm에서 script에서 설정해준 build:win을 실행합니다.

여기까지 완성하고 난다면 dist폴더안에 배포를 위한 .exe 파일이 생김으로써 배포를 위한 준비가 끝났습니다.

참고 사이트

https://electronjs.org

http://blog.dramancompany.com/2015/12/electron%EC%9C%BC%EB%A1%9C-%EC%9B%B9-%EC%95%B1-%EB%A7%8C%EB%93%A4%EB%93%AF-%EB%8D%B0%EC%8A%A4%ED%81%AC%ED%86%B1-%EC%95%B1-%EB%A7%8C%EB%93%A4%EA%B8%B0/

https://ourcodeworld.com/articles/read/204/using-native-desktop-notification-with-electron-framework

https://suwoni-codelab.com/electron/2017/04/17/Electron-distribution/   

New Multi-Channel Dynamic CMS

Electron 개념정리

안녕하세요 데스크톱 애플리케이션을 개발하기 위해 공부한 일렉트론이라는 프레임 워크에 대해서 정리해 보겠습니다.

Electron 2013 GitHub이 텍스트 편집기인 Atom을 만들기 위해 개발한 프레임 워크입니다. 2014 Atom Electron이 오픈 소스로 공개가 되면서지금은 많은 개발자들과, 회사들에게 사랑 받는 인기 있는 도구가 되었습니다

기본적인 웹 지식만 있다면 누구든 데스크톱 애플리케이션 개발자로 만들어주는 일렉트론에 대해서 지금부터 설명하겠습니다.

1. 일렉트론(Electron)이란?

ElectronNative애플리케이션이며 Chromium Node.js1개의 런타임으로 통합하여 JavaScript, HTML, CSS만 가지고도 데스크톱 애플리케이션을 만들 수 있도록 해주는 편리한 프레임 워크입니다.

Native어플리케이션이란? 특정한 플랫폼이나 디바이스를 이용하기 위해 개발된 소프트웨어 프로그램으로서, 우리가 흔히 말하는 어플리케이션을 의미 합니다.

2. 일렉트론 특징

크로스 플랫폼

ElectronHTML, CSS, 자바스크립트를 사용해 Window, Mac OS, Linux 3개의 운영제제와 호환이 되는 크로스 플랫폼 데스크톱 어플리케이션을 만들기 위해 GitHub에서 개발한 오픈 소스 라이브러리입니다, Electron으로 작성된 애플리케이션을 Window, Mac OS, Linux 용으로 패키지 함으로써 크로스플랫폼 애플리케이션이 탄생하게 되는 원리입니다.

오픈 소스

ElectronCheng Zhao가 개발한 오픈 소스 프레임 워크로(https://electronjs.org/apps) 에서 일렉트론으로 개발된 다양한 프로그램들의 오픈 소스 프로젝트들을 확인 할 수 있습니다.

웹 기술

데스크톱 애플리케이션이라면 왠지 일반적인 웹 개발과는 다를 것 같다는 편견을 바꿔준 일렉트론은 Node.js기반의 프레임워크라 일반적인 Node.js의 개념만 알아도 데스크톱 어플리케이션을 손쉽게 뚝딱 만들 수 있도록 도와줍니다이런 일렉트론은 만약 웹 사이트를 구축 할 수 있는 개발자라면 데스크톱 애플리케이션 또한 쉽게 만들 수 있도록 도와주는 프레임 워크이며, 만약 데스크톱 애플리케이션을 개발을 계획하고 있다면 한번쯤은 고려해 볼 만한 Native 애플리케이션이라 추천하고 싶은 프레임 워크 입니다.

3. 일렉트론으로 만들어진 애플리케이션 

현재 많은 회사들이 일렉트론을 사용하고 있으며 그 중에는 우리가 이름만 들어도 알만한 유명한 어플리케이션들이 많습니다. 이 중 Atom Skype, Visual Studio Code는 개발자라면 한번은 들어 봤을 만한 익숙한 프로그램으로서, Electron이 얼마나 유용한 프레임 워크인지 알 수 있습니다.

그림 참조: https://electronjs.org/

4. 일렉트론 구조

일렉트론에서 가장 중요한 개념인 MainRenderer 프로세스에 대하여 설명하겠습니다.

Main Process

Main Process Node.js기반으로 Node.js에서 사용되는 모든 모듈들을 사용 할 수 있으며, back-end의 영역입니다. package.json 파일의 main 스크립트를 실행하는 프로세스를 메인 프로세스라고 부르며, 메인 프로세스에서 실행되는 스크립트는 웹 페이지들을 GUI로 표시합니다. Electron 애플리케이션은 항상 하나의 메인 프로세스를 가지고 있으며 렌더러 프로세스는 여러 개가 존재 할 수 있지만 메인 프로세스는 애플리케이션 당 항상 하나만 존재 할 수 있습니다.

Renderer Process.

Renderer Process HTML, CSS, JavaScript로 이루어지는 영역이며 front-end 영역입니다. Electron은 웹 페이지를 보여주기 위해 Chromium을 사용하고 있기 때문에 Chromium의 멀티 프로세스 아키텍처가 그대로 이용되고 있습니다. Electron 안에서 보여지는 각각의 웹 페이지는 자신의 프로세스 안에서 동작하는데, 이 프로세스를 renderer 프로세스라고 부릅니다.

Main Process Renderer Process의 차이점.

메인 프로세스는 BrowserWindow 클래스를 생성하여 웹 페이지를 만듭니다일렉트론에서의 Window는 이 BrowserWindow클래스로 만들어 지며 이 BrowserWindow는 메인 프로세스에서만 사용 할 수 있습니다.

각각의 BrowserWindow 인스턴스는 자체 renderer 프로세스를 호출하여 웹 페이지를 실행합니다. BrowserWindow 인스턴스가 소멸하면 해당 renderer 프로세스도 종료됩니다.

즉 메인 프로세스는 모든 웹 페이지와 각 페이지들이 소유한 renderer 프로세스들을 관리하는 관리자 역할이며, 각각의 renderer 프로세서는 서로 독립적으로 동작하고 그들이 실행된 웹 페이지 내에서만 관여를 합니다.

, 일렉트론은 메인 프로세스와 renderer 프로세스에서 사용할 수 있는 api들을 제공하는데 이 api들은 프로세스 타입에 따라 사용됩니다 대부분의 api는 메인 프로세스에서만 사용할 수 있고, 일부는 renderer 프로세스에서만, 또 일부는 양쪽 모두에서 사용할 수 있습니다. 각 프로세스에 대한개별 api에 대한 문서는 일렉트론 공식홈페이지(https://electronjs.org/docs) 에서 확인 할 수 있습니다.

프로세스간의 통신은?

일렉트론은 메인 프로세스와 렌더러 프로세스의 통신으로 이루어진 프레임워크입니다. 메인 프로세스와 렌더러 프로세스의 통신에는 몇 가지의 방법이 사용되며, IPC모듈과 remote 모듈에 의해서 통신이 이루어 집니다.

IPC모듈은 ipcMain ipcRenderer이 있으며 ipcMain은 메인 프로세스에서 renderer 프로세스들로의 비동기적 통신을, ipcRenderer은 렌더러 프로세스에서 메인 프로세스로의 비동기적 통신을 담당합니다.

ipcMain에서는 데이터를 받을 때 ipcMain.on(channel, listener)함수를 사용하여 channel을 수신하고, 새로운 메시지가 도착하면 listener listener(event, arg)으로 호출됩니다. 받은 비동기 메시지에 응답하기 위해선 event.sender.send()를 호출 할 수 있습니다.

ipcRenderer에서는 데이터를 전송할 때 ipcRenderer.send(channel,[arg])함수를 사용하여 channel을 통해 main프로세스에 비동기 메시지를 보내고 ipcRenderer.on (channel, listener) 함수를 사용하여 channel을 수신하고, 새로운 메시지가 도착하면 listenerlistener(event, arg)로 호출됩니다.

remote 모듈은 메인 프로세스에서만 사용 가능한 api들을 렌더러 프로세스에서 이용할 수 있게 해주는 모듈이며 렌더러 프로세스에서 BrowserWindow를 만들기 위해 remote 모듈을 중간자(middle-man)로써 사용하는 방법으로 BrowserWindow를 메인 프로세스가 아닌 렌더러 프로세스에서 생성 할 수 있습니다.

 

아래 링크를 통해 일렉트론 관련 글을 참고해보세요.

일렉트론 개발환경 구축하기

 

New Multi-Channel Dynamic CMS

Vue.js를 이용한 Todo리스트 예제 만들기(2)

 

Vue.js를 이용한 Todo리스트 예제 만들기(2)

 

저번 포스트에 이어서 카테고리, 검색어 입력을 통한 검색 기능/보기 방식 변경/정렬 방식 변경/완료한 항목 체크 기능을 구현하면서 사용한 vue 기능을 정리해보려고 합니다.

 

3. 카테고리, 검색어 입력을 통한 검색 기능

* 결과 화면

 

* html(form부분만)

<form class="s-form">
    <select v-model="option" class="form-group" v-on:change="filter">
        <option value="all">All</option>
        <option v-for="c in categories" v-bind:value="c">{{ c }}</option>
    </select>
    <input v-model="search" v-on:keyup="filter" type="text" placeholder="search..." class="form-group">
</form>

* js(데이터&사용한 함수 부분만)

var app = new Vue({
el: "#app",
data: {
option: 'all',
search: '',
categories: [],
todos: [],
viewType: 'list',
sortType: 'sort-numeric-up',
buttons: {
'view': [
{'class':'list', 'title':'view in list', selected:true},
{'class':'th-large', 'title':'view in thumbnail', selected:false}
],
'sort': [
{'class':'sort-numeric-up', 'title':'sort by deadline', selected:true},
{'class':'sort-alpha-down', 'title':'sort by alphabet', selected:false},
{'class':'star', 'title':'sort by stars', selected:false}
]
}
},
methods: {
filter: function(){
var isFiltered = false;
var todoObj = {};

for(var i = 0; i < this.todos.length; i++){
isFiltered = false; // init
todoObj = this.todos[i];

// -- filter by category
if(this.option !== 'all' && todoObj.category !== this.option){
isFiltered = true;
}

// -- filter by search keyword
if(!isFiltered && todoObj.title.toUpperCase().indexOf(this.search.toUpperCase()) == -1){
isFiltered = true;
}

if(isFiltered){
todoObj.filtered = "y";
} else {
todoObj.filtered = "n";
}
}

}

검색 조건이 복잡하지 않고, 항목이 많지 않아서 재조회를 하는 대신에 검색 조건에 맞지 않는 항목을 필터링해서 v-show 지시어를 사용해서 화면에서 잠시 감춰주는 방식으로 구현하였습니다. 검색 버튼을 별도로 두지 않고 select 태그에서 change 이벤트가 발생할 때, input 태그에서 keyup 이벤트가 발생할 때 바로 검색을 하도록 구현해보았습니다. filter라는 함수가 입력한 검색 조건에 맞지 않는 항목을 필터링하는 역할을 합니다.

3-1. v-model

v-model 지시어는 form 요소에 데이터 바인딩을 할 때 사용합니다. 위의 예제에서는 select 태그에 option 데이터를 바인딩하고, input 태그에 search 데이터를 바인딩하였습니다. 이렇게 하면 초기에 렌더링할 때 해당 데이터의 value가 각 태그의 value로 바인딩되는 것을 확인할 수 있습니다. 그 후 화면에서 select의 옵션을 바꾸거나 input 태그에 글자를 입력하면 데이터도 사용자가 선택/입력한 것으로 업데이트 됩니다.

v-model 지시어를 통해 form 요소에 사용자가 선택/입력한 value로 데이터를 업데이트 하면, filter라는 함수에서 이 데이터를 받아 기존 todo 리스트에서 카테고리 혹은 타이틀이 검색 조건과 맞지 않는 요소를 걸러내는 작업을 합니다.

3-2. Reactive Data

이 부분을 구현하다가 주의할 점 하나를 발견했습니다. 반응하는 데이터가 되기 위해서는 vue 인스턴스를 생성할 때부터 있는 데이터여야 한다는 점입니다. 쉽게 전달하기 위하여 제 경우를 들어 설명해보겠습니다. 맨 처음에 todo 리스트 아이템이 가지고 있던 속성은 다음과 같았습니다.

{
    "thumbnail": "/data/images/vegetables.jpg",
    "category": "shopping",
    "title": "Buying some healthy food",
    "startDate": "20180310152031",
    "deadline": "20180315180000",
    "endDate": "",
    "important": "3"
}

추후 검색어를 입력하거나 카테고리를 선택할 때 실행되는 filter 메소드에서 todo 리스트 아이템 객체에 filtered라는 속성을 추가해줄 생각이었습니다. 그래서 마크업에서는 리스트 항목이 보일 지 정하는 조건을     v-show="item.filtered!=='y'"라고 작성하였습니다. 그런데 이게 작동을 하지 않았습니다. 분명 콘솔에 찍어봤을 때는 객체가 filtered라는 속성을 갖고 있음에도 불구하고 작동하지 않았습니다. 왜냐면 처음에 데이터를 세팅할 당시에는 객체에 filtered라는 속성이 없었기 때문입니다. 결론적으로, 저는 샘플 데이터 json파일에서 각 아이템에 filtered라는 속성을 추가해줬습니다.

이 부분과 관련해서 vue js 가이드 문서에도 명확히 언급하고 있습니다. '나중에 추가한 property는 화면 업데이트를 촉발시키지 않을 것이다. 만약 추후에 사용할 속성이라면 처음부터 어떤 초기 값을 세팅해야 한다.' 라고 말이죠.(vue 가이드 문서-Data and Methods)

 

4. 보기 방식 변경&정렬 방식 변경

* 결과 화면

 

* html(전체)

<div id="app">
    <form class="s-form">
        <select v-model="option" class="form-group" v-on:change="filter">
            <option value="all">All</option>
            <option v-for="c in categories" v-bind:value="c">{{ c }}</option>
        </select>
        <input v-model="search" v-on:keyup="filter" type="text" placeholder="search..." class="form-group">
    </form>
    <div class="content">
        <div class="button-group">
            <div class="buttons">
                <span>view</span>
                <button v-for="b in buttons.view" v-bind:title="b.title"
                        v-bind:class="viewType == b.class?'selected':''"
                        v-on:click="toggleList('view',b.class)">
                    <i class="fas" v-bind:class="'fa-'+b.class"></i>
                </button>
            </div>
            <div class="buttons">
                <span>sort</span>
                <button v-for="b in buttons.sort" v-bind:title="b.title"
                        v-bind:class="sortType == b.class?'selected':''"
                        v-on:click="toggleList('sort',b.class)">
                    <i class="fas" v-bind:class="'fa-'+b.class"></i>
                </button>
            </div>
        </div>
        <ul class="todo-list list" v-if="viewType === 'list'">
            <li v-for="(item,index) in todos"
                v-bind:class="[substring(item.deadline, 0, 8) == getTodayDate? 'deadline':'', item.endDate? 'done':'']"
                v-show="item.filtered!=='y'"
                v-on:click="checkTodo(index)">
                <i class="far" v-bind:class="item.endDate? 'fa-check-square':'fa-square'"></i>
                <span class="title">{{ item.title }}</span>
            <span class="stars">
                <i v-for="n in toNumber(item.important)" class="fas fa-star"></i>
            </span>
            </li>
        </ul>
        <ul class="todo-list thumbnail" v-else>
            <li v-for="(item,index) in todos" v-show="item.filtered!=='y'"
                v-bind:class="item.endDate? 'done':''" v-on:click="checkTodo(index)">
                <div class="label">
                    <span class="category">{{item.category}}</span>
                    <span class="stars">
                        <i v-for="n in toNumber(item.important)" class="fas fa-star"></i>
                    </span>
                </div>
                <img v-bind:src="item.thumbnail">
                <p class="info">
                    <span class="title">{{ item.title }}</span>
                    <span class="deadline">
                        <i class="far" v-bind:class="item.endDate? 'fa-check-square':'fa-square'"></i>
                        until {{ formatDate(item.deadline) }}
                        <i v-if="substring(item.deadline, 0, 8) == getTodayDate" class="fas fa-bell"></i>
                    </span>
                </p>
            </li>
        </ul>
    </div>
</div>

* js(보기 방식 변경, 정렬 방식 변경 관련 일부)

var app = new Vue({
el: "#app",
data: {
option: 'shopping',
search: '',
categories: [],
todos: [],
viewType: 'list',
sortType: 'sort-numeric-up',
buttons: {
'view': [
{'class':'list', 'title':'view in list'},
{'class':'th-large', 'title':'view in thumbnail'}
],
'sort': [
{'class':'sort-numeric-up', 'title':'sort by deadline'},
{'class':'sort-alpha-down', 'title':'sort by alphabet'},
{'class':'star', 'title':'sort by stars'}
]
}
},
methods: {
toggleList: function(type, className){
switch (type){
case 'view':
this.viewType = className;
break;
case 'sort':
this.sortType = className;

if(className === 'sort-numeric-up')
this.todos.sort(function(a,b){return a['deadline']>b['deadline']});
else if(className === 'sort-alpha-down')
this.todos.sort(function(a,b){return a['title']>b['title']});
else if(className === 'star')
this.todos.sort(function(a,b){return a['important']<b['important']});

break;
}
},
...

버튼 마크업은 static으로 하지 않고, vue 데이터에서 buttons 속성으로 보기 방식과 관련된 버튼 정보는 view라는 이름의 배열로, 정렬과 관련된 버튼 정보는 sort라는 이름의 배열로 구성하여, 각각 이 항목만큼 반복을 하여 버튼이 렌더링 되도록 구성하였습니다. 버튼의 개수만큼 직접 태그를 작성하면, 태그마다 조건에 따른 클래스의 변경과 관련한 v-bind:class 속성과 클릭 이벤트 속성인 v-on:click, v-bind:title 속성을 각 요소마다 똑같이 적어야 하는 번거로움이 발생하는데, 이 점을 피하고 싶었습니다.

4-1. 클래스 toggle하기

화면 개발을 하다보면 사용자가 선택한 탭이나 버튼의 스타일을 변경하는 처리를 자주 하게 됩니다. 이 때는 선택된 요소만이 갖는 클래스에 스타일을 정하여, 선택한 요소에는 해당 클래스를 부여하고 이전에 클래스를 가지고 있던 요소에서는 클래스를 삭제하여 처리를 하곤 합니다. 이렇게 클래스를 토글하는 것도 vue를 사용하면 아주 간단하게 처리할 수 있습니다.

jQuery를 사용하여 처리할 때는 기존 요소를 찾아 removeClass()를 통해 클래스를 지워주고, 선택된 요소를 찾아 addClass()로 클래스를 부여하는 식으로 처리를 합니다. 어렵지는 않지만 선택자를 통해 이전에 선택된 요소와 새로 선택된 요소를 찾아야 하므로 조금 번거롭게 느껴졌습니다.

v-bind:class="sortType == b.class?'selected':''"

vue를 사용한 위의 예제에서는 data에서 정의한 viewType과 버튼 data가 가지고 있는 'class'라는 값과 비교하여 같은 경우 선택되었다는 의미의 'selected'라는 클래스를 부여하도록 버튼 태그에 조건을 부여하였습니다. 그리고 버튼을 클릭했을 때, toggleList 함수에서 인자로 넘긴 버튼 자신의 class값으로 viewType 데이터를 업데이트 해줍니다. 그러면 데이터의 업데이트를 감지하여 클래스가 toggle됩니다.

4-2. 리스트 정렬방식 변경하기

리스트 정렬방식 변경도 vue를 사용하면 아주 간단하게 처리할 수 있습니다. 정렬 방식 변경을 위해서 기존의 노드를 삭제하거나 노드 순서 변경 등의 노드 조작을 할 필요가 없습니다. 단지 해야할 일은 todo 리스트를 구성하는 todos 라는 배열의 순서를 변경해주는 것입니다. vue가 데이터의 업데이트를 감지하여 화면에서 리스트를 다시 구성해줍니다.

if(className === 'sort-numeric-up')
   this.todos.sort(function(a,b){return a['deadline']>b['deadline']});
else if(className === 'sort-alpha-down')
   this.todos.sort(function(a,b){return a['title']>b['title']});
else if(className === 'star')
   this.todos.sort(function(a,b){return a['important']<b['important']});

정렬 방식 변경을 위해서 실질적으로 작업한 부분은 배열 순서를 key값에 따라 정렬하는 이 로직 뿐입니다.

 

5. 완료한 항목 체크

* 결과 화면

* html(리스트 아이템 부분만)

<li v-for="(item,index) in todos"
    v-bind:class="[substring(item.deadline, 0, 8) == getTodayDate? 'deadline':'', item.endDate? 'done':'']"
    v-show="item.filtered!=='y'"
    v-on:click="checkTodo(index)">
    <i class="far" v-bind:class="item.endDate? 'fa-check-square':'fa-square'"></i>
    <span class="title">{{ item.title }}</span>
<span class="stars">
    <i v-for="n in toNumber(item.important)" class="fas fa-star"></i>
</span>
</li>

* js(체크 부분만)

methods: {
  checkTodo: function(index){
      var todoObj = this.todos[index];

      if(todoObj.endDate){
          todoObj.endDate = '';
      } else {
          var d = new Date();
          this.todos[index].endDate = this.getTodayDate + d.toTimeString().replace(/[^0-9]/g, '').substr(0, 6);
      }
  },
  ...

}

항목을 체크했을 때 체크박스에 체크가 되면서 글자 가운데 선을 넣는 것도 위에서 사용한 class toggle 방법을 사용하였습니다. 우선 리스트 아이템을 구성하는 <li> 태그에 v-on:click 속성으로 클릭 시 checkTodo를 호출하도록 하고, v-for 구문에서 얻은 index를 파라미터로 보내도록 하였습니다.

클릭을 하면 checkTodo가 호출되면서 내부에서 파라미터로 넘어온 index값을 받아 리스트 중 해당 인덱스의 endDate 값을 설정해줍니다. 이미 체크가 되어 endDate가 있는 경우에는 endDate를 공백으로 만들어서 체크가 해제되도록 하였습니다.

그리고 이 endDate값에 따라 클래스가 바뀌도록 합니다. <li> 태그에는 item에 endDate가 있을 경우, done이라는 클래스를 부여합니다. font awesome을 사용한 체크 아이콘 역할을 하는 <i> 태그에는 item에 endDate가 있을 경우 'fa-check-square'라는 체크가 된 박스 아이콘을, 아닌 경우에는 'fa-square'라는 빈 박스 아이콘을 나타내는 클래스를 부여합니다. 이렇게 하면 vue가 endDate 값이 업데이트되는 것을 감지해 태그가 반응하여 클래스를 toggle 하도록 해줍니다.

 

 이렇게 2편의 포스트에 걸쳐서 간단한 예제로 vue 기능을 살펴보았습니다. 사실 간단한 예제여서 화면 개발에서 주로 쓰이는 필수 기능들만 다뤘는데, vue에는 이 외에도 component를 비롯한 강력한 기능들이 많습니다. vue에 관심이 있으시면, 가이드 문서가 잘 정리되어 있으니 쭉 읽어보시면서 예제 코드를 만들어보시면서 vue의 강력한 기능들을 경험해보시면 좋을 것 같습니다.

 


Vue.js 시리즈 1편에 대한 내용을 살펴보고 싶다면, 아래 링크를 클릭해주세요.

▶ Vue.js를 이용한 Todo리스트 예제 만들기(1)


 

New Multi-Channel Dynamic CMS

Vue.js를 이용한 Todo리스트 예제 만들기(1)

 

Vue.js를 이용한 Todo리스트 예제 만들기(1)

 

프론트엔드 개발에 관심이 있으시다면 Vue.js를 한 번 쯤은 들어보거나 이미 프로젝트에서 사용하신 경험이 있으시리라 생각합니다. 저는 실무에서 Vue.js를 사용해볼 경험이 없어서, 'Vue.js라는 javascript 프레임워크가 있다.' 정도만 알고, 공식 사이트에 가서 대충 내용만 훑어본 적이 있습니다.

그런데 최근에 주위에서 실시간 데이터 바인딩이 필요한 프로젝트에서 Vue.js를 사용한다는 이야기를 들었습니다. 저도 머지않아 실무에서 사용할 일이 있을 것이라는 생각이 들어, 개인적으로 공부를 해보기로 하였습니다. 가이드 문서가 아주 잘 정리되어 있어서, 가이드 문서만 쭉 읽어내려가다보니 이론적으로만 접근하는 것은 무언가 부족한 느낌이 들었습니다.

아주 간단한 것이라도 예제를 만들어보아야 실무에서도 좀 더 잘 활용할 수 있을 것 같아서 간단한 'Todo 리스트'를 Vue.js를 사용하여 만들어 보았습니다. 목적없이 문서를 읽을 때보다 예제를 만들면서 읽으니 필요한 기능을 더욱 적극적으로 익힐 수 있었습니다. 이 포스트를 포함하여 두 번의 포스트에 걸쳐 예제를 만들면서 각각의 기능에서 사용한 vue.js의 사용법을 소개해보려고 합니다.

 

우선 1차적으로 완성된 화면입니다. 느낌 가는대로 만들어서 디자인은 약간 정돈되지 않아 보일 수도 있습니다.(저에게는 최선입니다.ㅎㅎ) 필요한 기능만 간단하게 만들거라서 모바일 화면 사이즈에 맞게 크기를 잡았습니다. 현재 구현된 기능은 다음과 같습니다.

  1. todo 리스트 구성하기
  2. 카테고리, 검색어 입력을 통한 검색 기능
  3. 보기 방식 변경
  4. 정렬 방식 변경
  5. 완료한 항목 체크

vue.js를 설치하는 방법은 상황에 맞게 여러가지가 있으니, 골라서 사용하시면 됩니다.(Vue.js Installation guide)저는 minified된 소스를 다운받아서 화면에 직접 import하는 방식을 택했습니다.

그리고 예제 데이터를 json 파일로 만들어서 준비하였습니다. 예제 데이터는 영어로 해야 화면이 예쁘게 나오는 것 같아서 생각나는 항목들을 영어로 만들었습니다. 썸네일 방식을 위한 이미지도 구글에서 찾아두었습니다.

{"todos": [
  {
    "thumbnail": "/data/images/vegetables.jpg",
    "category": "shopping",
    "title": "Buying some healthy food",
    "startDate": "20180310152031",
    "deadline": "20180314180000",
    "endDate": "",
    "important": "3",
    "filtered":"n"
  },
  {
    "thumbnail": "/data/images/book.jpg",
    "category": "shopping",
    "title": "Buying some new books",
    "startDate": "20180310152031",
    "deadline": "20180317180000",
    "endDate": "",
    "important": "2",
    "filtered":"n"
  },
  {
    "thumbnail": "/data/images/cleaning.jpg",
    "category": "cleaning",
    "title": "Spring Cleaning",
    "startDate": "20180315120000",
    "deadline": "20180317120000",
    "endDate": "",
    "important": "5",
    "filtered":"n"
  },
  {
    "thumbnail": "/data/images/jogging.jpg",
    "category": "exercise",
    "title": "Jogging at the park",
    "startDate": "20180318093000",
    "deadline": "20180318130000",
    "endDate": "",
    "important": "3",
    "filtered":"n"
  },
  {
    "thumbnail": "/data/images/present.jpg",
    "category": "shopping",
    "title": "Buying mom's present",
    "startDate": "20180317120000",
    "deadline": "20180319120000",
    "endDate": "",
    "important": "4",
    "filtered":"n"
  },
  {
    "thumbnail": "/data/images/yoga.jpg",
    "category": "exercise",
    "title": "Enroll yoga class",
    "startDate": "20180320093000",
    "deadline": "20180330130000",
    "endDate": "",
    "important": "3",
    "filtered":"n"
  }
],
"category": ["shopping", "cleaning", "exercise"]
}

1. Vue 객체 만들기

<body>
    <div id="app">
    </div>
</body>
<script>
    var app = new Vue({
        el: "#app",
        data: {
            option: 'all',
            search: '',
            categories: [],
            todos: [],
            viewType: 'list',
            sortType: 'sort-numeric-up',
            buttons: {
                'view': [
                    {'class':'list', 'title':'view in list', selected:true},
                    {'class':'th-large', 'title':'view in thumbnail', selected:false}
                ],
                'sort': [
                    {'class':'sort-numeric-up', 'title':'sort by deadline', selected:true},
                    {'class':'sort-alpha-down', 'title':'sort by alphabet', selected:false},
                    {'class':'star', 'title':'sort by stars', selected:false}
                ]
            }
        },
        created: function(){
            sendAjax({
                url: '/data/todo.json',
                method:'GET',
                success: function(resp){
                    var respObj = JSON.parse(resp);
                    app.categories = respObj.category;
                    app.todos = respObj.todos;
                }
            });
        }
    })
</script>

Vue 객체 만들기는 아주 쉽습니다. Vue를 사용하여 렌더링할 래퍼 태그를 만들고, new Vue()로 vue 객체를 만들면 됩니다. Vue 객체를 만들 때 파라미터로 object을 넘기는데, 몇 가지 속성을 설정할 수 있습니다.

  1. el - vue를 사용할 엘리먼트
  2. data - vue에서 데이터 바인딩에 사용할 데이터
  3. created -  vue의 라이프사이클 중 인스턴스가 만들어진 후 단계의 콜백

데이터를 비동기로 호출하여 불러온다는 상황을 가정하여, 우선 data.categories와 data.todos를 빈 배열로 선언한 후 vue 인스턴스가 만들어진 후에 발생할 콜백(created) 함수에서 ajax로 데이터를 불러와 조회한 데이터로 다시 값을 할당해주었습니다.

 

2. todo 리스트 구성하기

* 결과 화면

* html(리스트만)

<ul class="todo-list list" v-if="viewType === 'list'">
    <li v-for="(item,index) in todos"
        v-bind:class="[substring(item.deadline, 0, 8) == getTodayDate? 'deadline':'', item.endDate? 'done':'']"
        v-show="item.filtered!=='y'"
        v-on:click="checkTodo(index)">
        <i class="far" v-bind:class="item.endDate? 'fa-check-square':'fa-square'"></i>
        <span class="title">{{ item.title }}</span>
    <span class="stars">
        <i v-for="n in toNumber(item.important)" class="fas fa-star"></i>
    </span>
    </li>
</ul>
<ul class="todo-list thumbnail" v-else>
    <li v-for="item in todos" v-show="item.filtered!=='y'" v-bind:class="item.endDate? 'done':''">
        <div class="label">
            <span class="category">{{item.category}}</span>
            <span class="stars">
                <i v-for="n in toNumber(item.important)" class="fas fa-star"></i>
            </span>
        </div>
        <img v-bind:src="item.thumbnail">
        <p class="info">
            <span class="title">{{ item.title }}</span>
            <span class="deadline">
                <i class="far" v-bind:class="item.endDate? 'fa-check-square':'fa-square'"></i>
                until {{ formatDate(item.deadline) }}
                <i v-if="substring(item.deadline, 0, 8) == getTodayDate" class="fas fa-bell"></i>
            </span>
        </p>
    </li>
</ul>

* js

var app = new Vue({
    el: "#app",
    data: {
        option: 'all',
        search: '',
        categories: [],
        todos: [],
        viewType: 'list',
        sortType: 'sort-numeric-up',
        buttons: {
            'view': [
                {'class':'list', 'title':'view in list', selected:true},
                {'class':'th-large', 'title':'view in thumbnail', selected:false}
            ],
            'sort': [
                {'class':'sort-numeric-up', 'title':'sort by deadline', selected:true},
                {'class':'sort-alpha-down', 'title':'sort by alphabet', selected:false},
                {'class':'star', 'title':'sort by stars', selected:false}
            ]
        }
    },
    methods: {
      toNumber: function(number){
          if(typeof number == 'string'){
            number = Number(number);
          }
          return number;
      },
      substring : function(str, startIdx, length){
          return str.substr(startIdx, length);
      },

      formatDate: function(str){
          var result = str.substr(0, 4)+'-'+str.substr(4, 2)+'-'+str.substr(6, 2);
          if(result.replace(/-/g, '') == this.getTodayDate){
              result = "today " + str.substr(8, 2)+':'+str.substr(10, 2)+':'+str.substr(12, 2);
          }
          return result;
      },
      checkTodo: function(index){
          var todoObj = this.todos[index];

          if(todoObj.endDate){
              todoObj.endDate = '';
          } else {
              var d = new Date();
              this.todos[index].endDate = this.getTodayDate 
                  + d.toTimeString().replace(/[^0-9]/g, '').substr(0, 6);
          }
      }
    },
    computed: {
      getTodayDate: function(){
          var d = new Date();
          var arr = d.toLocaleString().replace(/ /g, '').split('.');
          return arr[0]+(arr[1].length==1?"0"+arr[1]:arr[1])+(arr[2].length==1?"0"+arr[2]:arr[2]);
      }
    },
    created: function(){
        sendAjax({
            url: '/data/todo.json',
            method:'GET',
            success: function(resp){
                var respObj = JSON.parse(resp);
                app.categories = respObj.category;
                app.todos = respObj.todos;
            }
        });
    }
})

1-1. v-if, v-else-if, v-else

todo 항목 리스트는 ul 태그를 사용하였고, 리스트 방식과 썸네일 방식 두 가지 화면을 보여주려고 하는데, 각각 방식마다 보여주는 정보와 태그 구성이 달라서 ul 태그를 두 개로 나누었습니다. ul 태그의 v-ifv-else지시어를 통해 사용자가 선택한 view 방식에 맞는 리스트를 보여줄 수 있습니다.

Vue.js에서는 값에 따라 렌더링 하는 태그를 지정할 때 v-if, v-else-if, v-else지시어를 사용합니다. 저는 이 방식이 아주 깔끔하다는 생각이 듭니다. 별도의 커스텀 태그를 추가하지 않고, 태그에 직접 attribute로 추가함으로써 깔끔하게 보여줄 수 있기 때문입니다. 다만, 같은 조건을 적용할 v-if, v-else-if, v-else 속성을 가진 태그는 바로 이전/이후 형제 노드로 구성되어야 하며, 그 사이에 다른 노드를 추가하면 인식하지 못하는 제약이 있습니다.

예제의 경우는 ul 태그 하나만 구분을 하면 되지만, 만약 다수의 태그가 조건에 따라 달라져야 한다면 불필요하게 div 태그 등으로 묶을 필요없이 아래와 같이 <template>태그로 해결할 수 있습니다.

1-2. v-for

반복을 해야하는 경우에는 v-for지시어를 사용합니다. 반복시킬 태그에 v-for attribute를 추가하고 value를 설정해주면 됩니다. value는 상황에 맞게 여러가지 방법으로 설정할 수 있습니다 반복을 돌릴 대상은 array도 되고 object도 가능합니다. 반복문을 설정할 때 영역 안에서 배열의 경우 index를, object의 경우 key, value, index 값을 사용할 수 있어 편리합니다.

  • array 반복(items->array명, item->array 요소[사용자 지정]): item in items, (item, index) in items
  • object 반복(object->object명): value in object, (value, key) in object, (value, key, index) in object

반복문을 array 혹은 object가 아니라 특정 숫자를 지정하여 사용하고 싶은 경우에는 v-for="n in 5"와 같은 식으로 사용할 수 있습니다. 저는 todo 리스트에서 각 아이템 마다 '중요도'라는 속성명으로 숫자를 부여하여 그 수만큼 별 아이콘을 생성하기 위하여 v-for="n in toNumber(item.important)"라는 구문을 사용하였습니다. toNumber는 Vue객체의 methods에 직접 정의한 문자열을 숫자로 바꿔 리턴하는 함수입니다.

1-3. v-bind

태그 attribute에 데이터를 바인딩할 때는 v-bind 지시어를 사용합니다. 위의 예제와 같이 v-bind:class="", v-bind:src="" 같은 식으로 사용할 수 있습니다. 단순히 데이터 바인딩을 할 수 있을 뿐만 아니라, 조건식을 넣어서 상황에 따라 다른 값이 바인딩되도록 할 수 있습니다.

v-bind:class="[substring(item.deadline, 0, 8) == getTodayDate? 'deadline':'', item.endDate? 'done':'']"

예제에서 이런 식으로 class 바인딩을 해보았습니다. 바인딩 시킬 클래스가 2개라서 [ ] 괄호로 감싸고 각각의 조건식을 , 로 연결해서 작성합니다. substring과 getTodayDate는 위의 js 소스에서 각각 methods와 computed속성에 정의한 함수입니다. 이렇게 조건식 안에서 Vue 객체에 정의한 함수들을 사용할 수 있습니다.

이 식의 의도는 '마감일(item.deadline)에서 앞 8자리(yyyyMMdd)와 오늘 날짜(yyyyMMdd) 문자열이 같다면deadline이라는 클래스를 추가하고, item.endDate에 값이 있다면done이라는 클래스를 추가한다.'는 것입니다.

1-4. v-show

조건에 따라 요소를 보여주거나 감추고 싶을 때는 v-show 지시어를 사용합니다. value로 요소를 보여줄 조건을 작성합니다. 저는 사용자의 키보드 이벤트를 받아 입력한 문자열에 따라 리스트를 필터링할 용도로 v-show 지시어를 사용했습니다. 어떻게 보면 v-if와 하는 일이 비슷합니다. 조건에 따라 요소를 보여주거나 안보여주거나 하는 역할을 하기 때문입니다. 하지만, 화면 뒤에서 일어나는 동작 방식에 차이가 있습니다.

v-if는 조건에 따라서 요소를 보여주거나 감출 때 노드를 삭제하고 다시 생성하는 방식을 사용합니다. 조건이 false인 경우에는 아예 렌더링 자체를 안해버리는 것이죠. 반면에,v-show는 조건과 상관없이 렌더링을 합니다. 다만 css 기반의 토글(display:block; display:none;)을 통해 보여주거나 감춥니다.

따라서 두 가지 지시어는 상황에 맞게 써야합니다. v-if는 노드 자체에 변화가 일어나므로 작업의 비용이 크다고 볼 수 있습니다. 그러므로 렌더링 후 toggle이 자주 일어나는 경우보다는 조건에 따라 요소를 렌더링 할지 말지를 결정하는 경우 사용하는 것이 더 좋겠습니다. v-show는 노드 자체의 변화가 일어나는 것은 아니므로 보다 작동 방식이 가볍습니다. 그러므로 사용자의 클릭이나 키보드 이벤트를 받아 요소를 보이거나 감추는 등의 비교적 자주 일어나는 경우에 사용하는 것이 적합할 것 같습니다.

1-5. v-on

이벤트를 바인딩할 때는 v-on: 지시어를 사용합니다. v-on:click처럼 콜론 뒤에 이벤트명을 씁니다. value로는 함수명을 적습니다. 저는 Vue 객체를 생성할 때 methods라는 속성에 정의한 함수를 이벤트 핸들러로 사용했지만, 꼭 methods에 정의한 함수만을 쓸 수 있는 것은 아닙니다.

v-on:submit, v-on:keyup 처럼 click 이벤트 외에도 다양한 이벤트를 바인딩 할 수 있습니다. 이벤트 바인딩 시 사용하기 정말 편리하다는 생각이 드는 기능이 있습니다. 그 중 하나는 v-on:click.prevent 와 같은 방식으로 이벤트명 뒤에 수식어(Modifiers)를 쓸 수 있다는 점입니다. .prevent를 붙이게 되면 이벤트 발생 시 event.preventDefault()를 호출합니다. .stop을 붙이면 이벤트 발생 시 event.stopPropagation()을 호출합니다. 수식어는 keyup이벤트에도 붙일 수 있는데, v-on:keyup.enter 혹은 v-in:keyup.13 과 같은 식으로 특정 키에 이벤트를 걸 수도 있습니다. 수식어를 통해 이벤트 핸들러에 필요한 로직 이외에 이벤트 제어와 관련한 코드를 넣지 않아도 되어서 더욱 깔끔한 코드 작성이 가능합니다.

1-6. computed/methods

데이터 바인딩 시 단순하게 불러온 데이터를 바인딩 하는 경우도 있지만, 가끔은 로직에 의해 데이터를 변형할 필요가 있는 경우가 있습니다. 만약 mustache 표현식 안에 javascript 로직을 넣는다면 화면 구성을 위한 마크업에 로직이 섞여 알아보기도, 수정하기도 힘든 코드가 될 것입니다.

그래서 vue에서는 복잡한 로직을 위해 methodscomputed 속성을 제공합니다. 이 속성으로 함수들을 정의할 수 있는데, 차이점은 computed 속성은 의존성에 기반을 두고 캐시가 된다는 점입니다. 즉, computed에 정의한 함수에서 사용한 데이터가 업데이트 되지 않는다면, 함수를 다시 실행하지 않고 바로 이전에 계산된 값을 반환합니다.

computed와 methods의 차이점에 근거하여, 예제를 만들 때 어떤 함수는 computed로 어떤 함수는 methods에 정의를 하였습니다. getTodayDate는 오늘 날짜를 yyyyMMdd 형태로 반환하는 함수입니다. 오늘 날짜는 한 번만 계산을 하면 호출할 때 마다 매번 코드가 실행될 필요는 없으므로 computed의 속성으로 설정하였습니다.

 

todo 리스트를 구성하는 부분까지 사용한 vue 기능을 정리를 해봤습니다. 글이 너무 길어지는 것 같아서 다음 포스트에서 검색 기능 구현부터 이어서 작성하도록 하겠습니다.

 


Vue.js에 대한 더 자세한 내용을 살펴보고 싶다면, 아래 링크를 클릭해주세요.

▶ Vue.js를 이용한 Todo리스트 예제 만들기(2)


 

 

New Multi-Channel Dynamic CMS

Three.js를 이용한 3D 그래픽 입문기


Three.js 란?




The aim of the project is to create an easy to use, lightweight, 3D library.


웹기반의 3D그래픽 표현작업을 간소화 해주는 라이브러리인 Three.js입니다.

주요 브라우저 업체들이 지원하는 웹기반 3D표현기술인 WebGL을 lightweight한 가벼움과 간단한 코드 작성만으로 멋있는 3D 그래픽을 만들 수 있습니다.

API 구조의 인터페이스로 구성되어 있어 자바스크립트를 다루시는 분들이라면 익히기 쉽고, 
외국에서도 이미 많이 사용하고 있습니다. 

물론, IE나 모바일기기 브라우저에서 WebGL이 호환되지 않지만, Three.js에는 WebGL의 결과물을 캔버스나 SVG로 변환하실 수 있어 동일한 모양의 결과물을 확인할 수 있습니다.

Three.js 사이트에 들어가시면 다른 유저들이 올린 작품을 보실 수 있는데 화려하고 멋있는 작품들을 보실 수 있습니다. 저도 언젠가는 그런 멋진 작품을 만들 날을 기약하며, API문서에 있는 간단한 예제를 따라해보면서 3D 그래픽에 대해 익혀보려고 합니다.

시간 나실때 three.js 사이트에 들어가셔서 다른 유저의 작품들을 꼭 구경해보시길 추천드려요!


설치는 git에서 minified한 버전을 다운받으시거나 다운받아 import하셔도 됩니다.
git : https://github.com/mrdoob/three.js/


Three.js에서는 크게 scene, camera, renderer 로  나누어지는데
카메라(camera)로 보여지는 화면(scene)을 랜더링(renderer) 해주는 방식으로 각각 영역을 나누어 세부적으로 설정해 줄 수 있습니다.

카메라로 사진을 찍는다고 가정해볼게요, 우선 찍을 장소(scene)와 부피와 질감을 갖고 있는 피사체(mesh)가 있어야 겠죠?  찍은 화면을 종이에 인화해주면(renderer)을 사진이 나옵니다.


다른 방식으로 말하자면, 물체를 가리키느 '메쉬(mesh)' 와 빛을 뜻하는 '라이트(light)'로 공간을 구성하고 시점을 카메라로 조정하며 화면에 보이게 도와줍니다.

그럼 예제를 통해서 한줄씩 알아가보도록 할게요!

var scene = new THREE.Scene(); var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); var renderer = new THREE.WebGLRenderer(); renderer.setSize( window.innerWidth, window.innerHeight ); document.body.appendChild( renderer.domElement );


순서대로 보자면, THREE.Scene(); 으로 장소를 생성해 주고,

THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); 로 카메라를 세팅해줍니다.

PerspectiveCamera의 들어가는 속성들을 보기전에 추가적으로 이야기하자면 Three.js에는 많지는 않지만 여러 카메라가 존재한답니다.....

위에 예제에서 사용된 PerspectiveCamera 외에도 OrthographicCamera라는 카메라가 있습니다.  물론,모델링 하시는 분이라면 저보다 자세히 알고계실테니 그냥 넘어가도 무관합니다만 모르시는 분들을 위해 둘의 차이를 잠깐 알아보고 가려합니다.

영어단어 그대로 해석해서 이해하자면 Perspective(원근법) 와 Orthographic(정투영법)인데, 너무나 잘 알듯이 원근법은 실제 눈으로 보이는 것과 비슷하죠. 물체와의 거리와 각도에 영향을 받아 같은 너비에 제품도 가까이 있는 것이 크게 멀리 있는 것이 작게 보입니다.

그와 다르게, 정투영법은 같은 크기는 거리와 관계없이 같은 크기로 표시해주는 방법입니다.
물체의 정확한 크기를 표현해주는 것이죠. 정투상 상태에서 모델링을 할때는 격자를 참고해서 정확하게 표현해주는것이 중요합니다. 입체적인 모습보다는 상대적 크기를 파악하고 사용할때 유용하죠.

아래 사진의 projection image에 집중해보면 이해하기 쉬울거다. 둘의 실제적인 사이즈대로 보여주는 (a)가 정투영법 가까이 있는 파란 물체가 더 크게 보이는 (b)가 원근법입니다.


출처: https://www.researchgate.net/figure/Projection-modes-a-orthographic-projection-and-b-perspective-projection_fig2_265693452


이제 예제에서 원근법카메라를 사용한 것을 알았으니 안에 들어가는 속성에 대해 보면 다음과 같습니다.

THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );

위에 들어가는 4개의 매개변수는 (시야, 가로 세로비율, 가까운 정도, 먼 정도) 라고 보시면 됩니다.
가까운 정도와 먼정도라뇨...? 그게 어느 정도 까지의 가까이있는 또는 멀리 있는 물체를 볼 것인지를 설정해주는 속성입니다. 가까운정도와 먼정도의 범위에 해당되지 않으면 출력되지 않아 보여지지 않습니다. 주로 게임에 많이 사용되어지죠.


자, 화면도 카메라도 다 설정했다면 이제 render해줘야죠. THREE.WebGLRenderer(); 로 renderer를 생성해 주는데 WevbGL은 위에서 언급한것처럼 호환되지 않는 브라우저들이 있죠...( IE10이하의 버전 ) 그런 경우를 대비해서 호환되는지를 window.WebGLRenderingContext 로 체크를 해서 안되는 경우 THREE.CanvasRenderer() 를 이용하시면 canvas 기반 렌더러가 대신 사용되어 정상적으로 작동 된다고 합니다. (짝짝짝) 단지, 속도가 느리고 품질이 낮아질 뿐이죠....ㅠ,ㅠ


기본적인 구성을 다 이루었으면 renderer의 사이즈를 세팅해주고 화면에 appendChild 해주시면 이제 그림을 그릴 준비가 다 되신겁니다. 

기본 세팅이 완성 되신겁니다. 그럼, 이제 뽑을 피사체를 그려봐야겠쥬?
3D그래픽은 메시를 만들어 기본적인 구조에 재질을 입히는 방식으로 만들어지는데요.
다시 말해, mesh(피사체)는 geometry + meterial 로 구성됩니다. 


var geometry = new THREE.BoxGeometry( 1, 1, 1 ); var material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); var cube = new THREE.Mesh( geometry, material ); scene.add( cube ); camera.position.z = 5;

 

도형의 가로세로높이의 비율은 geometry를 생성할 때 정의해 주고, 재질의 색상은 material을 정의해줄 때 추가해 준 후 cube라는 변수에 three.js에서 제공하는 BoxGeometry로 생성된 geometry와 기본적인 재질인 MeshBasicMeterial 을 매개변수에 넣어주고 메시를 만들어 정의해주고 scene에 추가해주는 과정입니다.

scene.add()를 통해 추가하게 되면 (0,0,0) 위치에 추가되기 때문에 아직 화면에서 제대로 된 큐브를 보실 수 없습니다. 그렇기때문에 간단히 카메라를 움직여서 위에서 만든 피사체를 확인해보겠습니다.

function animate() { requestAnimationFrame( animate ); renderer.render( scene, camera ); } animate();

 위의 소스를 requestAnimationFrame 같은 경우 사용자가 다른 탭으로 잠시 넘어간다면 애니메이션 작동을 멈추어 컴퓨터의 소중한 프로세스 전력을 낭비하지 않는답니다.

여기까지 소스를 돌린다면 화면에는 나타나지만 멈추어있는 사각형을 발견하실 수 있습니다.
큐브의 동적인 움직임을 위해 위에 만든 animate() 기능에 아래처럼 회전기능을 추가합니다.

cube.rotation.x += 0.1; cube.rotation.y += 0.1;

그렇다면 아래와 같이 보이실겁니다. 형광 초록색 네모가 돌아가가는 것이 보이시죠?


여기서 더 나아가 속성값들을 바꿔보고 API문서에 있는 다양한 기능들을 추가해보시길 바랍니다!


참고 : http://threejs.org/docs





New Multi-Channel Dynamic CMS

Node.js 기초부터 튼튼히 (3) 이벤트

 

 

Node.js 를 공부하고 있다면 비동기, 이벤트 루프, 단일 Thread 에 대해 많이 들어보셨을겁니다.

Node.js 는 기존의 웹 모델의 multi-thread 방식의 문제점을 보완해주는 이벤트 기반의 비동기 방식 프레임워크로  API 실행시, 하나의 Thread로 데이터 반환까지 기다리지 않고 다음 API를 실행시켜주는 뛰어난 처리 성능을 자랑하죠.

기존의 웹 모델이 서버에 요청이 오면 Thread를 생성하여 작업하는 멀티-Thread 방식이지만,
단일한 Thread로 짧은시간에 많은 요청을 처리하기 위해서 Event Callback 방식을 사용합니다.

 

어떻게 돌아간다는 것이죠?

하나의 Thread에서 이벤트 루프를 돌려, 이벤트가 발생하면 해당 되는 함수를 실행시키기 때문에 단일한 Thread로 여러 요청 처리가 가능합니다.

다시 설명해드리자면, 

요청이 들어오면 요청 메세지를 Event Queue에 넣어주고, 들어와 있는 작업들을 Event Loop가 순차적으로 Thread Pool에 있는 Thread에 작업을 할당해주고 작업이 끝난 이벤트들을 감지하여 해당되는 Callback함수를 다시 Event Queue에 집어넣습니다.

실제로 요청을 처리하는 Event Loop는 단일 Thread지만, Thread Pool 내부는 여러개의 Thread를 사용하여 Event Loop가 Callback함수를 내부에 여러 Thread들에게 요청을 할당해줍니다.

 

 

비동기 방식이다 보니 요청된 작업이 언제 끝날지, Callback함수를 호출하는 시점은 언제인지 알 수 없어 Callback함수에 Callback.. callback.. callback.. callback...  Callback Hell 에 빠질 위험이 있습니다.

 


 

EventEmitter는 객체의 상태 변화를 관찰하고 상태 변화가 있을때 메소드를 실행시켜주는 옵서버 패턴의 구현으로 이벤트를 추가할 수 있는 객체들은 EventEmitter 객체의 상속을 받는다. 

즉, 상속을 받게 되면 EventEmitter가 될 수 있다
이벤트를 새롭게 등록할 때는 EventEmitter 객체를 생성해주고 진행하면 된다. 

const EventEmitter = require('events');

process 객체로도  EventEmitter 객체를 생성할 수 있다.

var myEvent = new process.EventEmitter();
 

이벤트 연결하기 (리스너 등록)

이벤트를 추가하려면, emitter.addListener(eventName, listener) 로도 등록 가능하지만, jQuery를 사용한다면 익숙할 emitter.on(eventName, listener)를 사용하면 된다.

노드 API문서의 예를 활용하면, 서버가 연결되면 콘솔창에 누군가 접속했음을 알리는 로그가 기록된다.

server.on('connection', (stream) => {
  console.log('someone connected!');
});
// 서버가 연결되면 console창에 'someone connected' 출력

마찬가지로, 최초 한번만 실행되고 이벤트를 제거할때는 .once(event, listener) 를 사용하면 된다.

server.once('connection', (stream) => {
  console.log('Ah, we have our first user!');
});
// 서버가 최초로 연결되면 console창에 'Ah, we have our first user!' 출력

기본적으로 EventEmmiters는 10개 이상의 리스너가 특정한 이벤트에 붙으면 메모리 누수를 막는것을 돕기위해 경고 메세지를 프린트해준다. 

특정개수의 리스너를 붙일 경우 emitter.setMaxListenrs(n) 매개변수에 개수를 입력해주면 된다. 
(무한개로 지정할 때는 매개변수로 0 또는 Infinity 작성)

연결된 리스너의 개수를 알고 싶을때는 emitter.listenerCount(eventName) 로 해당 이벤트에 등록된 리스너 개수를 반환해준다. 개수 말고 리스너 배열 자체를 반환할 때는 emitter.listener(eventName) 
사용하면 된다.

 

이벤트 삭제하기

이벤트 삭제할때는 emitter.removeListener(eventName, listener) 메서드를 사용하면 특정 이벤트의 이벤트리스너를 제거할 수 있다. 

const callback = (stream) => {
  console.log('someone connected!');
};
server.on('connection', callback);
// ...
server.removeListener('connection', callback);
// 연결되었던 callback함수가 삭제

 특정 리스너를 지울 수 도 있고 emitter.removeAllListeners([eventNam]) 메소드로 해당 이벤트에 모든 리스너를 제거할 수도 있다.

 

이벤트 호출하기

 emitter.emit(eventNam[, ...args]) 순서대로 각 리스너를 등록된 이벤트에 맞게 호출한다.
또한, 메소드가 존재하면 true, 존재하지 않으면 false를 반환한다. emit 메소드로 이벤트를 강제로 호출하게 되는 경우 리스너는 실행되지만 실제 이벤트가 발생하는것은 아니라는 것은 주의해야한다. 

 

 

다음번에는 외부모듈을 집중적으로 알아보겠습니다. 

Node.js의 대부분이 외부모듈 형태로 동작하는데 멋진 플러그인들이 많이 준비되있습니다.

웹 관련해서 EJS 모듈과 Jade 모듈과 다른 개발자들이 배포한 모듈 몇가지를 중점으로 살펴보도록 하겠습니다.

 

 

참조 - https://nodejs.org/docs/latest-v7.x/api/events.html#events_emitter_emit_eventname_args

 


Node.js 시리즈 1,2편에 대한 내용을 살펴보고 싶다면, 아래 링크를 클릭해주세요.

▶ Node.js 기초부터 튼튼히 (1) 시작하기

▶ Node.js 기초부터 튼튼히 (2) 내장 모듈


 

 

 

 

New Multi-Channel Dynamic CMS