1. 계기

ajax를 사용하게된 계기는 클라이언트와 서버를 모두 개발하면서 클라이언트-서버 간 http 메시지 통신을 하고싶었기 때문이다. Thymeleaf를 사용했던 터라 form 태그로 할 수도 있었지만, 클라이언트 동작방식을 더 이해하고 싶어서 ajax를 선택했다.

 

1-1. ajax란?

ajax는 JavaScript를 사용한 비동기 통신, 클라이언트-서버 간 xml, json 데이터를 주고 받는 기술이다. ajax는 비동기로 동작하기 때문에 전체 페이지를 새로 고치지 않고 뷰를 갱신할 수 있다.

 

1-2. ajax 사용 이유

페이지 전체를 새로고침하지 않고 web 화면에서 데이터를 조회하고 싶은 경우에 사용할 수 있다. json 이나 xml 형태로 필요한 데이터만 받아 갱신하기 때문에 그만큼의 자원과 시간을 아낄 수 있다.

 

1-3. ajax 진행과정

1) XMLHttpRequest Object를 만든다

브라우저에게 request를 보낼 준비 시키는 과정이다.

이를 위해 필요한 method를 갖춘 object가 필요하다.

 

2) callback 함수를 만든다

callback 함수는 어떤 이벤트에 의해 호출되는 함수를 의미한다.

서버에서 response가 왔을 때 callback 함수를 실행한다.

HTML 페이지를 업데이트 한다.

 

1-4. ajax 사용법

ajax를 사용하기 위해서는 jquery를 import 해야한다. 

 

build.gradle

implementation 'org.webjars:jquery:3.1.1-1'

 

헤더

<head>
	<script src="/webjars/jquery/jquery.min.js"></script>
</head>

 

ajax 코드

<script>
function completeAuthEmail() {
    const parmas = {}; // body에 넣을 json 데이터

    $.ajax({
        url : url, // 메시지 보낼 url
        type : 'post',
        contentType : 'application/json; charset=utf-8;',
        dataType : 'json',
        data : JSON.stringify(params), // json 데이터를 JSON.stringify()를 해야 에러가 발생하지 않는다.
        success : function(response) {
            alert('인증이 완료 되었습니다.');
        },
        error : function(response, status, error) {
            alert('에러 발생');
        }
    })
}
</script>

기본적으로 HTTP 메시지를 주고 받는 것이기 때문에 HTTP 메시지 구조(start line, header, body)로 코드를 작성해야 한다. 위 코드에서 보이는 필드 중 url, type은 start line에, contentType, dataType은 header에, data는 body에 해당한다. 그렇기에 각각에 맞는 내용들을 입력하면 된다. 메시지를 보낸 후 성공 응답인 경우는 succes, 실패 응답이라면 error에 선언한 콜백함수가 실행된다.

 

여기서 주의해야할 점은 body에 해당하는 data에 해쉬 그대로 넣으면 에러가 발생한다. 그래서 JSON.stringify()로 형변환을 한 뒤 전송해야 한다.

 

  • XMLHttpRequest 객체를 얻은 뒤, url을 통해 요청하고 응답을 받으면 응답 결과에 맞는 함수를 실행하는 구조이다
  • 효율적인 Ajax 사용을 위해 Jquery에서 구현한 ajax 기능을 사용했다

 

구체적인 예시 - 인증 이메일 전송하기

function completeAuthEmail() {
    var email = $('#email').val();
    var authKey = $('#authKey').val();
    const params = {
        'email' : email,
        'authKey' : authKey
    }

    $.ajax({
        url : `/v1/auth/email/complete`,
        type : 'post',
        contentType : 'application/json; charset=utf-8;',
        dataType : 'json',
        data : JSON.stringify(params),
        success : function(response) {
            alert('인증이 완료 되었습니다.');
        },
        error : function(response, status, error) {
            alert(JSON.parse(response.responseText).message);
        }
    })
}

기본 ajax 구조를 가지고 실제로 작성했던 코드이다. 구조는 동일하지만, body에 들어가는 구체적인 값을 jQuery로 가져오고, 실제 사용하는 api url을 추가했다. 각 상황에 맞게 성공일 때 동작방식과 실패일 때 동작방식을 설정해주면 된다. 

 

