Spring/SpringBoot 실습

[SpringBoot 실습] 등록, 수정, 삭제, 조회 화면 만들기

메성 2021. 1. 3. 22:25
반응형

등록, 수정, 삭제, 조회 화면 만들기

게시글 등록 화면 만들기

게시글 등록 화면을 만들기 위해 오픈소스 부트스트랩을 사용하겠다.

부트스트랩, 제이쿼리 등 프론트엔드 라이브러리를 사용할 수 있는 방법은 2가지가 있는데, 하나는 외부 CDN을 사용하는 것이고, 하나는 직접 라이브러리를 받아서 사용하는 것이다.

우리는 외부 CDN을 사용하여 프론트엔드를 구현하겠다.

실무에서는 외부 CDN 방식을 사용하지 않는다. 직접 구현해야지!!

 

레이아웃 방식을 사용하여 부트스트랩과 제이쿼리를 index.mustache에 추가해야한다.

레이아웃 방식 : 공통 영역을 별도의 파일로 분리하여 필요한 곳에서 가져다 쓰는 방식

 

해당 부트스트랩과 제이쿼리를 머스테치 화면 어디에서다 필요하므로 공통으로 사용되는 레이아웃 파일을 만들어 추가하는 것이 좋다.

src/main/resources/templates 디렉토리에 layout 디렉토리를 추가로 생성한다. footer.mustache, header.mustache 를 추가하겠다.

image

header.mustache

<!-- 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.mustache

<!-- footer.mustache -->
<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 부분의 실행이 늦어지기 때문에 js는 body 하단에 두어 화면이 다 그려진 뒤 호출하는 것이 좋다.

반면, css는 당연히 화면을 그리는 역할을 하는 것으로 head에 불러오는 것이 좋다. 추가로 bootstrap.js의 경우 제이쿼리가 필수적이므로 bootstrap.min.js보다 먼저 호출하게 하였다. 이런 상황을 bootstrap.js가 제이쿼리에 의존한다라고 보면된다.

 

 

이제 head와 footer를 나누었으니, index.mustache의 코드는 꼭 필요한 코드만 남길 수가 있다.

index.mustache

<!-- index.mustache -->
{{>layout/header}}
    <h1>스프링 부트로 시작하는 웹 서비스</h1>
{{>layout/footer}}}

그럼 이제 index에 글 등록하는 화면을 만들어보자.

index.mustache - version2

{{>layout/header}}
<h1>스프링부트로 시작하는 웹 서비스 Ver.2</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}}

a태그를 통해 글 등록 버튼이 생성되었는데, 이 버튼을 클릭 시 이동할 페이지의 주소는 /posts/save가 되는 것이다.

 

IndexController.java

그럼 /posts/save에 관련된 Controller를 생성하자.

@Controller
public class IndexController {
    ...

    @GetMapping("/posts/save")
    public String postsSave() {
        return "posts-save";
    }
}

posts-save 페이지를 나타낼 posts-save.mustache를 생성하자. 위치는 index.mustache와 동일하다.

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}}

그럼 이제 다시 프로젝트를 실행해보자.

image

하지만 현재는 등록버튼을 눌러도 기능이 존재하지 않아 아무것도 등록할 수가 없다. 그럼 이제 등록을 할 수 있도록 js를 구현해보자.

src/main/resources에 static/js/app 디렉토리를 생성하자.

index.js

image

const index = {
    init: function () {
        const _this = this;
        $('#btn-save').on('click', function () {
            _this.save();
        });
    },
    save: function () {
        const 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));
        });
    }
};
index.init();

첫 문장을 보면 const index = {...}라는 코드를 선언했는데, 이렇게 선언한 이유에 대해서 살펴보자.

만약 index.js가 아래와 같이 function을 작성한 상황이라면,

const init = function() {
  ...
};

const save = function() {
  ...
};

init();

이런 상황일 때, index.mustache에 a.js가 추가되어 a.js도 a.js만의 init과 save function이 존재한다면 어떨까? 계속 중복된 function들이 존재할 것이다. 이를 방지하기 위해 브라우저 스코프를 사용하게 된다.

브라우저 스코프는 공용 공간으로 쓰이기 때문에 나중에 로딩된 js의 init, save가 먼저 로딩된 js의 function을 덮어쓰게 된다.

여러 사람들이 참여하는 프로젝트에서는 중복된 함수 이름이 자주 발생할 수 있는데, function을 만들 때 모든 function의 이름을 확인하면서 function을 만들 수는 없다. 그러므로, index.js에서만 유효한 범위(스코프)를 만들어 사용하는 것이다. 즉, const index란 객체를 만들어 해당 객체에서 필요한 모든 function들을 선언하게 되고, 해당 function들은 index 객체 안에서만 유효하기 때문에 다른 js와 겹칠 위험이 사라진다.

 

 

자 그럼 index.js를 머스테치 파일에서 사용할 수 있도록 footer.mustache에 추가해보자.

footer.mustache

<!-- footer.mustache -->
<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>

<!--index.js 추가-->
<script src="/js/app/index.js"></script>

</body>
</html>

index.js 호출 코드를 보면 절대 경로(/)로 바로 시작하는데 그 이유는, 스프링 부트는 기본적으로 src/main/resources/static에 위치한 자바스크립트, CSS, 이미지 등 정적 파일들은 URL에서 /로 설정된다.

Ex. url 설정

  • src/main/resources/static/js/... : http://도메인/js/
  • src/main/resources/static/css/... : http://도메인/css/
  • src/main/resources/static/image/... : http://도메인/image/

그럼 이제 게시글 등록을 해보자.

image

Alert 창이 정상적으로 나왔으니, 이제 DB에 접속하여 데이터가 등록되었는지 확인해보자.

Localhost:8082/h2-console에 접속.

image

등록 기능이 정상적으로 작동한 것을 확인할 수 있다.

반응형