반응형

form 관련 기능 정리

  • Thymeleaf의 form과 관련된 기능들을 정리해보고 이를 응용해 간단한 학생 등록, 조회 서비스 제작

form 태그 생성

  • Controller에서 form을 호출할 때, Student 타입의 "student" 객체를 생성해 model을 통해 전달
<form action="/student/register" th:object="${student}" method="post">
  • form에서 th:object를 사용해 "student"를 전달 받음
  • action, method를 사용함으로서 form을 submit 하면 POST /student/register 로 form 값이 전송되게 지정

  • 이 form을 만들 예정

th:field, input 태그로 이름, 학번, 나이 입력받기

<label th:for="name">이름 : </label>
<input type="text" th:field="*{name}" placeholder="이름을 입력하세요"/>
  • input 태그에서 th:field를 사용함으로써 여기에 입력되는 값이 student.name에 들어가게 됨
  • 원래는 ${student.name}으로 받아야 함
  • form태그에서 th:object를 해줬기 때문에 form이 student와 매핑되어서 *{name}만으로도 받을 수 있음
  • th:field는 태그의 id, name을 자동으로 지정해주는 기능도 있음
    • th:field="*{name}" => HTML의 id="name" name="name"으로 채워줌
  • 학번, 나이는 숫자이기 대문에 type="number"로 해줌

성별 : radio button

<div>&lt;성별&gt;</div>
<div>
    <label th:for="isMale">남성</label>
    <input type="radio" th:field="*{isMale}" value="true"/>
    <label th:for="isMale">여성</label>
    <input type="radio" th:field="*{isMale}" value="false"/>
</div>
  • 성별은 남성, 여성 두가지 입력이 존재하고 하나만 선택할 수 있음
    • radio button 사용
  • Student의 isMale이 true이면 남성, false이면 여성으로 판단
  • th:field를 똑같이 맞춰줘야 하나가 선택되면 다른 하나의 선택이 취소됨
    • name이 같아짐
    • id는 순서대로 자동 생성(밑에서 자세히 설명)
  • 성별이 아무것도 선택되지 않을 수 있기때문에 Controller에서 student를 넘길 때, student.isMale 값을 true 혹은 false로 초기화 해줬음

학년 : radio button, ENUM 사용

  • 학년도 성별과 마찬가지로 4개의 옵션 중 하나만 선택할 수 있어야 함
    • radio button 사용
  • 성별처럼 하나씩 해줘도 되지만 이번엔 ENUM을 사용해 봄
  • enum 타입의 Grade를 먼저 생성해 줌
    • name : Freshman, Sophomore, Junior, Senior
    • description : 1학년, 2학년, 3학년, 4학년
public enum Grade {
    Freshman("1학년"), Sophomore("2학년"), Junior("3학년"), Senior("4학년");

    private final String description;

    Grade(String description) {
        this.description = description;
    }

    public String getDescription() {
        return description;
    }
}
  • 이제 Controller에서 성별과 마찬가지로 student.grade를 Grade.Freshman으로 초기화
  • Contoller에서 넘길 때, "grades" 키값으로 Grade.values()를 넘겨줌
<div>
    <div>&lt;학년&gt;</div>
    <div th:each="grade : ${grades}">
        <label th:for="${#ids.next('grade')}" th:text="${grade.description}"/>
        <input type="radio" th:field="*{grade}" th:value="${grade.name()}"/>
    </div>
</div>
  • Controller에서 보낸 "grades"를 반복문을 돌면서 th:text, th:value로 출력

th:field

  • 학년을 화면에 출력할 때 #ids.next('grade')와 th:field="*{grade}"를 사용했음
  • 이렇게 해줬을 때 HTML에는 아래와 같이 매핑됨

  • 위에서 보다시피 name은 모두 grade로 통일되어서 하나를 선택하면 같은 name을 가진 태그들의 선택이 취소됨
  • id는 중복되면 안되기 때문에 "grade1", "grade2", ... 와 같이 자동으로 생성됨
  • 위에서 했던 성별도 th:field를 사용했기 때문에 name은 같고, id는 순서대로 자동생성 되었음

