스프링과 JPA 기반 웹 애플리케이션 개발 #28 프로필 수정 폼

스프링과 JPA 기반 웹 애플리케이션 개발 #28 프로필 수정 폼

해당 내용은 인프런, 스프링과 JPA 기반 웹 애플리케이션 개발의 강의 내용을 바탕으로 작성된 내용입니다.

강의를 학습하며 요약한 내용을 출처를 표기하고 블로깅 또는 문서로 공개하는 것을 허용합니다 라는 원칙 하에 요약 내용을 공개합니다. 출처는 위에 언급되어있듯, 인프런, 스프링과 JPA 기반 웹 애플리케이션 개발입니다.

제가 학습한 소스코드는 https://github.com/n00nietzsche/jakestudy_webapp 에 지속적으로 업로드 됩니다. 매 커밋 메세지에 강의의 어디 부분까지 진행됐는지 기록해놓겠습니다.


프로필 수정

  • 컨트롤러
    • Bio, Link, Occupation, Location 정보만 입력받아서 Account 정보를 수정한다.

Profile 클래스 생성

@Data
public class Profile {
    private String bio;
    private String url;
    private String occupation;
    private String location;

    public Profile(Account account) {
        this.bio = account.getBio();
        this.url = account.getUrl();
        this.occupation = account.getOccupation();
        this.location = account.getLocation();
    }
}

Profile 클래스는 Account에서 Profile에 관련된 부분만 빼서 멤버로 만든 클래스이다.

SettingsController 클래스 생성

@Controller
@RequiredArgsConstructor
public class SettingsController {
    private final AccountRepository accountRepository;

    @GetMapping("/settings/profile")
    // 리턴 타입을 void 로 바꿔도,
    // `@GetMapping`의 value() 엘리먼트를 이용하여
    // 뷰 네임을 추측해서 자동으로 `/settings/profile`로 가긴 한다.
    public String profileUpdateForm(@CurrentUserAccount UserAccount userAccount, Model model) {
        Account currentAccount = accountRepository.findById(userAccount.getId()).get();
        model.addAttribute(currentAccount);
        model.addAttribute(new Profile(currentAccount));
        return "settings/profile";
    }
}

SettingsController는 설정과 관련된 페이지에 대한 컨트롤러이다. profileUpdateForm 메소드는 Account 정보 중 이전에 생성했던 Profile과 관련된 부분만 모델로 내려주면서 settings/profile 뷰를 반환하는 메소드이다.

사실 스프링에서 @GetMapping이 적용된 메소드의 리턴 타입을 void로 해도, @GetMapping 애노테이션의 value() 엘리먼트를 보고 자동으로 뷰의 경로를 추측하는 기능이 있어서 /settings/profile을 자동으로 찾아주지만, 명시적으로 작성하기 위해 뷰의 이름을 직접 반환해주었다.

Fragments.html 추가 작성

settings-menu (currentMenu)

<!-- (currentMenu) 라는 곳으로 인자를 받아서 어떤 것을 active 할 지 선택할 수 있다. -->
<div th:fragment="settings-menu (currentMenu)" class="list-group">
  <a class="list-group-item list-group-item-action"
     th:classappend="${currentMenu == 'profile'}? active"
     href="#" th:href="@{/settings/profile}">프로필</a>
  <a class="list-group-item list-group-item-action"
     th:classappend="${currentMenu == 'password'}? active"
     href="#" th:href="@{/settings/password}">패스워드</a>
  <a class="list-group-item list-group-item-action"
     th:classappend="${currentMenu == 'notifications'}? active"
     href="#" th:href="@{/settings/notifications}">알림 설정</a>
  <a class="list-group-item list-group-item-action"
     th:classappend="${currentMenu == 'tags'}? active"
     href="#" th:href="@{/settings/tags}">관심 주제</a>
  <a class="list-group-item list-group-item-action"
     th:classappend="${currentMenu == 'zones'}? active"
     href="#" th:href="@{/settings/zones}">활동 지역</a>
  <a class="list-group-item list-group-item-action list-group-item-danger"
     th:classappend="${currentMenu == 'account'}? active"
     href="#" th:href="@{/settings/account}">계정</a>
