템플릿 엔진(Template Engine)
입력 자료를 가공하여 결과물(웹 문서)을 출력하는 소프트웨어를 의미한다.
웹 템플릿 엔진에는 두 가지 종류가 있는데,
1) 서버 템플릿 엔진 (Server Side Template Engine)
: 서버에서 가져온 데이터를 Template 엔진에 넣어서 HTML을 생성, 클라이언트에 전달해주는 역할을 한다.
ex) JSP(Java Server Page) : 서버 템플릿 엔진을 이용한 화면 생성. 서버 단에서 Java 코드로 문자열을 만든 뒤 이 문자열을 HTML로 변환하여 브라우저로 전달한다.
2) 클라이언트 템플릿 엔진 (Client Side Template Engine)
HTML 형태로 코드를 작성, 동적으로 DOM(Documnet Object Model) 을 그리게 해주는 역할을 한다.
ex) JavaScript : 브라우저 단에서 화면을 생성, 서버에서는 Json 혹은 Xml 형식의 데이터만 전달하고 이를 클라이언트에서 조립하는 것이다.
자바 진영에서는 JSP, Velocity, Freemarker, Thymeleaf 등의 다양한 서버 템플릿 엔진이 존재한다.
그 중에서 Mustache라는 서버 템플릿 엔진을 사용할 건데, 그 이유는 다음과 같다.
- 문법이 다른 템플릿 엔진보다 심플하다
- 로직 코드를 사용할 수 없어, View와 서버의 역할이 명확히 구분
- 하나의 문법으로 클라이언트/서버 템플릿을 모두 사용할 수 있다.
Mustache 사용하기
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>스프링 부트 웹서비스</title>
</head>
<body>
<h1>스프링 부트로 시작하는 웹 서비스</h1>
</body>
</html>
main/resources/templates/index.mustache
'mustache'라는 확장자를 가지고 있는 파일이다.
단순히 HTML 코드로 이루어져 있으며 아래 Controller에 의해 View Resolver가 반환된 index를 처리한다.
@Controller
public class IndexController {
@GetMapping("/")
public String index(){
return "index";
}
}
IndexControllerTest.java
@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("스프링 부트로 시작하는 웹 서비스");
}
}
TestRestTemplate을 사용해 "/"를 호출했을 때, index.mustache에 포함된 코드가 있는 지 확인하면 된다.
게시글 등록 화면 만들기
위 mustache파일의 header와 footer파일을 나눠 분리해서 아래와 같이 만들 수 있다.
{{>layout/header}}
<h1>스프링 부트로 시작하는 웹 서비스</h1>
{{>layout/footer}}
{{ > }} : 현재 머스테치 파일을 기준으로 다른 파일을 가져온다.
{{>layout/header}}
<h1>스프링 부트로 시작하는 웹 서비스</h1>
<div class = "col-md-12">
<div class = "row">
<div class = "col-md-6">
<a href="/posts/save" role="button" class="btn btn-primary">글 등록</a>
</div>
</div>
</div>
{{>layout/footer}}
글 등록 버튼을 만들고, 이동할 페이지를 "/post/save"로 설정한다.
이제 API를 호출할 수 있는 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) // JS 객체를 JSON 문자열로 반환
}).done(function(){
alert('글이 등록되었습니다. ');
window.location.href = '/';
}).fail(function(error){
alert(JSON.stringify(error));
});
}
};
main.init();
구조는 대강, index의 변수의 속성으로 main function을 추가하였고,
그 내부에서 index.js의 init과 save function을 구현하였다.
init과 save를 하나의 함수로 취급해서 모든 자바스크립트 파일의 내용을 구현하지 않도록 하는 이유이다.
{{#posts}} : posts라는 List를 순회
{{id}} 등의 변수명 : List에서 뽑아낸 객체의 필드를 사용한 부분
public interface PostsRepository extends JpaRepository<Posts, Long> {
@Query("select p from Posts p order by p.id desc")
List<Posts> findAllDesc();
}
PostsRepository에 id를 기준으로 정렬된 Posts를 가져오는 메서드를 선언한다.
map 함수를 사용한 부분에서 에러가 뜨는데,
map(PostsListResponseDto::new)
map(posts -> new PostsListResponseDto(posts))
postsRepository에서 불러온 Posts의 Stream을 map을 통해 PostsListResponseDto로 변환하고,
이를 다시 Collectors의 내부 메서드를 통해 List로 변환하는 작업이다.
즉 생성자 중에 매개변수를 Posts 객체로 받는 것이 PostsListReponseDto에 위치해있어야 하는 것이다.
@Getter
public class PostsListResponseDto {
private Long id;
private String title;
private String author;
private LocalDateTime modifiedDate;
// convert Posts to PostsListResponseDto
public PostsListResponseDto(Posts entity){
this.id = entity.getId();
this.title = entity.getTitle();
this.author = entity.getAuthor();
this.modifiedDate = entity.getModifiedDate();
}
}
이렇게 생성자를 만들어줌으로써 해결!
posts라는 이름으로 findAllDesc()의 결괏값을 View에 넘겨준다.
이때 사용하는 Model은 아래의 특징을 가진다.
- 서버 템플릿 엔진에서 사용할 수 있는 객체를 저장할 수 있다.
- postsService.findAllDesc()의 결과를 posts라는 이름으로 index.mustache에게 전송한다.
게시글 등록, 삭제 화면 만들기
먼저 posts-udpate.mastche 파일을 생성해 view 화면을 만들어준다.
{{>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="id" value="{{post.id}}" readonly>
</div>
<div class="form-group">
<label for="title">제목</label>
<input type="text" class="form-control" id="title" value="{{post.title}}">
</div>
<div class="form-group">
<label for="author"> 작성자 </label>
<input type="text" class="form-control" id="author" value="{{post.author}}" readonly>
</div>
<div class="form-group">
<label for="content"> 내용 </label>
<textarea class="form-control" id="content">{{post.content}}</textarea>
</div>
</form>
<a href="/" role="button" class="btn btn-secondary">취소</a>
<button type="button" class="btn btn-primary" id="btn-update">수정 완료</button>
<button type="button" class="btn btn-danger" id="btn-delete">삭제</button>
</div>
</div>
{{>layout/footer}}
이런 readonly 기능은 Input 태그에 읽기 가능만 허용하는 속성이다.
아래는 index.js의 수정본이다.
var main = {
init : function(){
var _this = this;
$('#btn-save').on('click', function(){
_this.save();
}); // 저장 버튼 생성
$('#btn-update').on('click', function(){ // (1)
_this.update();
}) // 수정 버튼 생성
},
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) // JS 객체를 JSON 문자열로 반환
}).done(function(){
alert('글이 등록되었습니다. ');
window.location.href = '/';
}).fail(function(error){
alert(JSON.stringify(error));
});
},
update : function(){ // (2)
var data = {
title : $('#title').val(),
content: $('#content').val()
};
var id = $('#id').val();
$.ajax({
type : 'PUT', // (3)
url : '/api/v1/posts/' + id, // (4)
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();
(1) btn-update라는 id를 가진 버튼이 활성화되면, update function을 실행하도록 함
(4) 게시글 번호를 지정하여 url을 호출한다. 이때 id = $("#id").val()이다.
게시글 삭제는 생략
'Framework > Spring' 카테고리의 다른 글
[SpringSecurity] JWT (Json Web Token) 기반의 인증 인가 구현하기 (0) | 2023.07.09 |
---|---|
[3주차] Spring Security 구글 소셜 로그인 구현, 세션 관리 (0) | 2022.05.22 |
[1주차] 스프링 부트 테스트 코드 작성, JPA로 데이터베이스 접근하기 (0) | 2022.05.08 |
Spring 첨부파일 다운로드 코드 리팩토링 (0) | 2022.03.28 |
[IBAS] 첨부파일 업로드(FileService, FileController) WebMvcTest (0) | 2022.03.26 |