#ids.next, #ids.prev

  • th:field를 사용해서 input 태그들의 id가 자동으로 생성되었음
  • label 태그의 for에는 #ids.next('grade')를 사용해 줬는데 뒤에 나오는 input 태그의 아이디와 같도록 자동으로 매핑됨
  • 만약 input 태그가 label 태그 전에 나왔다면 #ids.prev('grade')를 써줬어야 함

재학 여부 : single checkbox

  • checkbox는 radio button과 달리 여러개를 선택할 수 있음
  • 재학 여부는 재학중이면 체크, 휴학중이면 체크하지 않는 방식으로 입력받음
  • 굳이 checkbox를 사용하지 않아도 되지만 예시를 위해 사용해 봄
<div>
    <div>&lt;재학 여부&gt;</div>
    <div>
        <label th:for="isAttending">재학중이면 체크하세요</label>
        <input type="checkbox" th:field="*{isAttending}" />
    </div>
</div>

참여 활동 : multi checkbox

  • 참여 활동의 종류는 임의로 선정
    • 중앙동아리, 과동아리, 연합동아리, 과학생회, 총학생회
  • 여러가지 활동을 동시에 할 수 있기 때문에 checkbox 사용
  • 참여 활동 리스트(clubs)는 Controller에서 생성해서 보내줘야 함
<div>
    <div>&lt;참여 활동&gt;</div>
    <div th:each="club : ${clubs}">
        <input type="checkbox" th:field="*{clubs}" th:value="${club.key}" />
        <label th:for="${#ids.prev('clubs')}" th:text="${club.value}" />
    </div>
</div>

전공 : select

  • 전공은 임의로 지정한 학과 중 선택해서 입력받는 형식
    • 컴퓨터공학부, 기계공학부, 전자전기공학부
  • 전공 리스트(majors)도 Controller에서 생성해서 보내줘야 함
<div>
    <div>&lt;전공&gt;</div>
    <select th:field="*{major}" style="width: 150px;">
        <option value="">전공을 선택하세요</option>
        <option th:each="major : ${majors}" th:value="${major}" th:text="${major}"/>
    </select>
</div>
  • 여기까지 각각의 기능에 대해 설명했고 이제 전체 코드와 결과를 확인해보자

전체 코드

Student.java

  • 학생 객체
@Data
public class Student {

    private int id;
    private String name;
    private int studentId;          // 학번
    private int age;
    private Boolean isMale;         // 성별 (true: 남성, false: 여성)
    private Grade grade;            // 학년
    private Boolean isAttending;    // 재학 여부
    private List<String> clubs;     // 참여 활동
    private String major;           // 전공
}

Grade.java

  • 학년을 나타내는 enum class
public enum Grade {
    Freshman("1학년"), Sophomore("2학년"), Junior("3학년"), Senior("4학년");

    private final String description;

    Grade(String description) {
        this.description = description;
    }

    public String getDescription() {
        return description;
    }
}

RegisterForm.html

  • 위에서 설명한 기능들을 모두 모아놓은 코드
  • 학생 추가를 담당하는 페이지