</div>

위는 처음 사용하는 패턴의 fragment인데, th:fragment의 속성 값에 마지막에 (currentMenu)라는 문자열을 추가하여 argument 처럼 이용한 형태이다.

th:classappend 속성을 이용하여 해당 argument를 활용할 수 있다. class에 active라는 문자를 append하면 활성화된 메뉴로 보인다.

profile.html 작성

<!DOCTYPE html>
<html lang="en"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
    <title>회원가입</title>
    <th:block th:replace="fragments :: headLibraryInjection"></th:block>
</head>

<body class="bg-light">

<th:block th:replace="fragments :: main-nav"></th:block>
<div class="alert alert-warning" role="alert" th:if="${account != null && !account?.emailVerified}">
    스터디올래 가입을 완료하려면 <a href="#" th:href="@{/check-email}" class="alert-link">계정 인증 이메일을 확인</a>하세요
</div>

<div class="container">
    <!-- row의 기본은 col-12만큼의 크기를 갖는다.-->
    <div class="row mt-5 justify-content-center">
        <div class="col-2">
            <div th:replace="fragments :: settings-menu(currentMenu='profile')"></div>
        </div>
        <div class="col-8">
            <div class="row">
                <h2 class="col-sm-12" th:text="${account.nickname}">현재 사용자 닉네임</h2>
            </div>
            <div class="row mt-3">
                <form class="col-sm-6" action="#" th:action="@{/settings/profile}"
                      th:object="${profile}" method="post" novalidate>
                    <div class="form-group">
                        <label for="bio">한 줄 소개</label>
                        <input id="bio" type="text" th:field="*{bio}" class="form-control"
                               placeholder="간략한 소개를 부탁합니다." aria-describedby="bioHelp" required>
                        <small id="bioHelp" class="form-text text-muted">
                            길지 않게 35자 이내로 입력하세요.
                        </small>
                        <small class="form-text text-danger" th:if="${#fields.hasErrors('bio')}" th:errors="*{bio}">
                            조금 길어요
                        </small>
                    </div>

                    <div class="form-group">
                        <label for="url">링크</label>
                        <input id="url" type="text" th:field="*{url}" class="form-control"
                               placeholder="https://jake_study.com" aria-describedby="urlHelp" required>
                        <small id="urlHelp" class="form-text text-muted">
                            길지 않게 35자 이내로 입력하세요.
                        </small>
                        <small class="form-text text-danger" th:if="${#fields.hasErrors('url')}" th:errors="*{url}">
                            올바른 URL이 아닙니다. 예시처럼 입력해주세요.
                        </small>
                    </div>

                    <div class="form-group">
                        <label for="occupation">직업</label>
                        <input id="occupation" type="text" th:field="*{occupation}" class="form-control"
                               placeholder="어떤 일을 하고계신가요?" aria-describedby="occupationHelp" required>
                        <small id="occupationHelp" class="form-text text-muted">
                            개발자? 매니저? 취준생? 대표님?
                        </small>
                    </div>

                    <div class="form-group">
                        <label for="location">활동 지역</label>
                        <input id="location" type="text" th:field="*{location}" class="form-control"
                               placeholder="Redmond, WA, USA" aria-describedby="locationHelp" required>
                        <small id="locationHelp" class="form-text text-muted">
                            주요 활동(사는 곳이나 직장을 다니는 곳 또는 놀러 다니는 곳) 지역의 도시 이름을 알려주세요.
                        </small>
                    </div>

                    <div class="form-group">
                        <button class="btn btn-primary btn-block" type="submit"
                                aria-describedby="submitHelp">수정하기</button>
                    </div>
                </form>
            </div>
        </div>
    </div>



    <th:block th:replace="fragments :: footer"></th:block>
</div>

<script th:replace="fragments :: form-validation"></script>
</body>
</html>

좋은 웹페이지 즐겨찾기