https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation
@PostMapping("/add")
public String add(@Valid Page page, BindingResult bindingResult, RedirectAttributes attr) {
//Validation 결과 Error가 있으면 이전페이지로 이동
if (bindingResult.hasErrors()) {
return "admin/pages/add";
}
// 검사 통과 시
attr.addFlashAttribute("message","성공적으로 페이지 추가됨");
attr.addFlashAttribute("alertClass","alert-success"); // 부트스트랩 경고창(성공색으로 변경)
// Slug 검사
// Slug를 미입력 시 Title을 소문자로 하고 공백을 - 으로 변환, 입력시에도 소문자 공백은 - 변환
String slug = page.getSlug() == "" ? page.getTitle().toLowerCase().replace(" ", "-") : page.getSlug();
Page slugExist = pageRepo.findBySlug(slug); // Slug로 DB검색하여 있으면 Page로 Return
if (slugExist != null) { // 같은 Slug가 존재할 시 저장안함
attr.addFlashAttribute("message","Slug가 이미 존재하고 있습니다. 다시 입력해주세요.");
attr.addFlashAttribute("alertClass","alert-danger"); // 부트스트랩 경고창(성공색으로 변경)
attr.addFlashAttribute("page", page);
} else {
page.setSlug(slug); // 소문자, -으로 수정된 Slug를 Update
page.setSorting(100); // 기본 Sorting 값
pageRepo.save(page);
}
return "redirect:/admin/pages/add";
}
@GetMapping("/edit/{id}")
public String edit(@PathVariable("id") int id, Model model) {
Page page = pageRepo.getById(id); // id로 데이터 검색
model.addAttribute(page);
return "admin/pages/edit"; // 수정페이지로 이동
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="/fragments/head :: head-admin"></head>
<body>
<nav th:replace="/fragments/nav :: nav-admin"></nav>
<main role="main" class="container">
<div class="display-2">페이지 수정</div>
<a th:href="@{/admin/pages}" class="btn btn-primary my-3">돌아가기</a>
<form method="post" th:object="${page}" th:action="@{/admin/pages/edit}">
<div th:if="${#fields.hasErrors('*')}" class="alert alert-danger">에러 발생</div>
<div th:if="${message}" th:class="${'alert ' + alertClass}" th:text="${message}"></div>
<!-- th:field를 사용하면 id, name은 중괄호 안 변수명, value는 변수값으로 자동으로 설정됨-->
<input type="hidden" th:field="*{id}" />
<input type="hidden" th:field="*{sorting}" />
<div class="form-group">
<label for="">제 목</label>
<input type="text" class="form-control" th:field="*{title}" placeholder="제목" />
<span class="error" th:if="${#fields.hasErrors('title')}" th:errors="*{title}"></span>
</div>
<div class="form-group">
<label for="">슬러그</label>
<input type="text" class="form-control" th:field="*{slug}" placeholder="슬러그" />
</div>
<div class="form-group">
<label for="">내용</label>
<textarea class="form-control" th:field="*{content}" cols="30" rows="10" placeholder="내용"></textarea>
<span class="error" th:if="${#fields.hasErrors('content')}" th:errors="*{content}"></span>
</div>
<button type="submit" class="btn btn-danger">수 정</button>
</form>
</main>
<footer th:replace="/fragments/footer :: footer"></footer>
</body>
</html>
@PostMapping("/edit")
public String edit(@Valid Page page, BindingResult bindingResult, RedirectAttributes attr) {
//Validation 결과 Error가 있으면 이전페이지로 이동
if (bindingResult.hasErrors()) {
return "admin/pages/edit";
}
// 검사 통과 시
attr.addFlashAttribute("message","성공적으로 페이지 수정됨");
attr.addFlashAttribute("alertClass","alert-success"); // 부트스트랩 경고창(성공색으로 변경)
// Slug 검사
// Slug를 미입력 시 Title을 소문자로 하고 공백을 - 으로 변환, 입력시에도 소문자 공백은 - 변환
String slug = page.getSlug() == "" ? page.getTitle().toLowerCase().replace(" ", "-") : page.getSlug();
Page slugExist = pageRepo.findBySlugAndIdNot(slug, page.getId()); // Slug로 DB검색하여 있으면 Page로 Return (현재 출력 중인 id는 제외)
if (slugExist != null) { // 같은 Slug가 존재할 시 저장안함
attr.addFlashAttribute("message","Slug가 이미 존재하고 있습니다. 다시 입력해주세요.");
attr.addFlashAttribute("alertClass","alert-danger"); // 부트스트랩 경고창(성공색으로 변경)
attr.addFlashAttribute("page", page);
} else {
page.setSlug(slug); // 소문자, -으로 수정된 Slug를 Update
page.setSorting(100); // 기본 Sorting 값
pageRepo.save(page);
}
return "redirect:/admin/pages/edit/" + page.getId();
}
@GetMapping("/delete/{id}")
public String delete(@PathVariable("id") int id, RedirectAttributes attr) {
pageRepo.deleteById(id);
attr.addFlashAttribute("message", "성공적으로 삭제되었습니다.");
attr.addFlashAttribute("alertClass", "alert-success");
return "redirect:/admin/pages";
}
$(function () {
$('a.deleteConfirm').click(function () {
if (!confirm('삭제하겠습니까?')) return false; // 취소시 삭제안됨
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.0/jquery-ui.min.js"></script>
<script>
$('table#pages').sortable({
items: 'tr:not(.home)',
placeholder: 'ui-state-highlight',
update: function () {
//순서가 바뀔때 이벤트 발생
let ids = $('table#pages').sortable('serialize'); //id를 문자열로 순서대로 시리얼라이즈
let url = '/admin/pages/reorder';
// console.log(ids);
$.post(url, ids, function (data) {
//AJAX post로 ids를 전송하고 결과를 data로 받는다.
console.log(data); //콘솔확인
});
},
});
</script>
@PostMapping("/reorder") // AJAX로 오기 때문에 view가 필요없음, @ResponseBody 앞에 넣어줘서 View로 Return 하지 않고 문자열 "ok"로 Return
public @ResponseBody String reorder(@RequestParam("id[]") int[] id) {
int count = 1;
Page page;
// 변경될 때 마다 테이블 모든 id값들이 넘어와서 전부 값이 재정렬됨
for (int pageId : id) {
page = pageRepo.getById(pageId); // 1. DB에서 id로 page 객체 검색
page.setSorting(count); // 2. 불러온 page객체에 Sorting값 변경
pageRepo.save(page); // 3. 변경된 page객체 저장
count++; // 4. count 값 증가
}
return "ok";
}
@GetMapping
public String index(Model model) {
List<Page> pages = pageRepo.findAllByOrderBySortingAsc();
model.addAttribute("pages", pages);
return "admin/pages/index";
}