かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その23 )( 入力画面2を作成する2 )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その22 )( 入力画面2を作成する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 入力画面2の作成
    • 前回に引き続き Javascript の処理を実装します。以下の処理を実装します。
      • メールアドレスチェック
      • 「前の画面へ戻る」ボタンがクリックされた時は入力されているものだけ入力チェックする
    • Form.js のメソッド内の処理で Array.reduce に変更可能なものは変更しようと思いましたが、forEach 内で if 文で分岐して長めの処理にしていると Array.reduce に変更するメリットがなさそう(ソースが見にくくなりそう)に思えたので、止めます。

参照したサイト・書籍

  1. 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

目次

  1. メールアドレスチェックを実装する
  2. 「前の画面へ戻る」ボタンがクリックされた時は入力されているものだけ入力チェックする
  3. 次回は。。。

手順

メールアドレスチェックを実装する

正しいメールアドレスのチェック方法 の記事を参考に、以下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となり、

f:id:ksby:20170927013745p:plain

@ が2つあったり、スペースがあるとエラーになります。

f:id:ksby:20170927014004p:plain f:id:ksby:20170927014103p:plain f:id:ksby:20170927014204p:plain

電話番号を入力してメールアドレスが未入力の時は、チェックOKになります。

f:id:ksby:20170927014429p:plain

「前の画面へ戻る」ボタンがクリックされた時は入力されているものだけ入力チェックする

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に入力して「次へ」ボタンをクリックします。

f:id:ksby:20170927201508p:plain

入力画面2が表示されます。

f:id:ksby:20170927201732p:plain

何も入力せずに「次へ」ボタンをクリックすると、必須チェックが実行されてエラーメッセージが表示されます。

f:id:ksby:20170927201900p:plain

「前の画面へ戻る」ボタンをクリックすると、必須チェックは実行されず入力画面1へ戻ります。

f:id:ksby:20170927202300p:plain

「次へ」ボタンを押して入力画面2に遷移した後、今度は「電話番号」の一番左側の項目に 3 とだけ入力してから「前の画面へ戻る」ボタンをクリックすると、入力チェックエラーが発生して入力画面1には戻りません。

f:id:ksby:20170928035031p:plain

3 を消して「前の画面へ戻る」ボタンをクリックすると、入力画面1へ戻ります。。。と思いましたが、戻らずに「電話番号とメールアドレスのいずれか一方を入力してください」のエラーメッセージが表示されました?

f:id:ksby:20170928034935p:plain

調べてみると「前の画面へ戻る」ボタンをクリックした時の処理は実行されておらず、フォーカスが外れたことで入力チェックが実行されていたためでした。「前の画面へ戻る」ボタンを押した時に全ての項目にフォーカスがセットされたことにしているので、入力チェックが実行されてしまうのが原因でした。

「前の画面へ戻る」ボタンが押された時は入力チェックの時だけフォーカスがセットされたことにして、チェック終了後に元に戻すことにします。

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 を入力して「前の画面へ戻る」ボタンをクリックするとエラーメッセージが表示されますが、

f:id:ksby:20170928040314p:plain

電話番号をクリアして「前の画面へ戻る」ボタンをクリックすると入力画面1へ戻るようになりました。

f:id:ksby:20170928040540p:plain

次回は。。。

サーバ側の処理を実装します。

履歴

2017/09/28
初版発行。