#14 Spring JavaScript 生年月日から年齢を自動計算[間違いがあったので編集中]

# #14 Spring 生年月日から年齢を自動計算[間違いがあったので編集中]
今回はJavaScriptを用いて生年月日から年齢を自動で求めるようなプログラムを記述していきます。
最初に書いておきますが、入力フォーム内に``/,-``のような数字以外の文字が含まれていても問題なく動作することができました。

# 前回まで
前回は新規登録画面を作成しました。

https://qiita.com/Jackoguro/items/db35aa456341a484254b

入力フォームに誕生日1,2という項目があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884987/3309863c-63ce-537e-5560-f33f61f454bf.png)

誕生日1は``type="date"``を用いて入力する形式で、誕生日2は``type="text"``を用いて``yyyy/MM/dd``形式を用いて入力します。

# 構築環境
1. 各バージョン
Spring Boot ver 2.7.5
jquery ver 3.6.1
bootstrap ver 5.2.2
webjars-locator ver 0.46

2. 依存関係
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884987/cb4b6343-824d-9b31-be74-5b1cb03cdc62.png)
今回の処理はJavaScriptメインで行うので、必要なのはThymeleafのみです。

# 成果物
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884987/bb143c40-0598-b066-a221-d82f57fadd89.png)
年齢が自動で入力

# 今回行うこと
今回は以下の流れに沿って進めていきます。
1．HTML内にbirthday.js(JavaScriptファイル)のリンクを挿入

~~2. birthday.js内に処理を記述~~
　1. addEventListener
　2. document.getElementById('id名').value;
　3. new Date()
　4. getTime() 
　5. Math.floor

3．th:field

4．birthday.js内に処理を記述(正しいコード)
　1. replace()、正規表現
　1. getFullYear()、getMonth()、getDate()
　1. PadStart()


:::note warn
``2. birthday.js内に処理を記述``に関して、このやり方だと閏年対応ができていなかったため正しいコードを新たにを記述します(2022/11/23)。 
:::

## 1. HTML内にbirthday.js(JavaScriptファイル)のリンクを挿入
今回はHTMLとJavaScriptの処理を別々のファイルに記述するので、src/main/resources/static/js内にbirthday.jsファイルを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884987/671ed874-52e7-2030-b03b-e6abe51c4cb3.png)

```html:signup.html
<!--省略-->

<!-- JS読込 -->
<script th:src="@{/webjars/jquery/jquery.min.js}" defer></script>
<script th:src="@{/webjars/bootstrap/js/bootstrap.min.js}" defer></script>
<script src="https://ajaxzip3.github.io/ajaxzip3.js" charset="UTF-8"></script>
<script th:src="@{/js/birthday.js}" defer></script>
<title th:text="#{user.signup.UsreRegistration}"></title>
</head>
<body class="bg-light">
	<form id="signup-form" method="post" action="/user/signup" class=form-signup th:object="${signupForm}" enctype="multipart/form-data">
		<h1 class="text-center" th:text="#{user.signup.UsreRegistration}"></h1>

        <!-- 省略 -->

		<!-- 誕生日1(date,textのどちらが入力しやすいか検討中) -->
		<div class="form-group">
			<label for="birthday" th:text="#{user.signup.Birthday} + 1"></label>
			<input type="date" class="form-control" th:field="*{birthday1}" 
			th:errorclass="is-invalid">
			<div class="invalid-feedback" th:errors="*{birthday1}"></div>
		</div>
		<!-- 誕生日2(date,textのどちらが入力しやすいか検討中) -->
		<div class="form-group">
			<label for="birthday" th:text="#{user.signup.Birthday} + 2"></label>
			<input type="text" class="form-control" th:field="*{birthday2}"  th:errorclass="is-invalid">
			<div class="invalid-feedback" th:errors="*{birthday2}"></div>
		</div>
		<!-- 年齢(誕生日から自動算出) -->
		<div class="form-group">
			<label for="age" th:text="#{user.signup.Age}"></label>
			<input type="text" class="form-control"
			th:field="*{age}" th:errorclass="is-invalid">
			<div class="invalid-feedback" th:errors="*{age}"></div>
		</div>

        <!-- 省略 -->

	</form>
</body>
</html>
```
パスはsrc/main/resources/staticからの相対パスを指定します。
```html
<script th:src="@{/js/birthday.js}" defer></script>
```
:::note
defer：HTML、JavaScriptの読込を平行して行えるようになるため、読込が速くなる
:::

## 2. birthday.js内に処理を記述
最終的なコードは以下になります。今回はこのコード内で使用されているクラス、メソッド等をまとめていきます。
```javascript:birthday.js
// yyyyMMdd, yyyy/MM/dd, yyyy-MM-dd形式に対応。
// 基準日指定可能。基準日を指定しなければ処理日を基準日とする
birthday1.addEventListener("input", function(){
	// 入力された誕生日を取得
	var birthday1 = document.getElementById('birthday1').value;
	
	// 現在の日時を取得する
	var today = new Date();
	
	// 生まれた日時を取得する
	var birthday = new Date(birthday1);
	
	//(現在)-(誕生日)
	var diff = today.getTime() - birthday.getTime();
	
	//日付を出す処理
	var daysPast = Math.floor(diff/(1000*24*60*60));
	
	//年数を出す
	var age = Math.floor(daysPast/365);
	
	document.getElementById('age').value = age;
});
```
### 1. addEventListener
``addEventListener("change", function(){});``と記述することでフォームの入力項目が変更されたときに処理を実行することができます。
``"change"``,``"input"``の2つの内どちらかを選択することができ、
- change：入力が終わりフォーカスが外れたときに処理を実行
- input：入力中に随時処理を実行

