배틀스타 갤럭티카(Battlestar Galactica)라는 미국 SF 드라마를 약 10년만에 다시 보기 시작했다.

Flight of The Phoenix (시즌 2, 에피소드9)을 보던 중에

드라마 내용이 직장 생활과 겹쳐지며 이런 저런 생각이 들기 시작해서 몇 가지 정리하고 싶은 생각이 들었다.


Flight of The Phoenix에서는 오랫동안 강력한 적(Cylon)을 피해 도망 다니는 반복되는 삶이 그려지는데,

지친 병사들은 서로에게 도전적인 태도를 취하게 된다.

오래된 장비들은 자꾸 문제를 일으키고, 서로를 탓하며 짜증내는 가운데, 이러한 태도는 상관에게도 망설임이 없어진다.

그러던 중 갑판장 (Chief)은 자신에게 문제가 있다고 생각하고, 해결 방법을 찾던 중에 새로운 비행체 제작을 시작한다.

처음에는 모두 부정적이라 업무 시간 이후에 혼자서 시작한다.

당연하지만 여러 어려움을, 여러 사람들이 참여하면서 새로운 비행체를 완성하고 갈등을 풀어간다는 것이 주요 내용이다.


서로가 서로를 도전적으로 대하는 모습에,

그리고 그 모습을 (강력한 적에 대항하기 위해) 어쩔 수 없이 참고 대해야 하는 모습에 직장 생활이 겹쳐졌다.

같은 자리, 같은 사람, 같은 일…

5년, 10년 반복되는 직장 생활이 그대로 표현된 것 같았다.

서로의 이해 관계에 의해 떠나지 못하는 자와 떠나 보내지 못하는 모습이 똑같아 보였다.


다른 점은 문제의 해결 방법인 것 같다.

드라마에서는 우주에서 다른 곳으로 갈 데가 없지만, 현실에서는 퇴사를 하는 경우가 대부분 일 것이다.

드라마에서는 퇴사 없이 해결하는 방법으로 새로운 프로젝트의 시작을 제시하였다.

모든 것이 익숙하고 오랜 된 것에, 무엇인가를 새로운 것이 만들어 진다는 것이 활력이 될 것 같다.

SW 개발 업종에서는 보다 도전적인 내부 프로젝트 참여나, 개인적으로 진행하는 개발 프로젝트가 지친 직장생활에 활력이 될 수 있을 것이다.

특히, SW 개발자는 고객이나 직장 상사의 지시에 의해 개발을 하는 을의 입장인 경우가 많다.

자신이 생각하고 자신이 자신에게 지시하는 개인 프로젝트가 오랜 직장 생활(Flight of The Phoenix)을 유지할 수 있는 방법 중 하나가 아닐까 싶다.

누군가 같이 하면 더 좋을 것이고, 그것이 오픈 소스를 하는 의미 중 하나가 아닐까 한다.


Flight of The Phoenix이 자신의 임무에 충실한 직원들의 이야기라면,

이후의 에피소드(ep 10 이후)에서는 말 안 듣는 부하들 이야기가 많이 나온다.

아폴로와 스타벅이 대표적이다.

상명하복을 중시한다는 군대에서 두 사람은 드라마 내내 상관을 무시하고, 상관과 싸운다.

하지만 아이러니 하게도 가장 유능한 조종사로 그려진다.

상관인 제독의 명령에 따르지 않고 단독으로 움직여 부활선의 존재와 같은 중요한 정보를 얻기도 하고 (Resurrection Ship, ep 11),

페가수스 함장의 잘못된 판단을 막기 위해 고군분투 한다 (The Captain's Hand, ep 17).

(The Captain's Hand는 유능한 엔지니어가 별도의 교육 없이 리더가 되면 조직을 말아 먹을 수 있다는 교훈을 주는 것 같다.)


회사에서도 유능한 직원일수록 상사의 말을 잘 안 듣는다.

자신이 옳다고 생각하는 일을 하기 때문으로, 불평도 많고 말도 많다.

주어진 일에 대하여 납득이 되지 않으면 일을 하지 않기도 한다.

이들이 상사가 되면 현실에서는 엄청난 고통이 따르기도 한다.

영화나 현실이나 모두들 조직의 목적을 위해 움직이는 것으로 협력하는 방법을 찾는 것이 중요하고 해결 방법은 없어 보인다.

그냥 자신들의 길을 가면서 충돌하기도 하고 협력하기도 할 뿐…


다만, 스타벅은 자신이 상관에게 했던 행동을 그대로 돌려받는다.

술 주정뱅이 부함장이라며 무시하고 따르지 않았던 스타벅은,

자신이 리더가 된 후 갈등하는 상황에서 부하에게서 주정뱅이니 무능력자니 하는 말을 들으며 무시 받는다 (scar, ep 15).

흔하지 않지만 직장생활에서 종종 보던 장면이고, 직접 당해본 장면이다.

적(scar)을 잡기 위해 부하에게 기회를 제공하는 장면에서는,

제아무리 뛰어난 능력의 상관이라도 목표를 이루기 위해서는 부하 직원을 밀어줘야 하는 것이 상관이 아닐까 라는 생각이 들었다.


이외에도 부드러운 리더쉽의 윌리엄 아다마 함장, 꼰대 스타일의 사울 타이 부함장, 독선적인 헬레나 케인 제독, 자신 만을 위해 사는 가이우스 발타 등

다양한 캐릭터가 모두 때로는 실수하고, 때로는 싸우고, 때로는 도우면서 드라마가 진행되어 간다.

다양한 캐릭터가 모두 때로는 실수하고, 때로는 싸우고, 때로는 도우면서 직장 생활이 진행되어 간다.

자기 중심적인 가이우스 발타만 대부분 악역을 하지만, 살기 위해서 때로는 로슬린 대통령을 살리는 일을 하기도 한다 (ep 13).



팀장과의 관계가 힘들어 부서 변경을 한 직원 때문인지,

갈등 속에 자신의 발전을 위해 이직하는 직원 때문인지,

이렇게 우주를 배경으로 하는 SF 드라마가 직장 생활을 표현 한 것 같다 (특히 시즌 2).




'개발 이야기' 카테고리의 다른 글

개발자가 하면 안 좋은 일  (2) 2018.10.28
두 마리 소 II  (0) 2018.09.11
4살과 국회의원  (0) 2018.04.12
두 마리 소  (0) 2017.10.13
꿩 잡는 게 매다  (1) 2017.08.05

이미지 편집기 PaintWeb은 웹에서 이미지를 편집할 수 있는 웹 어플리케이션으로

Mihai Şucan이 Javascript로 제작하였다.

윈도우의 그림판을 웹으로 구현했다고 할 수 있다.

라이브러리데모만 제공되고,

사용법(특히 서버에서 이미지 저장하는 방법)에 대한 설명이 부족해서,

Spring4 기반으로 이미지를 저장하고 관리하는 예제(github)와 설정 파일 수정하는 방법(블로그) 등을 이전에 정리하였다.


이후 필요하여, 이미지 회전, 텍스트 입력, 확대/축소 후 저장 기능 등을 추가 구현하였다.

이미지 회전은 90도 단위로 이미지를 회전하는 기능으로 새로 추가하였다.

텍스트 입력은 기존에 있던 기능으로 불편하게 구현된 것을

기존의 그림판처럼 그림(canvas) 위에서 문자를 입력하도록 구현하였다.

확대/축소 후 프로그램을 종료하면 저장되지 않던 것을 저장되도록 변경하였다.

해당 예제는 기존의 github에서 받을 수 있고,

github에서 다운 받아 실행하는 방법은 다른 문서에 정리되어 있다.

이 문서에서 github 주소와 다운받을 폴더명만 적절하게 바꾸어 주면 된다.

설치 후 실행해서 확인 할 수 있고,

저장기능을 제외한 기능을 데모 페이지에서 확인할 수 있다.


github에서 다운 받으면 다음 그림과 같이 파일들이 저장된 것을 볼 수 있다.

webapp > js > paintweb 폴더가 PaintWeb 라이브러리 폴더로

사용하고자 하는 프로젝트의 폴더에 복사해서 사용하면 된다.

webapp > WEB-INF > jsp > imageEditor.jsp 파일이 PaintWeb을 실행하는 파일로 작성된 코드는 다음과 같다.

먼저, PaintWeb 라이브러리를 사용하기 위해서는 Paintweb.js 파일을 가져와야 한다 [라인7].

이외에도 html2canvas.min.js를 사용하는데 [라인 8],

html2canvas는 html을 이미지로 변환하는 라이브러리로

사용자가 입력한 텍스트를(HTML) 이미지로 변환하기 위해 사용하였다.

Chrome에서는 문제가 없지만, 그 외의 웹 브라우저에서는 속도가 조금 떨어지는 현상이 발생한다.


PaintWeb은 이미지 태그(editableImage [라인 11])의 내용을 읽어서[라인 34] 편집한다.

기본적으로 제공되는 예제는 메인 페이지에서 이미지(uploadImage)를 지정하고,

별창에서 PaintWeb을 실행하는 방식으로 구현했다.

(PaintWeb 관련 코드만 보이기 위해서 별창으로 구현했다.)

따라서 별창에서 메인 페이지에 있는 이미지(uploadImage)를 가지고 와서 [라인 19]

별창의 이미지 태그(editableImage)에 넣어주고 [라인 22]

이 이미지를 PaintWeb이 사용하도록 코드를 작성하였다 [라인 34].

메인 페이지의 이미지를 바로 지정하지 않고 이렇게 한 것은 PaintWeb이 부모(opener)의 이미지를 인식하지 못해서 그런 것으로,

중요한 버그가 아니라 수정하지 않았다.


PaintWeb을 생성하고 [라인 31],

각자의 환경에 맞추어 설정을 한 후, 실행하면 된다 [라인 39].

설정과 관련된 보다 자세한 설명은 이전 문서를 참고하면 되고,

새로 추가한 imageSaveURL, imageDownloadURL, afterImageSave를 정리하면 [라인 36~38]

imageSaveURL은 PaintWeb에서 저장한 이미지를 서버에서 받아서 저장할 URL을 지정한다.

저장할 이미지는 Base64로 전송이 되기 때문에 이 내용을 받아서 파일로 저장하는 코드가 있는 URL (스프링의 경우 컨트롤)을 지정하면 된다.

스프링으로 작성된 saveImage의 코드는 여기서 확인할 수 있다.

imageDownloadURL은 이미지를 저장하고, 다시 받아오기 위한 URL로 imageSaveURL과 마찬가지로 ImageEditor.java 파일에 구현되어 있고 github에서 확인할 수 있다.

afterImageSave는 저장 후 호출되는 함수로,

여기서는 이미지 저장 후 별창을 닫도록 하였다(close) [라인 43].


이외의 설정이나 상세한 설명은 이전 문서를 참고하면 된다.


이상으로 수정된 PaintWeb에 대하여 정리하였고,

저장기능을 제외한 기능을 설치없이 데모 페이지에서 확인할 수 있다.




조직(기업) 구성원의 협업 지원 소프트웨어인 그룹웨어9(Groupware9)은

과제 관리 시스템 PMS9 (Project Management System)에 이은 두번째 프로젝트로

빠른 개발을 위해 만든 Java 웹 프로젝트 템플릿인 Project9 (Spring 4 + MyBatis 3 + MariaDB)을 기반으로

그룹웨어의 주요 기능을 간단하게 구현한 웹 프로젝트이다.

그룹웨어의 주요 기능인 전자결재, 일정관리, 메일 관리 기능을 구현한 웹 사이트로,

초보자들의 학습용으로 제작하였다.

즉, 구현된 주요 기능에 보완 기능을 추가하거나, 버그를 수정하면서 전체적인 개발 개념을 잡기 위한 용도로 작성하였다.

소스는 github에서 다운 받을 수 있다.

설치 방법은 Project9과 동일하기 때문에 Project9 설치를 참고하면 되고,

설치시 guthub 주소를 다음과 같이 바꾸어 주면 된다.

       https://github.com/gujc71/groupware9.git

설치를 완료한 뒤,

웹 브라우저에서 http://localhost:8080/groupware9/로 접속하여 확인한다.



다음은 간단한 화면 설계서 이다.




테이블 구성(ERD)은 다음과 같다.

Project9은 AmaterasERD로 작성되어 project9.erd 파일로 제공된다.

Groupware9은 그룹웨어와 관련된 테이블들을 ERMaster로 별도 제작하였다 (groupware9.erm).

ERMaster 설치는 간단한 검색으로 설치할 수 있다.


논리모드

물리모드


마지막으로 그룹웨어 기능과 관련된 프로그램 명세서 이다.

명세서 우측에 2개의 필드를 추가하여,

CRUD(Create, Read, Update, Delete)의 어떤 기능을 수정한 것인지 표시 하였다.

실제로 그룹웨어의 대부분 기능을 Project9의 CRUD 예제를 복사/붙여넣기 하여 구현하였다(Copy, Paste, Replace).

관련 방법(?)은 PMS9에서 정리하였다.


메뉴

Action (URL)

설명

CRUD

주요 SQL

전자결재

signListTobe 결재 받을 문서 리스트 리스트 SELECT
signListTo 결재 할 문서 리스트 리스트 SELECT
signDocTypeList 기안양식 선택 리스트 SELECT
signDocForm 기안작성 SELECT
signDocSave 작성된 기안 저장 저장 INSERT / UPDATE
signDocRead 작성된 기안 읽기 읽기 SELECT
signDocDelete 기안 삭제 삭제 DELETE
signDocCancel 기안 회수 상태변경 UPDATE
signSave 기안 결재 상태변경 UPDATE

일정관리

schList 일정 리스트 (월) 리스트 SELECT
schForm 새로운 일정 작성 SELECT
schSave 작성된 일정 저장 저장 INSERT / UPDATE
schRead4Ajax 작성된 일정 읽기. Ajax 용으로 리스트에서 마우스 이동시 사용 읽기 SELECT
schRead 작성된 일정 읽기 읽기 SELECT
schDelete 작성된 일정 삭제 삭제 DELETE

메일

receiveMails 받은 메일 리스트 리스트 SELECT
receiveMailRead 받은 메일 읽기 읽기 SELECT
receiveMailDelete 받은 메일 삭제 삭제 DELETE
receiveMailsDelete 받은 메일들 삭제 삭제 DELETE
sendMails 보낸 메일 리스트 리스트 SELECT
sendMailRead 보낸 메일 읽기 읽기 SELECT
sendMailDelete 보낸 메일 삭제 삭제 DELETE
sendMailsDelete 보낸 메일들 삭제 삭제 DELETE
mailForm 새 메일 작성  DELETE
mailSave 작성된 메일 저장 저장 INSERT / UPDATE

메일 서버 정보

getReceiveMail 메일 서버 정보 등록후 기존 받은 메일 가지고 오기    
mailInfoList 등록된 메일 서버 정보 리스트 리스트 SELECT
mailInfoForm 메일 서버 등록 폼  SELECT
mailInfoSave 작성된 서버 정보 저장 저장 INSERT / UPDATE
mailInfoDelete 서버 정보 삭제 삭제 DELETE








'Java > 기타' 카테고리의 다른 글

과제 관리 시스템 (PMS9)  (8) 2016.12.01
Project9  (8) 2016.09.18
Project9 설치  (5) 2016.09.18
데이터를 Excel 파일로 다운로드  (2) 2016.09.18

몇 개월 전에 가족들과 같이 어떤 모임에 참석하고 다같이 저녁을 먹으러 갔다.

저녁을 먹는 중에 유명한 국회의원이 식당에 들어왔다.

앉아서 식사를 하던 사람들이 일어나 한 명씩 그 국회의원과 악수를 했고,

우리 딸도 일어나 90도로 인사를 했다.

사람에 가린 것인지, 악수를 하다가 못 본 것인지 그냥 지나쳤고 딸은 시큰둥하게 내 무릎 위에 앉았다.

사람들이 길게 늘어서 앉아 있는 상황에 한쪽 끝에서 국회의원이 이야기를 했고,

우리는 반대쪽 끝에 앉아 있었다.

모든 사람들이 국회의원의 말을 경청 하는데,

맑고 청아한 목소리가 날카롭게 퍼졌다.

“아이, 시끄러워”

딸이 자기 인사를 안 받아줘서 삐진 것을 표시한 것이었다.

딸의 인사를 안 받아준 것을 아는 이쪽 사람들은 웃었고,

상황을 모르는 국회의원 근처의 사람들은 순간 멈칫하다가 이야기를 이어갔다.


자기를 무시했다고 생각하니 나름의 복수(?)내지는 의견을 표시한 것 같았다.

4살짜리도 무시 받았다는 생각에 감정이 상한 것 같았다.


그런데, 자기 생각이 강한 어른이면 어떨까?

무시 받고 참는 사람은 어떤 생각을 하는지 궁금해졌다.

무시 받고 복수하는 악인들 이야기들도 생각났다.

내가 무시 받은 적을 떠올려도 봤고, 내가 남을 무시했던 기억도 찾아봤다.


SW 개발을 하면서

하나 더 안다고 남을 무시하고

모른다고 무시당하는게 흔하다는 생각으로 이어졌다.

이전에 많이도 남을 무시한 것 같다.

다행이 나이가 들면서 최근 몇 년 동안에는 그런 기억이 없는 것 같다 (기억 못할 수도…).


실수한 신입을 크게 나무라는 과장을 보며


그림과 같이 입력한 값을 보여주는 입력상자(EditText) 하나와 15개의 버튼으로 구성된 간단한 계산기를 만들면서 안드로이드 프로그래밍 기초를 정리한다.

정리 방법은 다음과 같이 단계별로 예제를 구현하고, 예제 소스는 GitHub에서 받을 수 있다.

  1. 계산기 디자인과 기본 코드 (branch: master)
  2. 생성한 클래스를 이벤트 리스너로 사용 (branch: step1)
  3. 코드 줄이기  (branch: step2)
  4. 실제 계산 기능 구현 (branch: step3)
  5. 후위 표기법으로 계산 기능 구현 (branch: step4)
  6. 동적 생성 (branch: step5)

각각의 단계별 예제는 GitHub의 branch에서 받을 수 있다.


먼저, 계산기를 그림과 같이 디자인 하면서

레이아웃(Layout) 사용법과 디자인된 개체에 이벤트를 연결하는 방법을 익힌다.


안드로이드 스튜디오에서 새로운 프로젝트를 생성하고,

디자인을 위해 activity_main.xml (app/src/main/res/layout/) 파일을 열어서 다음과 같이 코드를 작성한다.


전체코드

전체 레이아웃을 LinearLayout으로 지정하여 생성하고 [라인 1],

전체 레이아웃의 자식으로 EditText(입력상자) 하나 [라인 10]와 4개의 LinearLayout를 생성한다 [라인 18~].

하위에 있는 4개의 LinearLayout은 각각 4개(마지막은 3개)의 버튼을 자식으로 가진다 [라인 23~].

자식으로 가진다는 의미는 XML에서 <LinearLayout>와 </LinearLayout>사이에 코딩 한다는 의미이다.


간단하게 LinearLayout, EditText, Button의 주요 속성을 정리한다.

LinearLayout의 layout_width와 layout_height는 위젯(widget)의 크기를 의미한다.

너비(width)와 높이(height)를 지정하는 것으로 match_parent나 wrap_content로 지정하거나 직접 값을 dp단위로 입력할 수 있다.

match_parent는 부모 크기에 100%로 맞추는 것이고,

wrap_content는 그 위젯의 내용물(content)에 맞추어 자동으로 크기를 조절하는 것이다.

여기에서는 스마트폰의 해상도에 맞게 크기가 조절되도록 값을 직접 입력하지 않고,

match_parent나 wrap_content를 이용하였다.


최상위 LinearLayout은 너비와 높이를 모두 부모에 맞추었다 [라인 5, 6].

최상위 LinearLayout은 부모가 없기 때문에 휴대폰 크기에 맞추

orientation은 자식 위젯의 나열 방향을 의미하는 것으로

EditText와 4개의 LinearLayout는 수직(vertical)으로 쌓았고, 각 버튼들은 수평(horizontal)으로 쌓았다.

LinearLayout외에도 FrameLayout,TableLayout, GridLayout, RelativeLayout 등이 있다.


EditText는 사용자가 입력하는 값을 받는 위젯으로 너비와 높이는 레이아웃과 동일하게 사용한다 [라인 10].

너비는 화면 너비만큼[라인 12], 높이는 컨텐츠 높이로 지정하였다 [라인 13].

id는 Java에서 XML에 작성된 위젯을 찾기 위한 식별자를 의미한다 [라인 11].

실제 Id의 값은 @+id/위에 지정되고, 지정된 id는 안드로이드에서 관리된다.

gravity는 입력한 값에 대한 정렬을 의미하는 것으로 왼쪽(left), 오른쪽(right), 위쪽(top) 등으로 지정할 수 있다 [라인 16].


Button의 id, 너비, 높이는 EditText나 Layout과 동일한 개념이고,

버튼에 표시할 텍스트를 나타내는 text 속성과 가중치(layout_weight) 속성 등이 있다.

현재 Button이 4개 생성되었고, 버튼의 너비는 컨텐츠 길이만큼(wrap_content) 생성하도록 지정하였다.

이경우 화면이 Button의 너비 합보다 크기 때문에 우측에 여백이 생기게 된다.

이 여백을 각 Button의 가중치만큼 나누어 가지도록 하는 속성이 layout_weight이다 [라인 27,~].

모두 1씩 지정하였으니 같은 비율로 커지게 된다.


디자인을 마쳤으니, MainActivity.java (/src/main/java/com/gujc/calculatorsample/)을 열어서 다음과 같이 작성한다.


전체코드

먼저 사용할 위젯을 변수로 지정하여 Java에서 제어한다.

EditText는 TextView 클래스로 선언한 editText (인스턴스)변수로 제어한다 [라인2].

이 변수와 EditText 위젯을 연결하기 위해 findViewById 함수를 사용한다 [라인10].

위젯 생성시 지정한 id를 이용하여 해당 위젯을 찾아서 변수에 할당하고,

이 클래스 변수를 이용하여 값을 가지고 오거나 지정한다.


15개의 버튼 중 2개의 버튼만 코드로 작성했다 (나머지는 각자).

button0은 클래스내에서 전역 변수로 [라인 3],

button1은 onCreate 함수(이벤트)내에서 사용하는 지역 변수로 선언하였다 [라인20].

개념을 위해 이렇게 한 것으로 용도에 따라 전역으로 사용할 것인지, 지역으로 사용할 것인지 결정하면 된다.


setOnClickListener를 이용하여 각 버튼을 클릭했을 때 [라인 14, 22],

editText에 버튼의 값이 찍히도록 작성하였다.

클릭된 버튼의 값을 얻기(getText) 위해 클릭된 버튼을 알아야 한다.

여기서는 button0, button1을 각각 다르게 두가지 방식으로 작성했다.

button0은 클릭하면 button0의 값을 가지고 오도록 했고 [라인 16],

button1은 클릭 이벤트의 파라미터를 이용하였다 [라인 24, 25].

클릭 이벤트의 파라미터는 클릭된 개체(버튼)를 넘겨 주는데, Button 클래스가 아닌 클래스들의 부모(View)로 넘겨준다.

따라서 이것을 버튼으로 형변환 한 뒤에 사용하면 된다 [라인 24].

button0 코딩보다 button1 코딩이 더 깔끔하고 방어가 잘 된 코딩인데

그 이유는 나머지 13개의 버튼에 대하여 클릭 이벤트를 작성해 보면 알 수 있다.

버튼을 클릭하면 클릭된 버튼의 값만 출력된다.

앞서 클릭된 모든 버튼의 값이 이어서 출력되도록 작성해 보길 바란다.


모든 버튼에 클릭 이벤트를 작성하면,

두 번째 방식아 코드 양은 한 줄 더 많지만, 복사/붙여넣기 하고 수정하는 글자 수가 적다는 것을 알 수 있다.

이외에 더 많은 장점이 있다.

두 번째 예제로 이 장점을 정리하고, 이벤트 코드 양을 줄이는 방법을 정리한다.


전체 코드

앞서의 예제와 다르게 이상의 코드는 위젯을 찾고 이벤트 리스너를 등록하는 코드에서 button0, button1 등의 변수를 사용하지 않았다 [라인 2-16].

클래스 변수로 뭔가를 할 경우에는 사용하지만, 그렇지 않다면 생략하고 사용해도 된다.


첫 예제와 다른 것은 이벤트 클래스를 생성해서 사용하는 것이다.

첫 예제는 다음 코드처럼 OnClickListener 클래스를 생성(new)해서 변수에 담지 않고 버튼의 이벤트 리스너로 지정했다.

             button0.setOnClickListener(new View.OnClickListener() {

이번 예제는 생성해서 mClickListener 변수에 담아서 사용한다 [라인 19].

             Button.OnClickListener mClickListener = new View.OnClickListener() {

그리고 이 mClickListener변수를 모든 버튼에 지정했다.

즉, 15개의 버튼이 하나의 이벤트 리스너로 처리 되는 것이다.


OnClickListener 이벤트에서는 사용자가 클릭한 버튼을 파라미터(View)로 제공한다 [라인 20].

클릭한 버튼(view)의 고유번호(id)를 이용하여(getId()) [라인 21],

클릭한 버튼이 Button0이면 입력상자(editText)에 0을 넣고,

클릭한 버튼이 Button1이면 입력상자(editText)에 1… 등을 넣어 준다.

코드를 2개의 버튼에 대해서만 작성했지만, 15개 버튼에 대해서 모두 작성해야 한다.


이렇게 클릭된 버튼이 무엇인지 파악해서 필요한 행동을 하게 작성하는데 switch 문(다수의 if)이 사용되었다 [라인 22, 25].

이러한 코드는 일반적인 코드 작성법이지만 15개의 버튼에 대해서 작성한다면 아주 많은 코드를 작성해야 한다.

이 부분에 정리는 뒤에 하고 여기서는 넘어간다.


여기서 id (getId())는 고유한 숫자 값이다 [라인 21].

앞서 xml에서 지정한 id는 문자열이지만,

이 값을 안드로이드가 숫자로 변환해서 가지고 있고, 개발자가 지정한 문자는 일종의 상수처럼 선언되어 사용된다.

그래서 id를 사용할 때 “button0”처럼 사용하지 않고, R.id.button0으로 사용한다 [라인 22, 25].


이 예제 코드는

15개의 findViewById() 함수와

15개의 case 문 (실제로는 if문)이 사용되었다.

더욱이 이 예제는 버튼을 클릭하면 해당 값만 출력되지만 다음 코드와 같이 누적되게 작성하면 더 복잡하게 보인다.

           editText.setText(editText.getText().toString()  + "0");

첫 예제에서 클릭된 변수를 인식하는 2번째 방식을 이용하여 [라인 24],

복잡한 코드를 다음과 같이 줄여서 구현할 수 있다.

    Button.OnClickListener mClickListener = new View.OnClickListener() {
        public void onClick(View view) {
            Button button= (Button) view;
            editText.setText(editText.getText().toString() + button.getText().toString());
        }
    };


개념은 아주 간단하다.

클릭된 버튼이 무엇인지 알아야 했던 이유는

해당 버튼이 (view.getId())

무엇인지 (button0, button1, button2...) 알아서 해당 값(0, 1, 2 ...)을 지정하기 위한 것이었다.

1번 버튼이 클릭되면 값 1을 지정하기 위한 것이다.

이 1 값은 버튼의 텍스트(text)로 지정되어 있으니,

버튼이 클릭되면 해당 버튼의 값(getText())을 editText의 값에 넣어준다 (setText()).


세 번째 예제로, 15번이 사용된 findViewById() 함수를 줄여본다

두 번째 예제의 코드를 살펴보면,

버튼의 id가 button0, button1, button2, button? … 으로 숫자만 바뀌는 것을 알 수 있다.

따라서, 반복문을 이용하여 0부터 14까지 15번을 반복하고,

해당 id를 문자열로 생성해서 가지고 올 수 있으면, 다음과 같이 몇 줄로 구현할 수 있다.

    for(int i=0; i<15; i++) {
        String buttonID = "button" + i;
        int resID = getResources().getIdentifier(buttonID, "id", getPackageName());
        findViewById(resID).setOnClickListener(mClickListener);
    }

전체 코드

"button" 문자열과 변수 i의 값을 이용하여, 15 버튼의 id를 문자열로 생성한다 (buttonID).

앞서서 몇 번 정리했지만,

안드로이드는 xml에서 문자열로 지정한 id를 고유 번호를 부여해서 따로 관리하기 때문에 변수buttonID의 값으로 찾을 수 없다.

따라서 리소스 정보에 접근할 수 있는 getResources()를 이용하여, 지정된 문자열에 맞는 id 값을 받고 (resID),

이 id를 findViewById()함수로 찾아서 구현한다.


안드로이드(Android) 프로그래밍은 간단한걸 구현해도 제법 많은 코드가 필요하다.

그래서 Kotlin 등이 각광 받을 테지만

이상의 방법과 같이 작성하면

Android를 사용하든 Kotlin을 사용하든, 쉽고 가독성 좋은 코드를 작성할 수 있을 것이다.


다음으로 실제 계산하는 기능을 구현한다.



부록

지금까지 Java(Android) 사용법과 코드를 줄이는 방법을 정리하였다.

이번에는 화면(XML)에서 코드를 줄이는 방법을 간단하게 정리한다.


다음의 activity_main.xml 파일을 자세히 보면,

모든 위젯은 너비(layout_width)와 높이(layout_height)가 있고,

Layout은 orientation가,

Button은 layout_weight가 동일하게 (“=” 버튼 예외) 사용된 것을 알 수 있다.

        ~~ 생략 ~~   
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/button7"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="7"
            android:textSize="14sp" />

        <Button
            android:id="@+id/button8"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="8" />
        ~~ 생략 ~~   

전체코드

이렇게 디자인과 관련된 코드는 웹 개발처럼 스타일(Style)로 미리 지정해서 사용하면 쉽게 사용할 수 있다.

app/src/main/res/values/styles.xml 파일을 열어서 다음과 같이 작성한다.

<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

    <style name="layoutStyle">
        <item name="android:layout_width">match_parent</item>
        <item name="android:layout_height">wrap_content</item>
        <item name="android:orientation">horizontal</item>
    </style>
    <style name="buttonStyle">
        <item name="android:layout_width">wrap_content</item>
        <item name="android:layout_height">wrap_content</item>
        <item name="android:layout_weight">1</item>
    </style>
</resources>

전체코드

layoutStyle은 LinearLayout에 공통으로 사용할 스타일이고

buttonStyle은 Button에 공통으로 사용할 스타일이다.

공통으로 사용할 스타일을 styles.xml 파일에 미리 등록해서 사용한다.


그리고, 다음과 같이 activity_main.xml 파일을 수정한다.

        ~~ 생략 ~~   
    <LinearLayout  style="@style/layoutStyle">
        <Button
            android:id="@+id/button7"
            android:text="7"
            android:onClick="mClickListener"
            style="@style/buttonStyle"/>

        <Button
            android:id="@+id/button8"
            android:text="8"
            android:onClick="mClickListener"
            style="@style/buttonStyle"/>

        <Button
            android:id="@+id/button9"
            android:text="9"
            android:onClick="mClickListener"
            style="@style/buttonStyle"/>
        ~~ 생략 ~~   
       

전체코드

기존의 반복 코드를 지우고,

style 속성에 layoutStyle, buttonStyle을 지정해서 사용한다.

HTML의 CSS 클래스처럼 사용하는 것이다.


activity_main.xml 파일을 보면,

모든 버튼에 다음 코드가 추가 된 것을 볼 수 있다.

         android:onClick="mClickListener"

이벤트 리스너를 Java에서 지정한 것이 아니고,

HTML처럼 디자인에서 지정하였다.

버튼을 클릭하면(onClick) mClickListener함수를 호출한다.


MainActivity.java 파일을 다음과 같이 수정해야 한다.

    public void mClickListener (View view) {
            Button button= (Button) view;
            editText.setText(editText.getText().toString() + button.getText().toString());
    };

전체코드

기존에는 mClickListener를 OnClickListener 클래스의 변수로 선언해서 사용했는데,

이 클래스 변수를 디자인(xml)에서는 알 수가 없기 때문에

전역(public) 함수로 선연해서 디자인에서 호출할 수 있도록 해준다.




그림과 같이 입력한 값을 보여주는 입력상자(EditText) 하나와 15개의 버튼으로 구성된 간단한 계산기를 만들면서 안드로이드 프로그래밍 기초를 정리한다.

정리 방법은 다음과 같이 단계별로 예제를 구현하고, 예제 소스는 GitHub에서 받을 수 있다.

  1. 계산기 디자인과 기본 코드 (branch: master)
  2. 생성한 클래스를 이벤트 리스너로 사용 (branch: step1)
  3. 코드 줄이기  (branch: step2)
  4. 실제 계산 기능 구현 (branch: step3)
  5. 후위 표기법으로 계산 기능 구현 (branch: step4)
  6. 동적 생성 (branch: step5)

각각의 단계별 예제는 GitHub의 branch에서 받을 수 있다.


앞서의 예제에서는 계산기 화면을 쉽고 간단하게 만드는 방법을 정리했다.


네번째 예제로, 이번에는 실제 계산이 가능하도록 구현한다.

실제 계산기로 계산하는 과정을 정리하면,

     ① 사용자가 숫자를 입력하고

     ② 사칙 연산자를 선택한 뒤

     ③ 다시 숫자를 입력하고,

     ④ “=” 버튼을 클릭하여 계산을 하게 된다.

이처럼 계산기의 사용법 자체는 단순하다


이것을 조금 더 세분화해서 정리하면

     ① 사용자가 숫자를 입력하고

     ② 사칙 연산자를 선택하면

          - 현재까지 입력한 숫자는 첫 입력 값으로 저장하여 보관하고

          - 두 번째 값의 입력을 위해 기존 숫자는 지운다.

          - 선택된 연산자는 연산을 위해 보관한다.

     ③ 다시 숫자를 입력하고

     ④ “=” 버튼을 클릭하면 다시 입력한 숫자(③)를 두 번째 값으로 처리하여 두 값에 대하여 사칙 연산을 처리하고 결과 값을 출력한다.


여기까지는 쉬우니 (?) 직접 해보길 바라고, 조금 더 기능을 부여해서 구현해 본다.


① ,②, ③번의 과정은 동일하지만

④ 번에서 “=” 대신에 사칙 연산자를 선택할 수 있다.

④ 번을 세부화하여,

두 번째 값을 입력하고 사용자가 사칙 연산을 입력하면 앞서 입력한 두 값과 앞서 입력한 사칙 연산을 이용하여 계산한다.

“=” 버튼을 클릭한 것과 동일하다.

사칙 연산자를 선택한 것은 계산을 계속하겠다는 의미이므로, 계산 결과를 출력한 뒤 ⑤ 계산 결과를 첫 번째 값으로 보관한다.

사용자가 다시 숫자를 입력하고 사칙연산자나 “=”를 누르면 연산을 실행한다.

사칙 연산자를 누르면 ⑤번 과정을 반복하게 된다.

사칙 연산자가 아닌 “=”를 선택했을 경우, 그냥 계산 결과를 출력하고 사용자가 입력한 값들과 연산자를 초기화 해주면 된다.

다만, 다시 계산하기 위해 값을 입력하면 출력한 결과 값을 지우고, 사용자 입력 값이 출력되도록 하면 된다.

이 외에도 필요한 기능은 더 많지만 이 정도 수준에서 구현해 본다.

이 정도 수준이면 제법 복잡하고, 잡아야 할 버그도 제법 많다.


다시 정리하면

     ① 사용자가 숫자를 입력하고

     ② 사칙 연산자를 선택하면

          - 현재까지 입력한 숫자는 첫 입력 값으로 저장하여 보관하고

          - 두 번째 값의 입력을 위해 기존 숫자는 지운다.

          - 선택된 연산자는 연산을 위해 보관한다.

     ③ 다시 숫자를 입력하고

     ④ “=” 나 사칙 연산자 버튼을 클릭하면

          - 다시 입력한 숫자(③)를 두 번째 값으로 처리하여, 두 값에 대하여 사칙 연산을 처리하고 결과 값을 출력한다.

          - 클릭한 것이 사칙 연산자이면 계산 결과를 첫 번째 값으로 보관하고, 클릭한 사칙 연산자를 보관한다.

          - 사칙 연산자가 아닌 “=”를 선택했을 경우, 그냥 계산 결과를 출력하고 사용자가 입력한 값들과 연산자를 초기화 해주면 된다.


전체 코드

기존 예제의 OnClickListener 리스너를 다음과 같이 수정하면 된다.

코드가 복잡해 보이지만

앞서 정리한 문장을 이해하면 쉽게 이해할 수 있는 코드이다.

따라서 다음과 같이 문장과 코드를 같이 보면 된다.


① 사용자가 숫자를 입력하고 [라인 51]

② 사칙 연산자를 선택하면 [라인 18~44]

  - 현재까지 입력한 숫자는 첫 입력 값으로 저장하여 보관하고 [라인 18]

  - 두 번째 값의 입력을 위해 기존 숫자는 지운다 [라인 20].

  - 선택된 연산자는 연산을 위해 보관한다 [라인 42].

③ 다시 숫자를 입력하고 [라인 51]

④ “=” 나 사칙 연산자 버튼을 클릭하면 [라인 18~44]

  - 다시 입력한 숫자(③)를 두 번째 값으로 처리하여 [라인 23],

    두 값에 대하여 사칙 연산을 처리하고[라인 24~30] 결과 값을 출력한다 [라인 32].

  - 클릭한 것이 사칙 연산자이면 계산 결과를 첫 번째 값으로 보관하고[라인 40],

    클릭한 사칙 연산자를 보관한다 [라인 42].

  - 사칙 연산자가 아닌 “=”를 선택했을 경우[라인 36],

    그냥 계산 결과를 출력하고[라인 32]

   사용자가 입력한 값들과 연산자를 초기화 해주면 된다 [라인 46~48].


[주의] 이상의 예제를 실행하면 제법 많은 버그가 나온다. 찾아서 수정해 보길 바란다.

     - 숫자를 입력하고 사칙연산 선택, 또 사칙연산 선택시 오류

     - 숫자를 입력하고 “=” 입력시 오류 등 다양한 예외에 대한 처리가 필요하다.

다섯 번째 예제는 후위 표기법 방식으로 구현하는 것이다.

후위 표기법에 대한 개념은 검색 해보길 바라고 여기서는 간단하게 정리한다.

후위 표기법에 대한 개념이 없으면 다음 내용을 제대로 이해하기 어려울 수 있다.


앞서의 계산기는 연산자나 “=”을 입력하면 연산이 되도록 한 것이고,

이번에는 사용자가 입력한 모든 값과 연산자를 가지고 있다가 “=” 버튼을 클릭하면 계산하는 방식이다.

즉, 1 + 2 + 3 + 4 - 5 (수식) 를 입력하면 입력 박스에 그대로 출력되고,

“=” 버튼을 클릭하면 이 값을 분석해서 계산하게 된다.


위 수식을 중위표기법이라 부르고, 이것을 후위 표기법으로 변환해서 처리하게 된다.

후위 표기법으로 변환하는 이유는 쉽게 코딩 할 수 있기 때문으로,

개인적인 개념으로 정리하면 그냥 숫자와 연산자를 분리 / 보관해서 처리하는 것이다.


따라서 기본 개념을 정리하면

1. 사용자가 입력한 수식을 숫자와 연산자(+-*/)로 분리한다.

2. 첫 번째 숫자와 두 번째 숫자를 첫 번째 연산자로 계산해서 결과를 저장한다.

3. 첫 번째 결과와 세 번째 숫자를 두 번째 연산자로 계산해서 결과를 저장한다.

4. 숫자가 없을 때까지 이전 결과와 다음 숫자를 다음 연산자로 계산해서 결과에 저장한다.

★ 연산시 곱하기(*)과 나누기(/)가 먼저 실행되어야 한다.


이 문장을 자세히 보면 2~4번은 같은 의미를 나타낸다.

2~4번 문장은 이전 결과와 다음 숫자를 다음 연산자로 계산해서 결과에 저장하는 것이다.

이 경우 2번 문장의 첫 번째 숫자가 문제가 된다.

첫 번째 숫자를 계산 전에 결과에 넣고, 결과와 다음 숫자를 연산하면 된다.


따라서, 다음과 같이 정리된다.

1. 사용자가 입력한 수식을 숫자와 연산자(+-*/)로 분리한다.

2. 첫 번째 숫자를 결과에 저장한다.

3. 숫자가 없을 때까지 이전 결과와 다음 숫자를 다음 연산자로 계산해서 결과에 저장한다.

★ 연산시 곱하기(*)과 나누기(/)가 먼저 실행되어야 한다.


마지막으로 ★로 표기한 곱하기(*)과 나누기(/)가 수식에서 어디에 있던 먼저 실행되도록 해야 한다.

먼저 실행되게 하는 방법은,

연산자가 더하기(+)와 빼기(-)일 때는 그냥 넘어가고, 곱하기(*)과 나누기(/)일때만 처리한다.

그리고, 곱하기(*)과 나누기(/)가 끝나면 다시 더하기(+)와 빼기(-)를 처리한다.


따라서, 두 번의 반복문(While)이 필요한데, 약간의 트릭을 사용한다.

곱하기(*)과 나누기(/) 처리시 더하기(+)일 때는 결과 값을 그대로 저장하고, 빼기(-)일 때는 결과값을 음수화( * -1)해서 저장한다.

이렇게 하면 곱하기(*), 나누기(/), 더하기(+)의 결과는 그대로 저장될 것이고, 빼기는 결과값이 음수화되어 저장되니,

결과 값들을 모두 더하기 하면, 연산자 확인 없이 더하기(+)와 빼기(-) 처리가 된다.


다시 정리하면

1. 사용자가 입력한 수식을 숫자와 연산자(+-*/)로 분리한다.

2. 첫 번째 숫자를 결과에 저장한다.

3. 숫자가 없을 때까지 이전 결과와 다음 숫자를 다음 연산자로 계산해서 결과에 저장한다.

4. 이때, 곱하기(*)과 나누기(/)이면 연산해서 결과를 저장하고, 더하기(+)면 그대로 결과에 저장하고, 빼기면 음수화해서 저장한다.

5. 모든 결과들을 더해서 계산을 마친다.


전체코드

먼저, 사용자가 입력한 값이

"=" 이 아니면 (숫자나 연산자) 그대로 값을 추가해서 수식을 만들고 [라인 9],

"="이면 Calc() 함수를 호출해서 입력한 수식을 계산한다 [라인 7].


실제 계산을 하는 Calc() 함수는 다른 사람이 작성한 코드를 정리한 것으로

설명과 코드를 일치시켜서 정리하면,

1. 사용자가 입력한 수식(formulaStr)을 숫자[라인 16]와 연산자(+-*/)로 분리한다 [라인 15].

2. 첫 번째 숫자를 결과에 저장한다 [라인 19].

3. 숫자가 없을 때까지[라인 20] 이전 결과와 다음 숫자를 다음 연산자로 계산해서 결과에 저장한다 [라인 27, 31].

4. 이때, 곱하기(*)과 나누기(/)이면 연산해서 결과를 저장하고 [라인 25~32],

   더하기(+)면 그대로 결과에 저장하고, 빼기면 음수화해서 저장한다 [라인 33~38].

    5. 모든 결과들을 더해서 계산을 마친다 [라인 41~44].


계산 결과들을 저장하는 변수를 스택(Stack) 클래스를 사용한다 [라인 18].

스택(Stack)은 한 쪽 끝에서만 자료를 넣거나 뺄 수 있는 선형 구조(LIFO - Last In First Out)로,

왜 Stack을 사용한 것인지 스스로 생각해보길 바란다 (hint: LIFO).


학습을 위해 이상의 두 예제에 대한 개념을

파워포인트 등을 이용하여 플로우차트(Flowchart) 로 작성해 보면 많은 도움이 될 것이니 시도해 보길 바란다.


여섯 번째 예제는 동적 생성이다.

이전 예제는 XML 파일에 위젯들을 선언하고, Java 파일에서 이 위젯을 찾아서 사용했다.

이번에는 XML 파일이 아닌 Java파일에서 위젯들을 생성하는 방법을 정리한다.


개념을 익히기 위한 것이라 모든 위젯을 생성하지 않고, 4개의 레이아웃과 15개의 버튼만 동적으로 생성한다.

이 문제를 구현하기 위해서는 알아야 할 것은

동적으로 위젯(클래스)를 생성하는 방법 (new 클래스)과 하나의 레이아웃에 4개씩 버튼을 추가하는 방법이다.



그리고, 4개의 레이아웃과 15개의 버튼을 각각 생성하면 아주 많은 코드를 작성해야 한다.

따라서 다소 간단하게 생성하는 방법을 찾아야 한다.


위 그림을 토대로 구현 방법을 정리하면

    1. 생성할 버튼의 개수만큼 반복해서 버튼을 생성한다.

    2. 생성한 버튼에 필요한 속성을 지정하고,

    3. Layout에 추가한다.

    4. Layout에 버튼이 4개가 추가 되면, 새로운 Layout을 생성한다.

15개 버튼이니 4개의 Layout이 생성됨.


이 개념을 가지고 다음 코드를 살펴 본다.


    1. 생성할 버튼[라인 7]의 개수만큼 반복해서[라인 13] 버튼을 생성한다 [라인 20].

    2. 생성한 버튼에 필요한 속성을 지정하고 [라인 21~23],

    3. Layout에 추가한다 [라인 24].

    4. Layout에 버튼이 4개가 추가 되면 [라인 14], 새로운 Layout을 생성한다 [라인 15].

추가한 Layout과 버튼의 속성은 XML 파일에서 지정한 것과 동일하게 지정하면 된다.

“=” 버튼은 좀더 크게 만들어야 하는데 (예외), 생략했으니 직접 구현해 보길 바란다.


위 개념에는 없는 클래스(위젯) 생성과 부모에 추가하는 방법을 정리하면,

클래스는 new로 생성한다 [라인 15, 20].

생성된 클래스는 추가할 부모에 addView() 함수로 추가한다 [라인 18, 24].

XML에서는 태그 사이에 작성하는 것과 같다.


이상의 코드에서 버튼의 Layout 속성을 버튼을 생성할 때마다 지정하지 않고,

속성 클래스(LayoutParams) 하나를 생성해서, 생성하는 버튼에 지정하는 방식을 사용했다 [라인 10],

LinearLayout은 LinearLayout를 생성할 때 마다 매번 생성해서(new) 지정했다 [라인 16].

어느 방법이 더 좋을지 판단해 보길 바라고,

14라인에서 사용된 IF 문의 의미를 이해하고 넘어가길 바란다.


코드를 실행하기 전에 activity_main.xml에서

최상위 LinearLayout과 EditText를 제외하고는 모두 삭제한다.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
tools:context="com.gujc.mycalc1.MainActivity">

<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="text"
android:gravity="right" />
</LinearLayout>



AngularJS는 자바스크립트 기반의 오픈 소스 프론트엔드 웹 애플리케이션 프레임워크의 하나로,

싱글 페이지 애플리케이션 개발 중에 마주치는 여러 문제들을 해결하기 위해 개발되었다 (위키).

AngularJS의 기본 문법들은 간단해서 쉽게 익힐 수 있지만, MVC와 MVVM이 익숙하지 않으면 시작하기 어려운 단점이 있다.

더욱이 Angular CLI는 MVC(MVVM) 개념이 없으면 시작하기 어렵다.

여기에서는 간단한 게시판(CRUD)을 만들면서

AngularJS의 기본 문법을 익히고,

Angular CLI로 예제를 확장하면서 Angular의 특징을 익힐 수 있도록 정리하였다.

    1.  AngularJS 기반 게시판
    2.  Angular CLI 기반 게시판
    3.  Mongoose 연동 (준비중)


여기서는 NodeJS와 AngularJS 문법에 대한 설명을 정리하지 않는다.

몰라도 문제는 없을 것으로 여겨지지만, 조금은 알고 있는 것이 도움이 될 것이다.


먼저, AngularJS의 기본 예제를 작성하면서 문법을 익힌다.

AngularJS의 기본 예제는 W3School에 잘 정리되어 있다.

영어를 몰라도 왼쪽의 메뉴를 클릭해서 예제를 보다 보면 눈치로 기본 문법을 익힐 수 있다 (책을 읽는 것이 가장 좋지만… ).

첫 예제는 W3School의 예제로 입력상자(Text)에서 이름을 입력하면

이름에 ‘Hello’를 붙여서 하단에 출력하는 예제 (위 그림 하단의 코드)이다.

[Try it Yourself] 버튼을 눌러서 실행해 볼 수 있다.

먼저, AngularJS를 사용하기 위해 JS 파일을 포함한다 [라인3].

그리고, AngularJS 사용영역이라고 ng-app 지시자를 이용하여 표시한다 [라인6].

ng-app 지시자의 하위 (자식) 테그(Tag)는 모두 AngularJS로 제어할 수 있다.

입력상자에서 ng-model 지시자로 name이라는 이름을 주고 [라인7],

입력상자에서 사용자가 입력한 값을 {{}} 기호로 출력한다 [라인8].


기존의 JavaScript(JS) 방식으로 설명하면

입력상자의 이름을 name으로 지정하고 (ng-model),

이 name이라는 이름으로 입력상자의 값을 가져와 사용하는 예제이다.

AngularJS에서는 별다른 JS 코드 없이 몇 가지 지정만으로 사용자가 입력한 글이 아래에 출력된다.

ng-app, ng-model, {{}} 를 기억해 두면 된다.


다음 예제는 JS (AngularJS)에서 변수(hello)를 선언하고, 변수(hello)의 값을 HTML에서 {{}}로 출력하는 예제이다.

ng-app의 이름을 myApp으로 지정하고

컨트롤(ng-controller)를 myCtrl로 지정하여 제어하는 예제이다.

대부분의 코드가 컨트롤에서 작성된다.


HTML에서 앱 이름(myApp)과 컨트롤러 이름(myCtrl)을 부여하였다 [라인 18].

이 이름은 자바 스크립트에서 Angular JS가 사용하고 있다.

AngularJS 앱을 지정하고 [라인 9],

앱의 컨트롤러 [라인 11]을 지정하여,

변수 hello에 “Hello World”라는 문자열을 지정하였다 [라인 12].

모든 변수는 $scope 하위에 선언하여 사용한다 [라인 12].

변수의 값은 HTML에서 {{변수}}로 출력한다 [라인 19].

이 구조가 AngularJS의 가장 흔한 기본 구조이니 기억해야 한다.


다음으로 구현할 예제는 다음 그림과 같이 배열의 값을 게시판 리스트 출력하듯이 화면에 출력하는 예제이다.

게시판을 만들기 위해서는

서버에서 Oracle이나 MariaDB 같은 DBMS를 이용하여 저장하지만,

여기서는 AngularJS의개념을 익히기 위하여 간단하게 구현한 것이라

데이터 베이스에 데이터를 저장하도록 처리하지 않고, 배열에 저장하여 구현하였다.

데이터를 저장하기 위해 필드는 JSon으로, 행은 배열([])로 작성하였다 [라인 13~16].

필드는 id, title, writer의 3가지로 구성하였고, 2개의 행을 생성해서 boardlist라는 이름으로 지정하였다.


배열의 값은 ng-repeat 지시자(반복문)로 한번에 모두 출력한다 [라인 28].

ng-repeat 지시자를 TR 태그에 사용하였기 때문에

배열의 개수인 두 개의 TR 태그가 생성된다.

각 행의 값은 ng-repeat에서 지정한 item 변수에 넣어지고

item 변수의 키값(id, title, writer)를 추출해서 값을 출력한다 [라인 30, 31].

$index는 AngularJS에서 제공하는 변수로

현재 출력하는 배열의 위치(인덱스) 값을 제공한다 [라인 29].


CRUD 게시판을 구현하기 위해 이번에는 그림과 같이 한 행을 삭제하는 예제를 제작한다.

기존 코드에 다음 코드와 같이 remove 함수를 추가해서 구현한다.

app.controller("myCtrl", function($scope) {
    $scope.boardlist = [
        {id: 1, title: 'Title1', writer: 'Writer1'},
        {id: 2, title: 'Title2', writer: 'Writer2'}
    ];
   
    $scope.remove = function (id) {
        if (!id) return;

        var idx = -1;
        for (var i = 0; i < $scope.boardlist.length; i++) {
            if ($scope.boardlist[i].id === id) {
                idx = i;
                break;
            }
        }

        if (idx === -1) return;
        $scope.boardlist.splice(idx, 1);
    }
});
</script>    
</head>
<body>
    <div ng-app="myApp" ng-controller="myCtrl">
        ~~ 생략 ~~
                <td style="width: 50px"><button type="button" ng-click="remove(item.id)">삭제</button></td>
        ~~ 생략 ~~



실행해 보기    전체코드


앞서의 그림처럼 테이블 테그에 열(td)을 추가해서 버튼 태그을 생성하고, 해당 버튼을 클릭하면 remove() 함수를 호출하다.

함수 호출시 파라미터로 행의 키 값인 id 값을 생성한다.

id 값은 각 행의 고유 값(Primary key)을 나타내는 필드로 사용하였다.

일반적인 Click 이벤트 연결은 onclick=”remove(1)”처럼 구현하지만, AngularJS의 컨트롤과 연동하기 위해 ng-click 지시자를 사용한다.

즉, 이 지시자를 이용하여 HTML에서 AngularJS에 있는 함수를 호출할 수 있다.


변수를 $scope 하위에 boardlist로 사용하듯이

함수도 $scope 하위에 선언해서 사용한다 $scope.remove = function (id).


삭제하는 방법은 간단하다.

주어진 id값을 데이터(boardlist)에서 찾아서 지우면 된다.

지정된 행을 찾기 위해서는 모든 데이터들을(boardlist) 처음부터 행(length)의 개수만큼 반복해서 (for)

If문으로 값을 비교하고 if ($scope.boardlist[i].id === id),

id값이 일치하면 그 위치의 행을 삭제한다 .

배열을 삭제하는 것이기 때문에 splice() 함수를 이용한다.


주어진 id의 행을 찾는 것은 for문 대신에 다음과 같이 배열의 findIndex 함수를 사용하여 쉽게 구현할 수도 있다.

IE 하위버전에서 사용할 수 없다.

        var idx = $scope.boardlist.findIndex(function (item) {
            return item.id === id;
        });


이상의 삭제 예제에서 알아야 할 것은,

AngularJS에서 함수를 생성하는 방법과 어떻게 HTML에서 AngularJS의 함수를 호출하는 가이다.

$scope와 ng-click 을 기억해야 한다.


이번에는 그림과 같이 사용자가 입력한 값을 추가하는 기능을 구현한다.

기존 코드에 다음 코드와 같이 addBoard함수와 Form 테그 등을 추가해서 구현한다.

    $scope.boardForm = {};

    $scope.addBoard = function () {
        var newId = ! $scope.boardlist.length ? 1 : $scope.boardlist[$scope.boardlist.length - 1].id + 1;
        var newItem = {
            id: newId,
            title: $scope.boardForm.title,
            writer: $scope.boardForm.writer
        };

        $scope.boardlist.push(newItem);
    }
});
</script>    
</head>
<body>
    <div ng-app="myApp" ng-controller="myCtrl">
        <form ng-submit="addBoard()">
            Title: <input type="text" ng-model="boardForm.title" autofocus> <br/>
            Name: <input type="text" ng-model="boardForm.writer"><br/>
            <button type="submit">Add</button>
        </form>
       

실행해 보기    전체코드


제목(boardForm.title)과 이름(boardForm.writer)을 입력 받을 수 있도록 HTML 입력상자(Text)를 생성하고,

ng-model로 각각 이름을 boardForm.title과 boardForm.writer로 지정하였다.

boardForm라는 Json 형식의 변수를 AngularJS에서 선언하고, 구성 멤버를 id, title, writer로 지정하여 사용한다.

이 구성 멤버를 HTML에서 이름(boardForm.title, boardForm.writer)으로 바인딩시켜,

사용자가 값을 입력하면 자동으로 AngularJS의 boardForm 변수에 저장된다.

사용자가 저장 버튼을 클릭하면(submit), AngularJS의 addBoard함수를 호출하여, 새로운 행(Json)을 생성하여 배열에 넣어준다 (push).

HTML 입력상자(text)와 Angular 변수를 연결해서(Binding) 사용하는 개념을 이해해야 한다.

이번 예제의 핵심 개념이다.

추가하기 전에 각 행을 구별하는 키 필드인 id 값을 계산해야 한다.

id 값이 순서대로 정렬되어 있다는 전제로,

마지막 id 값이 가장 크다고 여겨서 마지막 id 값에 +1 한 값을 새로운 행의 id 값이 되게 구성하였다.

이 ID 값으로 삭제를 구현하였다.

이 ID 값을 이용하여 구현하지 않은 수정기능을 각자 구현해 보길 바란다.


이상으로 배열과 AngularJS로 간단하게 CRUD 게시판을 작성하였다.

AngularJS가 쉽고 재미난 프레임워크라는 것을 알 수 있다.

다음으로 AngularJS CLI를 정리한다.

AngularJS CLI로 가기 전에 앞서의 예제에서 구현하지 않은 수정 기능을 구현해 보는 것이 도움이 될 것 같다.


'Node.js > Angular JS' 카테고리의 다른 글

2. Angular CLI 기반 게시판  (4) 2018.02.28

Angular CLI(Command Line Interface)는 AngularJS와 비슷한 것 같지만 제법 많은 차이가 있다.

AngularJS는 JavaScript 기반으로 프론트 처리에 중점이 있고,

Angular CLI는 TypeScript 기반으로 백단(서버) 처리에 중점을 두고 있다.

(AngularJS는 1.x 버전을, Angular는 2.0 이후 버전을 의미한다.)

따라서 Angular CLI는 NodeJS 기반에서 운영되고 사용법이 제법 복잡하다.

더욱이 Angular CLI는 MVC(MVVM) 개념이 없으면, 많은 파일에 헷갈려서(?) 시작하기 어렵다.

간단한 게시판(CRUD)을 만들면서 Angular CLI의 개념을 익힐 수 있도록 정리하였다.

    1.  AngularJS 기반 게시판
    2.  Angular CLI 기반 게시판
    3.  Mongoose 연동 (준비중)


여기서는 NodeJS와 AngularJS 문법에 대한 설명을 정리하지 않는다.

몰라도 문제는 없을 것으로 여겨지지만, NodeJS와 Angular CLI 설치법은 알고 있어야 한다.

Angular CLI에 대한 사용법은 Angular CLI 공식 홈페이지에 예제로 잘 설명되어 있다.

영어가 부담스러우면 구글에서 한글 검색으로 angular heroes를 검색하면 된다.


여기서는 Angular의 Heroes 예제를 나름의 방식으로 쉽게 정리하였다.


먼저, NodeJS를 설치한 후에, 다음과 같이 npm을 이용하여 Angular CLI를 설치한다.

npm install -g @angular/cli


다음 그림과 같이 ng version으로 설치한 Angular CLI의 버전을 확인 할 수 있다.

Angular 5.2.5, Angular CLI 1.6.7이 설치되어 있다.


이하의 예제는 GITHUB에서 받을 수 있다.

다음 명령어를 실행하여 예제를 확인할 수 있다.

- git clone https://github.com/gujc71/angularBoard.git

- npm install

- ng serve


Angular CLI가 설치 되었으면,

Angular CLI로 angular-board 프로젝트를 생성한다.

ng new angular-board


cd angular-board를 실행하여 생성한 angular-board 폴더로 변경한다.

다음 명령어로 Angular CLI에서 제공되는 웹 서버를 실행한다.

Angular CLI는 nodeJS 기반이라 npm start로 시작해도 되지만 다음 명령어로 쉽게 실행할 수 있다.

ng serve


웹 브라우저에 http://localhost:4200/를 입력해서 다음 그림과 같이 실행되면, Angular CLI를 사용할 준비가 된 것이다.

IE 11 이하에서는 작동하지 않으니, Edge 이상을 사용해야 하고,

개발을 쉽게 하기 위해서는 Angular를 위해서 다양한 기능을 제공하는 Chrome을 사용하는 것이 좋다.


탐색기로 angular-board 폴더를 열어 보면, 매우 많은 파일들이 생성되어 있는 것을 볼 수 있다.

이중에서 src폴더 하위의 app 폴더에 있는 3개의 파일이,

이상의 그림과 같이 웹을 실행하여 사용자 눈에 보이는 데에 사용되는 파일이다.

app.component.html이 HTML을 작성하는 파일이고,

app.component.css가 CSS를 작성하는 파일이다.

대부분 웹 개발에서 이 두 파일을 구분하여 사용한다.

다만, app.component.ts는 TypeScript로 Anguler 프로그램을 작성하는 파일로

앞서의 AngulerJS 예제에서 작성한 JavaScript 코드를 여기에 작성한다고 보면 된다.

Java나 닷넷으로 치면 컨트롤 코드를 작성하는 파일이다.


각 파일에 기본으로 작성된 코드는 각각 다음과 같다.

app.component.css는 빈 파일로 CSS를 사용하지 않았다.


app.component.html

app.component.html 파일에는 앞서의 시작화면을 보여주기 위한 HTML코드가 작성되어 있다.

Anguler 로고를 출력하기 위해 img 태그가 있고 [라인 5],

3개의 링크(href)를 사용하였다.

AngulerJS와 동일하게 {{}}를 이용하여 title이라는 변수의 값을 출력한다[라인 3].


app.component.ts

app.component.ts의 내용은 일반적인 Anguler 기본 구조 코드로,

Anguler에서는 3개의 파일이 한 세트이고,

ts 파일에서 사용할 HTML 파일(templateUrl)과 CSS 파일(styleUrls)을 지정한다 [라인 5,6].

selector로 HTML에서 Anguler가 사용될 앱 영역을 지정한다 [라인4].

기본 클래스(AppComponent)에서 title 변수에 app라는 문자열을 지정하여 HTML에서 출력하게 된다.

위 그림의 Welcome to App이 이렇게 출력된 것으로 여기에서 값을 수정하면 바뀌는 것을 확인 할 수 있다.

AngulerJS에서는 $scope의 하위에 변수나 함수를 추가해서 사용했지만,

Angular CLI에서는 객체지향 프로그램처럼 클래스(class)를 이용하여 구현한다 [라인8].


title 변수의 값을 'app'에서 'Anguler'로 바꾸어 저장하면,

자동으로 webpack이 컴파일을 실행하고 웹브라우저의 실행 결과가 바뀌어 있는 것을 볼 수 있다.



이제부터 게시판을 구현하기 위한 본격적인 Angular 프로그램을 작성한다.

먼저, 다음과 같이 board 컴포넌트를 생성한다.

컴포넌트는 애플리케이션의 화면을 구성하는 뷰(View)를 생성하고 관리하는 것이다.

즉, 앞서 정리한 3개 파일(html, css, ts)을 의미한다.

기본 컴포넌트로 개발해도 되지만, Angular 문서에서는 별도의 컴포넌트를 생성해서 사용하는 것을 권장한다.

ng generate component board


다음과 같이 3개의 파일이 생성된다.

(board.component.spec.ts 파일도 있지만 여기서 정리하지 않는다.)

이 파일들을 직접 생성해도 되지만 generate component를 이용해서 한번에 쉽게 생성할 수 있다.


board.component.ts의 내용은

app.component.ts와 비슷한 것 같지만 기본 클래스가 좀더 복잡하게 구성되어 있다.

그리고, board 컴포넌트의 기본 selector로 app-board가 지정되어 있다.

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-board',
  templateUrl: './board.component.html',
  styleUrls: ['./board.component.css']
})
export class BoardComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }

}

board.component.ts

board.component.htm 파일에는 한 문장을 출력하도록 작성되어 있다.

<p>
  board works!
</p>

board.component.html

빈 CSS 파일을 포함해서 3개의 파일이 생성되었다.

웹 브라우저로 실행시킨 결과는 변한 것 없이 동일하게 Angular 로고가 출력된다.


app.component.html 파일 내용을 모두 지우고, 다음 코드와 같이 app-board 태그를 추가한다.

앞서 board.component.ts에서 selector로 지정한 값이다.

<app-board></app-board>

app.component.html

다음 그림과 같이 기존 내용이 사라지고 board works!가 출력된 것을 확인할 수 있다.

기본 app 컴포넌트 대신에 추가한 board 컴포넌트의 내용이 실행된 것이다.


이제부터 게시판 코드를 board.component.html와 board.component.ts에 작성하여 예제를 개발하면 된다.

앞서 AngularJS에서 작성한 마지막 예제(sample4.html)

board.component.html와 board.component.ts에 적절하게 나누어 작성한다.

파일 수정과 동시에 다름 그림과 같이 실행되는 것을 볼 수 있다.


먼저, board.component.html 파일의 내용을 다음과 같이 작성한다.

기본 코드는 AngularJS로 만든 마지막 예제(sample4.html의 HTML 부분과 동일하다.

board.component.html

AngularJS의

ng-model 대신에 [(ngModel)]을 사용하고,

ng-click 대신에 (click)을,

ng-repeat 대신에 *ngFor를 사용한다.

board.component.ts

board.component.js 파일도 JavaScrpt부분과 거의 유사하게 작성하였다.

변수 선언과 함수 사용도 거의 비슷하다.

근본적인 차이는 클래스 사용에 있다.

AngularJS는 JavaScript 기반이라 함수 기반으로 구성되었고,

Angular CLI는 TypeScript 기반으로 보다 객체 지향적으로 제작한다.

코드에서 보듯이 클래스(class) 구조로 작성한다.


AngularJS에서는 Json으로 사용한 boardForm이

Angular CLI에서는 BoardVO 클래스 변수로 사용된다.

Boardlist도 동일하다.

Angular CLI에서는 클래스 개념을 강조하여 MVC(MVVM)처럼 사용하여, Java(Spring, Struts)의 MVC와 거의 흡사하게 사용한다.


이상의 코드만 작성해서 실행하면 그림과 같은 오류가 발생한다.

웹 브라우저에서는 아무 것도 나오지 않기 때문에 오류 발생을 알 수 없고, 크롬(Chrome) 개발자 도구(F12)의 [Console] 탭에서 확인할 수 있다.

ng serve를 실행한 뒤, cmd 창(콘솔창)에 오류 메시지가 출력되기도 하지만

대부분의 오류는 크롬의 콘솔창에서 확인할 수 있다.

디버깅도 크롬의 sources 창에서 할 수 있으니 찾아보길 바란다.


HTML의 값을 가지고 오기 위해 사용하는 [(ngModel)]를 사용하기 위해서는

다음과 같이 빨간색으로 표시한 코드를 app.module.ts에 추가해야 한다.

Angular에서 제공하는 다양한 입력(폼) 기능을 사용한다는 의미이다.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule }   from '@angular/forms';

