SpringBootでとりあえず業務アプリを自作してみた ～TOP #2～

久しぶりの投稿です。
暫く子供の体調不良が続いたのであまり進められていませんでした。

今回は前回に続いてになりますが
TOPについて、少し手を加えたので記載していきます。

トップ画面
--
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3901612/a1f8909a-5f47-e4fc-a5d5-6621b41a595f.png)

* メニューに権限管理の追加
* 現在日時の追加
* カレンダーの追加

Thymeleaf＋jQueryということで、たびたび躓きましたがなんとか実装できました。

例のごとくHTML/cssはきっとだめだめです。

#### メニューに権限管理の追加
ユーザ検索画面をもとに、権限管理用のページを作成しました。
ひとまずは検索結果の表示のみです。また後日詳細に記載します。
メニューはすべてのHTMLにべた書きしているのでメンテナンスが面倒だなと思っており
ゆくゆくは共通化できたらなあと思っています。

#### 現在日時の追加
あまり悩みませんでした。
jQueryのDateを使用して実装しました。
秒数は別に要らなかったのですが勉強のために実装しました。

こちらを参考にして作成しました。

https://www.pyxofy.com/how-to-make-a-digital-clock-using-javascript/

#### カレンダーの追加
ベースは以下ですが、色々加工しました。

https://qiita.com/kan_dai/items/b1850750b883f83b9bee

``` top.html
<main>
	<!-- 時計 -->
	<div class="container">
		<div class="clock"></div>
		<!-- カレンダー -->
		<div class="calendar-widget">
			<div id="calendar"></div>
			<button class="calendar-btn" id="btn_prev">＜</button>
			<button class="calendar-btn" id="btn_next">＞</button>
		</div>
</main>
```
jsは機能を抜粋します。
``` top.js
// -------------- カレンダーを生成する機能 -------------- 
$(function calendar(year, month) {

	// 曜日算出用の配列
	let week = ['日', '月', '火', '水', '木', '金', '土'];

	// 日付項目の取得
	if (year == 0 && month == 0) {
		// 初期表示の場合
		let date = new Date();
		year = date.getFullYear();
		month = date.getMonth() + 1;
	}

	let startDate = new Date(year, month - 1, 1);// 月の最初の日を取得
	let endDate = new Date(year, month, 0); // 月の最後の日を取得
	let endDayCount = endDate.getDate(); // 月の末日
	let lastMonthEndDate = new Date(year, month - 1, 0); // 前月の最後の日の情報
	let lastMonthendDayCount = lastMonthEndDate.getDate();// 前月の末日
	let startDay = startDate.getDay();// 月の最初の日の曜日を取得
	let dayCount = 1; // 日にちのカウント
	let calendarHtml = ''; // HTMLを組み立てる変数

	calendarHtml += '<h1 id="calendar-year-day" style="margin-top:10px; margin-left:48px;">' + year + '/' + month + '</h1>'
	calendarHtml += '<table class="calendar-table">';

	// 曜日の行を作成
	for (let i = 0; i < week.length; i++) {
		calendarHtml += '<td class="calender-td">' + week[i] + '</td>'
	}

	// ひとまず6行の表
	for (let w = 0; w < 6; w++) {
		calendarHtml += '<tr>'

		for (let d = 0; d < 7; d++) {
			// 処理中の日付
			let inputDate;

			if (w == 0 && d < startDay) {
				// 1行目で1日の曜日の前（前月の日付）
				let num = lastMonthendDayCount - startDay + d + 1
				inputDate = year + "-" + String(month - 1) + "-" + num;
				calendarHtml += '<td class="is-disabled" id="' + inputDate + '">' + num + '</td>'
			} else if (dayCount > endDayCount) {
				// 末尾の日数を超えた（次月の日付）
				let num = dayCount - endDayCount
				inputDate = year + "-" + String(month + 1) + "-" + num;
				calendarHtml += '<td class="is-disabled" id="' + inputDate + '">' + num + '</td>'
				dayCount++
			} else {
				inputDate = year + "-" + month + "-" + dayCount;
				calendarHtml += '<td class="calender-td" id="' + inputDate + '">' + dayCount + '</td>'
				dayCount++
			}

		}
		calendarHtml += '</tr>'
	}
	calendarHtml += '</table>'

	document.querySelector('#calendar').innerHTML = calendarHtml;

	// 対象月の祝日を取得
	window.sessionStorage.setItem('calendarYear', year);
	window.sessionStorage.setItem('calendarMonth', month);
	getHoliday();
	let holiday = JSON.parse(window.sessionStorage.getItem('holiday'));

	// 前後1カ月ずつに祝日がある場合
	if (holiday.length > 0) {
		for (let i = 0; holiday.length > i; i++) {
			let target = holiday[i];
			// 対象のカレンダーセルのclass名を取得
			let className = $('#' + target[0].replaceAll("/", "-")).attr('class');
			// 前月か次月の日付の場合
			if (className == "is-disabled") {
				// class名を整形
				className = className + ' dis-holiday'

			} else {
				// class名を整形
				className = className + ' holiday'
			}
			$('#' + target[0].replaceAll("/", "-")).attr('class', className)
		}
	}
});
```
``` top.js
// カレンダーの「＜」を押したとき
	$('#btn_prev').click(function() {
		// 現在表示しているカレンダーの年月を取得
		let yearMonth = $("#calendar-year-day").text();
		let date = new Date(yearMonth + "/01");
		let year = date.getFullYear();
		let month = date.getMonth() + 1;

		if (month == 1) {
			// 現在の表示月が1月の場合は前年の12月を設定
			year = year - 1;
			month = 12;
		} else {
			// ひと月前を取得
			month = month - 1;

		}

		// カレンダーを再取得	
		calendar(year, month);
	});

	// カレンダーの「＞」を押したとき
	$('#btn_next').click(function() {
		// 現在表示しているカレンダーの年月を取得
		let yearMonth = $("#calendar-year-day").text().split('/');
		let date = new Date(yearMonth + "/01");
		let year = date.getFullYear();
		let month = date.getMonth() + 1;

		if (month == 12) {
			// 現在の表示月が12月の場合は次年の1月を設定
			year = year + 1;
			month = 1;
		} else {
			// ひと月次を取得
			month = month + 1;

		}

		// カレンダーを再取得
		calendar(year, month);
	});
``` 

