【もりけん塾】JS課題29 – Vanilla JSでメールアドレス変更機能を作成 –

JavaScript

ログイン中の画面で、登録済みのメールアドレスを変更できる機能を作成しました。

こんにちは。Webコーダーのはるです。

現在、所属している「もりけん塾」でJavaScriptのハンズオン課題 に取り組み、レビューをいただいています。

今日は、課題29 についてアウトプットをしていきます。
(前回までのアウトプットは、こちらです。)

実装する過程で学んだことを、学習ノートとして記録していきます。

認識の間違えている箇所がありましたら、お問い合わせからご指摘いただけるとうれしいです。

JavaScript課題29の仕様

    (もりけんさんのJavaScriptハンズオン課題より)

    JavaScript課題29の学習記録

    主にレビューいただいた箇所の復習として記録を残します。

    HTML

    メールアドレス変更ページのform部分です。

    // 新規メールアドレス入力input
    <div class="form__item">
      <label for="email">新規E-mailアドレス入力<span class="form__required">必須</span></label>
      <input type="email" class="form__item-input js-form-email" id="email" name="email" autocomplete="off" aria-describedby="email-constraints" tabindex="1" required>
      <p class="error" id="email-constraints"></p>
    </div>
    レビュー①
    新規E-mailアドレス入力のinputには、オートコンプリート機能がない方が使いやすいと教えていただきました。<input>に autocomplete=”off”をつけました。

     

    レビュー②
    アクセシビリティ向上のため、<input>と入力ルール(今回はエラー文として表示される)を紐づけるべきと教えていただきました。describedby属性とid属性で紐付けます。
    label → inputの入力タイプ → 紐付けた説明の順で読み上げられます。
    以下コード
    <input type="email" aria-describedby="password-constraints">
    <p id="password-constraints"></p> // エラー文表示部分

    バリデーション

    以下の3つのinput要素を配列に入れます。

    ① 新規メールアドレス
    ② 確認用新規メールアドレス
    ③ 登録済みパスワード

    const emailOfInput = document.querySelector(".js-form-email");
    const confirmEmailOfInput = document.querySelector(".js-form-confirm-email");
    const passwordOfInput = document.querySelector(".js-form-password");
    const formElements = [emailOfInput, confirmEmailOfInput, passwordOfInput];
    配列formElementsに対して、forEachでそれぞれの要素にバリデーションを行います。
    このように書きました。
    formElements.forEach(element => {
      element.classList.add("invalid");
    
      element.addEventListener("blur", (e) => {
        if(e.relatedTarget === eyeIcon) {
          if(passwordOfInput.value && errorOfPassword.textContent === "入力してください") errorOfPassword.textContent = "";
          return;
        }
    
        checkFormValidityInBlur(submitButton, element);
    
        if(emailOfInput.value && confirmEmailOfInput.value) errorOfConfirmEmail.textContent = isMatchValue(emailOfInput, confirmEmailOfInput) ? "" : "上記のE-mailアドレスと異なります。もう一度入力してください。";
        if(hasInvalidClass()) return;
    
        confirmIfCanSubmit();
      });
    });

    レビューコメントを記録しながら、分解します。

    ① 全てにinvalidクラスを付与します。
    ※空欄チェック/形式チェックが通ると、invalidクラスが外れます。

    formElements.forEach(element => { 
      element.classList.add("invalid"); 

    ② elementがblurされたらバリデーションを空欄チェック・形式チェックを走らせる。
    if(e.relatedTarget === eyeIcon) {・・・ は後ほどパスワード表示/非表示機能の部分で説明します。

    checkFormValidityInBlur(submitButton, element); 

    このコードは、modulesで切り出したvalidationコードをimportしています。
    今までも何度も記録で出てきているので割愛します。

    ③ 新メールアドレス、確認用新メールアドレスが入力されていたら、値がマッチしているか調べる。

    if(emailOfInput.value && confirmEmailOfInput.value) errorOfConfirmEmail.textContent = isMatchValue(emailOfInput, confirmEmailOfInput) ? "" : "上記のE-mailアドレスと異なります。もう一度入力してください。";
    レビュー:外側のスコープを参照しない
    isMatchValue()関数
    元々は関数内でグローバル変数を参照していました。
    const isMatchPasswordFields = () => passwordOfInput.value === confirmPasswordOfInput.value;
    関数内で外側のスコープを参照すべきではないと教わりました。

    関数を使用する側で、引数としてグローバル変数を渡すよう修正しました。

    const isMatchValue = (input, confirmInput) => input.value === confirmInput.value;

    ④ invalidクラスがないか確認

    if(hasInvalidClass()) return;
    レビュー
    invalidクラスの有無を、サイト全体ではなくピンポイントに走査する。
    元々のif条件文
    document.getElementsByClassName(“invalid”).length === 0

    3つのinputを格納した配列formElements 内で走査するよう考え直しました。formElements.some(element => element.classList.contains(“invalid”))

    some()を使用することで、1つでも条件式が当てはまればtrueが返ります。

    ⑤ バリデーションがOKであれば、submitボタンのdisabledを解除する。

    confirmIfCanSubmit();
    const confirmIfCanSubmit = () => submitButton.disabled = !isMatchValue(emailOfInput, confirmEmailOfInput);
    レビュー
    やっていることの割に関数名が長すぎる。
    元々、checkFormValidityToEnableSubmitButton()と命名していました。
    confirmIfCanSubmit()に修正しました。
    関数名が長いなと思ったら、関数の仕事範囲が広くなってしまっている可能性もあると教わりました。適度な粒度で、よりシンプルに命名できるように気をつけたいです。

    パスワードの表示/非表示機能

    目のアイコンを押すと、パスワードの表示・非表示を切り替えられるようにしました。

    レビュー①
    パスワード入力途中で目のマークを押すと、バリデーションが走ってしまう。
    (目のアイコンを押すことで、inputをblurしたとみなされてしまう)
    ユーザーは、パスワードの表示・非表示をしたいだけなのでは?とご指摘いただきました。その通りだと思い、以下の修正を行いました。
    ↓修正
    ① 先程のバリデーションのコード内で、以下を実行。

    element.addEventListener("blur", (e) => {
     if(e.relatedTarget === eyeIcon) { 
       if(passwordOfInput.value && errorOfPassword.textContent === "入力してください") errorOfPassword.textContent = "";
       return;
      }

    e.relatedTargetで、elementがblurした先の要素を取得することができます。

    e.relatedTarget === eyeIconの時は、バリデーションをreturnするようにしました。

    また、パスワードが入力されており、すでに「入力してください」エラーが出ているときは、そのエラーのみ消去するようにしました。

    ② 目のアイコンをblurした時の挙動も実装しました。

    eyeIcon.addEventListener("blur", (e) => {
      if(e.relatedTarget === passwordOfInput) return;
      checkFormValidityInBlur(submitButton, passwordOfInput);
    
      if(hasInvalidClass()) return;
      confirmIfCanSubmit();
    });

    e.relatedTargetがパスワードのinput内の時は、バリデーションは走らない

    – それ以外の場所をblurした時は、バリデーションを走らせる
    ようにしました。

    レビュー②
    目のアイコンのクリックイベントの際、関数の呼び出し側でe.targetを参照する。
    -> コードがスマートになる元々、e.targetを引数で渡していた。
    icon.addEventListener(“click”, (e) => togglePasswordDisplay(e.target));

    このようにして、togglePasswordDisplay()内で、e.targetを参照できる。

    eyeIcon.addEventListener(“click”, togglePasswordDisplay);

    // togglePasswordDisplay.js

    export const togglePasswordDisplay = (e) => {
      const selectedInput = e.target.nextElementSibling;

    送信ボタンのクリックイベント

    【クリックイベントでやりたいこと】

    • ログイン時に発行されるtokenが存在するか確認(URLの直叩き防止)
    • 登録済みのパスワードと入力値が一致するか確認
      • 一致 -> メールアドレス変更
      • 不一致 -> エラーを出す
    • URLパラメータにtokenを付与して、登録完了ページへ遷移

    このように書きました。
    ※ 学習のため、ローカルストレージを使用しています。適さないことを理解しています。

    submitButton.addEventListener("click", () => {
      const token = localStorage.getItem("token");
      const registeredData = JSON.parse(localStorage.getItem("registeredData"));
    
      if(!token) {
       window.location.href = "./notautherize.html";
       return;
      }
    
      if(!registeredData) {
       localStorage.removeItem("token");
       window.location.href = "./notautherize.html";
       return;
      }
    
      if(!isMatchPassword(registeredData)){
       document.querySelector('[data-name="current-password-error"]').textContent = "パスワードが一致しません";
       submitButton.disabled = true;
       return;
      }
    
      changeAndSetEmail(registeredData);
      const urlParameter = `?token=${token}`;
      window.location.href = `./reset-email-done.html${urlParameter}`;
    });

     

    レビュー
    getItem()した返り値がnullの場合、その後のコードがエラーになってしまう。
    そのため、nullチェックが必要と教えていただきました。
    const token = localStorage.getItem("token");
    const registeredData = JSON.parse(localStorage.getItem("registeredData"));
    -> これがnullになる可能性もある

    そのため、以下のnullチェックを追加しました。
    if(!token) {
     window.location.href = "./notautherize.html";
     return;
    }
    
    if(!registeredData) {
     localStorage.removeItem("token");
     window.location.href = "./notautherize.html";
     return;
    }
    レビュー②
    パスワードが登録済みのものと一致しているかは、submitボタン押下時に確認すべきとレビューいただきました。
    元々、バリデーションチェックの部分で実装していました。
    今はローカルストレージでの実装ですが、本来はサーバーとの通信で確認する必要があるので、submitボタンを押した時の方が適しているとわかりました。

     

    今回のコード

    Sign Upから会員登録 -> ログインをして、管理画面 -> メールアドレス変更に遷移することで実際に挙動を確認することができます。

    //
    学習に使用している本は、JavaScript本格入門・独習JavaScriptです。

      

    あとがき

    もりけんさん(@terrace_tec)、さえさん(@sae_prog)、まいさん(@mai2022web)、ちひろさん(@chihiro7029) お忙しい中レビューいただきありがとうございました!

    今回も、細かな動作確認から、関数の切り出し方や命名、フォームのアクセシビリティなど様々なレビューをいただき、より良いコードにRefactorすることができました。

    教えていただいたことを生かして、次の課題に進みたいと思います!

    今日は以上です。

    //

    【もりけん塾で勉強しています】

    もりけんさん(@terrace_tec)のHPはこちら