現在、もりけん塾にてJavaScriptを勉強させていただいております。その記録。
今回は課題26についての備忘録です。

考え方が間違っている箇所もあるかと思いますが、お気づきの際はご指摘いただければと思います。

課題26について

仕様

ログイン画面(login.html)を作ります
※今回固定値として下記を設定しています。
◆ユーザーネーム:tanaka
◆email address : tanaka.gmail.com
◆パスワード: N302aoe3

  • ユーザー名とメールアドレスどちらも入れられるinputにしてください
  • パスワードの実装は25と同じ。
  • パスワード忘れてた方 forgotpassword.htmlを作って遷移できるようにしてください。ページ内の実装はしないで良いです。
  • 会員登録はこちらから課題24,25で作ったregister.htmlへ遷移できる。
  • パスワードは仮に固定値としてN302aoe3とし、メールアドレス、ご自身で決めたものでいいです。(パスワードは固定値N302aoe3)
  1. ログインページに遷移したらトークンがlocalStorageにあるかどうか確認してください。(トークンはログインでメールアドレス、パスワードがあっていれば発行されます(下記※1で検証した時です)。通常はAPIで受け取りますが今回はダミーで固定値として作ります)
  2. トークンがあれば、コンテンツ画面ページへ、なければログインページをそのまま出します。
  3. ログインページ内は送信ボタン押下時に。
{
  email: "kejimorita@gmail.com", // <- なんでも良いです
  password: "N302aoe3" // 固定値
}

というものがsubmitの引数として渡ってきたら、 Promise を使ってnameとpasswordをそれぞれの値のプリミティブ比較で合っているか検証してください(※1)。(ここのPromise辺りの実装はなんでもいいです) サーバー内で検証していることを想定しているので、awaitしている関数内で行ってください。 合っていたらresolve、違う場合 reject。

その返値にトークンを返して判定してください。 返値のトークンはChanceを使用し生成してください。

// 成功時
{ token: "fafae92rfjafa03", ok: true, code: 200 }
// 失敗時
{ ok: false, code: 401 }

それに応じて画面ページか失敗しましたページへ遷移
成功したらそのtokenを後、そのログイン画面内でlocalStorageで埋め込んでください。
以後は、ログインページやコンテンツページに遷移したら localStoregeに値があるかチェックして、なければログイン画面に飛ばしてください。さらにコンテンツ画面側(yahoo風コンテンツ)にログアウトボタンをつけて 押したらローカルストレージ削除してください。

※ローカルストレージに関しての危険性はローカルストレージは使うなを一読のこと。ここの課題のローカルストレージ使用はあくまで学習の為に使います。

ここでの課題はそれが確認できればokです。

制作物

コードはこちらから(codesandbox)

実装内容

入力されたデータ(名前/メールアドレス・パスワード)の検証

①ログインフォームで入力された値を取得。
②受け取った値をcheckData()関数に渡して検証。
③値がOKだったらPromiseでresolveされ、渡ってきた値(token)をローカルストレージに保存、NEWSコンテンツに遷移。
(値がNGだった場合、reject、ログイン失敗のページへ遷移)

概要は下記コード部分↓

const checkData = ({ name_mail, user_password }) => {
  const userData = {
    name: "tanaka",
    email: "tanaka@gmail.com",
    pass: "N302aoe3"
  }; 
  return ((name_mail === userData.name || name_mail === userData.email) && (user_password === userData.pass));
}


const userDataVerification = () => {
  const userData = Object.fromEntries([...new FormData(form)]);
  return new Promise((resolve,reject) => {
    if (checkData(userData)){
      resolve({ token: chance.apple_token(), ok: true, code: 200 });
    } else {
      reject({ ok: false, code: 401 }); 
    }
  })
}

const loginVerification = async() => {
  try {
    const serverResponseData = await userDataVerification();
    const token = serverResponseData.token; 
    localStorage.setItem("token", JSON.stringify(token));
    return true;
  }
  catch {
    return false;
  }
}

const init = async () => {
  const verificationResult = await loginVerification();
  window.location.href = verificationResult ? "./news.html" : "./loginFailure.html"
}

submitButton.addEventListener('click',init);

関数ごとに説明します。

◆userDataVerification()

フォームにて入力された値を取得しcheckData()関数に渡す。
checkData()関数での検証結果がtrueの場合、resolveとなり、値(サーバーからのレスポンスデータを想定)を次の関数へ渡す。

今回、入力値はFormDataを使用して値を取得しました。(これ便利だと思った。)inputタグに設定しているname属性と入力値がセットで配列に入ってきて、それをオブジェクトに変換し、checkData()関数へ渡しています。

今回の課題でトークンの生成はchance.jsを使用すると教えていただきました。
apple_tokenを使用してランダムなトークン値を生成してます。

◆checkData()

渡ってきた入力値を検証する関数。
今回は、ユーザーネームもしくはメールアドレスが合っていたらなので、下記としました。(ユーザーネームかつメールアドレス、とパスワード。)合っていたらtrue、間違っていたらfalseを返す。

((ユーザーネーム || メールアドレス) && パスワード)

◆loginVerification()

resolveされた値が渡ってきたら、token値をローカルストレージへ保存(setItem)し、trueを返す。
rejectされた場合はfalseを返す。

◆init()

trueが渡ってきたら、./news.html(NEWSコンテンツページ)、falseが渡ってきたら./loginFailure.html(ログイン失敗ページ)へと遷移する。

ログアウトボタンでトークン削除

ログアウトボタンを押したらlocalStrageを削除し、ログインページへ遷移します。

const logoutBtn = document.getElementById('js-logout-btn');
logoutBtn.addEventListener('click', () => {
  localStorage.removeItem('token');
  window.location.href = './index.html';
});

localStoregeに値があるかを確認し、値がある場合はコンテンツページへ遷移させる。

都度localStrageに値があるかを確認し、ある場合はコンテンツページへ遷移します。(ログイン状態の保持)
つまり、ログアウトボタンを押さない限りは(保存してあるlocalStrageを削除しない限りは)、ログイン状態となる。

if(localStorage.getItem('token')) {
  window.location.href = "./news.html";
}

//if(localStorage.hasOwnProperty('token')) {
//  window.location.href = "./news.html";
//}
レビューPOINT

トークンの値が保存されているかの確認をhasOwnPropertyを使用していました。特定のキーが存在するかどうかを確認するものと理解していたのですが、値を確認するという意味で言ったらgetItemが良いのではとレビューいただきそちらに変更しました。

getItemの「値を取得」は、値を取得してそれを使いたい時というイメージがあったので少し違うかな?と思ったのですが、hasOwnPropertyはキーがあっても値が空でもtrueを返すそう。今回の課題では、キーと値を一緒に埋め込むので、キーがあって値がないということはないはずなのでこちらでも問題はなさそうだけど、値があるかの確認ではgetItemかとなったので変更。(少し考えた箇所)

課題25から変更した点

バリデーションイベント関数の分割

validかinvalidかで動作する関数について、分割できるのではとレビューいただきましたので、そのままアイディアを採用させていただきました。それぞれ関数が短くなりました。

const setValidationEvents = (isValid, targetForm) =>
  isValid ? validEvent(targetForm) : invalidEvent(targetForm);

const validEvent = (targetForm) => {
  targetForm.classList.add("valid");
  targetForm.classList.remove("invalid");
  targetForm.nextElementSibling.textContent = "";
  isValidStatus[targetForm.id] = true;
};

const invalidEvent = (targetForm) => {
  targetForm.classList.add("invalid");
  targetForm.classList.remove("valid");
  targetForm.nextElementSibling.textContent =
    validationInfo[targetForm.id].errorMessage;
  isValidStatus[targetForm.id] = false;
};

//元の関数
// const setValidationEvents = (isValid, targetForm) => {
//   if (isValid) {
//     targetForm.classList.add('valid');
//     targetForm.classList.remove('invalid');
//     targetForm.nextElementSibling.textContent = ''; 
//     isValidStatus[targetForm.id] = true;
//   } else {
//     targetForm.classList.add('invalid');
//     targetForm.classList.remove('valid');
//     targetForm.nextElementSibling.textContent = validationInfo[targetForm.id].errorMessage;
//     isValidStatus[targetForm.id] = false; 
//   }
// }

バリデーション

課題25では、ユーザーネーム・メールアドレス・パスワード、それぞれでバリデーションの仕様が分かれていましたが、今回は、ユーザーネームかつメールアドレスという仕様になったため、そこのバリデーションの記述はなくし、値が入っているかどうかの確認のみに変更しました。(パスワードのバリデーションはそのまま)

const isValidStatus = {
  name_mail: false,
  password: false,
}

const validationInfo = {
  name_mail: {
    validation: (value) => value
  },
  password: {
    validation : (value) => /(?=.*[a-zA-Z])(?=.*[0-9])[a-zA-z0-9]{8,}/.test(value),
    errorMessage: '※8文字以上の大小の英数字を交ぜたものにしてください。'  
  }
}

//元のバリデーション
// const isValidStatus = {
//   name: false,
//   mail: false,
//   password: false,
// }

// const validationInfo = {
//   name: {
//     maxNameLength: 16,
//     minNameLength: 1,
//     validation: (value) => value.length > validationInfo.name.minNameLength && value.length < validationInfo.name.maxNameLength,
//     errorMessage: '※ユーザー名は1文字以上15文字以下にしてください。',
//   },
//   mail: {
//     //Reference: https://1-notes.com/javascript-determine-if-it-is-an-email-address/ 
//     validation : (value) => /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(value),
//     errorMessage: '※メールアドレスの形式になっていません。' 
//   },
//   password: {
//     validation : (value) => /(?=.*[a-zA-Z])(?=.*[0-9])[a-zA-z0-9]{8,}/.test(value),
//     errorMessage: '※8文字以上の大小の英数字を交ぜたものにしてください。'  
//   }

その他レビューPOINT

今回、関数名や変数名にて適切でないとのご指摘が多かったと思います。(いつもか)自分でも思い込みで命名していたり、簡易的につけすぎたりしていたので、注意しようと思いました。
ただ、いつもながら、最後は動かすことで精一杯になって、命名は後回しになってしまう。。気をつけよう。。

後記

当初はTailwindCSSも一緒に実装していたのですが、やることや気になることがどんどん出てきて途中断念。それよりもまずはJSに集中しようと思いました。それにしてもログインログアウトの流れを考えながらの実装は面白かった。振り返ると、修正箇所がすごい量になってたけど、色々ご指摘いただき、認識を整理整頓することができました。毎度のことながらありがとうございます!

c.sakyou

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA