【Java】パスワード変更を遷移なしでやろうとした話【Ajax】

備忘録としての投稿です
記事にしておきながらなんですが、人に見せることは目的をしておりません。
ですが、見かけた方が内容についていろいろとツッコミを入れてくださると嬉しいです。

【自分について】
となるITベンチャー企業に入社し、日々勉強中です。
主にJavaを勉強しています。

## 事の発端
現在、Javaの動的WEBプロジェクトで簡単な従業員管理サイトのようなシステムを作っている。
管理者のパスワード変更機能を付けたいと思ったのですが、変更のためだけに別でページを作るのもなーなんて思い、モーダルウィンドウでパスワード変更画面を出し、ページ遷移することなく変更処理ができたら良いよねと思う。

## 画面遷移なく Servlet の処理を実行することは可能か？
パスワード変更機能の追加で一番実現させたかったのは、画面遷移なくパスワード変更の処理を実行すること

このシステムでは、基本的には JSP で画面表示、Servlet で内部処理を行っている。
（基本そうかもしれませんが、、）
JSP → Servlet → JSP のやり取りは Servlet の URLマッピング で sendRedirect や forward で遷移を行うのが一般的かと思う。

しかし、sendRedirect や forward では画面遷移をしてしまうし、JSP のフォームの input:submit も必ず画面遷移するらしい

どうしたもんかと思っているところ、Servlet + Ajax の組み合わせで行けるらしいと発見
たしかにこれなら Javascript から Servlet を呼び出せるので行けそう

## 実装
実装した部分を大まかに記載してます

・パスワード変更画面 (モーダルウィンドウで表示されるようにしている)
```jsp:header.jsp
<div id="jsi-modal" class="header__modal">
			<div id="#jsi-modalContent" class="header__passChange">
				<h2>パスワード変更</h2>
				<form class="header__passChangeForm">
					<label>現在のパスワード：</label><input type="password" id="currentPass">
					<br>
					<label>新しいパスワード：</label><input type="password" id="newPass">
					<br>
					<label>パスワードの確認：</label><input type="password" id="newPassConfirm">
					<br>
					<button id="submit" class="submit" type="button">パスワード変更</button>
				</form>
				<input id="jsi-modalClose" class="modalClose" type="button" value="閉じる">
			</div>
		</div>
  ```

  ・Ajaxを実装したjQuery
  ```js:passChange.js
   /*
    フォームは複数回使うため、変数に格納
    */
    const $currentPass = $('#currentPass');
    const $newPass = $('#newPass');
    const $newPassConfirm = $('#newPassConfirm');
    
    /*
    パスワード変更のPOST通信を ajaxで行う
    */
    $('#submit').on('click', function() {
		 
		$.ajax({
			url: "change-password",
			type: "POST",
			data: {
				currentPass: $currentPass.val(),
				newPass: $newPass.val(),
				newPassConfirm: $newPassConfirm.val()
			}
		})
		.done(function(result) {
			
			/*
			変更処理後のアラートをOKを押すと、フォーム内容がリセットされる
			*/
			if(!alert(result)) {
				$currentPass.val("");
				$newPass.val("");
				$newPassConfirm.val("");
			}
		})
		.fail(function() {
			alert("パスワード変更に失敗しました。やり直してください");
		})
	})
 ```

 ・パスワード変更処理のServlet
 ```java:passwordChangeServlet.java
 HttpSession session = request.getSession(false);
		
		/*
		 * ajax通信で渡されたパラメータの受け取り
		 * */
		String currentPass = request.getParameter("currentPass");
		String newPass = request.getParameter("newPass");
		String newPassConfirm = request.getParameter("newPassConfirm");
		
		/*
		 * 処理後のメッセージ格納用の変数
		 * */
		String message = "";
		
		UserBean user = (UserBean) session.getAttribute("user");
		
		/*
		 * パスワード変更処理
		 * 1. 現在のパスワードが一致しない場合→変更実施せず、エラー表示
		 * 2. 新しいパスワードと確認用パスワードが一致しない場合→変更実施せず、エラー表示
		 * 3. すべて正しい場合→UserDAO の changePasword実行でパスワードの変更
		 * */
		if (!(currentPass.equals(user.getPassword()))) {
			message = "現在のパスワードが一致しません";
		} else if (!(newPass.equals(newPassConfirm))) {
			message = "新しいパスワードが一致しません";
		} else {
			String userId = user.getUserId();
			UserDAO dao = new UserDAO();
			
			try {
				dao.changePassword(userId, newPass);
				message = "パスワードが変更されました";
			} catch (ClassNotFoundException | SQLException e) {
				// TODO 自動生成された catch ブロック
				e.printStackTrace();
			}
		}
		
		/*ajax の done処理にメッセージを渡す*/
		PrintWriter out = response.getWriter();
		out.println(message);
  ```

  以上で、モーダルウィンドウで表示させたパスワード変更フォームから、画面遷移なく変更の実行ができた！

  ### 詰まったところ①：Servlet が2回実行され、500 : internal server error
  テストのため実行してみたところ、500 : internal erver error が発生した
  
  Ajax 通信の状況の確認や、フォームのデータの受け渡しが問題なくできているかを確認するため、Servlet のパラメーター受け取りの直下に、コンソール出力確認してみたところ、
