지난 실습의 연장선이라고 봐주시면 좋겠습니다.
위를 통해서 Rest API 를 구성하고 Rest Client 를 통해서 테스트를 해보는 과정까지는 진행이 완료되었고.. 그리고 이런 데이터들을 잘 핸들링하여 화면을 구성해보는 일이 남았는데.. 그 일환으로 HTML 과 jQuery 를 사용해 볼까 했습니다만... 지난 포스팅에서 살펴본.. SOFEA 가 문득 떠올라서 대안으로 불리우는 Backbone.js 나 Angular.js 같은 친구들이 먼저 눈길이 가더라구요. 기본적으로 해본적 있는 것들보다는 잘 모르는 걸 해봐야.. 될 것같아서 시작해 보았습니다. 하지만 그게 이렇게까지 저를 괴롭힐 줄은 꿈에도 몰랐죠 -_-;
일단 Bootstrap 으로 간단하게 UI 를 구성해보았고.. 거기에 AngularJS 를 추가하였습니다. 이는 기본적으로 w3gschool 에서 제공하는 튜토리얼 화면을 기반으로 진행을 했습니다. (링크 : http://www.w3schools.com/angular/angular_bootstrap.asp)
위와 같은 화면 구성입니다. 기능을 명세 하자면 다음과 같습니다.
1. 연락처 목록 조회
2. 새로운 연락처 생성
3. 기존의 연락처 수정
일단은 Rest API 는 이미 기존 소스에서 다 제공하도록 되어있습니다. 다음의 URL을 통해서 작업을 처리하도록 해 두었습니다.
1. 연락처 목록 조회 - GET / http://localhost:9998/ws/api/contacts/list
2. 새로운 연락처 생성 - PUT / http://localhost:9998/ws/api/contacts/put
3. 기존의 연락처 수정 - PUT / http://localhost:9998/ws/api/contacts/put
특징이라면 생성과 수정은 하나로 다 처리한다는 겁니다. HASH KEY 가 되는 ID 값이 동일하면 나머지 정보에 대해서는 Overwrite 를 하기 때문에 사실상 수정 효과와 같다고 보면 되겠네요. 그리고 생성, 수정 부분의 Rest API 의 로직을 다소 변경을 하였습니다. 지난번에 언급한 BeanParam 의 경우에는 Form 을 통해서 유입되는 파라미터에 대해서는 처리가 가능하나, JSON 형태로 유입되는 파라미터에 대한 처리는 불가하기 때문입니다.
따라서 아래와 같이 수정을 해보았습니다.
@Path("/put") @PUT @Produces("application/json") public SGResponse put(String contactsJson) { SGResponse response = null; try { User user = objectMapper.readValue(contactsJson, User.class); MapuserObj = new HashMap<>(); userObj.put("Id", new AttributeValue().withS(user.getId())); userObj.put("Name", new AttributeValue().withS(user.getName())); userObj.put("Email", new AttributeValue().withS(user.getEmail())); userObj.put("Mobile", new AttributeValue().withS(user.getMobile())); userObj.put("LoginDate", new AttributeValue().withS(dateFormatter.format(new Date()))); PutItemRequest forumRequest = new PutItemRequest().withTableName(tableName).withItem(userObj); client.putItem(forumRequest); response = SGResponseFactory.createOK(); } catch (ConditionalCheckFailedException cse) { System.err.println("Conditional check failed in " + tableName); return SGResponseFactory.createUnKnownError(); } catch (AmazonServiceException ase) { System.err.println("Error updating item in " + tableName); return SGResponseFactory.createNoRecordError(); } catch (JsonMappingException e) { e.printStackTrace(); } catch (JsonParseException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return response; }
파라미터에 있는 BeanParam을 제거하고 contactsJson 이라 불리우는 String 을 추가하였지요. 이후 Object 매핑을 통해서 기존과 같게 처리해줄 뿐 그 이상은 큰 차이는 없슴미다. 다음으로 화면 구성을 Angular JS 로 해보았습니다.
var app = angular.module("Contacts", []).config(function ($httpProvider) { }); app.controller("contactsController", function ($scope, $http, transformRequestAsFormPost) { $scope.id = ''; $scope.name = ''; $scope.email = ''; $scope.mobile = ''; $scope.loginDate = ''; $scope.contacts = null; $scope.edit = true; $scope.error = false; $scope.incomplete = false; $http.get("http://127.0.0.1:9998/ws/api/contacts/list").success( function (reponse) { $scope.contacts = reponse.value; }); $scope.editContacts = function (contacts) { if (contacts == "new") { $scope.edit = true; $scope.incomplete = true; $scope.name = ''; $scope.id = ''; $scope.email = ''; $scope.mobile = ''; } else { $scope.edit = false; $scope.name = contacts.name; $scope.id = contacts.id; $scope.email = contacts.email; $scope.mobile = contacts.mobile; } } $scope.$watch('id', function () { $scope.valid(); }); $scope.$watch('name', function () { $scope.valid(); }); $scope.$watch('email', function () { $scope.valid(); }); $scope.$watch('mobile', function () { $scope.valid(); }); $scope.valid = function () { $scope.incomplete = false; if ($scope.edit && (!$scope.name.length || !$scope.id.length || !$scope.email.length || !$scope.mobile.length)) { $scope.incomplete = true; } }; $scope.saveContacts = function () { var contactsData = { id: $scope.id, name: $scope.name, email: $scope.email, mobile: $scope.mobile }; $http.put('http://localhost:9998/ws/api/contacts/put', contactsData, {}).success(function () { alert("OK"); }); }; $scope.listContacts = function () { $http.get("http://127.0.0.1:9998/ws/api/contacts/list").success( function (reponse) { $scope.contacts = reponse.value; }); } });
가장 이해가 안가고 어려웠던 부분은 이부분입니다. 따라하고 완성하는데 포커스를 맞추다 보니 AngulaJS 의 대략적인 컨셉이나 추구하는 목적에 대해서 익히지 않다보니 소스를 이해하기에 어려움이 많은 것 같네요. 그래도 지속적인 반복은 이해를 동반하더군요. 주로 타인이 짜 놓은 소스를 보고 이해하려고 많이 노력했습니다. 먼저, 컨트롤러의 개념들을 스크립트 코드를 통해서 이해할 수 있었습니다. 여기서는 contactsController 라는 이름으로 컨트롤러를 생성했습니다. $scope 라는 파라미터는 거의 필수인 듯 하고 그외에는 선택적으로 고를 수 있는 것으로 보이네요.
좀 비유를 하자면 javascript 를 통해서 클래스를 만드는 것과 같이 구성을 해보았습니다.
1. Attributes : $scope.xxx 변수들
2. Methods : $scope.xxxx = function(...) 으로 선언한 함수들
3. Static resources or API : $http, $scope, $httpProvider 등
아직 Client-side 의 컨트롤러라는 것이 생소해서 시도하기가 여간 잔망스럽지 않은데.. 이는 스크립트를 짜는것 뿐만 아니라 HTML 에서도 그 영향을 미칠 수 있도록 설정을 해주어야 합니다. 이는 대부분 HTML 태그에 ng-* 처럼 ng라는 접두어를 포함한 attribute 들을 선언해 줌으로서 가능합니다. 또한 freemarker 나 jstl 또는 jsp 에서 사용하는 것과 같이 데이터를 표현해 주기 위한 약간의 Syntax 도 있습니다. {{ }} 라고 표시되어 있는 부분이 그렇습니다. HTML 태그는 좀 게시하기에 불편한 부분이 있어서 생략합니다. 자세한 것은 Git 에 소스를 공유할 것이니 그쪽을 통해서 확인 하시면 좋겠습니다.
다 완성한 데모 영상을 올려 보았습니다.
대략적으로 마치고 난 뒤의 소감과 더 알아야할 내용들이 있는데.. 이 부분은 다른 포스팅을 통해서 추가적으로 더 해야겠습니다. 너무 글의 길이가 길어져서 말이죠.
많은 삽질들이 있었지만 결과적으로는 많은 도움이 되는 시간이었던 것 같습니다. 그러합니다.
끝!