여기서 주의해야할 점은 응답 메시지에서 JSON key를 가져오는 방법이다. 응답 body의 메시지를 가져오고 싶은데 처음에는 response['message'] 와 같은 형식으로 데이터를 가져오려고 했는데 에러가 발생하고 원하는 값을 가져오지 못했다. 찾아보니 response가 Object이므로 '.'으로 바디를 꺼낸 뒤 JSON으로 파싱을 해야 응답 바디에서 원하는 key의 값을 가져올 수 있다.

// response body
{ status : NOT_FOUND,
  message : "존재하지 않는 이메일입니다."
}

// response body에 있는 message 키의 값 가져오는 코드
JSON.parse(response.responseText).message

 

 

참고

[JQUERY] SpringBoot - ajax 사용법 및 예제(+thymeleaf)

 

1. 계기

화면을 개발하면서 가장 불편했던 것이 사소한 태그 하나를 고치더라도 서버를 재시작해야 한다는 것이었다. 게다가 화면은 수시로 계속 변하기 때문에 정말 번거로운 작업이었다. 그래서 (인텔리제이 + 서버 재시작 없이 + 화면 업로드) 조합으로 구글링을 한 결과 인텔리제이에서 서버를 재시작할 필요없이 화면 변경사항을 반영하는 방법이 있어서 적용해봤다. 

 

2. 적용

2-1. 의존성 추가

compileOnly 'org.springframework.boot:spring-boot-devtools'

 

2-2. yml에 설정 추가

현재 thymeleaf를 사용하고 있기에 관련해서 설정을 추가로 해줘야 한다.

spring:
  devtools:
    livereload:
      enabled: true
    restart:
      enabled: true
  thymeleaf:
    cache: false

 

3. Settings 설정 

3-1. Compiler

맥북 기준으로 [cmd + ,] 를 누른뒤 Setting으로 이동해 Build > Compiler로 이동해 Build Project Automatically에 체크를 해준다.

 

3-2. Advanced Settings

구글링 해보면 registry에 가서 compiler.automake.allow.when.app.running를 누르라고 하는데, 2021.2 이후 버전의 인텔리제이라면 이렇게 하지말고 아래처럼 해야한다.

 

Settings > Advanced Settings > 'Allow auto-make to start ...' 에 체크

 

4. Gradle에서 IntelliJ IDEA로 변경

다른 블로그 설명을 따르면, 아래 그림처럼 Edit Configurations 에 들어가서 Running Application Update Policies을 변경하라고 하는데 On 'Update' action도 없고 다른 블로그에서 보이는 Settings 화면과 달랐다. 참고한 블로그에서도 똑같이 겪었고, 다음과 같은 방식으로 해결했다. 

 

Settings > Build, Execution, Deployment > Build Tools > Gradle -> IntelliJ IDEA

 

 

5. 실행

설정이 끝나면 서버를 재시작해야 적용된 것을 확인할 수 있다. 이제 정적 리소스(html, css)를 변경한 뒤 서버 리로드 할 필요 없이 브라우저에서 cmd + shift + r로 캐시 없애고 새로고침하면 변경된 것이 적용되는 것을 확인할 수 있다. 

 

참고

https://zoetechlog.tistory.com/92

1. 계기

이번 개인프로젝트는 서버 사이드 렌더링으로 만들었기에 Thymeleaf로 화면을 만들고 있다. 화면을 계속 만들다보니 몇가지 귀찮고 번거로운 작업들이 있는데, 그 중 하나가 중복된 <head> 태그를 똑같이 상단에 배치하는 것이었다. 

 

사용하는 헤더는 대략 다음과 같은데, 여기에서 새로운 <link>나 <script>를 추가해야하거나 변경해야하는 경우에는 모든 html 파일에 들어가서 수정해줘야 하는 번거로움이 있었고, 새로운 파일을 만들더라도 같은 내용을 계속 복붙을 해서 추가하는게 불필요한 작업이라고 생각했다. 그래서 이번에 레이아웃을 만들어서 적용해야겠다 싶었고, Thymeleaf를 적용하고 있으니 Thymeleaf 레이아웃 적용법을 공부해봐야겠다 싶었다.

 

일단 계속 사용하고 있는 헤더는 다음과 같다. 이거를 매번 새로운 html 파일 생성할 때마다 복붙하고, 변경이 생겼을 때 모든 파일에 적용하는 것이 얼마나 불편한지 겪어보지 않으면 모른다!

<head>
  <meta http-equiv="Content-Type" content="text/event-stream; charset=utf-8"/>
  <title>AccountBook</title>

  <!--아래 순서가 중요하다-->
  <script src="/webjars/jquery/jquery.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet">
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"></script>

  <script src="/static/js/index.js" type="text/javascript"></script>
</head>

 

 

2. 레이아웃 적용

일단 본 프로젝트에서는 Thymeleaf를 사용하기에 타임리프에서 레이아웃 적용법을 공부했다. 자세한 과정은 다음과 같다. 

 

2-1. 의존성 주입

gradle에서 타임리프와 타임리프 레이아웃 의존성을 추가해준다(타임리프 의존성이 있다면 생략 가능하다).

implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect'

 

2-2. header와 footer 만들기

모든 html 파일에 적용할 헤더와 푸터를 만들어야 하는데, 위치는 스프링에서 /resources/templates/fragments 디렉토리를 만들어 저장하면 된다. 

/resources/templates/fragments/header.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<th:block th:fragment="headerFragment">

    <head>
    적용할 <meta>, <script>, <link> 태그 정보를 추가하면 된다.
    </head>
    
</th:block>
</html>

 

/resources/templates/fragments/footer.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<th:block th:fragment="footerFragment">

    <head>
    적용할 <meta>, <script>, <link> 태그 정보를 추가하면 된다.
    </head>
    
</th:block>
</html>

 

 

2-3. 헤더와 푸터를 적용할 레이아웃 만들기

헤더와 푸터를 만들었으면 이들을 모든 html 파일에 적용할 수 있게 레이아웃 파일을 만들어야한다. 여기서는 default_layout이라고 명명했지만 이름은 상관 없다. 하지만 경로는 /resources/templates/layout이어야 한다.

 

/resources/templates/layout/default_layout.html

위 경로에 레이아웃 파일을 만들고 헤더위치에는 headerFragment를, 푸터 위치에는 FooterFragment의 <th:block>를 추가한다. 

중간에 들어갈 <body> 코드도 <th:block>으로 추가한다. 

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<th:block th:replace="fragments/header :: headerFragment"></th:block>

    <body>
        <th:block layout:fragment="content"></th:block>
    </body>
    
<th:block th:replace="fragments/footer :: footerFragment"></th:block>
</html>

 

 

3. 파일에 레이아웃 적용하기

만들어 놓은 레이아웃을 일반 파일에 적용하면 된다. 적용 구조는 다음과 같다.

파일명에 따라 아래에 layout:decorate의 값이 다를 수 있지만, 나머지 구조는 아래와 같이 동일하게 해야지 레이아웃이 적용된다. 마지막에 </th:block>과 </html> 태그 추가 하는 것을 까먹으면 안된다.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="layout/default_layout">
<th:block layout:fragment="content">

 내용(<body></body> 태그를 추가하지 않아도 된다)
 
</th:block>
</html>

 

 

위 과정을 거치면 이제 매번 헤더, 푸터를 복붙하지 않아도 된다!!

 

 

참고

https://zepettoworld.tistory.com/9

 

 

 

화면까지 구현하고자 했을 때 가장 먼저 해야할 것은 어떤 UI로 만들지 정하는 것이었다. UI 디자이너는 아니지만 피그마로 지금 내가 만들 수 있는 화면에 대해 구성을 해보았다. 계속 변경할 가능성이 있어서 실제 결과는 약간 다를 수도 있을 것 같다.

 

각 화면에 대한 기능이나 자세한 설명은 사진 하단 내용을 참고하면 될 것 같다.

 

 

1. 메인 화면

로그인 하기 전과 후에 화면이 다르게 구성했다.

 

- 로그인 전

메인 화면이 휑해서 추후에 내용을 채울 예정이다. 지금 생각하는 것으로는 프로젝트에 대한 간략한 소개와 가계부 프로젝트 이므로 경제 관련 기사를 크롤링해서 가져오는 것이 있다(어찌됐든 개발적으로 이것저것 경험하는 것이 좋을 것 같아서 크롤링까지 생각해봤다).

 

 

- 로그인 후

로그인 성공 후에는 가계부 달력과 지출관련 통계를 볼 수 있게 내비게이션 바 항목을 변경했다.

 

2. 회원가입

회원가입은 사용자 이름(닉네임), 이메일, 비밀번호를 입력한다. 이메일은 유일하며, 이를 인증하기 위해 작성한 이메일로 인증번호 전송 후 확인하는 방법으로 구현했다.

 

이메일을 입력한 후 '이메일 인증' 버튼을 누르면 작성한 이메일로 6자리 숫자로 구성된 인증번호가 전송되며, 인증번호를 입력할 수 있는 칸이 새로 생긴다. 

 

이메일로 전송받은 인증번호 입력 후 인증 완료 버튼을 누르면 alert가 뜨며 인증 성공을 알려준다. 이후 비밀번호를 입력한 후 '회원가입' 버튼을 누르면 회원가입에 성공한다.

 

3. 로그인

 

4. 가계부 달력

로그인 후 우측 상단에 있는 '가계부 달력'을 클릭하면 이번 달의 가계부 달력으로 이동한다. 

다른 달력을 보고 싶으면 왼쪽 상단에 있는 화살표로 이동하면 된다. 이동할 때마다 해당 월의 날짜 개수가 다르므로 화면은 동적으로 변한다.

오른쪽 상단에 '예산 알림 받기'를 누르면 화면에 있는 달의 예산 상태(여유, 초과) 알림을 알려준다.

달력 칸을 클릭하면 해당 날짜의 지출내역을 모달로 보여준다.

 

 

- 예산 알림 확인

'예산 알림 받기'를 클릭하면 오른쪽 상단에 알림이 온다.

예산이 남았다면 '남은 예산은 $$$$ 입니다.' 라고 온다.

예산이 초과되었다면 '예산이 초과되었습니다.' 라고 온다.

지출내역이 아예 존재하지 않는다면 '지출내역이 존재하지 않습니다.'라고 온다.

 

- 특정 날짜 클릭 시  관련 지출내역 조회

특정 날짜(예시로 3월 1일) 칸을 클릭하면 아래와 같이 모달이 뜬다.

모달에는 날짜에 해당하는 지출내역 정보가 금액 | 거래처 | 결제 수단 | 카테고리명 | 메모 형태의 리스트로 확인할 수 있다.

각 지출은 수정할 수 있으며, 우측에 있는 '수정' 버튼을 누르면 수정 페이지로 이동한다.

해당 날짜에 새로운 지출을 입력하고 싶으면 아래의 '+ 지출 입력하기' 버튼을 클릭하면 된다.

 

5. 지출 입력

지출 입력 항목은 금액, 카테고리, 거래처, 결제 수단, 메모, 시간이 있다.

 

금액, 거래처, 결제수단, 메모는 사용자가 직접 텍스트로 입력하면 된다.

카테고리는 사용자가 기존에 등록했던 카테고리 중에 선택해야 하므로, '카테고리를 선택하세요'라고 누르면 카테고리 리스트를 확인할 수 있다. 

시간은 'YYYY-mm-DD' 형태로 입력이 가능하며, 박스를 클릭하면 드롭박스로 날짜(ex. 2024-03-01)를 선택할 수 있다.

 

6. 카테고리

'카테고리를 선택하세요' 버튼을 누를 시 카테고리 리스트를 조회할 수 있는 모달이 뜬다.

카테고리 리스트는 사용자별로 동적으로 가져온다.

전체 카테고리 중 하나만 선택 가능하다(리스트가 라디오 버튼으로 구성되었기 때문).

각 카테고리별로 이름이 수정 가능하고, 카테고리 이름은 중복이 가능하다.

새로운 카테고리 추가할 때에는 '카테고리 추가' 버튼을 누르면 된다.

기존 카테고리를 수정/삭제 할 때에는 특정 카테고리 우측에 있는 '수정' 버튼을 누르면 수정/삭제하는 모달로 이동한다.

 

- 카테고리 추가

수정할 카테고리의 이름을 입력한 뒤 '완료' 버튼을 누르면 해당 카테고리가 저장된다.

그 동시에 이전의 카테고리 조회 모달로 이동한다.

 

- 카테고리 수정

수정할 카테고리 이름을 입력한 뒤 '완료' 버튼을 누르면 카테고리 수정이 반영된다.

'삭제' 버튼을 누르면 해당 카테고리가 삭제된다.

+ Recent posts