```:console
currentPass : password
newPass : newpassword
newPassConfirm : newpasword
currentPass : null
newPass : null
newPassConfirm : null
```
となぜか servlet が2回実行されていた

**解決：jsp の formタグに action属性を付けたままだった（ケアレスミス）
      formタグの name属性は削除していたので、空のデータが飛んでしまい、500エラーが発生していた。**

### 詰まったところ②：servlet から javascript へのデータの受け渡し
servlet から JSP へのデータの受け渡しは、requestスコープに値をセット (.setAttribute) して、RequestDispatcher の forward という処理をほぼ脳死のようにやってきた。

ここで、javascript でデータを受け取るにはどうしたらよいか？という疑問がでる。

調べてみると、Javascript内でも、下記のように記述すればデータを取得できるとの情報を発見

```js:Javascript
let message = "<%=request.getAttribute("message") %>";
```

しかし上手くいかない、、、

結果、PrintWriter クラスを使用することでどうやら可能らしいという情報を発見

**解決 : servlet から Javascript にデータを渡すときは PrintWriter クラスを使おう
     （詳しくはまだわからんが、うまくいくのでこれで行こう)**

### 詰まったところ③ : ajax通信のはずなのに画面が変わる事象で半日詰まる

さあ、これでうまくいくのでは！と思ったところ、Ajax通信も問題なくできているし、変更の処理も問題ないし、その後のメッセージも表示されるのだが、

なぜか画面が変わる

とりあえず、モーダルウィンドウでフォーム入力し、ボタンを押すと処理が行われ、画面が変わらないまま、メッセージが表示されれば良いのに、なぜか画面が変わる、、、

検証用に簡単な Ajax通信のみを行うサンプルプロジェクトを作り、デベロッパーツールでネットワークを見ながら試してみるも、そのプロジェクトでは画面遷移は起こらない、、、

**解決 : buttonタグの type属性が違った (結構単純な理由だった)**

基本フォームでは input:submitを使っていたが、それでは画面遷移してしまうので、button タグに変更し、クリックイベントで Ajax通信を行うようにしていた

が、なんとbutton タグでの、type="submit" だったら画面遷移してしまうらしい

ということで、結構単純なことで半日悩んでいたと分かり、落胆しつつも希望通りの実装が完了した

## まとめ

なんやかんやありながら、Ajax通信で画面遷移なくパスワードの変更を実装することはできました。
結構悩みながらやった割には、「こんな単純なことで、、、」というレベルの間違いがほとんどでした。

まだまだ初学者なので、コードが正しいかどうかは分かりません、、、
プロから見ればいろいろとツッコミどころが多い内容かもしれません

ですが、いろいろ知れた良い経験でした。