import { AppComponent } from './app.component';
import { BoardComponent } from './board/board.component';


@NgModule({
  declarations: [
    AppComponent,
    TestComponent
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.module.ts

코드를 추가할 때는 코드 수정시 webpack이 즉시 컴파일해서 웹 브라우저에서 바로 확인이 가능했지만,

위 수정을 한 경우 서버를 재가동해야 하는 경우가 있다.

개발 서버를 종료하고 다시 실행한다 (ng serve).


이상으로 단순한 CRUD 기능을 게시판으로 구현해 보았다.

위 코드를 모두 따라해서 작성해 볼 수 있고, GitHub에서 다운로드 받아서 확인할 수 있다.

GitHub의 step1 branch가 이상의 예제에 대한 코드이다.


마지막으로 앞서의 코드를 정리하여, MVC(MVVM)처럼 구현한다.

이 구현을 위해 두가지를 수정한다.


먼저, BoardVO 클래스를 컴포넌트 파일 내에 같이 작성했지만 BoardVO.ts 파일로 따로 작성한다.

Angular CLI 공식 문서에는 Hero라는 클래스를 별도의 ts 파일로 작성해서 사용한다.

src\app 폴더에 BoardVO.ts파일로 다음과 같이 생성한다.

export class BoardVO {
  id: number;
  title: string;
  writer: string;
}

BoardVO.ts

같은 파일 내에서는 사용하지 않았지만,

다른 파일일 경우에는 외부에서 사용한다는 의미로 class 앞에 export를 지정한다.


이렇게 외부로 뺀 클래스(BoardVO)는

다음과 같이 BoardVO 클래스 코드를 지우고 import 로 가져온다.

import { Component, OnInit } from '@angular/core';
import { BoardVO } from '../BoardVO';

var BoardList: BoardVO[] = [
  { id: 1, title: 'Title 1', writer: 'Mr. Nice' }
];

@Component({
  selector: 'app-test',
  templateUrl: './test.component.html',
  styleUrls: ['./test.component.css']
})
 ~~ 생략 ~~

board.component.ts

BoardVO.ts는 src\app 폴더에 있고

board.component.ts는 src\app\board 폴더에 있어서 상위 폴더를 나타내는 [../]가 사용한다.


다음으로, 서비스(service)를 구현한다.

MVC(Model–View–Controller, MVC)에서는 데이터와 관련된 처리나 반복된 기능은 서비스(service)에 구현하는 것이 추천된다.

서비스 사용법은 Angular CLI 예제에 잘 설명되어 있다.

여기서는 데이터 관련 처리를 DBMS에서 하지 않고, 배열로 처리하였다.

따라서, 배열 처리 부분을 모두 서비스에 작성한다.


서비스 파일을 작성하기 위해,

콘솔창(cmd)에서 ng generate service 명령어로 다음과 같이 board 서비스를 생성한다.

ng generate service board



src/app 폴더에 board.service.ts 파일이 생성된 것을 확인 할 수 있다.

생성된 소스는 다음과 같다.

import { Injectable } from '@angular/core';

@Injectable()
export class BoardService {

  constructor() { }

}

board.service.ts

generate service 지시어로 서비스 파일을 생성하고,

환경 설정까지 잘 이루어 지지만, 간혹 설정 문제로 오류가 발생할 수 있다.

app.module.ts 파일에 다음과 같이 코드가 추가되었는지 확인해야 한다.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule }   from '@angular/forms';

import { AppComponent } from './app.component';
import { BoardComponent } from './board/board.component';
import { BoardService } from './board.service';

@NgModule({
  declarations: [
    AppComponent,
    BoardComponent
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [BoardService],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.module.ts

이상으로 서비스를 사용할 준비가 되었다.

서비스 파일(board.service.ts)에 구현할 코드는 컴포넌트에 있는 코드를 모두 복사해서 사용한다.

단순 기능만 구현했기 때문에 컴포넌트에 있는 코드 모두가 데이터 관련 코드이기 때문에

컴포넌트(board.component.ts)에 있는 코드를 다음과 같이 복사해서 작성한다.

import { Injectable } from '@angular/core';

import { BoardVO } from './BoardVO';

var BoardList: BoardVO[] = [
  { id: 1, title: 'Title 1', writer: 'Mr. Nice' },
  { id: 2, title: 'Title 2', writer: 'Narco' }
];

@Injectable()
export class BoardService {

  constructor() { }

    boardlist = BoardList;

    getBoardList(): BoardVO[] {
        return this.boardlist;
    }
   
      removeBoard(id: number):void {
        var idx = this.boardlist.findIndex(function (item) {
            return item.id === id;
        });
        if (idx === -1) return;
        this.boardlist.splice(idx, 1);
    }
   
    addBoard(boardForm: BoardVO): void {
        var newId = ! this.boardlist.length ? 1 : this.boardlist[this.boardlist.length - 1].id + 1;
       
        var newItem = {
            id: newId,
            title: boardForm.title,
            writer: boardForm.writer
        };
        this.boardlist.push(newItem);
    } 
}

board.service.ts

컴포넌트에서 ../BoardVO로 작성한 것을 ./BoardVO로 점(.)을 하나 빼고 작성했다.

컴포넌트(board.component.ts)는 src/app/board폴더에 있고,

BoardVO는 src/app에 있어서 상위 폴더(..)를 지정했지만

서비스(board.service.ts) 파일은 같은 폴더(.)에 있기 때문이다.


다음으로 getBoardList 함수가 추가 되었다.

단순하게 데이터를 저장한 배열(boardlist)을 반환한다.

컴포넌트에서는 컴포넌트 내부의 배열이라 변수를 그냥 호출해서 사용했지만,

서비스로 배열을 옮겼기 때문에 getBoardList() 함수를 이용하여 받아온다.


board.component.ts

컴포넌트의 boardlist가 getBoardList()로 대체되면서,

이 boardlist를 참조하는 board.component.html의 코드도 다음과 같이 바뀌어야 한다.

    <tr *ngFor="let item of getBoardList(); let inx = index">
        <td>{{inx+1}}</td>
        <td>{{item.title}}</td>
        <td>{{item.writer}}</td>
        <td><button type="button" (click)="removeBoard(item.id)">삭제</button></td>
    </tr>

board.component.html

이상으로 Angulr를 MVC처럼 구현해 보았다.

위 코드를 모두 따라해서 작성해 볼 수 있고, GitHub에서 다운로드 받아서 확인할 수 있다.

GitHub의 step2 branch가 해당 내용이다.


여기서 정리한 내용은 Angular 개념을 잡기 위한 기초 내용으로, Angular는 더 많은 기능을 제공한다.

Routing, HTTP등의 기능부터, 입력값 검사 등의 다양한 기능이 제공되니 찾아서 익혀 보길 바란다.


여기서는 구현하지 않았지만, 수정 기능도 구현해 보길 바란다.

Angular CLI 예제(heroes)에서는 입력기능 없이 수정 기능이 구현되어 있으니, 참고해서 구현해 보면 Angular를 이해하는 데 도움이 될 것이다.

Angular CLI 예제는 bootstrap을 사용해서 보기 좋게 개발하는 방법도 알 수 있다.





'Node.js > Angular JS' 카테고리의 다른 글

1. AngularJS 기반 게시판  (5) 2018.02.28

구글에서 Javascript Image Editor로 검색하면, 제법 쓸만한 HTML5 기반의 유/무료 이미지 편집기가 조회된다.

이중에서 윈도우에서 제공하는 그림판과 유사하고 간단하게 사용할 수 있는 PaintWeb을 선호한다.

PaintWeb의 소스는 Github에서 다운 받을 수 있고, 웹 사이트에서 데모를 실행해 볼 수 있다.

하지만, PaintWeb을 Java, PHP, 닷넷(.Net) 등의 프로젝트에 넣어서 사용하는 방법에 대한 설명이 없다.

여기에서는 PaintWeb을 Java(Spring) 프로젝트에 넣어서 사용하는 데 필요한 사항을 2 가지로 정리하였다.


먼저, PaintWeb 라이브러리를 호출하여 실행하는 방법을 정리하였다.

PaintWeb을 실행하는 방법은 다운 받은 예제나 데모에서 확인 할 수 있지만, 작성된 코드가 제법 복잡해서 여기에 간단하게 정리하였다.


다음으로 작업한 이미지를 서버로 전송하여 저장하는 방법에 대하여 정리하였다.

작업한 이미지를 서버로 전송하여 저장하는 방법이 제공되지 않아서 Java로 제작하였고, 제작한 코드에 대하여 간단하게 정리하였다.

따라서, 첫 번째 내용만 조금 이해하면 PaintWeb을 자신의 프로젝트에 넣어서 사용할 수 있다.


이렇게 정리한 예제(image Editor)는 Github에서 다운받을 수 있다.


먼저 실행하는 방법을 정리한다.

다운 받은 소스에서 demos 폴더에 있는 데모(demo1.html)를 실행하면 Ajax 사용 등의 문제로 실행이 되지 않지만, 웹 프로젝트에 넣어서 경로 설정을 해주면 쉽게 사용할 수 있다.

다운 받은 파일 중 src 폴더가 PaintWeb의 라이브러리이므로, 사용할 웹 프로젝트의 자바 스크립트 폴더에 src 폴더를 paintweb 폴더라는 이름으로 복사한다.

demos 폴더에 있는 demo1.html을 웹 파일이 있는 폴더에 적절한 이름(demo1.jsp)으로 넣고 실행하면 된다.

demo1.html 파일을 열어서 PaintWeb 라이브러리 경로를 다음과 같이 수정한다.

수정 전   <script type="text/javascript" src="../build/paintweb.js"></script>
수정 후   <script type="text/javascript" src="js/paintweb/paintweb.js"></script>

demo1.html을 수정해서 실행시켜도 되고, 본 예제에서는 demo1.html 의 코드를 정리해서 index.jsp(실제로는 imageEditor.jsp)로 작성하였다.


예제로 작성한 프로젝트(imageEditor)는 Github에서 다운 받을 수 있다.

imageEditor에서는 PaintWeb라이브러리를 js 폴더에 넣어서 사용한다.

이외에 index.jsp와 ImageEditor.java, ImageEditor.jsp 파일이 예제로 작성된 파일이다.


index.jsp 은 편집할 이미지를 선택하는 페이지로 스프링(Spring) 프레임워크가 아닌 JSP 파일로 작성했다.

사용자가 편집할 이미지를 선택하면 해당 이미지를 서버로 전송하고(fileUpload)

이 이미지를 다시 다운 받아서 (fileDownload)

HTML 이미지 태그(img)에서 보여 주게 된다.

index.jsp에서는 이 기능을 구현해 두었고,

fileUpload, fileDownload 컨트롤을 ImageEditor.java에 작성하였다.


톰캣에서 다운 받은 예제를 실행한 후 웹 브라우저로 다음 주소를 입력하여 index.jsp의 실행을 확인할 수 있다.

http://localhost:8080/imageEditor/

화면 하단에 있는 [Load Image]을 클릭하여 편집할 이미지를 선택하면 다음 그림과 같이 수정(Modify) 하거나 삭제(Delete) 할수 있는 메뉴가 나타난다.

index.jsp에서 수정(Modify) 기능을 선택하면 PaintWeb 기반의 이미지 편집기가 실행된다.

이미지 편집기의 컨트롤(url)은 imageEditor로 Java로 작성된 코드는 없지만 imageEditor.jsp 파일을 뷰로 사용하여 이미지 편집을 할 수 있도록 하였다.

imageEditor.jsp 파일에서 PaintWeb를 실행하여 이미지를 편집하고, 이미지를 서버로 전송(saveImage)하여 저장한다.

saveImage 컨트롤도 ImageEditor.java에 작성되어 있다.


이상의 흐름을 순서대로 정리하면 다음과 같다.


demo1.html 파일의 PaintWeb 실행 코드를 보면 실행 시간을 측정하는 코드 등이 있어서 아주 복잡하게 보인다.

이 코드는 다음과 같이 단순화 해서 사용해도 된다.

단순화한 실제 코드는 PaintWeb을 Java에서 사용하기 위한 예제로 작성한 프로젝트 파일 중 imageEditor.jsp의 코드를 참조하면 된다.

    pw = new PaintWeb();
    pw.config.guiPlaceholder = document.getElementById('PaintWebTarget');
    pw.config.imageSaveTo   = imageSaveTo;
    pw.config.imageLoad      = document.getElementById('editableImage');
    pw.config.configFile        = 'config-example.json';
    pw.init();


이상으로 PaintWeb을 사용하는 방법과 예제 프로젝트의 전체 구조를 정리하였다.

추가적으로 PaintWeb의 속성과 사용법 몇 가지를 더 정리하였다.


guiPlaceholder는 이미지 편집기인 PaintWeb를 생성할 Div를 의미한다.

지정된 div의 자식으로 PaintWeb의 편집 도구들이 생성된다.

imageLoad는 편집할 이미지 태그(img)를 의미한다.

PaintWeb은 HTML 이미지 태그(img)의 이미지를 가져와서 편집한다.

configFile은 PaintWeb을 실행할 설정 파일로 제법 다양한 기능을 지정할 수 있다.

Init() 함수는 지정된 설정에 따라 PaintWeb을 실행하는 메소드이고,

파라미터로 지정된 pwInit 는 콜백(callback) 함수로 PaintWeb의 실행 결과를 반환한다.

생략해도 된다.

생략할 경우 편집할 이미지 태그를 숨기는 코드를 (display = 'none') 적당한 곳에 작성해야 한다.


configFile은 config-example.json이 예제로 제공된다.

소스는 JSon으로 작성하는데, 주요한 몇 가지 설정을 정리하면 다음과 같다.

속성

설명

lang

사용할 언어를 지정한다.

viewportWidth

이미지 편집 영역의 너비를 지정한다.

viewportHeight

이미지 편집 영역의 높이를 지정한다.

fillStyle

도형의 배경색을 지정한다.

strokeStyle

도형의 전경색을 지정한다.

tools

편집기에 사용할 도구들을 선택한다.

toolDefault

편집기가 실행되면 기본으로 사용될 도구를 지정한다.

fontFamilies

문자열(text) 입력시 사용할 폰트 리스트를 지정한다.

fontFamily

폰트 리스트 중 기본으로 사용할 폰트를 지정한다.


먼저 언어는 영어(en)가 기본으로 지정되어 있다.

한국어(ko)는 개인적인 필요로 몇 가지만 번역해서 github에 공유하였다.

이 파일을 PaintWeb의 lang 폴더에 넣고 configFile(config-example.json)의 lang 속성을 ko로 수정하면 한국어로 사용할 수 있다.



viewportWidth와 viewportHeight는 이미지를 편집할 영역의 너비와 높이를 지정하고,

fillStyle은 도형을 그릴 때 사용할 배경색, strokeStyle은 전경색의 기본값을 의미한다.

문자열(Text)일 경우 폰트 색을 나타내는 데, fillStyle와 strokeStyle와 구분되어 다소 이상하게 출력된다.

Tools는 PaintWeb에서 제공하는 편집 도구들로, tools 옾션에서는 자신에게 필요한 도구들만 선택해서 사용할 수 있다.

예로 ["bcurve", "cbucket", "cpicker"]를 지정하면 이 3가지 도구만 나타난다.

toolDefault는 편집기가 실행되면 기본으로 사용될 도구를 지정하는 것으로 선(line) 도형이 지정되어 있다.

그림판처럼 selection을 지정해서 사용해도 좋을 것이다.

fontFamilies은 문자열(text) 입력의 세부 옵션으로 이미지 편집기에서 사용할 폰트들을 배열로 지정하면 된다.

기본적으로 영어 폰트들이 지정되어 있고, [‘굴림체’, ‘바탕체’]처럼 한글 폰트를 지정해서 사용할 수 있다.


이상으로 PaintWeb을 사용하는 예제에 대해서 정리하였다.

다음으로 PaintWeb에서 편집한 이미지를 서버에 전송해서 저장하고,

이 이미지를 원래 이미지 태그(img)에 갱신하여 보여주기 위해 작성한 코드에 대해서 정리한다.

PaintWeb을 Java에서 사용하기 위한 예제로 작성한 프로젝트는

웹 페이지에서 이미지를 지정하고,

지정된 이미지를 PaintWeb로 편집하고,

편집된 이미지를 서버에 저장하고

처음에 지정된 이미지를 갱신해서 보여주도록 제작되었다.


웹 페이지(index.jsp)에서 이미지를 지정하고,

지정된 이미지를 PaintWeb으로 편집하고(action-imageEditor),

편집된 이미지를 서버에 저장하고(Ajax-imageSaveTo())

처음에 지정된 이미지를 갱신(# + 시간)해서 보여주도록 제작되었다.


웹 페이지에서 작성한 이미지를 서버로 전송하는 방법은 이미지를 일종의 문자열 값인 base64 코드로 변환하여 전송하는 방식을 이용한다.

PaintWeb의 저장 도구()를 클릭하면 이미지를 base64로 변환해서 작성자의 PC에 저장하는 기능이 PaintWeb에 구현되어(imageSave()) 있다.

base64로 변환된 값을 파일로 저장하는 대신 Ajax를 이용하여 서버로 전송하도록 작성하면 된다.

PaintWeb의 imageSave() 함수 중간에 별도의 함수로 imageSave()를 호출하도록 작성하였다.

PaintWeb의 imageSave()에서 작성해도 되지만 가급적 기존 코드를 손대기 않기 위해서 별도의 함수로 작성했다.

imageSave() 함수의 내용이 복잡해 보이지만 이미지 저장시 전송율을 보여주기 위해 진행 상태를 %로 보여주는 기능으로 코드가 복잡해 보일 뿐 실제 코드는 단순하다.

다음 코드는 전송율을 보여주기 위한 코드를 생략하고 작성한 코드이다.

 

Ajax를 이용하여 전송(send)이 완료되면 (onload) [라인 13]

이미지 태그를 생성하여 [라인 14]

전송한 이미지를 다시 다운로드 받아서(fileDownload) 넣어주고 [라인 19],

이렇게 다운 받은 이미지 주소를 기존 이미지에 지정하여 새롭게 갱신(reload)한다 [라인 16].

다운 받을 때 다른 파일(URL)이라는 의미로 파일명 뒤에 “#img”와 시간을 붙여서 사용한다 [라인 16].

이렇게 복잡하게 작성한 이유는 웹 브라우저가 기존의 이미지를 캐시(cache)로 가지고 있어서 이미지 갱신이 잘 안되기 때문이다.


이미지 갱신(reload)이 제대로 되니 위해서는 이상과 같이 자바 스크립트뿐만 아니라 파일을 다운로드 하는 Java에서도 다음 코드를 추가해 주어야 한다.

public void fileDownload(HttpServletResponse response, String path, String filename) {
    ~~ 생략 ~~
    response.setHeader("Pragma", "no-cache");
    response.setHeader("Cache-Control", "no-cache, must-revalidate");
    response.setDateHeader("Expires", 0);
    ~~ 생략 ~~
}   

이 코드가 없으면 IE에서 제대로 갱신되지 않는다.


Ajax에서 서버로 이미지를 전송하면(saveImage) [라인 5]

서버(java - saveImage)에서는 전송 받은 이미지 정보를 BASE64Decoder를 이용하여 이미지로 변환해서[라인 15] 지정된 경로에 파일로 저장한다.




10년 이상 경력의 두 개발자가 있었다.

한 명은 세심하고 이것 저것 따져가며 일하고,

또 한 명은 바보스러우리 만치 둔하고 자기 중심적인 개발자였다.


세심한 친구는 문서를 잘 챙기고 관리에 능하지만 개발 실력은 조금 부족했다.

다소 둔한 친구는 관리보다는 개발에 능했다.

세심한 친구는 멀리서 다른 사람들이 주고 받는 대화도 들을 만큼 주변 환경에 민감했고,

둔한 친구는 자신 외에는 관심이 없었다.

자료: http://cdn0.lostateminor.com

세심한 친구는 남에게 질문하거나 도움 받는 것을 피해를 준다고 생각하는지,

매우 조심하고 정중하게 이야기 하는 대신, 자신이 도와 줄 때는 다소 심하게 말하기도 했다.

둔한 친구는 자신이 조금해보고 할 수 없다고 여겨지면 남의(주로 나) 일도 못할 만큼 도움을 요청하고 괴롭혔다.

도와 줄 때는 뿌듯해 하는 것이 느껴졌다.


세심한 친구는 PM을 하면 직원들을 잘 챙겼지만 작성한 코드에 전혀 신경 쓰지 않았다.

둔한 친구는 프로젝트 중에 자신은 휴가를 가면서, 문제가 터지자 휴가간 개발자를 복귀 시키기도 했다.


세심한 친구는 논리적으로 설명하며 직장 상사에게도 대들다가, 결정 나면 자신의 마음과 달라도 조용이 투덜거리면서 조용히 있었다.

둔한 친구는 자신이 이해하지 못하면 계속 질문하고 대들었다.

심지어 어떤 일로 사장이 자신을 무시한다는 생각이 들자 항의 메일을 보내기도 했다.

나와 코드 구현 방식에 대하여  이야기 하다 격해진 적도 많았다.


둔한 친구는 별로 어울리는 직원이 없었고, 조용이 있다보니 회의시간만 되면 졸았다.

세심한 친구는 둔한 친구를 미워했고, 공개적인 자리에서 망신을 주기도 했다.

자료: http://cdn0.lostateminor.com

두 사람에 대한 직원들의 평가는 극명했고,

어느 날 사장님과 잡담 중에 두 사람에 대한 논쟁이 생겼다.

사장님이 둔한 친구에 대하여 안 좋은 감정을 그대로 표현했고, 나는 반대의견을 제시하였다.

나에게 세심한 친구나 둔한 친구나 둘 다 똑 같은 나쁜 직원이라는 말에 놀라워했다.


세심한 친구는 자신이 이로운 위치에 있다고 생각할 경우 행동이 매우 거칠고 안하무인이고,

일을 시키려고 해도 자신의 모든 상황을 따지고 거부하기도 하며,

때로는 일을 받는 게 아닌 도와 준다는 개념으로 행동하려 드는 단점이 있었다.


둔한 친구는 투덜대기는 해도 시키는 일을 모두 조용이 처리했다.

쉽게 일을 받다 보니 한동안 대부분의 외부 프로젝트는 둔한 친구가,

내부 관리는 세심한 친구가 처리 하는 식으로 다소 불공평하게 운영되었다.


사장님이 둔한 친구는 혼자 따로 논다고 지적했다.

세심한 친구는 부하 직원들을 잘 가르치기도 하지만 윽박지르고 잔소리도 심하다고 이야기 했다.

둔한 친구처럼 친화력이 떨어지는 건 어쩔 수 없는 개인적인 부분이라고 이야기 했다.

PM을 하면서 혼자만 휴가간 것은 자신이 팀장이 아닌데 같이한 직원에게 맘대로 휴가 가라고 말하기 어려웠고,

생색용 말은 하기 싫어서였던 것이라고 했다.

휴가자를 복귀시킨 것은 사고친 당사자이고, 자신이 모르는 부분이라 어쩔 수 없었다고 했다.


일 시키기 힘들고 각자의 방식으로 나를 대하며, 나를 힘들게 하는 두 직원 모두

나에게는 나쁜 직원이었다.


그렇게 시간이 흘러 회사 사정이 안 좋아 지면서, 둔한 친구는 구조조정 대상으로 회사를 떠났다.

말렸지만 되돌릴 수 없었고, 떠나는 둔한 친구를 배웅하는 사람은 많지 않았다.

자료: https://pixabay.com

다시 1년이 흘렀고

둔한 친구는 제법 괜찮은 회사를 잘 골라서 좋은 조건으로 직장 생활을 시작했다.

여기서 미움받는 것보다 새로 시작하는 것이 좋다는 생각과

그 친구 경력으로 좋은 회사에 갈 수 있다는 확신(취업 사이트를 검색해 봤다.)이 있어서 찬성했지만

남아 있는 사람이 힘들 거라는 걱정 때문에 반대도 했었다.

그리고 시간이 지나면서 걱정은 현실이 되었다.


둘이 나눠서 하던 PM을 세심한 친구 혼자서 하고, 늘어나는 외부 프로젝트에 짜증내는 빈도가 늘기 시작했다.

세심한 친구는 자신이 여유롭게 할 수 있는 만큼만 하는 직원이라 나에게 일이 오기 시작했다.

둔한 친구가 실력이 부족해서 항상 물어본다고 생각한 다른 개발자가,

실제로는 일을 못해서 물어봤다는 것을 조금은 알게 된 것 같았다.

그리고, 여러 가지 이유가 있었지만 오랜만에 프로젝트 개발 일정에 지연이 생겼다.


둔한 친구가 나간 뒤, 프로그램 구현 방법과 관련한 논쟁을 해 보지 못했다.

자신이 이해하지 못하면 이해할 때까지 질문하던 사람이 사라지니 논쟁을 벌일 이유가 없어졌다.

업무 중에 논쟁하던 우리 모습을 보며 사이가 안 좋다고 생각할 만큼 짜증나게 이야기 했었다.


회사 생활을 하다 보면 경쟁하기도 하고 미워하기도 한다.

하지만 두 마리의 소 이야기처럼 누가 더 일을 잘한다고 평가할 필요도, 미워할 필요도 없다고 생각한다.

다양한 사람이 다양한 능력을 잘 어울려서 자신들의 목적을 달성하기 위한 조직이 회사라고 생각한다.

나와 다른 것은 나쁜 것이 아니고 그냥 다른 것이고,

두 사람은 성격이 다른 것 뿐이었다고 생각한다.

(두 마리 소의 이야기는 단점 지적에 대한 우화로 이야기가 단순하다.

좀 더 깊게 따지고 보면 좀 더 잘한 소가 많이 먹을 수도 있고,

조금 부족한 소가 젊은 때는 잘했지만 나이가 들었을 수 있다.)

자료: http://m.inven.co.kr/

어릴적 많은 사람을 배척했던 경험이 지금은 마음 한켠에 무겁게 남아 있다.

내 눈에 가시 같다고 가시 뽑듯이 미운 사람을 치우면 속이 시원할 것 같았지만,

그 사람의 빈자리는 어떤 형태로든 누군가가 감당할 몫이 되었다.


배척 했다고 이긴것이 아니고,

배척 당했다고 무조건 무능하거나 잘못 된 것도 아니였다.

언젠가 이 말을 세심한 친구에게 해주고 싶지만 그 친구는 동의하지 않을 것이다.

(며칠전 낮에 둔한 친구가 잘 지낸다는 말을 세심한 친구가 들을 수 있도록 다른 사람에게 말했다.)



둔한 친구와 추석 안부 인사를 하고나서 ...





+ Recent posts