<!DOCTYPE html>
<html lang="ko">
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body align="center">
    <h1>학생 추가 Form</h1>
    <div align="center">
        <form action="/student/register" th:object="${student}" method="post">
            <div>
                <label th:for="name">이름 : </label>
                <input type="text" th:field="*{name}" placeholder="이름을 입력하세요"/>
            </div> <br/>

            <div>
                <label th:for="studentId">학번 : </label>
                <input type="number" th:field="*{studentId}"/>
            </div> <br/>

            <div>
                <label th:for="age">나이 : </label>
                <input type="number" th:field="*{age}"/>
            </div> <br/>

            <!-- radio button -->
            <div>&lt;성별&gt;</div>
            <div>
                <label th:for="isMale">남성</label>
                <input type="radio" th:field="*{isMale}" value="true"/>
                <label th:for="isMale">여성</label>
                <input type="radio" th:field="*{isMale}" value="false"/>
            </div> <br/>

            <div>
                <div>&lt;학년&gt;</div>
                <div th:each="grade : ${grades}">
                    <label th:for="${#ids.next('grade')}" th:text="${grade.description}"/>
                    <input type="radio" th:field="*{grade}" th:value="${grade.name()}"/>
                </div>
            </div> <br/>

            <!-- single checkbox -->
            <div>
                <div>&lt;재학 여부&gt;</div>
                <div>
                    <label th:for="isAttending">재학중이면 체크하세요</label>
                    <input type="checkbox" th:field="*{isAttending}" />
                </div>
            </div> <br/>

            <!-- multi checkbox -->
            <div>
                <div>&lt;참여 활동&gt;</div>
                <div th:each="club : ${clubs}">
                    <input type="checkbox" th:field="*{clubs}" th:value="${club.key}" />
                    <label th:for="${#ids.prev('clubs')}" th:text="${club.value}" />
                </div>
            </div> <br/>

            <!-- select -->
            <div>
                <div>&lt;전공&gt;</div>
                <select th:field="*{major}" style="width: 150px;">
                    <option value="">전공을 선택하세요</option>
                    <option th:each="major : ${majors}" th:value="${major}" th:text="${major}"/>
                </select>
            </div> <br/>

            <div align="center">
                <button type="button" th:onclick="|location.href='@{/student/}'|">취소</button>
                <button type="submit" style="margin-left: 30px">추가</button>
            </div>
        </form>
    </div>
</body>
</html>
<style>
form {
    border: black;
    border-style: solid;
    width:500px;
    text-align: left;
    padding: 20px;
}
button {
    height: 40px;
    width: 100px;
    font-size: large;
}
</style>

StudentInfo.html

  • 추가한 학생의 정보를 확인할 수 있는 페이지
  • RegisterForm에서는 입력을 받았다면 StudentInfo는 출력을 해줌
<!DOCTYPE html>
<html lang="ko">
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body align="center">
    <h1>학생 세부 정보</h1>
    <div align="center">
        <div class="form-style">
            <div th:text="'이름 : ' + ${student.name}"/> <br/>
            <div th:text="'학번 : ' + ${student.studentId}"/> <br/>
            <div th:text="'나이 : ' + ${student.age}"/> <br/>

            <div th:if="${student.isMale == true}" th:text="'성별 : 남성'" />
            <div th:unless="${student.isMale == true}" th:text="'성별 : 여성'" />
            <br/>

            <div th:text="'학년 : ' + ${student.grade.description}"/> <br/>

            <div th:if="${student.isAttending == true}" th:text="'재학 여부 : 재학중'" />
            <div th:unless="${student.isAttending == true}" th:text="'재학 여부 : 휴학중'" />
            <br/>

            <div>
                <div>&lt;참여활동&gt;</div>
                <div th:each="club : ${clubs}">
                    <input type="checkbox" th:field="${student.clubs}" th:value="${club.key}" disabled />
                    <label th:for="${#ids.prev('clubs')}" th:text="${club.value}"/>
                </div>
            </div>
            <br/>

            <div th:text="'전공 : ' + ${student.major}" /> <br/>
            <div align="center">
                <button type="button" th:onclick="|location.href='@{/student/}'|">뒤로가기</button>
            </div>
        </div>
    </div>
</body>
</html>
<style>
.form-style {
    border: black;
    border-style: solid;
    width:500px;
    text-align: left;
    padding: 20px;
}
button {
    height: 40px;
    width: 100px;
    font-size: large;
}
</style>

StudentList.html

  • 홈 화면이자 전체 학생 목록을 볼 수 있는 페이지
