4월 6일
오늘 배운 것
- add 완성하기
- Edit 및 Delete 만들기
- JQuery UI Sortable
JPA Query Methods 참고
- DB Query Method 만드는 규칙설명
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation
add 완성하기
- 제목에서 대문자를 소문자로 공백을 -로 변환하여 slug에 저장
@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";
}
- 이미 존재하는 slug값을 입력했을 때
Edit 및 Delete 만들기
Edit 만들기
- index 페이지의 수정, 삭제 버튼 클릭 시 넘어오는 id 값을 받아서 DB검색
@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"; // 수정페이지로 이동
}
- DB검색으로 선택된 데이터 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>
- 수정버튼 클릭 시 Post방식으로 넘어온 데이터들을 검사
- 현재 선택 중인 slug값을 제외하고 검색하기위해서 메소드를 변경해줌
@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();
}
- 수정글자 위 마우스커서 올릴 시 이동할 주소와 id값 출력
Delete 만들기
- JS로 Confirm명령어로 다시 한번 확인, 삭제문자에 deleteConfirm class 추가
@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; // 취소시 삭제안됨
});
});
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; // 취소시 삭제안됨
});
});
JQuery UI Sortable 사용하기
JQuery UI Library 추가하기
- Footer에 Library 추가(JQuery Library가 Slim 버전이면 작동하지 않으니 bundle버전으로 교체 해줌)
<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>
- index에 script 추가
- index에 출력 중인 Table의 Data들을 마우스로 드래그해서 위치변경이 가능하지만 새로고침시 다시 원상태로 돌아오기 때문에 AJAX로 변경 할 때마다 id값을 url주소로 보내어 새로고침을 하여도 바꾼위치 그대로 출력될 수 있게 함
JQuery UI sortable 참고
JQuery UI serialize 참고
<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>
- id값들이 위쪽부터 순서대로 배열로 넘어오기 때문에 int[]로 받아야 함
- 넘어온 순서대로 Sorting값을 주어 출력 시 Sorting값 크기순으로 출력
@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";
}
- index의 Table 출력 메소드 변경
@GetMapping
public String index(Model model) {
List<Page> pages = pageRepo.findAllByOrderBySortingAsc();
model.addAttribute("pages", pages);
return "admin/pages/index";
}
Pages 동일하게 Categories 생성
- 출력되는 데이터들을 제외하곤 Pages와 동일
Author And Source
이 문제에 관하여(4월 6일), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@tutu10000/4월-6일저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)