L43:JavaScript基礎(DOM・イベント・状態管理)+UI状態(loading/error/empty)最小実装(TRAINING ONLY/流用禁止)
【重要:本レッスンは訓練専用】
- このレッスンで作る「サンプルHTML/CSS/JS」「状態遷移の実装」「例外処理」「A11y対応(フォーカス/キーボード)」「デバッグ手順」等は訓練専用です。通常業務でそのまま使用することは禁止します(コピペ流用禁止)。
- 実サイト更新・実公開・実リポジトリ反映は禁止:本番環境/実CMS/実タグ/実URLには触れません(ローカルファイル・ダミーのみ)。
- 実データ・個人情報・未公開情報は禁止(実案件名、実ページID、実在のSKU/取引先/条件などは書かない)。
- 本番で必要になった場合は、訓練成果物を流用せず、情報を取り直し、別途レビュー/承認を経て新規作成してください。
本レッスンでは、JavaScriptを「コードを書く」よりも先に、UI要件(状態/例外/A11y)を“実装に落ちる粒度”にする練習をします。
L42で作ったUI要件(State表・例外表・A11y要件)を前提に、状態管理(state)→描画(render)→イベント(event)→例外(error)の最小構造を体験します。
このページの使い方
1レッスン=1LP(1ページ)です。上から順に当日の時間割に沿って進めてください。
各項目の冒頭に EC事業部/文房具カフェ事業部・準備室 の実施時間を併記しています。
※本レッスンはダミーのみで行います(実データ・個人情報・未公開情報は入力しない)。
このレッスンの狙い(到達状態)
- DOM/イベントの基本(querySelector/addEventListener)を説明できる
- UIを「画面の雰囲気」ではなく、状態(state)として扱える
- loading/error/emptyなどの状態を、UI要件→実装で一貫させられる
- 例外(未入力/通信失敗/二重送信)を実装側で止められる
- アクセシビリティ(キーボード/フォーカス/通知)を“要件として”入れられる
受講ルール(共通)
- 実データ禁止:実URL、実ページID、実アカウント、実顧客情報、未公開企画などは禁止
- 訓練成果物の流用禁止:訓練で作ったコード/要件/テンプレを通常業務へコピペしない
- 通常業務をしない:訓練日は講義・演習・レビュー・理解度確認に専念する
- 命令系統の具体化をしない:役割は「作成担当」「レビュー担当」「承認担当」など抽象ロール
- 相互レビュー2件以上:前日までの他者成果物に2件以上コメント(本ページの観点を使用)
今日の目標(できた範囲でOK)
受講者のレベル差があるため、強制の提出物は設けません。今日の目標を選び、できた範囲を「今日進めたこと」に記録してください。
- (A)TRN-JS43 Specヘッダ(v0.1):UI対象/状態/例外/完了条件を固定(L42を参照してOK)
- (B)state+render(最小):UI状態を1つのオブジェクトで持ち、renderで表示を揃える
- (C)例外処理(最低3つ):未入力/二重送信/擬似通信失敗 を止める
- (D)A11y最小対応:キーボード操作、フォーカス移動、通知(aria-live想定)
- (E)デバッグ手順(v0.1):再現条件→原因仮説→修正→再確認の型を作る
結論:JSは「イベント」より先に「状態(state)」を決める
ありがちな失敗(JSがごちゃつく理由)
- クリックのたびにDOMを直接いじってしまい、状態が破綻する
- loading/error/emptyが実装されず、連打・二重送信・迷子が起きる
- 例外(未入力/失敗)を想定せず、復帰導線がなくなる
- キーボード操作・フォーカスが抜けて、検収で差戻しになる
今日の最小アーキテクチャ(これだけで崩れにくい)
- state:今の状態を1箇所に集約(例:modalOpen, status, errorMessage)
- render():stateからUIを再描画(表示/非表示、disabled、メッセージ)
- event:イベントでstateを更新し、renderを呼ぶ
- guard:例外(未入力/二重送信/失敗)をifで止める
標準テンプレ(TRAINING ONLY)
A) TRN-JS43 Specヘッダ(v0.1)
【TRN-JS43 Specヘッダ(v0.1:訓練専用・流用禁止)】
Spec-ID:TRN-JS43
版:v0.1
状態:DRAFT / REVIEW / FINAL(訓練内)
UI対象(ダミー):
* 例:モーダル+フォーム送信 / タブ切替 / リストフィルタ
目的(1行):
* 例:state+renderで、loading/error/emptyを破綻なく実装する練習をする
範囲(やる/やらない):
* やる:DOM/イベント/state/render/例外処理/A11y最小対応(ダミー)
* やらない:実サイト更新、実公開、実運用データ反映、実承認
状態(最低8つのうち使うもの):
* default / focus / active / disabled / loading / success / error / empty / closed / open
例外(最低3つ):
* 未入力(validation error)
* 二重送信(loading中の連打)
* 擬似通信失敗(ダミー条件でerror)
A11y(最低ライン):
* キーボード(Enter/Space/Esc)
* フォーカス(開いたら中へ、閉じたら戻す)
* 通知(処理中/完了/エラーを伝える想定)
* 動き抑制(動きがなくても理解できる)
完了条件(採点可能):
* stateとrenderが分離され、イベントでstate更新→renderになっている
* loading中は操作が抑止され、二重送信ができない
* error/emptyが表示され、復帰導線がある
* 実データ混入がない(訓練専用の明記あり)
B) ダミー課題(1つ選べばOK)
JS-43A(易):タブ切替(矢印キー対応)+empty状態
状況(ダミー):
- 3タブで表示内容を切り替える
- タブの内容は配列(ダミー)から表示
- 該当データがない場合は empty を表示する
要求:
* state(activeTab, status)を持つ
* クリックとキーボード(左右矢印)で切替できる
* empty時の復帰(別タブへ切替)が明確
JS-43B(中):モーダル+フォーム送信(loading/success/error)
状況(ダミー):
- ボタンでモーダルを開く
- 必須入力が空ならエラー
- 送信でloading → success / error に遷移
- error時は再試行導線がある
- Escで閉じる、閉じたらフォーカスを元ボタンへ戻す
要求:
* 二重送信防止(loading中は送信不可)
* aria-live等で状態通知の想定
* 実データ/実URLは使わない
JS-43C(中):リスト取得(擬似API)+error/empty復帰
状況(ダミー):
- 「読み込み」クリックで擬似データを表示(setTimeout)
- 擬似失敗条件がある(例:チェックボックスONで失敗)
- emptyの場合は「条件を変える」導線で復帰
要求:
* status: idle/loading/success/error/empty をstateで管理
* render()で表示を揃える
サンプル(訓練用:JS-43B モーダル+フォーム送信)
※このサンプルは訓練用の型です。実務に流用しないでください。
1) HTML(index.html想定:抜粋)
<!-- TRAINING ONLY: TRN-JS43 -->
<div class="trn">
<h2>TRN-JS43:モーダル+フォーム送信(ダミー)</h2>
問い合わせ(ダミー)
“` <p id=”helper”>※実データ禁止。これは訓練用ダミーです。</p> <label for=”msg”>内容(必須・ダミー)</label> <textarea id=”msg” rows=”3″></textarea> <div id=”errorBox” role=”alert” hidden></div> <div class=”modal__actions”> <button id=”submitBtn” type=”button”>送信(ダミー)</button> <button id=”closeBtn” type=”button”>閉じる</button> </div> <div id=”resultBox” hidden></div> </div> “`
2) JS(script.js想定:抜粋)
/* TRAINING ONLY: TRN-JS43(流用禁止) */
const $ = (sel) => document.querySelector(sel);
const els = {
openBtn: $("#openBtn"),
closeBtn: $("#closeBtn"),
submitBtn: $("#submitBtn"),
modal: $("#modal"),
msg: $("#msg"),
errorBox: $("#errorBox"),
resultBox: $("#resultBox"),
statusLive: $("#statusLive"),
};
const state = {
modalOpen: false,
status: "idle", // idle | loading | success | error
errorMessage: "",
resultMessage: "",
lastFocusEl: null,
};
function setLive(text) {
// 訓練:状態通知の想定(実装方針は本番で別途設計)
els.statusLive.textContent = text;
}
function render() {
// モーダル表示
els.modal.hidden = !state.modalOpen;
// ボタン操作抑止(loading中)
const isLoading = state.status === "loading";
els.submitBtn.disabled = isLoading;
els.closeBtn.disabled = isLoading;
// エラー表示
if (state.status === "error") {
els.errorBox.hidden = false;
els.errorBox.textContent = state.errorMessage || "エラー(ダミー)";
setLive("エラーが発生しました(ダミー)");
} else {
els.errorBox.hidden = true;
els.errorBox.textContent = "";
}
// 結果表示
if (state.status === "success") {
els.resultBox.hidden = false;
els.resultBox.textContent = state.resultMessage || "完了(ダミー)";
setLive("送信が完了しました(ダミー)");
} else {
els.resultBox.hidden = true;
els.resultBox.textContent = "";
}
// loading通知
if (state.status === "loading") {
setLive("処理中です(ダミー)");
}
}
function openModal() {
state.lastFocusEl = document.activeElement;
state.modalOpen = true;
state.status = "idle";
state.errorMessage = "";
state.resultMessage = "";
render();
// フォーカス(最小対応)
els.msg.focus();
}
function closeModal() {
if (state.status === "loading") return; // 二重操作防止
state.modalOpen = false;
render();
// フォーカスを戻す(最小対応)
if (state.lastFocusEl && state.lastFocusEl.focus) {
state.lastFocusEl.focus();
} else {
els.openBtn.focus();
}
}
function validate() {
const v = (els.msg.value || "").trim();
if (!v) {
return { ok: false, message: "必須入力が空です(ダミー)" };
}
return { ok: true, message: "" };
}
function fakeRequest(payload) {
// 擬似通信:入力に「error」が含まれると失敗(再現可能)
return new Promise((resolve, reject) => {
setTimeout(() => {
if ((payload.message || "").includes("error")) {
reject(new Error("擬似通信失敗(ダミー条件:'error'を含む)"));
} else {
resolve({ ok: true });
}
}, 800);
});
}
async function onSubmit() {
if (state.status === "loading") return; // 二重送信防止(ガード)
const check = validate();
if (!check.ok) {
state.status = "error";
state.errorMessage = check.message;
state.resultMessage = "";
render();
els.msg.focus();
return;
}
state.status = "loading";
state.errorMessage = "";
state.resultMessage = "";
render();
try {
await fakeRequest({ message: els.msg.value.trim() });
state.status = "success";
state.resultMessage = "送信完了(ダミー)";
render();
} catch (e) {
state.status = "error";
state.errorMessage = e.message || "不明なエラー(ダミー)";
render();
// エラー時は入力に戻して復帰できるように
els.msg.focus();
}
}
els.openBtn.addEventListener("click", openModal);
els.closeBtn.addEventListener("click", closeModal);
els.submitBtn.addEventListener("click", onSubmit);
// Escで閉じる(最小)
document.addEventListener("keydown", (ev) => {
if (!state.modalOpen) return;
if (ev.key === "Escape") {
ev.preventDefault();
closeModal();
}
});
// 初期描画
render();
3) デバッグ観点(サンプルの確認ポイント)
- 未入力で送信 → error表示+フォーカスが入力へ戻る
- 入力に「error」を含めて送信 → 擬似失敗 → error表示+再入力できる
- 送信中(loading)に連打 → 二重送信されない(ボタンdisabled)
- Escで閉じる → 開いたボタンへフォーカスが戻る
品質ゲート(OK/差戻し/要相談:v1.0)
| 判定 | 基準 | 次アクション | 記録 |
|---|---|---|---|
| OK | state+renderが分離され、loading/error/empty等の状態が再現でき、例外で止められる(二重送信/未入力) | 次工程(L40の検収観点やL42のState表に接続)へ | 結果=OK、根拠 |
| 差戻し | 状態がDOM散在、loadingがなく二重送信可能、error/emptyの復帰がない、A11yが抜け(Esc/フォーカス) | v+0.1で修正(不足を埋める) | 差戻し理由(どこ/なぜ/完了条件) |
| 要相談(停止) | 実データ混入、実サイト更新・公開を誘導、権利/法務/安全に触れる断定、通常業務に流用する意図がある | 作業停止→相談(抽象ルート) | 停止理由、混入箇所、再開条件 |
ChatGPTに投げるプロンプト(コピペ用)
1) UI要件(L42)から「実装に落ちるstate設計」を作る
【L43 プロンプト①:state設計(訓練用)】
前提(安全):
* 教育訓練用ダミー。実データ・実URL・未公開情報は禁止。
* 訓練成果物は流用禁止。実サイト更新はしない。
入力:
* L42で作ったUI要件(State表+例外表+A11y要件)
* 選んだ課題(JS-43A/B/C)
出力形式(必須):
* state設計(キー一覧と意味)
* renderで更新するUI要素一覧
* イベント一覧(click/keydown/submit 等)とstate遷移
* ガード(未入力/二重送信/失敗)
2) 最小実装(state+render+event)を生成する
【L43 プロンプト②:最小実装(訓練用)】
前提:
* 外部通信はしない(fetchしない)。擬似通信はsetTimeout/Promiseで。
* 状態管理(state)と描画(render)を分ける。
* A11y:Esc、フォーカス戻し、通知(aria-live想定)を入れる。
入力:
* あなたのstate設計
* UI要件(抜粋)
出力形式:
* HTML(必要要素:ボタン/入力/メッセージ領域 等)
* JS(state/render/event/guard/擬似通信)
* コメントで「TRAINING ONLY/流用禁止」を明記
3) 自己レビュー(差戻し観点)
【L43 プロンプト③:自己レビュー(差戻し防止)】
入力:
* あなたのHTML/JS
* L43の品質ゲート
出力:
* 判定(OK/差戻し/要相談)
* 差戻し理由(どこ/なぜ/完了条件:最大5つ)
* v+0.1の改善案(差分)
相互レビュー観点(L43専用)
- 訓練専用の担保:流用禁止が明記され、実在情報が混入していないか
- state+render:状態が1箇所に集約され、renderで表示が揃っているか
- 例外:未入力/二重送信/失敗がガードでき、復帰があるか
- A11y:キーボード(Esc等)とフォーカスが最低限入っているか
- 再現性:成功/失敗が再現できる条件があり、検収ができるか(ランダム禁止推奨)
レビューコメントテンプレ(コピペ用)
【L43 相互レビューコメント】
対象(TRN-JS43):
版:
1. 良い点(1つ):
*
2. state+renderは分離できてる?
* OK / 要改善
改善案(1つ):
*
3. 例外(未入力/二重送信/失敗)は止められる?
* OK / 要改善
不足(1つ):
*
4. A11y(Esc/フォーカス/通知想定)は最低限ある?
* OK / 要改善
不足(1つ):
*
5. 次の一手(v+0.1で直すなら):
*
本日の流れ(タイムライン)
目次(クリックで移動)
- 出席・当日選択カリキュラムの内容確認
- 時間差相互評価(2件以上コメント)
- 休憩
- 自分への受領レビュー確認・改善方針メモ
- L43 レクチャー本編(講師説明・質疑込み)
- 昼休憩
- 個人演習①:state設計→最小実装
- 休憩
- 個人演習②:例外/A11y/差戻し→v+0.1改善
- 休憩
- 復習:共有できる形に整形(できた範囲でOK)
- 質問・コメント・感想の提出(指定スレッド)
1) 出席・当日選択カリキュラムの内容確認
【実施時間】
| 対象 | 時間 |
|---|---|
| EC事業部 | 08:30–09:10 |
| 文房具カフェ事業部/準備室 | 10:00–10:40 |
この時間にやること
- 今日の目標(A〜E)を選ぶ(できた範囲でOK)
- ダミー課題(JS-43A/B/C)を1つ選ぶ
- 注意点を1行で書く(例:stateとrenderを分ける/二重送信を止める/流用禁止)
セルフ棚卸し(コピペ用)
【L43 セルフ棚卸し】
1) 今日選ぶダミー課題:
- JS-43A / JS-43B / JS-43C
2. 自分が弱い点(1つ):
* (例:状態の洗い出し/例外処理/フォーカス設計/デバッグ)
3. 今日の目標(1行):
*
2) 時間差相互評価(前日までの他者成果物に2件以上コメント)
【実施時間】
| 対象 | 時間 |
|---|---|
| EC事業部 | 09:10–10:00 |
| 文房具カフェ事業部/準備室 | 10:40–11:30 |
この時間にやること
- 前日までの他者成果物を2件選び、L43レビュー観点でコメントする
- 「動いてる」より、状態/例外/復帰/A11yが揃っているかを見る
3) 休憩
【実施時間】
| 対象 | 時間 |
|---|---|
| EC事業部 | 10:00–10:15 |
| 文房具カフェ事業部/準備室 | 11:30–11:45 |
休憩:学習作業なし
4) 自分への受領レビュー確認・改善方針メモ(講師レビュー含む)
【実施時間】
| 対象 | 時間 |
|---|---|
| EC事業部 | 10:15–10:45 |
| 文房具カフェ事業部/準備室 | 11:45–12:15 |
改善方針メモ(コピペ用)
【L43 改善方針メモ】
受領した指摘の要点(最大3つ):
1)
2)
3)
## 直す理由(state散在/例外不足/復帰が曖昧/A11y不足 等):
直し方(どこを改善する?):
* state設計:
* render(表示/非表示/disabled):
* 例外(ガード):
* A11y(フォーカス/キー操作):
* 再現条件(成功/失敗):
今日の最優先ルール(1行):
*
5) 当日選択カリキュラム実施:L43レクチャー本編(講師説明・質疑込み)
【実施時間】
| 対象 | 時間 |
|---|---|
| EC事業部 | 10:45–12:00 |
| 文房具カフェ事業部/準備室 | 12:15–13:30 |
ここからが「読む/聞く」パート
JSは「正解コード」よりも、壊れない構造(state→render→event→guard)を先に作るのが重要です。
午後は、選んだダミー課題をstate設計から始め、例外/A11y/復帰まで揃えます。
6) 昼休憩
【実施時間】
| 対象 | 時間 |
|---|---|
| EC事業部 | 12:00–13:00 |
| 文房具カフェ事業部/準備室 | 13:30–14:30 |
昼休憩:学習作業なし
7) 個人演習①:state設計→最小実装
【実施時間】
| 対象 | 時間 |
|---|---|
| EC事業部 | 13:00–14:30 |
| 文房具カフェ事業部/準備室 | 14:30–16:00 |
演習①のやり方(必須)
- TRN-JS43 Specヘッダ(v0.1)を埋める(状態/例外/A11y/完了条件)
- state設計(キー一覧)を作る
- render()を作り、state→UIが揃うようにする
- イベント(click/keydown等)でstate更新→renderを徹底する
提出用フォーマット(演習①:コピペ用/できた範囲でOK)
【L43 演習① 提出(できた範囲でOK)】
Spec-ID:TRN-JS43
ダミー課題:
## (1) Specヘッダ(v0.1):
## (2) state設計(キー一覧):
(3) renderの方針(何をstateで制御する?):
*
8) 休憩
【実施時間】
| 対象 | 時間 |
|---|---|
| EC事業部 | 14:30–14:45 |
| 文房具カフェ事業部/準備室 | 16:00–16:15 |
休憩:学習作業なし
9) 個人演習②:例外/A11y/差戻し→v+0.1改善
【実施時間】
| 対象 | 時間 |
|---|---|
| EC事業部 | 14:45–15:45 |
| 文房具カフェ事業部/準備室 | 16:15–17:15 |
演習②のやり方(必須)
- 例外(未入力/二重送信/失敗)をガードし、復帰導線を作る
- A11y最小(Esc/フォーカス戻し/通知想定)を入れる
- 品質ゲートで自己判定(OK/差戻し/要相談)し、差戻しならv+0.1で直す
提出用フォーマット(演習②:コピペ用/できた範囲でOK)
【L43 演習② 提出(できた範囲でOK)】
Spec-ID:TRN-JS43
## (1) 例外(ガード)と復帰:
## (2) A11y最小対応(Esc/フォーカス/通知想定):
(3) 品質ゲート判定:
* 判定(OK/差戻し/要相談):
* 根拠(1行):
(4) 任意:v+0.1の改善ログ(何を/なぜ/どう直す):
*
10) 休憩
【実施時間】
| 対象 | 時間 |
|---|---|
| EC事業部 | 15:45–16:00 |
| 文房具カフェ事業部/準備室 | 17:15–17:30 |
休憩:学習作業なし
11) 復習:共有できる形に整形(できた範囲でOK)
【実施時間】
| 対象 | 時間 |
|---|---|
| EC事業部 | 16:00–16:30 |
| 文房具カフェ事業部/準備室 | 17:30–18:00 |
最終チェック(コピペ用)
【L43 最終チェック】
- 訓練専用(流用禁止)が明記されている
- 実データ/実URL/実案件が混入していない
- stateとrenderが分離されている(イベントでstate更新→render)
- loading中の二重送信が止められる
- error/emptyがあり、復帰導線がある
- A11y最小(Esc/フォーカス戻し/通知想定)がある
12) 講師への質問・コメント・感想の提出(指定スレッド)
【実施時間】
| 対象 | 時間 |
|---|---|
| EC事業部 | 16:30–17:00 |
| 文房具カフェ事業部/準備室 | 18:00–18:30 |
提出先(参考)
EC事業部・文房具カフェ事業部:ChatWork の指定スレッド/準備室:Slack の指定スレッド
提出テンプレ(コピペ用)
【L43 提出(本人レポート)】
1. 今日の学習内容(要約:3行)
*
*
*
2. 今日進めたこと(TRAINING ONLY:流用禁止)
* 選んだ課題(JS-43A/B/C):
* Specヘッダ:
* state設計:
* render方針:
* 例外(ガード):
* A11y最小対応:
* 品質ゲート判定:
3. 一番工夫した点(1つ)
-(例:state→renderに揃えた/二重送信を止めた/error復帰を作った 等)
理由:
*
4. 次に改善したい点(1つ)
*
## 理由:
5. 質問(最低1つ)
*