Spring Boot + npm + Geb で入力フォームを作ってテストする ( その23 )( 入力画面2を作成する2 )
概要
記事一覧はこちらです。
Spring Boot + npm + Geb で入力フォームを作ってテストする ( その22 )( 入力画面2を作成する ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
- 入力画面2の作成
- 前回に引き続き Javascript の処理を実装します。以下の処理を実装します。
- メールアドレスチェック
- 「前の画面へ戻る」ボタンがクリックされた時は入力されているものだけ入力チェックする
- Form.js のメソッド内の処理で Array.reduce に変更可能なものは変更しようと思いましたが、forEach 内で if 文で分岐して長めの処理にしていると Array.reduce に変更するメリットがなさそう(ソースが見にくくなりそう)に思えたので、止めます。
参照したサイト・書籍
- JavaScriptあれこれ/正規表現パターン
http://winter-tail.sakura.ne.jp/pukiwiki/index.php?JavaScript%A4%A2%A4%EC%A4%B3%A4%EC%2F%C0%B5%B5%AC%C9%BD%B8%BD%A5%D1%A5%BF%A1%BC%A5%F3#v919ec2e
目次
手順
メールアドレスチェックを実装する
正しいメールアドレスのチェック方法 の記事を参考に、以下2つのチェックのみ行うようにします。
- @で分割して要素数が2でなければエラー
- 分割後の各要素に空白、制御文字、非ASCII文字が入っていたらエラー
src/main/assets/js/lib/util/validator.js を以下のように変更します。
checkRegexp: function (form, idFormGroup, idList, pattern, errmsg) { var isValid = validateRegexp(idList, pattern); setSuccessOrError(form, idFormGroup, errmsg, isValid); }, /** * メールアドレスチェック用関数 * @param {Form} form - Form オブジェクト * @param {string} idFormGroup - Validation の SUCCESS/ERROR の結果を反映する要素の id * @param {string} id - チェックを行う要素の id * @param {string} errmsg - チェックエラー時に表示するエラーメッセージ */ checkEmail: function (form, idFormGroup, id, errmsg) { // @で分割して要素数が2つかどうかチェックする var elements = $(id).val().split("@"); var isValid = (elements.length === 2); // 1つ目及び2つ目の要素に空白、制御文字、非ASCII文字が含まれていないかチェックする if (isValid === true) { isValid = elements.reduce(function (p, element) { return p && element.match(/^[\x21-\x7E]+$/); }, true); } setSuccessOrError(form, idFormGroup, errmsg, isValid); }
checkEmail
関数を追加します。
src/main/assets/js/inquiry/input02.js を以下のように変更します。
var telAndEmailValidator = function (event) { var telIdFormGroup = "#form-group-tel"; var emailIdFormGroup = "#form-group-email"; var telIdList = ["#tel1", "#tel2", "#tel3"]; var emailIdList = ["#email"]; var idList = telIdList.concat(emailIdList); var validateFunction = function () { if (form.isAllEmpty(idList)) { var errmsg = "電話番号とメールアドレスのいずれか一方を入力してください"; form.setError(telIdFormGroup, errmsg); form.setError(emailIdFormGroup, errmsg); throw new Error(errmsg); } else { // 最初に入力チェックOKの状態にしておく form.setSuccess(telIdFormGroup); form.setSuccess(emailIdFormGroup); var errmsg = ""; // 「電話番号」に1つでも値が入力されていたら入力チェックする if (form.isAnyNotEmpty(telIdList)) { try { validator.checkRegexp(form, telIdFormGroup, ["#tel1"], "^0", "市外局番の先頭には 0 の数字を入力してください"); validator.checkRegexp(form, telIdFormGroup, ["#tel1", "#tel2"], "^[0-9]{6}$", "市外局番+市内局番の組み合わせが数字6桁になるように入力してください"); validator.checkRegexp(form, telIdFormGroup, ["#tel3"], "^[0-9]{4}$", "加入者番号には4桁の数字を入力してください"); } catch (e) { errmsg = e.message; } } // 「メールアドレス」が入力されていたら入力チェックする if (form.isAnyNotEmpty(emailIdList)) { try { emailIdList.forEach(function (id) { validator.checkEmail(form, emailIdFormGroup, id, "メールアドレスを入力してください"); }) } catch (e) { errmsg = e.message; } } // 入力チェックエラーが発生している場合には Error オブジェクトを throw する if (errmsg !== "") { throw new Error(errmsg); } } }; form.convertAndValidate(form, event, telIdFormGroup, idList, function () { converter.convertHanAlphaNumeric(telIdList); }, validateFunction ); form.convertAndValidate(form, event, emailIdFormGroup, idList, function () { converter.convertHanAlphaNumeric(emailIdList); }, validateFunction ); };
telAndEmailValidator
関数内のvalidateFunction
関数の以下の点を変更します。var errmsg = "";
を追加します。if (form.isAnyNotEmpty(telIdList)) { ... }
内の処理をtry { ... } catch (e) { errmsg = e.message; }
で囲みます。if (form.isAnyNotEmpty(emailIdList)) { ... }
の処理を追加します。if (errmsg !== "") { throw new Error(errmsg); }
を追加します。
動作確認してみます。
test@sample.co.jp
はチェックOKとなり、
@
が2つあったり、スペースがあるとエラーになります。
電話番号を入力してメールアドレスが未入力の時は、チェックOKになります。
「前の画面へ戻る」ボタンがクリックされた時は入力されているものだけ入力チェックする
src/main/assets/js/lib/util/validator.js を以下のように変更します。
- checkRequired 関数以外は、値が空の場合に入力チェックを行わないように変更します。
- ignoreCheckRequired プロパティを追加して、true の場合には checkRequired 関数何もしないようにします。
"use strict"; module.exports = { ignoreCheckRequired: false, /** * 初期状態に戻す */ reset: function () { this.ignoreCheckRequired = false; }, /** * 必須チェック用関数 * @param {Form} form - Form オブジェクト * @param {string} idFormGroup - Validation の SUCCESS/ERROR の結果を反映する要素の id * @param {Array} idList - チェックを行う要素の id の配列 * @param {string} errmsg - チェックエラー時に表示するエラーメッセージ */ checkRequired: function (form, idFormGroup, idList, errmsg) { if (this.ignoreCheckRequired === true) return; var isValid = !form.isAnyEmpty(idList); setSuccessOrError(form, idFormGroup, errmsg, isValid); }, /** * ひらがなチェック用関数 * @param {Form} form - Form オブジェクト * @param {string} idFormGroup - Validation の SUCCESS/ERROR の結果を反映する要素の id * @param {Array} idList - チェックを行う要素の id の配列 * @param {string} errmsg - チェックエラー時に表示するエラーメッセージ */ checkHiragana: function (form, idFormGroup, idList, errmsg) { if (form.isAllEmpty(idList)) return; var isValid = validateRegexp(idList, "^[\u3041-\u3096]+$"); setSuccessOrError(form, idFormGroup, errmsg, isValid); }, /** * 正規表現チェック用関数 * @param {Form} form - Form オブジェクト * @param {string} idFormGroup - Validation の SUCCESS/ERROR の結果を反映する要素の id * @param {Array} idList - チェックを行う要素の id の配列 * @param {string} pattern - チェックで使用する正規表現のパターン文字列 * @param {string} errmsg - チェックエラー時に表示するエラーメッセージ */ checkRegexp: function (form, idFormGroup, idList, pattern, errmsg) { if (form.isAllEmpty(idList)) return; var isValid = validateRegexp(idList, pattern); setSuccessOrError(form, idFormGroup, errmsg, isValid); }, /** * メールアドレスチェック用関数 * @param {Form} form - Form オブジェクト * @param {string} idFormGroup - Validation の SUCCESS/ERROR の結果を反映する要素の id * @param {string} id - チェックを行う要素の id * @param {string} errmsg - チェックエラー時に表示するエラーメッセージ */ checkEmail: function (form, idFormGroup, id, errmsg) { // 値が入力されていなければチェックしない if ($(id).val() === "") return; // @で分割して要素数が2つかどうかチェックする var elements = $(id).val().split("@"); var isValid = (elements.length === 2); // 1つ目及び2つ目の要素に空白、制御文字、非ASCII文字が含まれていないかチェックする if (isValid === true) { isValid = elements.reduce(function (p, element) { return p && element.match(/^[\x21-\x7E]+$/); }, true); } setSuccessOrError(form, idFormGroup, errmsg, isValid); } }; /** * 正規表現チェック用共通関数 * @param {Array} idList - チェックを行う要素の id の配列 * @param {string} pattern - チェックで使用する正規表現のパターン文字列 * @returns {boolean} true:チェックOK, false:チェックNG */ function validateRegexp(idList, pattern) { var regexp = new RegExp(pattern); return idList.reduce(function (p, id) { return p + $(id).val(); }, "").match(regexp); } /** * * @param {Form} form - Form オブジェクト * @param {string} idFormGroup - Validation の SUCCESS/ERROR の結果を反映する要素の id * @param {string} errmsg - チェックエラー時に表示するエラーメッセージ * @param {boolean} isValid - チェック結果 */ function setSuccessOrError(form, idFormGroup, errmsg, isValid) { if (isValid) { form.setSuccess(idFormGroup); } else { form.setError(idFormGroup, errmsg); throw new Error(errmsg); } }
ignoreCheckRequired: false
を追加します。reset
関数を追加します。checkRequired
関数内にif (this.ignoreCheckRequired === true) return;
を追加します。checkHiragana
関数とcheckRegexp
関数内にif (form.isAllEmpty(idList)) return;
を追加します。checkEmail
関数内にif ($(id).val() === "") return;
を追加します。
src/main/assets/js/inquiry/input02.js を以下のように変更します。
var telAndEmailValidator = function (event) { .......... var validateFunction = function () { if (validator.ignoreCheckRequired && form.isAllEmpty(idList)) { return; } .......... }; .......... }; .......... var btnBackOrNextClickHandler = function (event, url, ignoreCheckRequired) { // 全ての入力チェックを実行する try { validator.ignoreCheckRequired = ignoreCheckRequired; executeAllValidator(event); } finally { validator.reset(); } .......... }; $(document).ready(function () { .......... // 「前の画面へ戻る」「次へ」ボタンクリック時の処理をセットする $(".js-btn-back").on("click", function (event) { return btnBackOrNextClickHandler(event, "/inquiry/input/02/?move=back", true); }); $(".js-btn-next").on("click", function (event) { return btnBackOrNextClickHandler(event, "/inquiry/input/02/?move=next", false); }); .......... });
telAndEmailValidator
関数の中で定義しているvalidateFunction
関数内にif (validator.ignoreCheckRequired && form.isAllEmpty(idList)) { return; }
を追加します。btnBackOrNextClickHandler
関数の以下の点を変更します。- 引数に
ignoreCheckRequired
を追加します。 executeAllValidator(event);
の前にvalidator.ignoreCheckRequired = ignoreCheckRequired;
を呼び出し、処理した後にvalidator.reset();
が呼び出されるようにします。
- 引数に
$(document).ready(function () { ... }
内でbtnBackOrNextClickHandler
関数を呼び出しているところで、$(".js-btn-back")
の場合には第3引数に true を、$(".js-btn-next")
の場合には false を渡すようにします。
動作確認してみます。
入力画面1に入力して「次へ」ボタンをクリックします。
入力画面2が表示されます。
何も入力せずに「次へ」ボタンをクリックすると、必須チェックが実行されてエラーメッセージが表示されます。
「前の画面へ戻る」ボタンをクリックすると、必須チェックは実行されず入力画面1へ戻ります。
「次へ」ボタンを押して入力画面2に遷移した後、今度は「電話番号」の一番左側の項目に 3
とだけ入力してから「前の画面へ戻る」ボタンをクリックすると、入力チェックエラーが発生して入力画面1には戻りません。
3
を消して「前の画面へ戻る」ボタンをクリックすると、入力画面1へ戻ります。。。と思いましたが、戻らずに「電話番号とメールアドレスのいずれか一方を入力してください」のエラーメッセージが表示されました?
調べてみると「前の画面へ戻る」ボタンをクリックした時の処理は実行されておらず、フォーカスが外れたことで入力チェックが実行されていたためでした。「前の画面へ戻る」ボタンを押した時に全ての項目にフォーカスがセットされたことにしているので、入力チェックが実行されてしまうのが原因でした。
「前の画面へ戻る」ボタンが押された時は入力チェックの時だけフォーカスがセットされたことにして、チェック終了後に元に戻すことにします。
src/main/assets/js/lib/class/Form.js を以下のように変更します。
.......... /** * form.idList にセットされている id を全て form.focused にセットして * focused イベントが発生したことにする * @param {Form} form - Form オブジェクト */ Form.prototype.forceAllFocused = function (form) { form.setFocusedFromList(form, form.idList); }; /** * form.focused の値をバックアップする * @param {Form} form - Form オブジェクト */ Form.prototype.backupFocusedState = function (form) { form.backupFocused = form.focused.concat(); }; /** * form.focused の値を form.backupFocusedState を呼び出した時の状態に戻す * @param {Form} form - Form オブジェクト */ Form.prototype.restoreFocusedState = function (form) { form.focused = form.backupFocused.concat(); }; ..........
backupFocusedState
関数とrestoreFocusedState
関数を追加します。
src/main/assets/js/inquiry/input02.js の以下の点を変更します。
var btnBackOrNextClickHandler = function (event, url, ignoreCheckRequired) { // 全ての入力チェックを実行する try { if (ignoreCheckRequired) { validator.ignoreCheckRequired = ignoreCheckRequired; form.backupFocusedState(form); } executeAllValidator(event); } finally { if (ignoreCheckRequired) { validator.reset(); form.restoreFocusedState(form); } } ..........
form.backupFocusedState(form);
とform.restoreFocusedState(form);
を追加します。- また
executeAllValidator(event);
の前後の処理をif (ignoreCheckRequired) { ... }
で囲むようにします。
今度は入力画面2で「電話番号」の一番左側の項目に 3
を入力して「前の画面へ戻る」ボタンをクリックするとエラーメッセージが表示されますが、
電話番号をクリアして「前の画面へ戻る」ボタンをクリックすると入力画面1へ戻るようになりました。
次回は。。。
サーバ側の処理を実装します。
履歴
2017/09/28
初版発行。
IntelliJ IDEA を 2017.2.3 → 2017.2.4 へバージョンアップ
IntelliJ IDEA を 2017.2.3 → 2017.2.4 へバージョンアップする
IntelliJ IDEA の 2017.2.4 がリリースされたのでバージョンアップします。
- IntelliJ IDEA 2017.2.4 Release Notes
https://confluence.jetbrains.com/display/IDEADEV/IntelliJ+IDEA+2017.2.4+Release+Notes
※ksbysample-webapp-lending プロジェクトを開いた状態でバージョンアップしています。
IntelliJ IDEA のメインメニューから「Help」-「Check for Updates…」を選択します。
「IDE and Plugin Updates」ダイアログが表示されます。左下に「Update and Restart」ボタンが表示されていますので、「Update and Restart」ボタンをクリックします。
Plugin の update も表示されました。「Error-prone Compiler Integration」はバージョンアップすると動かなくなりますので、これだけチェックを外して「Update and Restart」ボタンをクリックします。
Patch がダウンロードされて IntelliJ IDEA が再起動します。
IntelliJ IDEA が起動すると画面下部に「Indexing…」のメッセージが表示されますので、終了するまで待機します。
IntelliJ IDEA のメインメニューから「Help」-「About」を選択し、2017.2.4 へバージョンアップされていることを確認します。
Gradle Tool Window のツリーを見ると「Tasks」の下に「other」しかない状態になっているので、左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。
clean タスク実行 → Rebuild Project 実行 → build タスクを実行して、"BUILD SUCCESSFUL" のメッセージが出力されることを確認しようとしたら、Rebuild Project で以下の画像のエラーが出ました。
エラーメッセージの途中に
com.haskforce.jps.model...
と出力されているので、どうも Haskell のためにインストールしている HaskForce Plugin が原因のようです。HaskForce Plugin をアンインストールしてから clean タスク実行 → Rebuild Project 実行 → build タスクを実行すると、今度は “BUILD SUCCESSFUL” のメッセージが出力されました。
Project Tool Window で src/test を選択した後、コンテキストメニューを表示して「Run ‘All Tests’ with Coverage」を選択し、テストが全て成功することを確認します。
ちなみに HaskForce Plugin は Scala Plugin をインストールしたら再び使えるようになりました。
Spring Boot + npm + Geb で入力フォームを作ってテストする ( その22 )( 入力画面2を作成する )
概要
記事一覧はこちらです。
Spring Boot + npm + Geb で入力フォームを作ってテストする ( その21 )( 入力画面1を作成する5 ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
- 入力画面2の作成
- Javascript の処理を実装します。
参照したサイト・書籍
電話番号に関するQ&A
http://www.soumu.go.jp/main_sosiki/joho_tsusin/top/tel_number/q_and_a.html正規表現でのメールアドレスチェックは見直すべき – ReDoS
https://blog.ohgaki.net/redos-must-review-mail-address-validation正しいメールアドレスのチェック方法
https://blog.ohgaki.net/how-to-check-email-address
目次
- input02.html 内の input タグに maxlength 属性を追加する
- 変換&入力チェック処理の仕様を決める
- validator.js の validateRegexp 関数を変更する
- Form.js に isAllEmpty, isAnyNotEmpty メソッドを追加する
- 入力チェックを実装する
- input02.html を修正する
- 動作確認
- 次回は。。。
手順
input02.html 内の input タグに maxlength 属性を追加する
INQUIRY_DATA テーブルのカラムに定義した文字数を src/main/resources/templates/web/inquiry/input02.html 内の <input type="text" .../>
に maxlength 属性として追加します。
変換&入力チェック処理の仕様を決める
各項目で以下の変換&入力チェック処理を行います。
項目 | 変換&入力チェック処理 |
---|---|
郵便番号 | 半角数字変換 |
必須チェック | |
7桁固定チェック | |
住所 | 必須チェック |
電話番号 | 半角数字変換 |
「市外局番」の先頭 0 チェック | |
「市外局番」+「市内局番」= 6桁固定チェック | |
「加入者番号」= 4桁固定チェック | |
メールアドレス | 半角文字変換 |
メールアドレスチェック |
- 文字数は maxlength 属性で制御されている前提とし、Javascript ではチェックしません(サーバ側では文字数チェックは行います)。
- 「郵便番号」のように項目が2つに分かれているものは、両方にカーソルが入った後か、「前の画面へ戻る」「次へ」ボタンをクリックした時でないと変換&入力チェック処理は行いません。例えば「郵便番号」の左側の項目にカーソルがある状態の時に文字列を入力してからマウスで「住所」にカーソルを移動した場合、「郵便番号」の変換&入力チェック処理は実行しません。
- 「電話番号」と「メールアドレス」は全てにカーソルが入った後にどちらも入力されていなければエラーメッセージを表示します。
- 「次へ」ボタンがクリックされた時は全ての入力チェックを行いますが、「前の画面へ戻る」ボタンがクリックされた時は入力されているものだけ入力チェックを行います(必須チェックを行いません)。
量が多いので、以下の処理は次回以降に実装します。
- メールアドレスチェック
- 「前の画面へ戻る」ボタンがクリックされた時は入力されているものだけ入力チェックする
validator.js の validateRegexp 関数を変更する
src/main/assets/js/lib/util/validator.js の validateRegexp 関数は idList で渡された id の要素を1つずつ正規表現にマッチするかチェックしていますが、id の要素の値を全て結合した文字列に対して正規表現にマッチするかチェックするように変更します。
src/main/assets/js/lib/util/validator.js を以下のように変更します。
/** * 正規表現チェック用共通関数 * @param {Array} idList - チェックを行う要素の id の配列 * @param {string} pattern - チェックで使用する正規表現のパターン文字列 * @returns {boolean} true:チェックOK, false:チェックNG */ function validateRegexp(idList, pattern) { var regexp = new RegExp(pattern); return idList.reduce(function (p, id) { return p + $(id).val(); }, "").match(regexp); }
validateRegexp
関数を上記の内容に変更します。
Form.js に isAllEmpty, isAnyNotEmpty メソッドを追加する
idList で渡された id の要素が全て未入力/未選択かをチェックする isAllEmpty
メソッド、idList で渡された id の要素の中に1つでも入力/選択されているものがあるかをチェックする isAnyNotEmpty
メソッドを追加します。
.......... /** * idList で渡された id の要素が全て未入力/未選択かチェックする * @param {Array} idList - チェックする id がセットされた配列 * @returns {boolean} true:全て未入力/未選択である, false:1つ以上入力/選択されているものがある */ Form.prototype.isAllEmpty = function (idList) { var allEmpty = true; idList.forEach(function (id) { if ($(id).attr("type") === "radio") { if ($(id + ":checked").val() !== undefined) { allEmpty = false; } } else if ($(id).val() !== "") { allEmpty = false; } }); return allEmpty; }; /** * idList で渡された id の要素の中に未入力/未選択のものがあるかチェックする * @param {Array} idList - チェックする id がセットされた配列 * @returns {boolean} true:未入力/未選択のものがある, false:全て入力/選択されている */ Form.prototype.isAnyEmpty = function (idList) { .......... }; /** * idList で渡された id の要素の中に1つでも入力/選択されているものがあるかチェックする * @param {Array} idList - チェックする id がセットされた配列 * @returns {boolean} true:1つでも入力/選択されている, false:全て未入力/未選択である */ Form.prototype.isAnyNotEmpty = function (idList) { var anyNotEmpty = false; idList.forEach(function (id) { if ($(id).attr("type") === "radio") { if ($(id + ":checked").val() !== undefined) { anyNotEmpty = true; } } else if ($(id).val() !== "") { anyNotEmpty = true; } }); return anyNotEmpty; };
今さらながら気づきましたが、vaildator.js は Array.reduce で実装していてこちらの処理も Array.reduce を使えばいいのに、Form.js は Array.forEach で実装していました。。。 次回あたりに修正します。
入力チェックを実装する
src/main/assets/js/inquiry/input02.js を以下のように変更します。
"use strict"; var Form = require("lib/class/Form.js"); var converter = require("lib/util/converter.js"); var validator = require("lib/util/validator.js"); var form = new Form([ "#zipcode1", "#zipcode2", "#address", "#tel1", "#tel2", "#tel3", "#email" ]); var zipcodeValidator = function (event) { var idFormGroup = "#form-group-zipcode"; var idList = ["#zipcode1", "#zipcode2"]; form.convertAndValidate(form, event, idFormGroup, idList, function () { converter.convertHanAlphaNumeric(idList); }, function () { validator.checkRequired(form, idFormGroup, idList, "郵便番号を入力してください"); validator.checkRegexp(form, idFormGroup, idList, "^[0-9]{7}$", "郵便番号は7入力してください"); } ); }; var addressValidator = function (event) { var idFormGroup = "#form-group-address"; var idList = ["#address"]; form.convertAndValidate(form, event, idFormGroup, idList, undefined, function () { validator.checkRequired(form, idFormGroup, idList, "住所を入力してください"); } ); }; var telAndEmailValidator = function (event) { var telIdFormGroup = "#form-group-tel"; var emailIdFormGroup = "#form-group-email"; var telIdList = ["#tel1", "#tel2", "#tel3"]; var emailIdList = ["#email"]; var idList = telIdList.concat(emailIdList); var validateFunction = function () { if (form.isAllEmpty(idList)) { var errmsg = "電話番号とメールアドレスのいずれか一方を入力してください"; form.setError(telIdFormGroup, errmsg); form.setError(emailIdFormGroup, errmsg); throw new Error(errmsg); } else { // 最初に入力チェックOKの状態にしておく form.setSuccess(telIdFormGroup); form.setSuccess(emailIdFormGroup); // 「電話番号」に1つでも値が入力されていたら入力チェックする if (form.isAnyNotEmpty(telIdList)) { validator.checkRegexp(form, telIdFormGroup, ["#tel1"], "^0", "市外局番の先頭には 0 の数字を入力してください"); validator.checkRegexp(form, telIdFormGroup, ["#tel1", "#tel2"], "^[0-9]{6}$", "市外局番+市内局番の組み合わせが数字6桁になるように入力してください"); validator.checkRegexp(form, telIdFormGroup, ["#tel3"], "^[0-9]{4}$", "加入者番号には4桁の数字を入力してください"); } } }; form.convertAndValidate(form, event, telIdFormGroup, idList, function () { converter.convertHanAlphaNumeric(telIdList); }, validateFunction ); form.convertAndValidate(form, event, emailIdFormGroup, idList, function () { converter.convertHanAlphaNumeric(emailIdList); }, validateFunction ); }; var executeAllValidator = function (event) { form.forceAllFocused(form); [ zipcodeValidator, addressValidator, telAndEmailValidator ].forEach(function (validator) { validator(event); }); }; var btnBackOrNextClickHandler = function (event, url) { // 全ての入力チェックを実行する executeAllValidator(event); // 入力チェックエラーがある場合には処理を中断する if (event.isPropagationStopped()) { // 一番最初のエラーの項目にカーソルを移動する $(".has-error:first :input:first").focus().select(); return false; } // 「前の画面へ戻る」「次へ」ボタンをクリック不可にする $(".js-btn-back").prop("disabled", true); $(".js-btn-next").prop("disabled", true); // サーバにリクエストを送信する $("#input02Form").attr("action", url); $("#input02Form").submit(); // return false は // event.preventDefault() + event.stopPropagation() らしい return false; }; $(document).ready(function () { // 入力チェック用の validator 関数をセットする $("#zipcode1").on("blur", zipcodeValidator); $("#zipcode2").on("blur", zipcodeValidator); $("#address").on("blur", addressValidator); $("#tel1").on("blur", telAndEmailValidator); $("#tel2").on("blur", telAndEmailValidator); $("#tel3").on("blur", telAndEmailValidator); $("#email").on("blur", telAndEmailValidator); // 「前の画面へ戻る」「次へ」ボタンクリック時の処理をセットする $(".js-btn-back").on("click", function (event) { return btnBackOrNextClickHandler(event, "/inquiry/input/02/?move=back"); }); $(".js-btn-next").on("click", function (event) { return btnBackOrNextClickHandler(event, "/inquiry/input/02/?move=next"); }); // 「郵便番号」の左側の項目にフォーカスをセットする $("#zipcode1").focus().select(); });
input02.html を修正する
「次へ」ボタンはいつでも押せるようにします。src/main/resources/templates/web/inquiry/input02.html を以下のように変更します。
<div class="text-center"> <br/> <p class="text-primary text-bold">※電話番号とメールアドレスのいずれか一方は必ず入力してください。</p><br/> <button class="btn bg-blue js-btn-back"><i class="fa fa-arrow-left"></i> 前の画面へ戻る</button> <button class="btn bg-green js-btn-next"><i class="fa fa-arrow-right"></i> 次へ</button> </div>
<button class="btn bg-green js-btn-next" disabled>
→<button class="btn bg-green js-btn-next">
へ変更します。
動作確認
npm run springboot
を実行し、Tomcat を起動して http://localhost:9080/inquiry/input/02/ にアクセスすると入力画面2が表示されます。
何も入力せずに Tab キーでカーソルを「前の画面へ戻る」ボタンまで移動すると赤色になってエラーメッセージが表示されます。
F5 キーを押してリロードした後「次へ」ボタンをいきなりクリックすると全ての項目がエラーになり、カーソルは一番最初のエラー項目である「郵便番号」の左側の項目へ移動します。また入力画面3へは遷移しません。今は「前の画面へ戻る」ボタンをクリックしても同じように全ての項目が赤色になり、画面遷移はしません。
F5 キーを押してリロードした後、全ての項目にエラーにならないようデータを入力すると、全ての項目が緑色になります。
「次へ」ボタンをクリックすると入力画面3へ遷移します。
F5 キーを押してリロードした後、電話番号とメールアドレスに “電話番号とメールアドレスのいずれか一方を入力してください” のエラーメッセージが表示されている状態にしてから、
電話番号を入力すると、入力されている値に従ったエラーメッセージが表示されます。電話番号が入力されていればメールアドレスは入力不要なので緑色になります。
電話番号には何も入力せずにメールアドレスを入力すると、電話番号もメールアドレスもどちらも緑色になります。
メールアドレスを入力して、かつ電話番号にエラーのある値を入力すると、電話番号だけエラーメッセージが表示されます。
問題なく動作しているようです。
次回は。。。
引き続き Javascript の処理を実装します。
今回実装しなかった以下の2つと、
- メールアドレスチェック
- 「前の画面へ戻る」ボタンがクリックされた時は入力されているものだけ入力チェックする
実装していて気づいた以下の1点の予定です。
- Form.js のメソッド内の処理で Array.reduce に変更可能なものは変更する
履歴
2017/09/21
初版発行。
Spring Boot + npm + Geb で入力フォームを作ってテストする ( その21 )( 入力画面1を作成する5 )
概要
記事一覧はこちらです。
Spring Boot + npm + Geb で入力フォームを作ってテストする ( その20 )( 入力画面1を作成する4 ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
- 入力画面1の作成
- サーバ側のテストを作成します。
参照したサイト・書籍
- Learning Ant path style
https://stackoverflow.com/questions/2952196/learning-ant-path-style
目次
- InquiryInput01Form クラスのテストを作成する
- ksbysample-webapp-lending から TestHelper, HtmlResultMatchers クラスをコピーする
- HtmlResultMatchers クラスに val メソッドを追加する
- テストデータを用意する
- InquiryInputController クラスのテストを作成する
- 次回は。。。
手順
InquiryInput01Form クラスのテストを作成する
src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/form/InquiryInput01Form.java で Ctrl+Shift+T を押して「Create Test」ダイアログを表示してから、以下の画像の値にした後「OK」ボタンをクリックします。
src/test/groovy/ksbysample/webapp/bootnpmgeb/web/inquiry/form/InquiryInput01FormTest.groovy が新規作成されるので、以下の内容を記述します。
package ksbysample.webapp.bootnpmgeb.web.inquiry.form import spock.lang.Specification import spock.lang.Unroll import javax.validation.ConstraintViolation import javax.validation.Validation class InquiryInput01FormTest extends Specification { def validator def inquiryInput01Form def setup() { validator = Validation.buildDefaultValidatorFactory().getValidator() inquiryInput01Form = new InquiryInput01Form( lastname: "田中" , firstname: "太郎" , lastkana: "たなか" , firstkana: "たろう" , sex: "1" , age: "30" , job: "1") } def "placeholder で表示している例の Bean Validation のテスト"() { when: Set<ConstraintViolation<InquiryInput01Form>> constraintViolations = validator.validate(inquiryInput01Form) then: constraintViolations.size() == 0 } @Unroll def "lastname の Bean Validation の Bean Validation のテスト(#lastname --> #size)"() { setup: inquiryInput01Form.lastname = lastname Set<ConstraintViolation<InquiryInput01Form>> constraintViolations = validator.validate(inquiryInput01Form) expect: constraintViolations.size() == size where: lastname || size "" || 2 "a" || 0 "a" * 20 || 0 "a" * 21 || 1 } @Unroll def "firstname の Bean Validation のテスト(#firstname --> #size)"() { setup: inquiryInput01Form.firstname = firstname Set<ConstraintViolation<InquiryInput01Form>> constraintViolations = validator.validate(inquiryInput01Form) expect: constraintViolations.size() == size where: firstname || size "" || 2 "a" || 0 "a" * 20 || 0 "a" * 21 || 1 } @Unroll def "lastkana の Bean Validation のテスト(#lastkana --> #size)"() { setup: inquiryInput01Form.lastkana = lastkana Set<ConstraintViolation<InquiryInput01Form>> constraintViolations = validator.validate(inquiryInput01Form) expect: constraintViolations.size() == size where: lastkana || size "" || 3 "a" || 1 "a" * 20 || 1 "a" * 21 || 2 "あ" || 0 "あ" * 20 || 0 "あ" * 21 || 1 "ア" || 1 "A" || 1 "1" || 1 "あa" || 1 } @Unroll def "firstkana の Bean Validation のテスト(#firstkana --> #size)"() { setup: inquiryInput01Form.firstkana = firstkana Set<ConstraintViolation<InquiryInput01Form>> constraintViolations = validator.validate(inquiryInput01Form) expect: constraintViolations.size() == size where: firstkana || size "" || 3 "a" || 1 "a" * 20 || 1 "a" * 21 || 2 "あ" || 0 "あ" * 20 || 0 "あ" * 21 || 1 "ア" || 1 "A" || 1 "1" || 1 "あa" || 1 } @Unroll def "sex の Bean Validation のテスト(#sex --> #size)"() { setup: inquiryInput01Form.sex = sex Set<ConstraintViolation<InquiryInput01Form>> constraintViolations = validator.validate(inquiryInput01Form) expect: constraintViolations.size() == size where: sex || size "" || 1 "0" || 1 "1" || 0 "2" || 0 "3" || 1 } @Unroll def "age の Bean Validation のテスト(#age --> #size)"() { setup: inquiryInput01Form.age = age Set<ConstraintViolation<InquiryInput01Form>> constraintViolations = validator.validate(inquiryInput01Form) expect: constraintViolations.size() == size where: age || size "" || 2 "0" || 0 "1" || 0 "999" || 0 "1000" || 1 "0.1" || 1 } @Unroll def "job の Bean Validation のテスト(#job --> #size)"() { setup: inquiryInput01Form.job = job Set<ConstraintViolation<InquiryInput01Form>> constraintViolations = validator.validate(inquiryInput01Form) expect: constraintViolations.size() == size where: job || size "" || 0 "0" || 1 "1" || 0 "2" || 0 "3" || 0 "4" || 1 } }
テストを実行して全て成功することを確認します。
ksbysample-webapp-lending から TestHelper, HtmlResultMatchers クラスをコピーする
テストに使用したいので、ksbysample-webapp-lending から TestHelper, HtmlResultMatchers クラスをコピーします。
src/test/java の下に ksbysample.common.test.helper パッケージを作成します。
src/test/java/ksbysample/common/test/helper の下に TestHelper.java をコピーします。
src/test/java/ksbysample/common/test の下に matcher パッケージを作成します。
src/test/java/ksbysample/common/test/matcher の下に HtmlResultMatchers.java をコピーします。
HtmlResultMatchers クラスは jsoup が必要なので導入します。build.gradle の以下の点を変更します。
dependencies { .......... // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの .......... testCompile("com.google.code.findbugs:jsr305:3.0.2") testCompile("org.jsoup:jsoup:1.10.3") .......... }
testCompile("org.jsoup:jsoup:1.10.3")
を追加します。
変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。
HtmlResultMatchers クラスに val メソッドを追加する
今回のテストでは value 属性の値をチェックしたいのですが、今の HtmlResultMatchers クラスには value 属性を検証するためのメソッドが用意されていないので追加します。
src/test/java/ksbysample/common/test/matcher/HtmlResultMatchers.java の以下の点を変更します。
public class HtmlResultMatchers { .......... public ResultMatcher text(final String expectedText) { return mvcResult -> assertThat(selectFirst(mvcResult).text(), is(expectedText)); } /** * HTML 内の cssQuery で指定された Element の value 属性の値を取得し、 * 引数で渡された文字列と同じかチェックする * * @param expectedText 文字列の期待値 * @return {@link ResultMatcher} */ public ResultMatcher val(final String expectedText) { return mvcResult -> assertThat(selectFirst(mvcResult).val(), is(expectedText)); } .......... }
val
メソッドを追加します。
テストデータを用意する
src/test/resources の下に ksbysample/webapp/bootnpmgeb/web/inquiry ディレクトリを新規作成します。
src/test/resources/ksbysample/webapp/bootnpmgeb/web/inquiry の下に InquiryInput01Form_001.yaml を新規作成し、以下の内容を記述します。
!!ksbysample.webapp.bootnpmgeb.web.inquiry.form.InquiryInput01Form # 最大文字数のデータ、選択肢の場合には一番最後のデータをセットする lastname: 12345678901234567890 firstname: 12345678901234567890 lastkana: あいうえおかきくけこさしすせそたちつてと firstkana: なにぬねのはひふへほまみむめもあいうえお sex: 2 age: 999 job: 3
InquiryInputController クラスのテストを作成する
src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/InquiryInputController.java で Ctrl+Shift+T を押して「Create Test」ダイアログを表示してから、以下の画像の値にした後「OK」ボタンをクリックします。
MockMvc を使用するので Groovy + Spock ではなく Groovy + JUnit4 で作成するのですが、「Create Test」ダイアログで JUnit4 を選択すると拡張子が .java になってしまうので、Spock を選択してファイルを生成してから中身を Groovy + JUnit4 に変更します。
src/test/groovy/ksbysample/webapp/bootnpmgeb/web/inquiry/InquiryInputControllerTest.groovy が新規作成されるので、以下の内容を記述します。
package ksbysample.webapp.bootnpmgeb.web.inquiry import ksbysample.common.test.helper.TestHelper import ksbysample.webapp.bootnpmgeb.web.inquiry.form.InquiryInput01Form import org.junit.Before import org.junit.Test import org.junit.experimental.runners.Enclosed import org.junit.runner.RunWith import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.mock.web.MockHttpSession import org.springframework.test.context.junit4.SpringRunner import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.MvcResult import org.springframework.test.web.servlet.setup.MockMvcBuilders import org.springframework.web.context.WebApplicationContext import org.yaml.snakeyaml.Yaml import static ksbysample.common.test.matcher.HtmlResultMatchers.html import static org.assertj.core.api.Assertions.catchThrowable import static org.assertj.core.api.AssertionsForClassTypes.assertThat import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrlPattern import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status @RunWith(Enclosed) class InquiryInputControllerTest { @RunWith(SpringRunner) @SpringBootTest static class 入力画面1のテスト { private InquiryInput01Form inquiryInput01Form_001 = (InquiryInput01Form) new Yaml().load(getClass().getResourceAsStream("InquiryInput01Form_001.yaml")) @Autowired private WebApplicationContext context MockMvc mockMvc @Before void setup() { mockMvc = MockMvcBuilders.webAppContextSetup(context) .build() } @Test void "初期表示時は画面の項目には何もセットされない"() { expect: mockMvc.perform(get("/inquiry/input/01")) .andExpect(status().isOk()) .andExpect(html("#lastname").val("")) .andExpect(html("#firstname").val("")) .andExpect(html("#lastkana").val("")) .andExpect(html("#firstkana").val("")) .andExpect(html("input[name='sex'][checked='checked']").notExists()) .andExpect(html("#age").val("")) .andExpect(html("select[name=job] option[selected]").notExists()) } @Test void "項目全てに入力して入力画面2へ遷移してから戻ると以前入力したデータがセットされて表示される"() { expect: "項目全てに入力して「次へ」ボタンをクリックする" MvcResult result = mockMvc.perform( TestHelper.postForm("/inquiry/input/01?move=next", inquiryInput01Form_001)) .andExpect(status().isFound()) .andExpect(redirectedUrlPattern("**/inquiry/input/02")) .andReturn() MockHttpSession session = result.getRequest().getSession() and: "再び入力画面1を表示する" mockMvc.perform(get("/inquiry/input/01").session(session)) .andExpect(status().isOk()) .andExpect(html("#lastname").val(inquiryInput01Form_001.lastname)) .andExpect(html("#firstname").val(inquiryInput01Form_001.firstname)) .andExpect(html("#lastkana").val(inquiryInput01Form_001.lastkana)) .andExpect(html("#firstkana").val(inquiryInput01Form_001.firstkana)) .andExpect(html("input[name='sex'][checked='checked']").val(inquiryInput01Form_001.sex)) .andExpect(html("#age").val(inquiryInput01Form_001.age)) .andExpect(html("select[name=job] option[selected]").val(inquiryInput01Form_001.job)) } @Test void "入力チェックエラーのあるデータで入力画面2へ遷移しようとするとIllegalArgumentExceptionが発生する"() { setup: "入力チェックエラーになるデータを用意する" inquiryInput01Form_001.lastname = "x" * 21 expect: "入力画面1の「次へ」ボタンをクリックする" Throwable thrown = catchThrowable({ mockMvc.perform( TestHelper.postForm("/inquiry/input/01?move=next", inquiryInput01Form_001)) .andExpect(status().isOk()) }) assertThat(thrown.cause).isInstanceOf(IllegalArgumentException) } } }
テストを実行して全て成功することを確認します。
次回は。。。
入力画面2を作成します。
履歴
2017/09/13
初版発行。
IntelliJ IDEA を 2017.2.2 → 2017.2.3 へバージョンアップ
IntelliJ IDEA を 2017.2.2 → 2017.2.3 へバージョンアップする
IntelliJ IDEA の 2017.2.3 がリリースされたのでバージョンアップします。
- IntelliJ IDEA 2017.2.3 Release Notes
https://confluence.jetbrains.com/display/IDEADEV/IntelliJ+IDEA+2017.2.3+Release+Notes
※ksbysample-webapp-lending プロジェクトを開いた状態でバージョンアップしています。
IntelliJ IDEA のメインメニューから「Help」-「Check for Updates…」を選択します。
「IDE and Plugin Updates」ダイアログが表示されます。左下に「Update and Restart」ボタンが表示されていますので、「Update and Restart」ボタンをクリックします。
Plugin の update も表示されました。「Error-prone Compiler Integration」はバージョンアップすると動かなくなりますので、これだけチェックを外して「Update and Restart」ボタンをクリックします。
Patch がダウンロードされて IntelliJ IDEA が再起動します。
IntelliJ IDEA が起動すると画面下部に「Indexing…」のメッセージが表示されますので、終了するまで待機します。
IntelliJ IDEA のメインメニューから「Help」-「About」を選択し、2017.2.3 へバージョンアップされていることを確認します。
Gradle Tool Window のツリーを見ると「Tasks」の下に「other」しかない状態になっているので、左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。
clean タスク実行 → Rebuild Project 実行 → build タスクを実行して、"BUILD SUCCESSFUL" のメッセージが出力されることを確認します。
Project Tool Window で src/test を選択した後、コンテキストメニューを表示して「Run ‘All Tests’ with Coverage」を選択し、テストが全て成功することを確認します。
Spring Boot + npm + Geb で入力フォームを作ってテストする ( その20 )( 入力画面1を作成する4 )
概要
記事一覧はこちらです。
Spring Boot + npm + Geb で入力フォームを作ってテストする ( その19 )( 入力画面1を作成する3 ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
- 入力画面1の作成
- サーバ側の処理を実装します。また実装後に見つかった HTML, Javascript 側の問題点も修正します。
参照したサイト・書籍
ModelMapper
http://modelmapper.org/Spring Boot ModelMapper Starter
https://github.com/rozidan/modelmapper-spring-boot-starter
目次
- Form クラスを作成する
- セッション保存用のクラスを作成する
- input01.html, input01.js を変更する
- ModdelMapper をインストールする
- 画面表示時と「次へ」ボタンクリック時の処理を実装する
- 動作確認
- HTML5 の autofocus 属性では項目にデータが入っている時に選択状態にしてくれない問題を修正する(Firefox の autokana の問題も「お名前(漢字)」の「名」が文字化けする問題もこれで解決!)
- 入力チェック前の状態ではなく入力チェックした後の状態で表示されるようにする
- autokana で自動入力すると maxlength 以上の文字列が入力される問題を修正する
- 次回は。。。
- メモ書き
手順
Form クラスを作成する
src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry の下に form パッケージを新規作成します。
src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/form の下に InquiryInput01Form.java を新規作成し、以下の内容を記述します。
package ksbysample.webapp.bootnpmgeb.web.inquiry.form; import ksbysample.webapp.bootnpmgeb.values.JobValues; import ksbysample.webapp.bootnpmgeb.values.SexValues; import ksbysample.webapp.bootnpmgeb.values.validation.ValuesEnum; import lombok.Data; import org.hibernate.validator.constraints.NotEmpty; import javax.validation.constraints.Digits; import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; import java.io.Serializable; /** * 入力画面1用 Form クラス */ @Data public class InquiryInput01Form implements Serializable { private static final long serialVersionUID = -7017712118767300185L; @NotEmpty @Size(min = 1, max = 20) private String lastname; @NotEmpty @Size(min = 1, max = 20) private String firstname; @NotEmpty @Size(min = 1, max = 20) @Pattern(regexp = "^[\\u3041-\\u3096]+$") private String lastkana; @NotEmpty @Size(min = 1, max = 20) @Pattern(regexp = "^[\\u3041-\\u3096]+$") private String firstkana; @ValuesEnum(enumClass = SexValues.class) private String sex; @NotEmpty @Digits(integer = 3, fraction = 0) private String age; @ValuesEnum(enumClass = JobValues.class, allowEmpty = true) private String job; }
セッション保存用のクラスを作成する
src/main/java/ksbysample/webapp/bootnpmgeb の下に session パッケージを新規作成します。
src/main/java/ksbysample/webapp/bootnpmgeb/session の下に SessionData.java を新規作成し、以下の内容を記述します。
package ksbysample.webapp.bootnpmgeb.session; import ksbysample.webapp.bootnpmgeb.web.inquiry.form.InquiryInput01Form; import lombok.Data; import java.io.Serializable; /** * セッションデータ用クラス */ @Data public class SessionData implements Serializable { private static final long serialVersionUID = -2673191456750655164L; private InquiryInput01Form inquiryInput01Form; }
input01.html, input01.js を変更する
src/main/resources/templates/web/inquiry/input01.html の以下の点を変更します。
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head th:replace="~{web/common/fragments :: common_header(~{::title}, ~{::link}, ~{::style})}"> <title>入力フォーム - 入力画面1</title> </head> <body class="skin-blue layout-top-nav"> <div class="wrapper"> <!-- Content Wrapper. Contains page content --> <div class="content-wrapper"> <!-- Content Header (Page header) --> <section class="content-header"> <h1> 入力画面1 </h1> </section> <!-- Main content --> <section class="content"> <div class="row"> <div class="col-xs-12"> <!--/*@thymesVar id="inquiryInput01Form" type="ksbysample.webapp.bootnpmgeb.web.inquiry.form.InquiryInput01Form"*/--> <form id="inquiryInput01Form" class="form-horizontal" method="post" action="" th:action="@{/inquiry/input/01/}" th:object="${inquiryInput01Form}"> <!-- お名前(漢字) --> <div class="form-group" id="form-group-name"> <div class="control-label col-sm-2"> <label class="float-label">お名前(漢字)</label> <div class="label label-required">必須</div> </div> <div class="col-sm-10"> <div class="row"> <div class="col-sm-10"> <input type="text" name="lastname" id="lastname" class="form-control form-control-inline" style="width: 150px;" maxlength="20" value="" placeholder="例)田中" autofocus th:field="*{lastname}"/> <input type="text" name="firstname" id="firstname" class="form-control form-control-inline" style="width: 150px;" maxlength="20" value="" placeholder="例)太郎" th:field="*{firstname}"/> </div> </div> <div class="row hidden js-errmsg"> <div class="col-sm-10"> <p class="form-control-static text-danger"> <small>ここにエラーメッセージを表示します</small> </p> </div> </div> </div> </div> <!-- お名前(かな) --> <div class="form-group" id="form-group-kana"> <div class="control-label col-sm-2"> <label class="float-label">お名前(かな)</label> <div class="label label-required">必須</div> </div> <div class="col-sm-10"> <div class="row"> <div class="col-sm-10"> <input type="text" name="lastkana" id="lastkana" class="form-control form-control-inline" style="width: 150px;" maxlength="20" value="" placeholder="例)たなか" th:field="*{lastkana}"/> <input type="text" name="firstkana" id="firstkana" class="form-control form-control-inline" style="width: 150px;" maxlength="20" value="" placeholder="例)たろう" th:field="*{firstkana}"/> </div> </div> <div class="row hidden js-errmsg"> <div class="col-sm-10"> <p class="form-control-static text-danger"> <small>ここにエラーメッセージを表示します</small> </p> </div> </div> </div> </div> <!-- 性別 --> <div class="form-group" id="form-group-sex"> <div class="control-label col-sm-2"> <label class="float-label">性別</label> <div class="label label-required">必須</div> </div> <div class="col-sm-10"> <div class="row"> <div class="col-sm-10"> <div class="radio-inline" th:each="sexValue : ${@vh.values('SexValues')}"> <label><input type="radio" name="sex" th:value="${sexValue.value}" th:text="${sexValue.text}" th:field="*{sex}"></label> </div> </div> </div> <div class="row hidden js-errmsg"> <div class="col-sm-10"> <p class="form-control-static text-danger"> <small>ここにエラーメッセージを表示します</small> </p> </div> </div> </div> </div> <!-- 年齢 --> <div class="form-group" id="form-group-age"> <div class="control-label col-sm-2"> <label class="float-label">年齢</label> <div class="label label-required">必須</div> </div> <div class="col-sm-10"> <div class="row"> <div class="col-sm-10"> <input type="text" name="age" id="age" class="form-control form-control-inline" style="width: 150px;" maxlength="3" value="" placeholder="例)30" th:field="*{age}"/> <p class="form-control-static-inline">歳</p> </div> </div> <div class="row hidden js-errmsg"> <div class="col-sm-10"> <p class="form-control-static text-danger"> <small>ここにエラーメッセージを表示します</small> </p> </div> </div> </div> </div> <!-- 職業 --> <div class="form-group" id="form-group-job"> <div class="control-label col-sm-2"> <label class="float-label">職業</label> </div> <div class="col-sm-10"> <div class="row"> <div class="col-sm-10"> <select name="job" id="job" class="form-control" style="width: 250px;"> <th:block th:each="jobValue,iterStat : ${@vh.values('JobValues')}"> <option th:if="${iterStat.first}" value="">選択してください</option> <option th:value="${jobValue.value}" th:text="${jobValue.text}" th:field="*{job}">会社員</option> </th:block> </select> </div> </div> <div class="row hidden js-errmsg"> <div class="col-sm-10"> <p class="form-control-static text-danger"> <small>ここにエラーメッセージを表示します</small> </p> </div> </div> </div> </div> <div class="text-center"> <button class="btn bg-green js-btn-next"><i class="fa fa-arrow-right"></i> 次へ</button> </div> </form> </div> </div> </section> <!-- /.content --> </div> <!-- /.content-wrapper --> </div> <!-- ./wrapper --> <!-- REQUIRED JS SCRIPTS --> <script src="/js/inquiry/input01.js"></script> </body> </html>
- form タグの上に
<!--/*@thymesVar id="inquiryInput01Form" type="ksbysample.webapp.bootnpmgeb.web.inquiry.form.InquiryInput01Form"*/-->
を追加します。 <form id="input01Form" ...
→<form id="inquiryInput01Form" ...
に変更します。- form タグの末尾に
th:object="${inquiryInput01Form}"
を追加します。 - 入力/選択項目のタグに
th:field="*{...}"
(… には入力項目に対応した変数を記述) を追加します。
form タグの id 属性を変更したので src/main/assets/js/inquiry/input01.js の以下の点を変更します。
var btnNextClickHandler = function (event) { .......... // サーバにリクエストを送信する $("#inquiryInput01Form").attr("action", "/inquiry/input/01/?move=next"); $("#inquiryInput01Form").submit(); .......... };
$("#input01Form")
→$("#inquiryInput01Form")
に変更します。
ModdelMapper をインストールする
今回 POJO 間のデータコピーには org.springframework.beans.BeanUtils
クラスではなく ModelMapper を使用してみます。インストールは ModelMapper を直接インストールするのではなく、Spring Boot の Auto Configuraion を見つけましたので Spring Boot ModelMapper Starter を使用してインストールします。
build.gradle の以下の点を変更します。
dependencies { .......... // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの compile("com.integralblue:log4jdbc-spring-boot-starter:1.0.1") compile("org.flywaydb:flyway-core:4.2.0") compile("com.h2database:h2:1.4.192") compile("com.github.rozidan:modelmapper-spring-boot-starter:1.0.0") testCompile("org.dbunit:dbunit:2.5.3") testCompile("com.icegreen:greenmail:1.5.5") testCompile("org.assertj:assertj-core:3.8.0") testCompile("org.spockframework:spock-core:${spockVersion}") testCompile("org.spockframework:spock-spring:${spockVersion}") testCompile("com.google.code.findbugs:jsr305:3.0.2") .......... }
compile("com.github.rozidan:modelmapper-spring-boot-starter:1.0.0")
を追加します。
変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。
画面表示時と「次へ」ボタンクリック時の処理を実装する
src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/InquiryInputController.java の以下の点を変更します。
@Controller @RequestMapping("/inquiry/input") @SessionAttributes("sessionData") public class InquiryInputController { .......... private final ModelMapper modelMapper; /** * コンストラクタ * * @param modelMapper {@link ModelMapper} オブジェクト */ public InquiryInputController(ModelMapper modelMapper) { this.modelMapper = modelMapper; } /** * 入力画面1 初期表示処理 * * @return 入力画面1の Thymeleaf テンプレートファイルのパス */ @GetMapping("/01") public String input01(InquiryInput01Form inquiryInput01Form , SessionData sessionData) { // セッションに保存されているデータがある場合にはコピーする if (sessionData.getInquiryInput01Form() != null) { modelMapper.map(sessionData.getInquiryInput01Form(), inquiryInput01Form); } return TEMPLATE_INPUT01; } /** * 入力画面1 「次へ」ボタンクリック時の処理 * * @return 入力画面2の URL */ @PostMapping(value = "/01", params = {"move=next"}) public String input01MoveNext(@Validated InquiryInput01Form inquiryInput01Form , BindingResult bindingResult , SessionData sessionData , UriComponentsBuilder builder) { if (bindingResult.hasErrors()) { throw new IllegalArgumentException("セットされるはずのないデータがセットされています"); } // 入力されたデータをセッションに保存する sessionData.setInquiryInput01Form(inquiryInput01Form); return UrlBasedViewResolver.REDIRECT_URL_PREFIX + builder.path(UrlConst.URL_INQUIRY_INPUT_02).toUriString(); }
@SessionAttributes("sessionData")
を追加します。private final ModelMapper modelMapper;
とコンストラクタを追加します。- input01 メソッドに上記の引数を追加し、セッションに保存されているデータがある場合にはコピーする処理を追加します。
- input01MoveNext メソッドに上記の引数を追加し、入力チェック処理と入力されたデータをセッションに保存する処理を追加します。
動作確認
動作確認します。npm run springboot
コマンドを実行し Tomcat を起動した後、ブラウザで http://localhost:9080/inquiry/input/01/ にアクセスします。
データを入力してから、
「次へ」ボタンをクリックして入力画面2へ遷移します。
「前の画面へ戻る」ボタンをクリックして入力画面1へ戻ると、
入力画面1へ戻りデータが表示されるのですが、以下の問題がありました。
- 「お名前(漢字)」の「姓」にカーソルが当たるのですが選択状態で表示されません。
- 入力チェックした後の状態ではなく入力チェック前の状態で表示されています。
- なぜか「お名前(漢字)」の「名」だけ文字化けしています。何度か試したところ、この現象は IE だけ出て Firefox と Chrome では出ず、IE でも最初から出る時と最初は出ずに入力画面2と何度か画面遷移させると出る時がありました。謎です。。。
また上記以外にもいろいろ試していて、以下の問題にも気づきました。
- Firefox だと一番最初の「お名前(漢字)」の「姓」から1度カーソルを移動しないと autokana が機能しません。
- 「お名前(漢字)」にかなを20文字入力すると「お名前(かな)」にもかなが20文字入力されるのですが、その後に「お名前(漢字)」にカーソルを移動して末尾の1文字を削除してから1文字追加すると「お名前(かな)」の maxlength は 20 で設定しているのに21文字入力されてしまいます。
あと、「お名前(漢字)」に何も入力せずに「お名前(かな)」を先に入力して、その後に「お名前(漢字)」にカーソルを移動すると「お名前(かな)」に入力していたデータが消えるのですが、これは autokana を入れている場合には正常な動作だと思うのでこのままにします。
HTML5 の autofocus 属性では項目にデータが入っている時に選択状態にしてくれない問題を修正する(Firefox の autokana の問題も「お名前(漢字)」の「名」が文字化けする問題もこれで解決!)
HTML5 の autofocus はフォーカスを移動してくれるだけで、選択状態にはしてくれないようです。Firefox や Chrome でもダメでした。
画面初期表示時に Javascript でフォーカスをセット&選択状態にするようにします。
src/main/assets/js/inquiry/input01.js の以下の点を変更します。
$(document).ready(function () { .......... // 「お名前(漢字)」の「姓」にフォーカスをセットする $("#lastname").focus().select(); });
$("#lastname").focus().select();
を追加します。
src/main/resources/templates/web/inquiry/input01.html の以下の点を変更します。
<input type="text" name="lastname" id="lastname" class="form-control form-control-inline" style="width: 150px;" maxlength="20" value="" placeholder="例)田中" th:field="*{lastname}"/>
<input type="text" name="lastname" ...
からautofocus
を削除します。
動作確認すると今度は入力画面2から入力画面1に戻った時に選択状態になり、ついでに Firefox でカーソルを移動しないと autokana が機能しない問題も解決していました。
さらになぜか「お名前(漢字)」の「名」だけ文字化けする問題も解決していました!(入力画面2と何度か画面遷移させても全く現象が出ませんでした) 最初 HTML5 の autofocus を見た時はこんな便利なものがあるのか、と思ったものでしたが、使えませんでした。残念です。。。
入力チェック前の状態ではなく入力チェックした後の状態で表示されるようにする
セッションにデータが保存されていた場合には、画面表示時に入力チェックが実行されるようにします。
src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/form/InquiryInput01Form.java の以下の点を変更します。
@Data public class InquiryInput01Form implements Serializable { .......... private boolean copiedFromSession = false; }
private boolean copiedFromSession = false;
を追加します。セッションからデータをコピーした時に true にします。
src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/InquiryInputController.java の以下の点を変更します。
@GetMapping("/01") public String input01(InquiryInput01Form inquiryInput01Form , SessionData sessionData) { // セッションに保存されているデータがある場合にはコピーする if (sessionData.getInquiryInput01Form() != null) { modelMapper.map(sessionData.getInquiryInput01Form(), inquiryInput01Form); inquiryInput01Form.setCopiedFromSession(true); } return TEMPLATE_INPUT01; }
input01
メソッド内にinquiryInput01Form.setCopiedFromSession(true);
を追加します。
src/main/resources/templates/web/inquiry/input01.html の以下の点を変更します。
<form id="inquiryInput01Form" class="form-horizontal" method="post" action="" th:action="@{/inquiry/input/01/}" th:object="${inquiryInput01Form}"> <input type="hidden" name="copiedFromSession" id="copiedFromSession" th:value="*{copiedFromSession}" />
- form タグのすぐ下に
<input type="hidden" name="copiedFromSession" id="copiedFromSession" th:value="*{copiedFromSession}" />
を追加します。
src/main/assets/js/inquiry/input01.js の以下の点を変更します。
var executeAllValidator = function (event) { form.forceAllFocused(form); [ nameValidator, kanaValidator, sexValidator, ageValidator, jobValidator ].forEach(function (validator) { validator(event); }); }; var btnNextClickHandler = function (event) { // 全ての入力チェックを実行する executeAllValidator(event); // 入力チェックエラーがある場合には処理を中断する if (event.isPropagationStopped()) { // 一番最初のエラーの項目にカーソルを移動する $(".has-error:first :input:first").focus().select(); return false; } // 「次へ」ボタンをクリック不可にする $(".js-btn-next").prop("disabled", true); // サーバにリクエストを送信する $("#inquiryInput01Form").attr("action", "/inquiry/input/01/?move=next"); $("#inquiryInput01Form").submit(); // return false は // event.preventDefault() + event.stopPropagation() らしい return false; }; $(document).ready(function (event) { // 「お名前(漢字)」が入力された時に、かな文字列を「お名前(かな)」に自動入力されるようにする $.fn.autoKana('#lastname', '#lastkana'); $.fn.autoKana('#firstname', '#firstkana'); // 入力チェック用の validator 関数をセットする $("#lastname") .on("blur", nameValidator) .on("blur", kanaAutoInputValidator); $("#firstname") .on("blur", nameValidator) .on("blur", kanaAutoInputValidator); $("#lastkana").on("blur", kanaValidator); $("#firstkana").on("blur", kanaValidator); $("input:radio[name='sex']").on("blur", sexValidator); $("#age").on("blur", ageValidator); $("#job").on("blur", jobValidator); // 「次へ」ボタンクリック時の処理をセットする $(".js-btn-next").on("click", btnNextClickHandler) // 初期画面表示時にセッションに保存されていたデータを表示する場合には // 入力チェックを実行して画面の表示を入力チェック後の状態にする if ($("#copiedFromSession").val() === "true") { executeAllValidator(event); } // 「お名前(漢字)」の「姓」にフォーカスをセットする $("#lastname").focus().select(); });
var executeAllValidator = function (event) { ... }
を追加します。btnNextClickHandler
関数内で全ての入力チェックを実行していた処理を executeAllValidator に置き換えます。$(document).ready(function () {
→$(document).ready(function (event) {
に変更します。$(document).ready(function (event) { ... }
内にif ($("#copiedFromSession").val() === "true") { ... }
を追加し、#copiedFromSession
の値が"true"
の場合には全ての入力チェックを実行します。
動作確認します。npm run springboot
コマンドを停止→実行し Tomcat を再起動した後、http://localhost:9080/inquiry/input/01/ にアクセスしてデータを入力します。
「次へ」ボタンをクリックして入力画面2へ遷移し、
「前の画面へ戻る」ボタンをクリックすると全ての項目が緑で表示されました。
autokana で自動入力すると maxlength 以上の文字列が入力される問題を修正する
keyup イベント発生時に maxlength の文字数を超えていたら超えた分を削除するようにします。
src/main/assets/js/inquiry/input01.js を以下のように変更します。
function delStringExceedingMaxlength(id) { $(id).val($(id).val().substring(0, $(id).attr("maxlength"))); } $(document).ready(function (event) { // 「お名前(漢字)」が入力された時に、かな文字列を「お名前(かな)」に自動入力されるようにする $.fn.autoKana('#lastname', '#lastkana'); $.fn.autoKana('#firstname', '#firstkana'); // autokana で自動入力されると maxlength の文字数を超える文字が自動入力される場合があるので // maxlegnth の文字数を超えた分を削除する $("#lastname").on("keyup", function (event) { delStringExceedingMaxlength("#lastkana"); }); $("#firstname").on("keyup", function (event) { delStringExceedingMaxlength("#firstkana"); }); // 入力チェック用の validator 関数をセットする ..........
function delStringExceedingMaxlength(id) { ... }
を追加します。$(document).ready(function (event) { ... }
内に以下の2つの処理を追加します。$("#lastname").on("keyup", function (event) { ... }
$("#firstname").on("keyup", function (event) { ... }
動作確認します。「お名前(漢字)」に あいうえおかきくけこさしすせそたちつてと
を入力すると「お名前(かな)」にも あいうえおかきくけこさしすせそたちつてと
が入力されて、
「お名前(漢字)」で末尾の と
を削除して な
を入力しても「お名前(かな)」は あいうえおかきくけこさしすせそたちつてと
のままでした。
次回は。。。
サーバ側のテストを書きます。入力画面1の実装は次で最後の予定です。
メモ書き
IntelliJ IDEA の正規表現チェック機能を使ってみる
今回 src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/form/InquiryInput01Form.java に以下の画像の正規表現(ひらがなだけかチェックする)を記述しましたが、
正規表現のところにカーソル移動してから Alt+Enter を押してコンテキストメニューを表示した後、「Check RegExp」を選択すると、
入力された文字が正規表現と一致するのか否かを検証できるダイアログが表示されます。
あいうえお
と入力すると右下に Matches!
と表示されて入力フィールドも緑色になりますが、
アイウエオ
に変えると No match
と表示されて赤色に変わります。
確か機能が追加されたのは 2017.2 ではなくもう少し前のはずですが、変更内容をきちんと見ていませんでした。正規表現の動作確認をしたい時はよくあるので、これは嬉しい機能です。
履歴
2017/09/07
初版発行。
Spring Boot + npm + Geb で入力フォームを作ってテストする ( その19 )( 入力画面1を作成する3 )
概要
記事一覧はこちらです。
Spring Boot + npm + Geb で入力フォームを作ってテストする ( その18 )( 入力画面1を作成する2 ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
- 入力画面1の作成
- 前回からの続きで、今回も Javascript の処理を実装します。
参照したサイト・書籍
【JQUERY】自動的にカナ入力してくれるAUTOKANA.JSプラグインはいまや必須!
http://plugmin.co/472/How to use jQuery-Plugins w/Webpack?
https://laracasts.com/discuss/channels/elixir/how-to-use-jquery-plugins-wwebpackWebpackを使い倒す
http://thujikun.github.io/blog/2014/12/07/webpack/ProvidePlugin
https://webpack.js.org/plugins/provide-plugin/
目次
- autokana で「お名前(漢字)」が入力されたら「お名前(かな)」を自動入力させる
- ProvidePlugin を利用して jQuery を各ファイルで require 不要にする
- 「お名前(漢字)」「お名前(かな)」をどちらも入力チェックエラーにしてから、「お名前(漢字)」を入力して「お名前(かな)」に autokana で自動入力されても「お名前(かな)」の入力チェックエラー状態が解除されないのを修正する
- メモ書き
手順
autokana で「お名前(漢字)」が入力されたら「お名前(かな)」を自動入力させる
日本語入力に対するふりがな/フリガナを自動的に別フィールドに入力する autokana という jQueryプラグインがあることを知りましたので、インストールして「お名前(漢字)」に入力された文字のかな文字列を「お名前(かな)」に自動入力されるようにしてみます。
autokana は npm ではインストールできませんので、autokana の GitHub のページから jquery.autoKana.js をダウンロードします。
src/main/assets/js/lib の下に vendor/autokana ディレクトリを新規作成した後、その下に jquery.autoKana.js を配置します。
src/main/assets/js/inquiry/input01.js の以下の点を変更します。
window.$ = window.jQuery = require("admin-lte/plugins/jQuery/jquery-2.2.3.min.js"); var Form = require("lib/class/Form.js"); var converter = require("lib/util/converter.js"); var validator = require("lib/util/validator.js"); require("vendor/autokana/jquery.autoKana.js"); .......... $(document).ready(function () { // 「お名前(漢字)」が入力された時に、かな文字列を「お名前(かな)」に自動入力されるようにする $.fn.autoKana('#lastname', '#lastkana'); $.fn.autoKana('#firstname', '#firstkana'); // 入力チェック用の validator 関数をセットする $("#lastname").on("blur", nameValidator); $("#firstname").on("blur", nameValidator); $("#lastkana").on("blur", kanaValidator); $("#firstkana").on("blur", kanaValidator); $("input:radio[name='sex']").on("blur", sexValidator); $("#age").on("blur", ageValidator); $("#job").on("blur", jobValidator); // 「次へ」ボタンクリック時の処理をセットする $(".js-btn-next").on("click", btnNextClickHandler) });
- jQuery を require する変数を
var $ =
→window.$ = window.jQuery =
に変更します。 require("vendor/autokana/jquery.autoKana.js");
を追加します。$(document).ready(function () { ... }
の中に以下の2行を追加します。$.fn.autoKana('#lastname', '#lastkana');
$.fn.autoKana('#firstname', '#firstkana');
動作確認します。npm run springboot
コマンドを実行し、Tomcat を起動した後、http://localhost:9080/inquiry/input/01/ にアクセスします。
「お名前(漢字)」に入力すると「お名前(かな)」にかな文字列が入力されました。これは便利ですね。
ProvidePlugin を利用して jQuery を各ファイルで require 不要にする
jQuery を利用する時には各 js ファイルで var $ = require("admin-lte/plugins/jQuery/jquery-2.2.3.min.js");
を記述していましたが、ProvidePlugin を利用すると省略できるそうなので設定してみます。
webpack.config.js を以下のように変更します。
var webpack = require('webpack'); module.exports = { entry: { "js/app": ["./src/main/assets/js/app.js"], "js/inquiry/input01": ["./src/main/assets/js/inquiry/input01.js"], "js/inquiry/input02": ["./src/main/assets/js/inquiry/input02.js"], "js/inquiry/input03": ["./src/main/assets/js/inquiry/input03.js"], "js/inquiry/confirm": ["./src/main/assets/js/inquiry/confirm.js"] }, output: { path: __dirname + "/src/main/resources/static", publicPath: "/", filename: "[name].js" }, resolve: { modules: [ "node_modules", "src/main/assets/js" ] }, plugins: [ new webpack.ProvidePlugin({ $: "admin-lte/plugins/jQuery/jquery-2.2.3.min.js", jQuery: "admin-lte/plugins/jQuery/jquery-2.2.3.min.js" }) ] };
var webpack = require('webpack');
を追加します。plugins: [ new webpack.ProvidePlugin({ ... }) ]
を追加します。
src/main/assets/js の下の全ての js ファイルから require("admin-lte/plugins/jQuery/jquery-2.2.3.min.js");
の行を削除します。
npm run springboot
コマンドを停止して起動し直すとエラーが出ずに起動し、入力チェックの処理や autokana も問題なく動作しました。
「お名前(漢字)」「お名前(かな)」をどちらも入力チェックエラーにしてから、「お名前(漢字)」を入力して「お名前(かな)」に autokana で自動入力されても「お名前(かな)」の入力チェックエラー状態が解除されないのを修正する
autokana で「お名前(かな)」に自動入力されるようにしたのですが、今の実装だと「お名前(漢字)」「お名前(かな)」をどちらも入力チェックエラーにしてから、
「お名前(漢字)」にカーソルを移動して漢字を入力し「お名前(かな)」にかな文字列が反映されても、「お名前(漢字)」の「名」から「お名前(かな)」の「姓」にカーソルが移動したタイミングでは「お名前(かな)」の入力チェックエラー状態が解除されません。
解除されるようにしてみます。
src/main/assets/js/lib/class/Form.js の以下の点を変更します。
.......... /** * 渡された id を form.focused にセットして * focused イベントが発生したことにする * @param {Form} form - Form オブジェクト * @param {string} id - focused イベントを発生したことにする要素の id */ Form.prototype.setFocused = function (form, id) { form.focused[id] = true; }; /** * 渡された idList にセットされている id を form.focused にセットして * focused イベントが発生したことにする * @param {Form} form - Form オブジェクト * @param {Array} idList - focused イベントが発生したことにする要素の id の配列 */ Form.prototype.setFocusedFromList = function (form, idList) { idList.forEach(function (id) { form.setFocused(form, id); }) }; /** * form.idList にセットされている id を全て form.focused にセットして * focused イベントが発生したことにする * @param {Form} form - Form オブジェクト */ Form.prototype.forceAllFocused = function (form) { form.setFocusedFromList(form, form.idList); }; /** * Form オブジェクトの idList に列挙された id の要素の focus イベントハンドラに * Form.focused 配列に id をセットする関数をセットする * @param {Form} form - Form オブジェクト */ function addFocusEventListener(form) { form.idList.forEach(function (id) { $(id).on("focus", function (event) { form.setFocused(form, id); }) }) }
Form.prototype.setFocused
メソッドを追加します。Form.prototype.setFocusedFromList
メソッドを追加します。Form.prototype.forceAllFocused
メソッド内の処理をform.idList.forEach(function (id) { ... }
→form.setFocusedFromList(form, form.idList);
に変更します。addFocusEventListener
関数内の処理をform.focused[id] = true;
→form.setFocused(form, id);
に変更します。
src/main/assets/js/inquiry/input01.js の以下の点を変更します。
var kanaAutoInputValidator = function (event) { var idList = ["#lastkana", "#firstkana"]; // 「お名前(漢字)」を入力して「お名前(かな)」が自動入力された時に、 // 「お名前(かな)」の入力チェックが実行されるようにする if (!form.isAnyEmpty(idList)) { form.setFocusedFromList(form, idList); kanaValidator(event); } }; .......... $(document).ready(function () { // 「お名前(漢字)」が入力された時に、かな文字列を「お名前(かな)」に自動入力されるようにする $.fn.autoKana('#lastname', '#lastkana'); $.fn.autoKana('#firstname', '#firstkana'); // 入力チェック用の validator 関数をセットする $("#lastname") .on("blur", nameValidator) .on("blur", kanaAutoInputValidator); $("#firstname") .on("blur", nameValidator) .on("blur", kanaAutoInputValidator); $("#lastkana").on("blur", kanaValidator); $("#firstkana").on("blur", kanaValidator); $("input:radio[name='sex']").on("blur", sexValidator); $("#age").on("blur", ageValidator); $("#job").on("blur", jobValidator); // 「次へ」ボタンクリック時の処理をセットする $(".js-btn-next").on("click", btnNextClickHandler) });
- kanaAutoInputValidator メソッドを追加します。
$(document).ready(function () { ... }
内の以下の点を変更します。$("#lastname")
と$("#firstname")
に.on("blur", kanaAutoInputValidator)
を追加します。
動作確認します。
http://localhost:9080/inquiry/input/01/ にアクセスして「お名前(漢字)」「お名前(かな)」を入力チェックエラー状態にした後、
マウスで「お名前(漢字)」の「姓」にカーソルを移動してから「姓」「名」を入力すると「お名前(かな)」にかな文字列が自動入力されて、かつ「お名前(漢字)」の「名」から「お名前(かな)」の「姓」にカーソルが移動した時に「お名前(かな)」も入力チェック OK の状態になります。
Ctrl+F5 を押してリロードした後、普通に「お名前(漢字)」を入力しても「お名前(かな)」が入力チェック OK の状態になります。
Ctrl+F5 を押してリロードした後、「お名前(漢字)」にかな文字列にできない英字を入力した場合には「お名前(かな)」の入力チェックは動作せず何も変わりません。
Ctrl+F5 を押してリロードした後、「お名前(漢字)」の「姓」だけ入力した場合には「お名前(漢字)」は入力チェックエラー状態になりますが「お名前(かな)」の入力チェックは動作せず何も変わりません。
問題なさそうです。次回は Form クラスを作成してセッションに保存する処理を実装します。
メモ書き
input01.js で関数を var xxx = function() { ... }
で記述しているのは IntelliJ IDEA が見やすく表示してくれるからである
最初は function xxx() { ... }
の形式で書いていたのですが、var xxx = function() { ... }
の形式で書くと IntelliJ IDEA が関数名毎に色を変えて表示してくれるので、input01.js では var xxx = function() { ... }
の形式で書くことにしました。
function xxx() { ... }
の形式で書くと IntelliJ IDEA 上では以下のように関数名は全て同じ色で表示されますが、
var xxx = function() { ... }
の形式で書くと IntelliJ IDEA の Semantic highlighting の機能で関数名毎に色を変えて表示してくれます(変数名とみなされているため)。
ただし var xxx = function() { ... }
は上に書いておかないと下の関数では使用できないようなので、その点だけ注意が必要です。
履歴
2017/09/02
初版発行。