初期表示は現在日付を基準としますが、その後にボタンの押下で
前後月も見られるようにしました。

祝日については、holiday_jp-jsを使用しようかと思っていたのですが
ファイルの読み込みについて実装してみたかったので
内閣府が公開しているCSVを取り込んで使用しています。

https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv

CSVの取り込みについては非常に難航しました。
まず、取得できない問題から始まり 取得できているのに値が空と認識されていたり
文字化けしていたりと初歩的な部分からそうでないところまで様々な壁がありました。
取得できない問題についてはパスの問題でした。

```top.js
// -------------- CSVデータの整形 -------------- 
$(function getData(data) {
	// 取得データを配列に変換
	let csv = [];
	csv = $.csv.toArrays(data);
	// 対象期間の前後1カ月のみに絞る
	let sYear = Number(window.sessionStorage.getItem('calendarYear'));
	let sMonth = Number(window.sessionStorage.getItem('calendarMonth'));
	let sDate = new Date(sYear, sMonth - 2, 1);
	let eDate = new Date(sYear, sMonth + 1, 1)
	// すべてのデータをループ
	let ret = [];
	// データが存在する場合
	if (csv.length > 0) {
		$(csv).each(function(i, elem) {
			if (i > 0) {
				let date = new Date(elem[0]);
				if (date >= sDate && eDate > date) {
					ret.push(elem);
				}
			}
		});
	}
	// 一覧表示でも使いたいので一旦セッションに格納
	let dataString = JSON.stringify(ret);
	window.sessionStorage.setItem('holiday', dataString);
});

// -------------- 祝日の取得 -------------- 
$(function getHoliday() {
	// CSVの読み込み jqueryのget関数 text型で読み込む
	holidays = $.get('./csv/syukujitsu.csv', getData, 'text');
});
```
>holidays = $.get('./csv/syukujitsu.csv', getData, 'text');

この部分ですが、サイトによってパスの記述が様々でした。
基本的には相対パスで指定すればよいはず...！と信じて
先頭の「.」がないことと、SpringSecurityの設定の問題と気づき解決。

```SecurityConfig.java
http.authorizeRequests(authorizeRequests - > // 認証リクエストを設定します
	authorizeRequests
	// 以下へのリクエストは認証なしで許可します
	.requestMatchers("/login", "/userRegister", "/css/**", "/images/**", "/common/**", "/csv/**").permitAll().anyRequest().authenticated() // それ以外の全てのリクエストは認証が必要です
).formLogin(formLogin - > // フォームベースのログインを設定します
	formLogin.loginPage("/login") // ログインページのURLを設定します
	.permitAll() // ログインページは認証なしで許可します
).logout(logout - > // ログアウトを設定します
	logout.logoutRequestMatcher(new AntPathRequestMatcher("/logout")) // ログアウトのリクエストURLを設定します
);
```
文字化けしていたのは、内閣府の提供しているCSVとアプリの文字コードが異なるからですね。CSVのほうの文字コードを変換して解決しました。

https://pasomaki.com/sakuraeditor-change-charcode/

祝日についてはカレンダー機能を充実させたい思いから一旦セッションに保存する形で保持していますが、なんでもかんでもセッションに突っ込むのは好ましくないので削るかもしれません。
セッションを使用したくない場合は普通にreturnすればよいと思います。

祝日のstyle設定についてどうするか悩みましたが、idとして日付を持つことで取得できるようにしました。
Date型は年月日で比較ができないため文字列として比較する必要があるみたいです。

https://qiita.com/maryjass/items/593cdea2cb02eab1759f

他に見たサイトはこの辺りです。備忘までに残します。
初見であったり復習であったりですが、良い脳トレになりました。

idが同HTML内に重複して存在しているとSEOでマイナス要素になるのは初耳でした。
あまり重要なこととしてみていませんでしたが、しっかり考える必要があるなと考えさせられた内容のひとつ。

https://www.kipure.com/article/345/

https://qiita.com/cnloni/items/5de03096a73c702b97ed

https://www.kogures.com/hitoshi/javascript/jquery/gaibu-file.html

https://qiita.com/the_red/items/43c7f88ccbba001a1a95

https://web-camp.io/magazine/archives/90781

https://www.support.makeshop.jp/design/?p=10390

https://zenn.dev/simsim/articles/3f3e043dd750e8

https://magazine.techacademy.jp/magazine/29496#ta-toc-2

長くなったので今回は一旦ここまで。
CSSは省略しました。この記事を見る人はあまりデザインにこだわらないかなと。（偏見）
class名の加工などはcssを考慮して実装したので、
リクエストがあれば追記します。その際はコメントください。