<!DOCTYPE html>
<html lang="ko">
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>전체 학생 리스트</h1>
    <div>
        <button type="button" th:onclick="|location.href='@{/student/register}'|">학생 추가</button>
    </div> <br/>

    <div align="center">
        <table>
            <thead>
            <tr>
                <th style="width: 50px;">ID</th>
                <th style="width: 150px;">학번</th>
                <th style="width: 100px;">이름</th>
                <th style="width: 200px">세부 정보</th>
            </tr>
            </thead>
            <tbody>
            <tr th:each="student : ${students}">
                <td th:text="${student.id}"/>
                <td th:text="${student.studentId}"/>
                <td th:text="${student.name}"/>
                <td>
                    <button type="button" th:onclick="|location.href='@{/student/{Id}(Id=${student.id})}'|"
                            style="width: auto">
                        세부 정보 보기
                    </button>
                </td>
            </tr>
            </tbody>
        </table>
    </div>
</body>
</html>
<style>
table{
    border-style: solid;
    border-color: black;
    width: 500px;
    text-align: center;
}
button {
    height: 40px;
    width: 100px;
    font-size: large;
}
</style>

StudentRepository

  • Service를 따로 안 만들고 Repository에서 비지니스 로직, DB 접근 병행
  • students라는 Map을 사용해 DB 대체
@Repository
public class StudentRepository {

    private Map<Integer, Student> students = new HashMap<>();
    private int sequence = 0;

    public int save(Student student) {
        student.setId(++ sequence);
        students.put(student.getId(), student);
        return student.getId();
    }

    public Student findById(int id) {
        return students.get(id);
    }

    public List<Student> findAll() {
        return new ArrayList<>(students.values());
    }
}

StudentController

  • Controller
  • 참고 : HTML 파일들이 resources/templates/thymeleaf_form 에 있음
@Controller
@RequestMapping("/student")
@RequiredArgsConstructor
public class StudentController {

    private final StudentRepository studentRepository;

    @GetMapping("")
    public String home(Model model) {
        model.addAttribute("students", studentRepository.findAll());
        return "thymeleaf_form/StudentList";
    }

    @GetMapping("/register")
    public String registerForm(Model model) {
        Student student = new Student();
        student.setIsMale(  true);
        student.setGrade(Grade.Freshman);
        model.addAttribute("student", student);
        model.addAttribute("grades", Grade.values());

        Map<Integer, String> clubs = new LinkedHashMap<>();
        clubs.put(1, "중앙 동아리");
        clubs.put(2, "과 동아리");
        clubs.put(3, "연합 동아리");
        clubs.put(4, "과 학생회");
        clubs.put(5, "총 학생회");
        model.addAttribute("clubs", clubs);

        List<String> majors = new ArrayList<>();
        majors.add("컴퓨터공학부");
        majors.add("기계공학부");
        majors.add("전자전기공학부");
        model.addAttribute("majors", majors);

        return "thymeleaf_form/RegisterForm";
    }

    @PostMapping("/register")
    public String registerStudent(@ModelAttribute Student student) {
        studentRepository.save(student);
        return "redirect:/student";
    }

    @GetMapping("/{id}")
    public String studentInfo(@PathVariable int id, Model model) {
        model.addAttribute("student", studentRepository.findById(id));
        System.out.println(studentRepository.findById(id));

        Map<Integer, String> clubs = new LinkedHashMap<>();
        clubs.put(1, "중앙 동아리");
        clubs.put(2, "과 동아리");
        clubs.put(3, "연합 동아리");
        clubs.put(4, "과 학생회");
        clubs.put(5, "총 학생회");
        model.addAttribute("clubs", clubs);

        return "thymeleaf_form/StudentInfo";
    }
}

결과

  • 홈 화면이자 전체 학생 리스트 (아무것도 추가하지 않았을 때)

  • 학생 추가 화면

  • 학생 추가 결과

  • 학생 세부 정보

반응형

↓ 클릭시 이동

복사했습니다!