Web版レシート管理はMVCアーキテクチャで設計しました。
サーバー側のプログラムは、Modelクラス、Viewクラス、Controllerクラスの3つに明確な役割分担を持たせ、ブラウザからのリクエストを処理して、レスポンスを返すというものです。
レスポンスを受け取ったブラウザは、サーバーからのHTMLを解析した後、画面を全更新します。
「全画面更新」は、MVCアーキテクチャに限らず、レスポンスをブラウザのレンダリングエンジンを介して再画面表示する以上、宿命的です。
「これが問題になるの?今までこれでやってきたじゃないの。」という素朴な疑問がありました。
しかし、毎回ページがリロードされると煩わしい状況は多々あります。
例えば、ECサイトで商品を「カートに追加」したとき、毎回ページがリロードされたら面倒です。
追加した商品情報だけが更新されたほうが使いやすいですよね。
実際、アマゾンをはじめほとんどのECサイトが「一部更新」になっています。
調べてみると、「一部更新」を取り入れているサイトは、いたるところにありました。
SNSの「いいね」ボタンをクリックすると ページ全体を更新せずに「いいね」の数だけ変わるとか、チャットアプリの送信したメッセージだけがリアルタイムで反映されるとか、検索候補の表示(オートコンプリート)で入力中に関連候補を動的に表示するとか・・・・。
こうした仕組みを取り入れることで、ユーザー体験が向上し、よりスムーズでインタラクティブなウェブサイト が作れるということです。つまり、使いやすいサイトが作れるということです。
なくてはならないものであり、私自身も知らず知らずのうちにその恩恵にあずかっていたのです。
これを実現するテクノロジーとしてJavaScriptの非同期処理の連携制御とDOM(Document Object Model)操作があります。
そして、この技術を自分のものにするために、Web版レシート管理システムのログイン認証をJavaScriptの非同期処理とDOM操作を活用して作成することにしました。
非同期処理の連携制御とDOM操作がどうして必要なのか。
従来であれば、ブラウザはリクエストをサーバーに送ったら処理は終了します。
サーバーからのレスポンスを受け取ったら、新たな処理が始まります。
非同期処理の連携では、サーバーに送ったリクエストのレスポンスが返ってくるのを待たなければなりません。
従来のようにリクエストを送って終了ではだめなのです。
もともと非同期処理は、タスクの実行終了を待たずに平行して別のタスクが実行されることを言うのですが、Webアプリにおいては、ブラウザとは別に動いているサーバーの完了を待つという制御が必要となります。
つまり、非同期処理(ブラウザとサーバー)を連携させる必要があります。
送ったリクエストに対するレスポンスが返ってくるのを待ち、返ってきたレスポンスを受け取って、処理を続行したいからです。
しかし、サーバーからのレスポンスがいつ返ってくるか分かりません。
待っている間、画面操作ができないのでは困ります。
リクエストを送った後、レスポンスが返ってくるまで画面操作ができ、レスポンスが返ってきたらその内容に応じた処理を行いたいわけです。
ログイン認証であれば [ログイン] ボタン がクリックされたら、サーバーにリクエストを送ります。
リクエストを送った後、ブラウザはサーバーからのリクエストが返ってくるのを待ちます。
待っている間、ブラウザ側では画面操作ができます。
サーバー側は、ログイン画面で入力された ユーザーID と パスワード が データベース に登録されているか確認し、登録されていれば 認証OK、未登録なら 認証NG のレスポンスを返します。
ブラウザ側は、サーバーからのレスポンスが届いたことを確認すると、レスポンスの内容を判定します。
認証OK ならレシート管理のトップページ画面に遷移します。
認証NG なら「認証エラー」のエラーメッセージを現在の画面に表示します。(一部更新)
非同期処理 を活用すれば、サーバーにリクエストを送った後、レスポンスの完了を待たずに 他の処理を進めることができます。
具体的には、非同期処理 では、レスポンスがいつ返ってくるか分からないため、コールバック関数や Promise、async/await を活用して、レスポンスが返ってきたタイミングで必要な処理を実行します。
ここら辺のことは、次回の投稿に載せようと思っています。
認証NG なら「認証エラー」のエラーメッセージを現在の画面に表示する処理(一部更新)は、DOMを操作することで、画面の一部を変更することができます。
DOM とは、サーバーからの HTML をブラウザが解析した後の ページのオブジェクト構造(ツリー構造) です。
具体的には、HTMLドキュメント の各要素(タグ)、テキスト、属性などが ツリー構造のあるオブジェクト として表されます。
例えば、<body> 要素の中に <div> や <p> などの子要素が含まれ、それぞれが独立したオブジェクトとして扱われるイメージです。
DOM を操作することで、 動的な変更が可能 になり、JavaScript を使えば DOM の各要素を操作して、ページの内容を書き換えたり、追加・削除したりすることができます。
「Fetchを利用した非同期Webアプリ」は3回くらいのシリーズで掲載していこうと思っています。
今回は、次の点について掲載しました。
- Fetchを利用した非同期Webアプリは、いろいろなサイトで活用されていて、ユーザー体験が向上し、よりスムーズでインタラクティブなウェブサイト が作られていること。
- テクノロジーとしてはJavaScriptの非同期処理とDOM操作が使われていること。
- この技術習得のために、Web版レシート管理のログイン認証機能を非同期処理で作成すること。
次回は、非同期処理 を掘り下げていく予定です。
最後に、fetch() を使って認証機能を実装し、JSON形式のレスポンスに基づいて処理を行う仕組みのサンプルコードをしめします。
サーバー側のサンプルコード(PHP)は1つ掲載しました。
ブラウザ側のサンプルコード(JavaScrip)は、次の3パターンを掲載しました。
3パターンとも、処理の内容は同じです。
- async/awaitを使うパターン アローン関数で記述
- async/awaitを使うパターン 通常の関数として記述
- then() を使うパターン(Promise チェーン)通常の関数として記述
それぞれのコードの解説は、このシリーズの次回以降で掲載する予定です。
サーバー側のサンプルコード(PHP)
<?php
header("Content-Type: application/json");
// ユーザーIDとパスワードを取得
$uid = $_POST['uid'] ?? '';
$password = $_POST['password'] ?? '';
// ダミーの認証処理(実際のシステムではデータベースで確認)
$validUsers = ["user1" => "password1", "user2" => "password2"];
if (isset($validUsers[$uid]) && $validUsers[$uid] === $password) {
echo json_encode(["status" => "ok", "redirect" => "TopMenu.php"]);
} else {
echo json_encode(["status" => "ng", "message" => "ログイン認証エラー"]);
}
exit();
async/awaitを使うパターン アローン関数で記述
document.getElementById('sendButton').addEventListener('click', async () => {
const usernameField = document.getElementById('usernameField');
const passwordField = document.getElementById('passwordField');
const errorMessage = document.getElementById('errorMessage');
// 入力内容を取得
const uid = usernameField.value.trim();
const password = passwordField.value.trim();
if (!uid || !password) {
errorMessage.textContent = "ユーザー名とパスワードを入力してください。";
errorMessage.style.display = "block";
return;
}
// `FormData` を作成
const formData = new FormData();
formData.append('uid', uid);
formData.append('password', password);
try {
const response = await fetch('../controller/AuthController.php', {
method: 'POST',
body: formData
});
const data = await response.json(); // JSON レスポンスを取得
console.log("サーバーレスポンス:", data);
if (data.status === "ok") {
window.location.href = data.redirect; // 成功時にリダイレクト
} else {
errorMessage.textContent = data.message; // 失敗時のメッセージを表示
errorMessage.style.display = "block";
}
} catch (error) {
console.error("Fetchエラー:", error);
errorMessage.textContent = "エラーが発生しました。";
errorMessage.style.display = "block";
}
});
async/awaitを使うパターン 通常の関数として記述
async function authenticateUser() {
try {
const response = await fetch('../controller/AuthController.php', { method: 'POST' });
const data = await response.json(); // JSONレスポンスを受け取る
console.log("サーバーレスポンス:", data);
if (data.status === "ok") {
window.location.href = data.redirect; // 成功時のリダイレクト
} else {
document.getElementById('errorMessage').textContent = data.message; // 失敗時のメッセージ表示
}
} catch (error) {
console.error("Fetchエラー:", error);
}
}
// ボタンイベントに関数を紐付ける
document.getElementById('sendButton').addEventListener('click', authenticateUser);
then() を使うパターン(Promise チェーン)通常の関数として記述
fetch('../controller/AuthController.php', { method: 'POST' })
.then(response => response.json()) // JSONレスポンスを取得
.then(data => {
console.log("サーバーレスポンス:", data);
if (data.status === "ok") {
window.location.href = data.redirect; // 成功時のリダイレクト
} else {
document.getElementById('errorMessage').textContent = data.message; // 失敗時のメッセージ表示
}
})
.catch(error => console.error("Fetchエラー:", error));
~~追 記~~
理解を深めるためにAIは欠かせないものになってきました。
Copilotと会話をしながら、「非同期処理」の理解を深めていきました。
AIは、概ねとても優秀です。
質問に対して期待以上の回答が返ってくることがあります。
理解したことを再確認することで、理解が深まります。
やり取りを繰り返すことで、「非同期処理」の概念や活用方法が整理されていきました。
実態の伴っていなかった「非同期処理」が、徐々に浮き彫りになっていく感覚を持ちました
プログラミングのように確立されている技術があって、それを組み合わせて物づくりをする世界は、特にAIの活用が有効です。
AIを有効活用するためには、常に自分の頭で考え、疑問点を明確にして、言語化する能力が求められることも痛感しました。
AIとうまく付き合っていくと、より一層、人生を楽しく、豊かにしてくれるかもしれません。