春コース9日目
125708 ワード
韓日
ページ表の変更/削除
変更
- 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";
}
Reference
この問題について(春コース9日目), 我々は、より多くの情報をここで見つけました https://velog.io/@0829kuj/스프링수업-9일차テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol