4월 6일

오늘 배운 것

  • add 완성하기
  • Edit 및 Delete 만들기
  • JQuery UI Sortable

JPA Query Methods 참고

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; // 취소시 삭제안됨
  });
});




JQuery UI Sortable 사용하기

JQuery UI Library 추가하기

https://releases.jquery.com/ui/

  • 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 참고

https://jqueryui.com/sortable/

JQuery UI serialize 참고

https://api.jqueryui.com/sortable/#method-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와 동일

좋은 웹페이지 즐겨찾기