日記の登録・詳細確認を画面からできるようにします。
日記登録
Form
登録用のFormを作成します。
formパッケージ内に「PostForm.java」を作成します。
package diary.form;
import java.util.Date;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
public class PostForm {
private int id;
private String categoryForm;
@NotNull (message = "日付を入力してください。")
private Date dateForm;
@NotNull (message = "題名を入力してください。")
@Size(min = 1, max = 25, message="25文字以内で入力してください。")
private String titleForm;
private String contentForm;
public String getCategoryForm() {
return categoryForm;
}
public void setCategoryForm(String categoryForm) {
this.categoryForm = categoryForm;
}
public Date getDateForm() {
return dateForm;
}
public void setDateForm(Date dateForm) {
this.dateForm = dateForm;
}
public String getTitleForm() {
return titleForm;
}
public void setTitleForm(String titleForm) {
this.titleForm = titleForm;
}
public String getContentForm() {
return contentForm;
}
public void setContentForm(String contentForm) {
this.contentForm = contentForm;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
中身を記載するとimport文でエラーになるかと思いますので解消していきます。
プロジェクト上で右クリック「Spring→Add Starters」を選択します。
I/O内から「Validation」を追加して「Next」を選択します。
「build.gradle」を選択して、「Finish」を選択します。
プロジェクト上で右クリックし、「Refresh」を選択するとエラーが消えるはずです。
Repository
新規登録用のメソッドを追加していきます。
インターフェース「IDiaryDao.java」に以下を追記します。
※formのimportも忘れずに
// 日記を登録する
int insert(PostForm form);
「DiaryDao.java」に以下を追記します。
// 日記を登録する
@Override
public int insert(PostForm form) {
// 登録件数を格納
int count = 0;
String sql = "INSERT INTO diary(category, title, content, date , update_datetime) "
+ "VALUES(:category, :title, :content, :date , :update_datetime)";
// パラメータ設定用Map
Map<String, Object> param = new HashMap<>();
param.put("category", form.getCategoryForm());
param.put("title", form.getTitleForm());
param.put("content", form.getContentForm());
param.put("date", form.getDateForm());
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
param.put("update_datetime", timestamp);
count = jdbcTemplate.update(sql, param);
return count;
}
★ポイント
パラメータ設定
検索時と同様登録する内容はMapに格納し、キー値とValues内の変数は紐づいています。
Service
「DiaryService.java」にもメソッドを追加します。
public int insert(PostForm form) {
return dao.insert(form);
}
Controller
「DiaryController.java」に2つメソッドを追加します。
/**
* 新規登録へ遷移
* @param model
* @return resources/templates/form.html
*/
@GetMapping("/form")
public String formPage(
Model model
) {
return "form";
}
/**
* 「一覧へ」選択時、一覧画面へ遷移
* @param model
* @return resources/templates/list.html
*/
@PostMapping(path={"/insert", "/form"}, params="back")
public String backPage(
Model model
) {
return "redirect:/diary";
}
/**
* 日記を新規登録
* @param postForm
* @param model
* @return
*/
@PostMapping(path="/insert", params="insert")
public String insert(
@Valid @ModelAttribute PostForm form,
BindingResult result,
Model model
) {
if(result.hasErrors()) {
model.addAttribute("error", "パラメータエラーが発生しました。");
return "form";
}
int count = diaryservice.insert(form);
model.addAttribute("postForm", form);
return "redirect:/diary";
}
画面作成
まずは「list.html」を修正していきます。
<form method="GET" th:action="@{/diary/form}">
<div class="footer-button row justify-content-end">
<div class="col-1 m-1">
<button type="submit" class="btn btn-primary">日記作成</button>
<input type="hidden" name="isUpdate" value=false>
</div>
</div>
</form>
th:action=”@{/diary/form}”を追加しています。
<input type=”hidden” name=”isUpdate” value=false>は別章で使用します。
form用のHTMLを作成します。
「template/form.html」
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>日記アプリ</title>
<!-- bootstrapを読み込む -->
<link rel="stylesheet" type="text/css" th:href="@{/css/bootstrap.min.css}">
<script type="text/javascript" th:src="@{/js/bootstrap.min.js}"></script>
<!-- jqueryを読み込む -->
<script type="text/javascript" th:src="@{/js/jquery-3.6.0.min.js}"></script>
<!-- bootstrap-datepickerを読み込む -->
<link rel="stylesheet" type="text/css" th:href="@{/css/bootstrap-datepicker.min.css}">
<script type="text/javascript" th:src="@{/js/bootstrap-datepicker.min.js}"></script>
<script type="text/javascript" th:src="@{/js/bootstrap-datepicker.ja.min.js}"></script>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light" style="background-color:#CCFFFF;">
<span class="navbar-brand">
<strong>日記アプリ</strong>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-book" viewBox="0 0 16 16">
<path d="M1 2.828c.885-.37 2.154-.769 3.388-.893 1.33-.134 2.458.063 3.112.752v9.746c-.935-.53-2.12-.603-3.213-.493-1.18.12-2.37.461-3.287.811V2.828zm7.5-.141c.654-.689 1.782-.886 3.112-.752 1.234.124 2.503.523 3.388.893v9.923c-.918-.35-2.107-.692-3.287-.81-1.094-.111-2.278-.039-3.213.492V2.687zM8 1.783C7.015.936 5.587.81 4.287.94c-1.514.153-3.042.672-3.994 1.105A.5.5 0 0 0 0 2.5v11a.5.5 0 0 0 .707.455c.882-.4 2.303-.881 3.68-1.02 1.409-.142 2.59.087 3.223.877a.5.5 0 0 0 .78 0c.633-.79 1.814-1.019 3.222-.877 1.378.139 2.8.62 3.681 1.02A.5.5 0 0 0 16 13.5v-11a.5.5 0 0 0-.293-.455c-.952-.433-2.48-.952-3.994-1.105C10.413.809 8.985.936 8 1.783z"/>
</svg>
</span>
<div class="collapse navbar-collapse justify-content-end">
<ul class="navbar-nav">
<li class="nav-item ">
<span class="navbar-text" th:text="ログインユーザ"></span>
</li>
</ul>
<form method="POST">
<button type="submit" class="btn btn-link">ログアウト</button>
</form>
</div>
</nav>
<div class="container-fluid">
<h2 th:if="${error}" style="color: red;">[[${error}]]</h2>
<div class="create m-2">
<form id="form" class="needs-validation" novalidate method="POST" th:action="@{/diary/insert}" onsubmit="return check()">
<p class="p-1 h4" th:text="新規作成"></p>
<div class="row justify-content-center">
<div class="form-group col-8">
<label for="dateForm">日付</label>
<input type="text" class="form-control" name="dateForm" id="date_sample" required>
<div class="invalid-feedback">必須入力です</div>
</div>
</div>
<div class="row justify-content-center">
<div class="form-group col-8">
<label for="categoryForm">分類</label>
<select id="categoryForm" class="form-select col-8" name="categoryForm" required>
<option selected></option>
<option value="1">仕事</option>
<option value="2">趣味</option>
<option value="3">その他</option>
</select>
<div class="invalid-feedback">必須入力です</div>
</div>
</div>
<div class="row justify-content-center">
<div class="form-group col-8">
<label for="title">題名</label>
<input type="text" class="form-control" name="titleForm" id="title" required>
<div class="invalid-feedback">25文字以内で入力してください</div>
</div>
</div>
<div class="row justify-content-center">
<div class="form-group col-8">
<label for="floatingTextarea">内容</label>
<textarea class="form-control" placeholder="日記の内容" name="contentForm" style="height: 200px"></textarea>
</div>
</div>
<div class="row justify-content-end">
<div class="col-1">
<button type="submit" class="btn btn-primary" name="insert" onClick="btn='insert'">登録</button>
</div>
<div class="col-1">
<button type="submit" class="btn btn-light" name="back" onClick="btn='back'">一覧へ</button>
</div>
</div>
</form>
</div>
</div>
</body>
<script>
$('#date_sample').datepicker({
format: 'yyyy/mm/dd',
language: 'ja',
autoclose: true,
});
const check = (event) => {
// 一覧ボタン押下した場合はチェックしない
if (btn === 'back'){
return true;
}
const form = document.getElementById('form');
if (!form.checkValidity()){
form.classList.add('was-validated');
return false;
} else {
return true;
}
};
</script>
</html>
動作確認
アプリを動かし、動作確認していきます。
①画面表示
新規登録ボタンを選択して、画面が表示されることを確認します。
②入力チェック(フロントエンド側)
何も入力しないで登録ボタンを選択します。
入力チェックに引っ掛かることを確認します。
③入力チェック(サーバー側)
必須項目に値を入力します。
題名には25文字を超えて入力し、登録ボタンを選択します。
あえて画面側で制御しなかったチェックです。
Controllerでのエラー処理が動作するかを確認します。
④登録
値を入力し、登録ボタンを選択します。
データが追加されることを確認します。
※画面だけでなく、DBを中身も確認してみましょう。
⑤登録画面から一覧へ戻る
一覧へボタンを選択して、一覧画面に戻れるかを確認します。
ここまで上手くいけば、登録処理は完了です!
日記詳細
一覧画面の詳細ボタンから登録した内容を詳細を確認できるようにします。
詳細画面からさらに編集ボタン押下することで内容を変更できるようにしていきます。
※編集は次章で実施していきます。
Repository
詳細画面へ表示する際のデータを再度検索します。
(一覧画面で検索したデータをもって再検索しなくても可能ですが、あえて再検索します。)
メソッドを追加していきます。
インターフェース「IDiaryDao.java」に以下を追記します。
// idを指定して日記を1件取得
Diary findById(int id);
「DiaryDao.java」に以下メソッドを追記します。
@Override
public Diary findById(int id) throws IncorrectResultSizeDataAccessException {
String sql = "SELECT d.id, d.category, d.title, d.content, TO_CHAR(d.date, 'YYYY/MM/DD') AS date, d.update_datetime, c.name "
+ "FROM diary AS d INNER JOIN category_code AS c ON d.category = c.cd "
+ "WHERE d.id = :id";
// パラメータ設定用Map
Map<String, Object> param = new HashMap<>();
param.put("id", id);
// 一件取得
Map<String, Object> result = jdbcTemplate.queryForMap(sql, param);
Diary diary = new Diary();
diary.setId((int)result.get("id"));
diary.setCategory((String)result.get("category"));
diary.setTitle((String)result.get("title"));
diary.setContent((String)result.get("content"));
diary.setDate((String)result.get("date"));
diary.setUpdate_datetime((Timestamp)result.get("update_datetime"));
diary.setName((String)result.get("name"));
return diary;
}
Service
「DiaryService.java」に以下を追記していきます。
public Diary findById(int id) {
try {
return dao.findById(id);
} catch(IncorrectResultSizeDataAccessException e) {
return null;
}
}
Controller
「DiaryController.java」に追記していきます。
/**
* 一件タスクデータを取得し、詳細ページ表示
* @param id
* @param model
* @return resources/templates/detail.html
*/
@GetMapping("/{id}")
public String showUpdate(
@PathVariable int id,
Model model) {
//Diaryを取得
Optional<Diary> diaryOpl = Optional.ofNullable(diaryservice.findById(id));
//NULLかどうかのチェック
if(diaryOpl.isPresent()) {
model.addAttribute("diary", diaryOpl.get());
return "detail";
} else {
model.addAttribute("error", "対象データが存在しません");
return "detail";
}
}
画面作成
まずは「list.html」を修正していきます。
「詳細」ボタンです。href属性を追加します。
<a class="btn btn-primary" th:href="@{/diary/{id}(id=${item.id})}">詳細</a>
詳細画面へ作成していきます。
「templates/detail.html」を作成します。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>日記アプリ</title>
<!-- bootstrapを読み込む -->
<link rel="stylesheet" type="text/css" th:href="@{/css/bootstrap.min.css}">
<script type="text/javascript" th:src="@{/js/bootstrap.min.js}"></script>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light" style="background-color:#CCFFFF;">
<span class="navbar-brand">
<strong>日記アプリ</strong>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-book" viewBox="0 0 16 16">
<path d="M1 2.828c.885-.37 2.154-.769 3.388-.893 1.33-.134 2.458.063 3.112.752v9.746c-.935-.53-2.12-.603-3.213-.493-1.18.12-2.37.461-3.287.811V2.828zm7.5-.141c.654-.689 1.782-.886 3.112-.752 1.234.124 2.503.523 3.388.893v9.923c-.918-.35-2.107-.692-3.287-.81-1.094-.111-2.278-.039-3.213.492V2.687zM8 1.783C7.015.936 5.587.81 4.287.94c-1.514.153-3.042.672-3.994 1.105A.5.5 0 0 0 0 2.5v11a.5.5 0 0 0 .707.455c.882-.4 2.303-.881 3.68-1.02 1.409-.142 2.59.087 3.223.877a.5.5 0 0 0 .78 0c.633-.79 1.814-1.019 3.222-.877 1.378.139 2.8.62 3.681 1.02A.5.5 0 0 0 16 13.5v-11a.5.5 0 0 0-.293-.455c-.952-.433-2.48-.952-3.994-1.105C10.413.809 8.985.936 8 1.783z"/>
</svg>
</span>
<div class="collapse navbar-collapse justify-content-end">
<ul class="navbar-nav">
<li class="nav-item ">
<span class="navbar-text" th:text="ログインユーザ"></span>
</li>
</ul>
<form method="POST">
<button type="submit" class="btn btn-link">ログアウト</button>
</form>
</div>
</nav>
<div class="container-fluid">
<div th:if="${error}">
<h2 th:if="${error}" style="color: red;">[[${error}]]</h2>
<p>管理者にご連絡ください</p>
<form method="POST" th:action="@{/diary/form}">
<button type="submit" name="back" class="btn btn-light">一覧へ</button>
</form>
</div>
<div th:if="${diary}" class="create m-2">
<form method="POST" th:action="@{/diary/form}" th:object="${diary}">
<p class="p-1 h4">詳細</p>
<div class="row justify-content-center">
<div class="form-group col-8">
<label for="dateForm">日付</label>
<p><span th:text="${diary.date}"></span></p>
</div>
</div>
<div class="row justify-content-center">
<div class="form-group col-8">
<label for="categoryForm">分類</label>
<p><span th:text="${diary.name}"></span></p>
</div>
</div>
<div class="row justify-content-center">
<div class="form-group col-8">
<label for="title">題名</label>
<p><span th:text="${diary.title}"></span></p>
</div>
</div>
<div class="row justify-content-center">
<div class="form-group col-8">
<label for="floatingTextarea">内容</label>
<p><span th:text="${diary.content}" style="white-space: pre-wrap;"></span></p>
</div>
</div>
<div class="row justify-content-end">
<div class="col-1">
<button type="submit" name="update" class="btn btn-primary">編集</button>
</div>
<div class="col-1">
<button type="submit" name="back" class="btn btn-light">一覧へ</button>
</div>
</div>
</form>
</div>
</div>
</body>
</html>
動作確認
画面で動作確認をしていきます。
①一覧画面から詳細ボタンを選択し内容確認
②改行テスト
内容には改行入力することが可能です。改行されて表示されるか確認してみましょう。
※「white-space: pre-wrap」を指定しているため、改行も正しく表示されます。
③エラーテスト
詳細ボタン選択した際に対象データが存在しない
一覧画面表示(データが表示されている状態)でDBより直接対象1件削除します。
その後詳細ボタンを選択します。
画面状は表示されているが、DB状からは改行テストのデータを削除
④詳細画面から一覧へ戻る
一覧へボタンを選択して、一覧画面に戻れるかを確認します。
ここまで上手くいけば、詳細確認は完了です!
最後に
だいぶ長くなってしまいましたが、以上で登録処理と詳細確認は完了です。
次回は編集処理と削除処理を書いていきます。