春コース9日目

125708 ワード

韓日

  • pagesテーブル
  • の変更/削除
  • ページソート機能
  • を追加
  • category設定
  • category CRUD
  • ページ表の変更/削除


    変更


    - AdminPageController -
    @GetMapping("/edit/{id}")
    public String edit(@PathVariable int id, Model model) {
    	Page page = pageRepo.getById(id);	// 테이블에서 id로 page검색
    	model.addAttribute("page", page);	// 수정페이지에 page정보 객체를 전달
    	return "admin/pages/edit";	// 수정 페이지로 보냄
    }
    - edit.html -
    <div 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>
        <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>
    </div>
    formの動作値を変更(add>edit)、input:非表示タイプidを使用してソートを追加

    [修正](Modify)ボタンをクリックして、情報の出力が正しいことを確認します.
    - AdminPageController -
    // 수정하기를 통해 작성한 값을 검사 후 업데이트
    @PostMapping("/edit")
    public String edit(@Valid Page page, BindingResult bindingResult, RedirectAttributes attr) {
    	//유효성검사 결과 에러가 있으면 다시 돌아감
    	if (bindingResult.hasErrors()) return "admin/pages/edit";
    	
    	attr.addFlashAttribute("message", "성공적으로 수정됨");
    	attr.addFlashAttribute("alertClass", "alert-success");	// 부트스트랩 경고창(color: succeess)
    	
    	// 슬러그 검사
    	String slug = page.getSlug() == "" ? page.getTitle().toLowerCase().replace(" ", "-") : page.getSlug();
    	Page slugExist = pageRepo.findBySlugAndIdNot(slug, page.getId());	// 슬러그로 DB검색 후 있으면 Page로 리턴 - 단, 현재 id의 slug는 중복에서 제외
    	
    	if (slugExist != null) {	// 동일한 slug가 존재하면 저장x
    		attr.addFlashAttribute("message", "입력한 slug가 이미 존재합니다.");
    		attr.addFlashAttribute("alertClass", "alert-danger");
    		attr.addFlashAttribute("page", page);
    		
    	} else {
    		page.setSlug(slug);	// 소문자, '-' 수정 된 slug를 업데이트
    		page.setSorting(100);
    		
    		pageRepo.save(page);
    	}
    	return "redirect:/admin/pages/edit/" + page.getId();	// /edit/{id} 로 주소가 작성되어있으므로 id값도 같이 넘겨야함
    }
    =>addの検証>は保存と似ていますが、保存するページとプラグの繰り返しチェック部分は異なります.
    - PageRepository.interface -
    Page findBySlugAndIdNot(String slug, int id);	// slug를 찾되 현재 id의 slug는 제외



    変更のテスト

    削除


    - AdminPageController -
    @GetMapping("/delete/{id}")
    public String delete(@PathVariable int id, RedirectAttributes attr) {
    	pageRepo.deleteById(id);
    	
    	attr.addFlashAttribute("message", "성공적으로 삭제되었습니다.");
    	attr.addFlashAttribute("alertClass", "alert-success");
    	
    	return "redirect:/admin/pages";		// 인덱스 페이지로 돌아감
    }

    - index.html -
    削除ボタンはクラスをタグに追加し、divタグを追加してメッセージを表示します.
    <div th:if="${message}" th:class="${'alert ' + alertClass}" th:text="${message}"></div>
    	...생략...
    <td><a th:href="@{'/admin/pages/delete/' + ${page.id}}" class="deleteConfirm">삭제</a></td>
    - app.js -
    削除ボタンをクリックすると確認のalertウィンドウが表示されます
    $(function () {
      $(
        'a.deleteConfirm'.click(function () {
          if (!confirm('삭제하시겠습니까?')) return false; // 취소 시 삭제안함
        })
      );
    });
    =>aラベルのクラス名がdeleteConfirmのラベルをクリックすると、警告ウィンドウが表示され、ユーザーにラベルを削除するかどうかを尋ねます.
    footer.<script th:src="@{/js/app.js}"></script>をhtmlに追加してjsファイルをリンクします.


    [OK]をクリックしてページを削除します.「≪取消|Cancel|emdw≫」をクリックしても機能しません.

    ホームページの変更削除不可


    - index.html -
    <td><a th:if="${page.slug != 'home'}" class="deleteConfirm" th:href="@{'/admin/pages/delete/' + ${page.id}}">삭제</a></td>
    繰り返し文th:各ラベル内の削除aラベルに条件チェックを追加し、slugがメインのときにaラベルを無効にします.
    Jクエリーを参照
    $(document).ready(function(){
    	...
    })
    
    $(function(){
      	...
    })
    前述の2つのロールは同じです
    =>画面のロードが完了したら、作成したコードを実行します.
    コメントリンク

    ソート機能の追加



    最初に追加した薄型バージョンにはこの機能がないため、2番目のクエリー・リンクを変更します.
    下の脚注.htmlをコピーして使用できます.
    - footer.html -
    <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 src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
    <script th:src="@{/js/app.js}"></script>
    - index.html -
    <!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">Pages</div>
          <a th:href="@{/admin/pages/add}" class="btn btn-primary my-3">추가하기</a>
          <div th:if="${message}" th:class="${'alert ' + alertClass}" th:text="${message}"></div>
    
          <div th:if="${!pages.empty}">
            <table class="table sorting" id="pages">
              <tr class="home">
                <th>제 목</th>
                <th>슬러그</th>
                <th>수 정</th>
                <th>삭 제</th>
              </tr>
              <tr th:each="page : ${pages}" th:id="'id_' + ${page.id}" th:class="${page.slug}">
                <td th:text="${page.title}"></td>
                <td th:text="${page.slug}"></td>
                <td><a th:href="@{'/admin/pages/edit/' + ${page.id}}">수정</a></td>
                <td><a th:if="${page.slug != 'home'}" class="deleteConfirm" th:href="@{'/admin/pages/delete/' + ${page.id}}">삭제</a></td>
              </tr>
            </table>
          </div>
          <div th:if="${pages.empty}">
            <div class="display-4">현재 페이지가 없습니다...</div>
          </div>
        </main>
    
        <footer th:replace="/fragments/footer :: footer"></footer>
        <script>
          // 테이블 중 id가 pages인 객체를 찾음
          $('table#pages').sortable({
            items: 'tr:not(.home)', // home클래스를 제외한 tr행을 sorting 가능하도록
          });
        </script>
      </body>
    </html>

    これからは、indexページでホームページ以外のページのtitleをドラッグして順序を変更できます.

    ソートの設定


    1)ドラッグによる順序変更を許可
    2)変更した順序をDBに保存する
    3)データベースに格納されているソート値順に出力
    上の順に並べ替えを設定します.
    1)ドラッグによる順序変更を許可
    - index.html -
    スクリプトの変更
    <script>
      $('table#pages').sortable({
        items: 'tr:not(.home)',
        placeholder: 'ui-state-highlight',
        update: function () {
          //순서가 바뀔때 이벤트 발생
          let ids = $('table#pages').sortable('serialize'); //id를 문자열로 순서대로 시리얼라이즈
    
          console.log(ids);
          let url = '/admin/pages/reorder';
        },
      });
    </script>
    let ids = $('table#pages').sortable('serialize')
    =>idは、文字列に順次変換してシーケンス化される.


    順序が変更されたときにコンソールに出力される文を確認します.
    - style.css -
    cssに追加
    table.sorting tr:not(.home) {
      cursor: pointer;
      /* 옮길 수 있는 행만 마우스포인터 표시 */
    }
    .ui-state-highlight {
      border: 1px dashed #ccc;
      /* 위치옮길 때 변경될 위치를 점선으로 미리보기할 수 있는 기능 */
    }
    - index.html -
    スクリプトに追加
    <script>
      $('table#pages').sortable({
        items: 'tr:not(.home)',
        placeholder: 'ui-state-highlight',
        update: function () {
          //순서가 바뀔때 이벤트 발생
          let ids = $('table#pages').sortable('serialize'); //id를 문자열로 순서대로 시리얼라이즈
    
          console.log(ids);
          let url = '/admin/pages/reorder';
    
          $.post(url, ids, function (data) {
            //AJAX post로 ids를 서버에 전송 후 결과를 data로 받음
            console.log(data); //콘솔확인
          });
        },
      });
    </script>
    2)変更した順序をDBに保存する
    - AdminPageController -
    @PostMapping("/reorder")
    public @ResponseBody String reorder(@RequestParam("id[]") int[] id) {
        
        int count = 1;
        Page page;
    
        for (int pageId : id) {
            page = pageRepo.getById(pageId);	// DB에서 id로 page객체 검색
            page.setSorting(count);				// setSorting에 count값을 넣어줌
            pageRepo.save(page); 	//sorting 값을 순서대로 저장
            count++;
        }
        return "ok";	// view페이지가 아니라 ok문자열로 리턴
    }
    @RequestParam("id[]") int[] id:前のコンソール.log(ids)に出力された配列を受信する@ResponseBody:ビューではなく通常の文字列(「OK」)を返すように追加
    =>テストが完了したら、変更順序をドラッグしてDBのソート値も変更されることを確認できます.
    ページオブジェクトをソート値でソートする機能は追加されていないため、ビューをリフレッシュしても出力は初期順に行われます.
    したがって、データベースに格納されているソート順にページをインポートするには、次のように変更する必要があります.
    3)データベースに格納されているソート値順に出力
    - AdminPageController -
    indexメソッドの変更
    @GetMapping
    public String index(Model model) {
    	List<Page> pages = pageRepo.findAllByOrderBySortingAsc();
    	model.addAttribute("pages", pages);
    	return "admin/pages/index";
    }
    =>すべてのページオブジェクトを検索し、インタフェースにメソッドを作成するのではなく、findAllByOrderBySortingAsssc()にメソッドを変更します.
    - PageRepository.interface -
    List<Page> findAllByOrderBySortingAsc();


    再起動してテストした後、DBのソート値に基づいて順次出力を決定できます.

    category設定



    コメント用フォルダ構造
    CREATE TABLE IF NOT EXISTS categories (
    	id int not null auto_increment,
        name VARCHAR(45) not null,
        slug VARCHAR(45) not null,
        sorting int not null,
    	PRIMARY KEY (id)
    );
    カテゴリテーブルの作成

    categoryクラスの作成
    - Category -
    @Entity
    @Table(name="category")
    @Data
    public class category {
    	@Id
    	@GeneratedValue(strategy = GenerationType.IDENTITY)
    	private int id;
    	
    	@NotBlank(message = "이름을 입력해주세요")
    	@Size(min = 2, message = "이름은 2자 이상")
    	private String name;
    	
    	private String slug;
    	private int sorting;
    }

    新しいインタフェースの作成
    - CategoryRepository.intergace -
    public interface CategoryRepository extends JpaRepository<Category, Integer>{
    }

    新しいコントローラクラスの作成
    - AdminCategoryController -
    @Controller
    @RequestMapping("/admin/categories")
    public class AdminCategoryController {
    
    	@Autowired
    	private CategoryRepository categoryRepo;
    	
    	@GetMapping
    	private String index(Model model) {
    		List<Category> catetories = categoryRepo.findAll();
    		model.addAttribute(catetories);
    		return "admin/categories/index";
    	}
    }


    コピーされたadmin/categories/index.htmlに変更するpage->category、page->categories
    - index.html -
    <main role="main" class="container">
      <div class="display-2">Categories</div>
      <a th:href="@{/admin/categories/add}" class="btn btn-primary my-3">추가하기</a>
      <div th:if="${message}" th:class="${'alert ' + alertClass}" th:text="${message}"></div>
    
      <div th:if="${!categories.empty}">
        <table class="table sorting" id="categories">
          <tr class="home">
            <th>제 목</th>
            <th>수 정</th>
            <th>삭 제</th>
          </tr>
          <tr th:each="category : ${categories}" th:id="'id_' + ${category.id}" th:class="${category.slug}">
            <td th:text="${category.name}"></td>
            <td><a th:href="@{'/admin/categories/edit/' + ${category.id}}">수정</a></td>
            <td><a th:if="${category.slug != 'home'}" class="deleteConfirm" th:href="@{'/admin/categories/delete/' + ${category.id}}">삭제</a></td>
          </tr>
        </table>
      </div>
      <div th:if="${categories.empty}">
        <div class="display-4">현재 페이지가 없습니다...</div>
      </div>
    </main>
    
    <footer th:replace="/fragments/footer :: footer"></footer>
    <script>
      $('table#categories').sortable({
      items: 'tr:not(.home)',
      placeholder: 'ui-state-highlight',
      update: function () {
      //순서가 바뀔때 이벤트 발생
      let ids = $('table#categories').sortable('serialize'); //id를 문자열로 순서대로 시리얼라이즈
    
      console.log(ids);
      let url = '/admin/categories/reorder';
    
      // $.post(url, ids, function (data) {
      //   //AJAX post로 ids 전송 후 결과를 data로 받음
      //   console.log(data); //콘솔확인
      // });
      },
      });
    </script>

    http://localhost:8080/admin/categoriesの最初のアクセス時に表示される画面.(データなし)
    insert into categories(id, name, slug, sorting)
    values (1, 'TEST', 'test', 1);
    DBのCategoriesテーブルにテストデータを入れて確認します.

    データがある場合

    カテゴリCRUD





    admin/categories/add.htmlパスにコピーして変更する
    - add.html -
    <div class="container">
      <div class="display-2">카테고리 추가</div>
      <a th:href="@{/admin/categories}" class="btn btn-primary my-3">돌아가기</a>
    
      <form method="post" th:object="${category}" th:action="@{/admin/categories/add}">
        <div th:if="${#fields.hasErrors('*')}" class="alert alert-danger">에러 발생</div>
        <div th:if="${message}" th:class="${'alert ' + alertClass}" th:text="${message}"></div>
    
        <div class="form-group">
          <label for="">이 름</label>
          <input type="text" class="form-control" th:field="*{name}" placeholder="이름" />
          <span class="error" th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></span>
        </div>
        <button type="submit" class="btn btn-danger">추 가</button>
      </form>
    </div>

    1.追加


    - AdminCategoryController -
    @GetMapping("/add")	// 	/admin/categories/add
    public String add(@ModelAttribute Category category) {
    	return "admin/categories/add";
    }
    [追加](Add)ボタンをクリックして[追加](Add)ページに移動

    - AdminCategoryController -
    @PostMapping("/add") 
    public String add(@Valid Category category, BindingResult bindingResult, RedirectAttributes attr) {
    	//유효성검사 결과 에러가 있으면 다시 돌아감
    	if (bindingResult.hasErrors()) return "admin/categories/add";
    	// 유효성 검사 통과 시
    	attr.addFlashAttribute("message", "성공적으로 페이지 추가됨");
    	attr.addFlashAttribute("alertClass", "alert-success");	// 부트스트랩 경고창(color: succeess)
    	
    	String slug = category.getName().toLowerCase().replace(" ", "-");
    	
    	Category nameExist = categoryRepo.findByName(category.getName());	// DB의 name을 검색하여 있으면 Category에 저장
    	
    	if (nameExist != null) {	// 동일한 이름의 category가 이미 있을 경우
    		attr.addFlashAttribute("message", "입력한 category가 이미 존재합니다.");
    		attr.addFlashAttribute("alertClass", "alert-danger");
    		attr.addFlashAttribute("category", category);	// 입력된 데이터가 저장된 페이지객체를 그대로 유지함
    		
    	} else {
    		category.setSlug(slug);	// 소문자, '-' 수정 된 slug를 업데이트
    		category.setSorting(100);	// 기본 sorting값
    		
    		categoryRepo.save(category);
    	}
    	return "redirect:/admin/categories/add";	// post-redirect-get
    }
    - CategoryRepository.interface -
    Category findByName(String name);


    認証とnameの繰り返しチェックが行われていない場合は、DBに正常に入力されます.

    name繰返しチェックが失敗した場合、入力値は≪追加|Add|emdw≫ページに戻り、エラー・メッセージが出力されます.

    2.修正


    - AdminCategoryController -
    @GetMapping("/edit/{id}")
    public String edit(@PathVariable int id, Model model) {
    	Category category = categoryRepo.getById(id);	// 테이블에서 id로 category검색
    	model.addAttribute("category", category);
    	return "admin/categories/edit";	// 수정 페이지로 보냄
    }
    admion/category/edit.すべてhtmlに変更されたpage->categories、page->category、title->name
    - edit.html -
    <div class="container">
      <div class="display-2">카테고리 수정</div>
      <a th:href="@{/admin/categories}" class="btn btn-primary my-3">돌아가기</a>
    
      <form method="post" th:object="${category}" th:action="@{/admin/categories/edit}">
        <div th:if="${#fields.hasErrors('*')}" class="alert alert-danger">에러 발생</div>
        <div th:if="${message}" th:class="${'alert ' + alertClass}" th:text="${message}"></div>
        <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="*{name}" placeholder="제목" />
          <span class="error" th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></span>
        </div>
        <button type="submit" class="btn btn-danger">수 정</button>
      </form>
    </div>

    データベース内のidで見つかったデータを出力確認が完了するまでオブジェクトに挿入します.
    入力データを更新して変更する機能が追加されました.
    - AdminCategoryController -
    @PostMapping("/edit")
    public String edit(@Valid Category category, BindingResult bindingResult, RedirectAttributes attr) {
    	//유효성검사 결과 에러가 있으면 다시 돌아감
    	if (bindingResult.hasErrors()) return "admin/categories/edit";
    	
    	attr.addFlashAttribute("message", "성공적으로 category 수정됨");
    	attr.addFlashAttribute("alertClass", "alert-success");	// 부트스트랩 경고창(color: succeess)
    	
    	String slug = category.getName().toLowerCase().replace(" ", "-");
    	Category nameExist = categoryRepo.findByName(category.getName());
    
    	if (nameExist != null) {	// 동일한 slug가 존재하면 저장x
    		attr.addFlashAttribute("message", "입력한 category가 이미 존재합니다.");
    		attr.addFlashAttribute("alertClass", "alert-danger");
    		attr.addFlashAttribute("category", category);
    		
    	} else {
    		category.setSlug(slug);	// 소문자, '-' 수정 된 slug를 업데이트
    		category.setSorting(100);
    		
    		categoryRepo.save(category);
    	}
    	return "redirect:/admin/categories/edit/" + category.getId();	// /edit/{id} 로 주소가 작성되어있으므로 id값도 같이 넘겨야함
    }



    DBへの変更を確認します.

    削除


    - AdminCategoryController -
    @GetMapping("/delete/{id}")
    public String delete(@PathVariable int id, RedirectAttributes attr) {
    	categoryRepo.deleteById(id);
    
    	attr.addFlashAttribute("message", "성공적으로 삭제되었습니다.");
    	attr.addFlashAttribute("alertClass", "alert-success");
    	
    	return "redirect:/admin/categories";
    }