の違いがあります。
個人的には``"change"``の方が良いと感じました(``"input"``は随時処理が実行されるので途中までの入力でも値が出力されてしまう)。

今回は``birthday1.addEventListener("change", function(){});``と記述したことにより、``id="birthday1"``の内容が変更されたときに処理を実行します。

```javascript
birthday1.addEventListener("change", function(){});
```
参考サイト

https://gray-code.com/javascript/execute-processing-when-the-form-entered/

### 2. document.getElementById('id名').value;
``document.getElementById('id名').value``と記述することで、指定したid属性が付いているフォームの入力内容を取得します。

```javascript
var birthday1 = document.getElementById('birthday1').value;
```

### 3. new Date()
- new Date() (引数無し)：インタンス化されたときの現在の日付と時刻を返す
- new Date(value) (引数有り)：引数に設定した日付と時刻を返す

```JavaScript
// 現在の日時を取得する
var today = new Date();
	
// 生まれた日時を取得する
var birthday = new Date(birthday1);
```
詳しくは以下のサイトを参考にしてみてください

参考サイト

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date/Date

### 4. getTime() 
``getTime()``は1970年1月1日00:00:00 UTCから指定した日時までの経過時間をミリ秒で表した数値になります。

```JavaScript
//(現在)-(誕生日)
var diff = today.getTime() - birthday.getTime();	
```

参考サイト

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date/getTime

### 5. Math.floor
``Math.floor``は関数は与えられた数値以下の最大の整数を返します。
Math.floor(5.9)⇒5
Math.floor(-5.1)⇒-6

```JavaScript
//日付を出す処理
var daysPast = Math.floor(diff/(1000*24*60*60));
	
//年数を出す
var age = Math.floor(daysPast/365);
```
``var daysPast = Math.floor(diff/(1000*24*60*60));``では現在から誕生日までの差分の時刻(ミリ秒)を日に直しています。
細かく式を分解すると、
diff/1000　：ミリ秒⇒秒
diff/1000×60　：秒⇒分
diff/1000×60×60　：分⇒時間
diff/1000×60×60×24　：時間⇒日
と変換されています。

最終的にこの式で求めた``datePath(日)``を365(日)で割ることで``age(歳)``が求められます。

参考サイト

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Math/floor

## 3. th:field
Spring独自の属性である``field``は、例えば``th:field="birthday"``のように入力すると、
``id="birthday", name="birthday"``のようにプログラム上では読み込まれます。なので、今回は別途``id``属性は用いていないです。

## 4. birthday.js内に処理を記述(正しいコード)
新たに記述した正しいコードが以下になります。
```JavaScript:birthday.js
const elemBirthday = document.getElementById('birthday2');
const elemAge = document.getElementById('age'); 

elemBirthday.addEventListener("input", () => {
	
	const birthday = elemBirthday.value.replace(/\D/g, '');
	
	const d = new Date();
	
	const today = d.getFullYear() 
		+ String(d.getMonth() + 1).padStart(2, '0')
		+ String(d.getDate()).padStart(2, '0');
		
	const age = Math.floor((today - birthday) / 10000);
	
	elemAge.value = age < 0 ? '--' : age;
});
```
### 1. replace()、正規表現
replaceメソッドを用いることで入力された数字以外の値を全て``''``に変換(なくす)ことができる。 例)2022/11/23⇒20221123
```javascript
const birthday = elemBirthday.value.replace(/\D/g, '');
```
:::note
- 正規表現：``//``で囲む
- \D：あらゆる(アラビア)数字以外にマッチする
- g：正規表現``//``の後に指定する。文字列全体を見て一致したモノを全て返す
:::

参考サイト

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/replace

https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_Expressions/Character_Classes

https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_Expressions


### 2. getFullYear()、getMonth()、getDate()
- getFullYear()：4桁の数字を返す
- getMonth()：0~11までの数字を返す、0は1月、1は2月...11は12月
- getDate()：1から31までの間の整数値を返す

参考サイト

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date/getFullYear

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date/getMonth

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date/getDate

### 3. PadStart()
PadStart()メソッドは結果の文字列が指定した長さになるように、指定した文字で埋めます。
``getMonth()``メソッドにより`1(2月の値)`が得られた場合、``PadStart()メソッド````String(d.getMonth() + 1).padStart(2, '0')``により`01`となる。

参考サイト

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/padStart

# 最後に
最後に以下を記述することにより、``id="age"``を持つフォームの内容に先ほど求めた値が挿入されます。
```JavaScript
document.getElementById('age').value = age;
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884987/bb143c40-0598-b066-a221-d82f57fadd89.png)
年齢が自動で入力