1. 템플릿 엔진
- 지정된 템플릿 양식과 데이터가 합쳐져 HTML 문서를 출력하는 소프트웨어
- ex ) JSP, Freemarker(서버템플릿 엔진), React, View(클라이언트 템플릿엔진)
자바스크립트에서 JSP나 Freemarker처럼 자바코드를 사용할 수는 없나요?
FE의 자바스크립트가 작동하는 영역과 JSP가 작동하는 영역이 다르다.
JSP 및 Freemarker은 주로 서버에서 동적 컨텐츠를 생성하고 이를 클라이언트로 보내는데 사용되는 서버 측 기술이고, 자바스크립트는 브라우저에서 실행되는 클라이언트 측 스크립트 언어이다.
서버에서 Java코드로 문자열을 만든 뒤 이 문자열을 HTML로 변환하여 브라우저로 전달하는데, 이 때 자바스크립트 코드는 단순한 문자열이기에 코드로서 동작하지 않는다.
반면 자바스크립트는 브라우저위에서 작동하므로 서버에서는 Json이나 Xml형태의 데이터만 전달하고 클라이언트에서 조립한다.
최근에는 리액트나 뷰와 같은 자바스크립트 프레임워크에서 서버 사이드 렌더링을 지원하는 모습을 볼 수 있다.
2. 머스테치
- 머스테치란?
수많은 언어를 지원하는 가장 심플한 템플릿 엔진
- 장점
(1) 문법이 다른 템플릿 엔진보다 심플
(2) 로직코드를 사용할 수 없어 View의 역할과 서버의 역할을 명확하게 분리된다
(3) Mustache.js와 Mustache.java 2가지가 다 있어, 하나의 문법으로 클라이언트/서버 템플릿을 모두 사용가능하다.
3. 기본 페이지 만들기
1) 머스테치 플러그인 설치
2) 머스테치 의존성 등록
- build.gradle
implementation 'org.springframework.boot:spring-boot-starter-mustache'
3) index.mustache 만들기
- 기본적으로 mustache의 위치는 src/main/resource/template
- 이 위치에 머스테치 파일을 두면 스프링부트에서 자동으로 로딩
<!DOCTYPE HTML>
<html>
<head>
<title>스프링 부트 웹서비스</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1>스프링 부트로 시작하는 웹 서비스</h1>
</body>
</html>
4) IndexController.java 생성
머스테치에 URL 매핑, URL 매핑은 Controller에서 진행
- web 패키지 안에 생성
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
public class IndexControllerTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
public void 메인페이지_로딩() {
// when
String body = this.restTemplate.getForObject("/", String.class);
// then
assertThat(body).contains("스프링 부트로 시작하는 웹 서비스");
}
}
- 머스테치 스타터 덕분에 컨트롤러에서 문자열을 반환할 때 앞의 경로와 뒤의 파일 확장자는 자동으로 지정
- 여기서는 "index"를 반환하므로, src/main/resource/templates/index.mustache로 전환되어 View Resolver가 처리
5) 테스트 코드 검증
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class IndexControllerTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
public void Mainpage_load(){
//when
String body = this.restTemplate.getForObject("/", String.class);
//then
assertThat(body).contains("스프링 부트로 시작하는 웹 서비스");
}
}
- 이번 테스트는 실제로 URL 호출시 페이지 내용이 제대로 호출되는 지에 대한 테스트
- HTML도 결국은 규칙이 있는 문자열
- TestRestTemplate을 통해 "/"로 호출했을 때 index.mustache에 포함된 코드들이 있는지 확인하면 된다.
4. 게시글 등록 화면 만들기
- 오픈소스인 부트스트랩을 이용
부트스트랩, 제이쿼리 등 프론트엔드 라이브러리를 사용할 수 있는 방법은 크게 2가지가 있는데 하나는 외부 CDN을 사용하는 것이고, 다른 하나는 직접 라이브러리를 받아 사용하는 것이다.
여기에서는 외부 CDN을 사용한다. 프로젝트에서 직접 내려받아 사용할 필요도 없고, 사용 방법도 HTML/JSP/Mustache에 코드만 한줄 추가하면 되니 굉장히 간단하다
실제 서비스에서는 이 방법을 잘 사용하지 않는다. 결국은 외부 서비스에 우리 서비스가 의존하게 되버려서, CDN을 서비스하는 곳에 문제가 생기면 덩달아 같이 문제가 생기기 때문이다.
- 2개의 라이브러리 부트스트랩과 제이쿼리를 index.mustache에 추가해야 한다.
- 레이아웃 방식으로 추가
레이아웃 방식이란?
공통 영역을 별도의 파일로 분리하여 필요한 곳에서 가져다 쓰는 방식
- 매번 해당 라이브러리를 머스테치 파일에 추가하는 것은 힘드니, 레이아웃 파일들을 만들어 추가
1) src/main/resource/templates 디렉토리에 layout 디렉토리 추가 생성한 후 header.mustache, footer.mustache 파일 생성
2) 코드 추가
- header.mustache
<!DOCTYPE HTML>
<html>
<head>
<title>스프링부트 웹서비스</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
</head>
<body>
- footer.musatche
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
</body>
</html>
코드에서 css와 js의 위차가 서로 다르다.
페이지 로딩속도를 높이기 위해 css는 header에 js는 footer에 두었다. HTML은 위에서부터 코드가 실행되기 때문에 head가 다 실행되고 나서야 body가 실행된다. 즉, head가 다 불러지지 않으면 사용자쪽에서는 백지화면만 노출된다.
그러므로 js는 body하단에 두어 화면이 다 그려진 뒤에 호출하는 것이 좋다.
3) 글 등록버튼 추가
- index.mustache
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8" >
<meta http-equiv="Content-Type" content="text/html;" charset="UTF-8" />
<title>스프링 부트 웹서비스</title>
</head>
<body>
{{>layout/header}}
<h1>스프링 부트로 시작하는 웹 서비스 Ver.2</h1>
<div class="col-md-12">
<div class="col-md-6">
<a href="/posts/save" role="button"
class="btn btn-primary">글 등록</a>
</div>
</div>
{{>layout/footer}}
</body>
</html>
- {{> }} : 현재 머스테치 파일을 기준으로 다른 파일을 가져옴
- a : 태그를 이용해 글 등록 버튼 생성
- 이동할 페이지 주소 : /posts/save
4) 컨트롤러 생성
- IndexController.java
@RequiredArgsConstructor
@Controller
public class IndexController {
private final PostsService postsService;
...
@GetMapping("/posts/save")
public String postSave() {
return "posts-save";
}
}
/posts/save를 호출하면 posts-save.mustache를 호출하는 메소드 추가
5) posts-save.mustache 추가
{{>layout/header}}
<h1>게시글 등록</h1>
<div class="col-md-12">
<div class="col-md-4">
<form>
<div class="form-group">
<label for="title">제목</label>
<input type="text" class="form-control" id="title" placeholder="제목을 입력하세요">
</div>
<div class="form-group">
<label for="author"> 작성자 </label>
<input type="text" class="form-control" id="author" placeholder="작성자를 입력하세요">
</div>
<div class="form-group">
<label for="content"> 내용 </label>
<textarea class="form-control" id="content" placeholder="내용을 입력하세요"></textarea>
</div>
</form>
<a href="/" role="button" class="btn btn-secondary">취소</a>
<button type="button" class="btn btn-primary" id="btn-save">등록</button>
</div>
</div>
{{>layout/footer}}
- UI 확인
이때 게시글 등록 화면에 등록 버튼은 기능이 없다.
그 이유는 API를 호출하는 JS가 없기 때문
6) JS파일 생성
- src/main/resource에 static/js/app 디렉토리 생성
- index.js
var main = {
init : function () {
var _this = this;
$('#btn-save').on('click', function () {
_this.save();
});
},
save : function () {
var data = {
title: $('#title').val(),
author: $('#author').val(),
content: $('#content').val()
};
$.ajax({
type: 'POST',
url: '/api/v1/posts',
dataType: 'json',
contentType:'application/json; charset=utf-8',
data: JSON.stringify(data)
}).done(function() {
alert('글이 등록되었습니다.');
window.location.href = '/';
}).fail(function (error) {
alert(JSON.stringify(error));
});
}
};
main.init();
- window.location.href = '/' : 글 등록이성공하면 메인 페이지(/)로 이동
index.js에서 var main = {}이라는 코드를 쓰는 이유는?
여러 사람이 참여하는 프로젝트에서는 중복된 함수 이름이 자주 발생할 수 있는데, 이런 문제를 피하려고 유효범위(scope)를 만들어 사용한다. 이렇게 한다면 index 객체 안에서만 function이 유효하기 때문에 다른 JS와 겹칠 위험이 사라진다.
7) 생성된 index.js를 머스테치 파일이 쓸 수 있도록 footer.musatche에 추가
<script src="/js/app/index.js"></script>
이 코드에서 절대경로로 바로 시작하는데, 스프링 부트는 기본적으로 src/main/resource/static에 위치한 자바스크립트, CSS, 이미지 등 정적 파일들은 URL에서 /로 설정된다.
8) 등록기능 테스트
'스프링부트와 AWS로 혼자 구현하는 웹서비스' 카테고리의 다른 글
Part 10. 로그인 기능 구현 (1) (1) | 2024.01.15 |
---|---|
Part 9. 게시글 화면 만들기 (2) (0) | 2024.01.13 |
Part 7. JPA Auditing으로 생성시간/수정시간 자동화하기 (1) | 2024.01.11 |
Part 6. 등록/수정/조회 API 만들기 (0) | 2023.12.17 |
Part5. 프로젝트에 Spring Data JPA 적용 (0) | 2023.12.14 |